Compare commits
	
		
			428 commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| f4248dcb73 | |||
| 5ba5862c9c | |||
| 04c7c052fa | |||
| d588469bb5 | |||
| f9474e59db | |||
| a09410f853 | |||
| 7615bdd746 | |||
| 36b864098a | |||
| b5a994760a | |||
| b03c3a6e88 | |||
| 2a829d4dfb | |||
| a7ba164768 | |||
| 0dc5c5a1ca | |||
| d7f42f568b | |||
| 9bfdbc71c2 | |||
| cab2d09737 | |||
| 1e044e7aef | |||
| ebba53c823 | |||
| abbcf7786a | |||
| 6c1ec7e2cc | |||
| 4bf3cde5fb | |||
| 992a024de6 | |||
| 8661a653d6 | |||
| 6a0a0841a1 | |||
| a72553c2de | |||
| 1d264fc54f | |||
| 80c83d5c9f | |||
| 77fedaaeaa | |||
| 636b209e9a | |||
| 520c89f4b2 | |||
| b6c93302b9 | |||
| 0340992932 | |||
| ed5e560ba4 | |||
| aa0443d0b5 | |||
| 0de7ef2550 | |||
| bbc277e81d | |||
| c3efe7a6f9 | |||
| 8d2f185d17 | |||
| ec88df3405 | |||
| 678ed53ab5 | |||
| 72d7fdd05f | |||
| eddb58c794 | |||
| 8a578b46a8 | |||
| 78c8b196bf | |||
| 2b5a5ec65a | |||
| b8ce799b12 | |||
| c0fc7fd464 | |||
| e01a7362f9 | |||
| 74a3f9210b | |||
| 95ed826629 | |||
| 22efffa11b | |||
| 0f2232648c | |||
| 14ea59a307 | |||
| 8d7cda5ec0 | |||
| cfa079de0d | |||
| dc87e32a63 | |||
| 2a689a871b | |||
| 76e189ac45 | |||
| 0e180bb229 | |||
| 98f023195b | |||
| 5dcc7d2fc6 | |||
| 2cf3569fcb | |||
| 6169a2d728 | |||
| ffa5c8701e | |||
| 48a51c54b1 | |||
| 74ce78dae9 | |||
| f2a737e0a8 | |||
| 6ae2590f50 | |||
| 1da71c72a9 | |||
| 010106406d | |||
| 992b5c012d | |||
| bf1fda008c | |||
| 5e5e9aedfc | |||
| 956a6d01b3 | |||
| 52ac41a82f | |||
| 04a9d9ced0 | |||
| 509000a996 | |||
| ce7015b224 | |||
| 53be77b5b9 | |||
| 42d24a5c3f | |||
| 494612235e | |||
| ca70050bec | |||
| 7a9be5d79b | |||
| 98bd9b0a46 | |||
| 7b3c7c182e | |||
| 77aaad1aa0 | |||
| 4041841c65 | |||
| cb994dc238 | |||
| baedf02d78 | |||
| 6efe2b7a46 | |||
| d5a8068261 | |||
| 73ac0af54e | |||
| 3c82ec9fdc | |||
| 81fef0e1d5 | |||
| 31886012ce | |||
| 086868d5b7 | |||
| f51f3d4f25 | |||
| 536903f3d8 | |||
| 75fdced678 | |||
| a739aed66a | |||
| 92bc00e134 | |||
| fb9d6991cd | |||
| bea7ed0254 | |||
| bece8863ef | |||
| 16a8bcf5c3 | |||
| 2dbbf977d4 | |||
| 23623aeb3d | |||
| 70a93f56d9 | |||
| ec87bcd682 | |||
| 3d3e221a5c | |||
| 4635a00caf | |||
| 64ad40fb96 | |||
| b4b93b122e | |||
| 5c4b7fd730 | |||
| 380d7e7b59 | |||
| 1cd9908afe | |||
| 6994d923a2 | |||
| 997bffc286 | |||
| 81aa9d4e1e | |||
| 45d568b77b | |||
| 4343dc517f | |||
| a2ccb8a35b | |||
| 8e81bab3bf | |||
| 466044eb77 | |||
| a65c138949 | |||
| 2c22df9c1d | |||
| 506e539c2d | |||
| 781521ca88 | |||
| 6d39f9f354 | |||
| e3359db4ca | |||
| c9c537af23 | |||
|   | 1cf6bd3c06 | ||
|   | d4edd8ee7a | ||
|   | 4b54cadcac | ||
| 8e94c5971e | |||
|   | aab2d6c955 | ||
|   | 34c9d24ffa | ||
| 1504375a25 | |||
| 346d041be4 | |||
| 4b4a804cb7 | |||
| 2ddf3e03e7 | |||
| fecac89a84 | |||
| 4f710c15d6 | |||
| 522e40f242 | |||
| 61a146a43d | |||
| 31b81d10e3 | |||
| 347e19a2c6 | |||
| ebcbab1a6c | |||
| ebacd14134 | |||
| d07f3b85a9 | |||
| 9d9cfd6fc7 | |||
| 0cd2e1f47d | |||
| 8723289204 | |||
| b471deef43 | |||
| 3263672934 | |||
| bbd1d06792 | |||
| 8bd69b12fd | |||
| fb94aacaa8 | |||
| ff0e19a72c | |||
| 937f2ee8b0 | |||
| afd4416eaa | |||
| a296370484 | |||
| 4b5680d969 | |||
| 590cffaa35 | |||
| d282e3c93a | |||
| e35fafad81 | |||
| de43ab03ca | |||
| a13784e93e | |||
| 8c09a0e73d | |||
| 4b49fda269 | |||
| 8c29a3e7d4 | |||
| 3b37723cd4 | |||
| cfc32d7ebd | |||
| ef8a47c1a1 | |||
| 516eda33d1 | |||
|   | 7f7a2af25f | ||
| d863305b94 | |||
| 6a685187d5 | |||
| 6ca85b21db | |||
| 6fd47572f2 | |||
| 5d02a5ee03 | |||
| 536f204a03 | |||
| efb2c8ba71 | |||
| 34926dde8c | |||
| b7c702f17d | |||
| b3677ea635 | |||
| 421c21cdec | |||
| e51ebdb28c | |||
| 4cfdebec77 | |||
| dd96f01a32 | |||
| 5083b2edc2 | |||
| 9670ace28c | |||
| 96e7c20a56 | |||
| a91e1b0c4c | |||
| 3115b4f19e | |||
| 6261831340 | |||
| 5f78698e06 | |||
| 7b0fe5aaf2 | |||
| 5ffb1edfbc | |||
|   | 214fb2dc0f | ||
|   | df3ae0179b | ||
| 0dc20ea52f | |||
| 3b11a72a81 | |||
| 2720b0444c | |||
|   | 040997b322 | ||
|   | 4fb3fa54ec | ||
| 949a543aa9 | |||
| 597a8d48ef | |||
| 65ad427413 | |||
| 9bf69e88b3 | |||
| 7cb0cf0d95 | |||
| d4e22b7f79 | |||
| 9e9390cff3 | |||
| a8f70fd498 | |||
| 17eafd7895 | |||
| 97e7537c4c | |||
| 4ddddc0933 | |||
| 01ab785c0d | |||
| 85dfd815dc | |||
| 428c772ea9 | |||
| 6c21581c7e | |||
| b14e59e29c | |||
| fa1170444d | |||
| e27556b98b | |||
| 6c6c0791fa | |||
| dda87b0e79 | |||
| b0139ccb13 | |||
| 89863c9fde | |||
| 22d1d44560 | |||
| 665c0271fd | |||
| f12361daa4 | |||
| 635a4e2568 | |||
| 8e9b4267f2 | |||
| 1d84f5e509 | |||
| 8eab09cd44 | |||
| f5731c1e3f | |||
| 5f8ed47165 | |||
| 5b2b28da19 | |||
| 608b75f1c8 | |||
| 689bd983bc | |||
| acef9b683e | |||
| 1d7f01dccb | |||
| 841eb204eb | |||
| 17f15429c8 | |||
| 354dfeda7f | |||
| db4f7d15a9 | |||
|   | e2c1e67843 | ||
| b87dee5c35 | |||
| 711488a412 | |||
| f4a5b78700 | |||
| 7d979a5749 | |||
| 396d4727dc | |||
| cd65a3f7b5 | |||
| e272675498 | |||
| 4051c2f9a5 | |||
| b05c9c5851 | |||
| 8080342620 | |||
| f1b7e01cbc | |||
| 3990b710c6 | |||
| 1800fe7c9e | |||
| 2c512a1ff7 | |||
| c7c98b12db | |||
| 11f859b71e | |||
| b70b522137 | |||
| 6db71366c6 | |||
| 4f481ce79b | |||
| 773ad4e1eb | |||
| bd1f12ffa0 | |||
| f64b60f3b5 | |||
| 07bb68ae9d | |||
| 2ae8a60b18 | |||
| b8f485d2d3 | |||
| 0cec511a9f | |||
| f2cf265d0c | |||
| 48caf96670 | |||
| 48e45941b8 | |||
| fc81b49d44 | |||
| 9df059e77a | |||
| 9587a86f32 | |||
| a45bf52fe7 | |||
| 085192de21 | |||
| bdb10523a4 | |||
| 11123b16fa | |||
| 0ec131e1a8 | |||
| 08d8993e5a | |||
| 69e529ce39 | |||
| e308b8b34f | |||
| bbd87c5707 | |||
| d834ec2e16 | |||
| 8256a4efea | |||
| 146b1c78aa | |||
| dbc91c7e6f | |||
|   | 9b74079bca | ||
| e56b0c9863 | |||
| 5fd52269af | |||
| 70e53daadf | |||
| 3c1a5ccdf4 | |||
| 5bf2906c51 | |||
| a3716050e9 | |||
| acc104cf95 | |||
| 2e76910134 | |||
| a97ea1b5d1 | |||
| 4833ebd54b | |||
| 4c13744a0c | |||
| b34b5705e0 | |||
| 77bf66996a | |||
| 0555329e86 | |||
| 070cb50a54 | |||
| aef3d2e139 | |||
| c970507c85 | |||
| 9c71572be8 | |||
| a02ccde5ff | |||
| 86421f8cc4 | |||
| 144e6e5412 | |||
| 38d74bcb21 | |||
| ae789e117b | |||
| f935a0efb7 | |||
| 2ea0689a05 | |||
| 57aee009b6 | |||
| 328c1fbaaf | |||
| 5d4a74c528 | |||
| adbdb2840b | |||
| 09b75367a7 | |||
| 2c548a3df6 | |||
| 8d66ee3830 | |||
| c3b9e1b665 | |||
| 7a0ddb1e0d | |||
| 0936181bf6 | |||
| 67fe55056f | |||
| 05b13e154f | |||
| 58b4db66cc | |||
| 3470750fc7 | |||
| 0e28cefc2d | |||
| e871eafcaf | |||
| 7ba0bec7f7 | |||
| 6a96d72b22 | |||
| 2567347f62 | |||
| c49f87aef5 | |||
| 396e17c0dc | |||
| 60604cd38b | |||
| c06785b98b | |||
| 0bac3ecc13 | |||
| e70bbb941b | |||
| 335217081c | |||
| a3e0cac006 | |||
| 9c09222762 | |||
| 3bbda09f84 | |||
| 7113369fca | |||
| 0316bafffc | |||
| d317612187 | |||
| ef61a836a4 | |||
| 4c449d5dc4 | |||
| 82645fb574 | |||
| 74eb054bc9 | |||
| 4a67756cf8 | |||
| 81dde67338 | |||
| f6f266acb5 | |||
| a565d750e3 | |||
| c824c0bdb5 | |||
| c4ce28e239 | |||
| beff6ef2dd | |||
| 2c112703a3 | |||
| d05b3cd544 | |||
| e16f3e5ccf | |||
| 1716990d26 | |||
| bc0949842e | |||
| 1cec746423 | |||
| a13f2218db | |||
| 075badc55a | |||
| 0ca62cbe91 | |||
| f3d595a974 | |||
| b1df34621e | |||
| bd85bfbcdc | |||
| 40d368602d | |||
| 53d9a5b3a4 | |||
| 5c0d46193f | |||
| 7e975dadfb | |||
| 71cdde4308 | |||
| 7e9a68bde1 | |||
| e5a2cfafa7 | |||
| 93dcb301f9 | |||
| f0248b2ec8 | |||
| 11a7546506 | |||
| 58b43637d1 | |||
| c3922747bb | |||
| e5b4e9800e | |||
| d5c851a5de | |||
| 50b45b2c56 | |||
| e675a95871 | |||
| 527c2f445d | |||
| e20feb5851 | |||
| 1a8550995b | |||
| 6dde146189 | |||
| 2f74920692 | |||
| 17af65c461 | |||
| ed996977f9 | |||
| 95a510c759 | |||
| 391bc395f0 | |||
| 0c043434e2 | |||
| 3a6d6a7ddd | |||
| 5f81ecfed2 | |||
| 49b7ef0382 | |||
| 61484aea70 | |||
| cb2ac10524 | |||
| 6ff8e8211a | |||
| 11f3218ea4 | |||
| ba0fc064d0 | |||
| 30a7d430d7 | |||
| 33687724bd | |||
| f9bce78d81 | |||
| a306e11cec | |||
| 66bf217ee8 | |||
| 2fc7709c56 | |||
| 0dee3c28aa | |||
| 21ac74f764 | |||
| 8ab637c402 | |||
| fab93d8ceb | |||
| 2a532dba86 | |||
| 2d2240244e | |||
| f34dc4a59c | |||
| 6a91788d9e | |||
| a47b6a02a0 | |||
| 61cfff0f91 | |||
| e0f30d7a96 | |||
| 8ce1eb7222 | |||
| 6e901de066 | |||
| f8849ee393 | |||
| 8495eea8ad | 
							
								
								
									
										7
									
								
								.gitignore
									
										
									
									
										vendored
									
									
								
							
							
						
						|  | @ -1,7 +1,14 @@ | ||||||
| *.pyc | *.pyc | ||||||
| *.pyo | *.pyo | ||||||
|  | *.swp | ||||||
|  | dist/ | ||||||
|  | var/ | ||||||
|  | *.egg-info | ||||||
| *.project | *.project | ||||||
| *.pydevproject | *.pydevproject | ||||||
|  | *.ropeproject | ||||||
| *.sublime-project | *.sublime-project | ||||||
| *.sublime-workspace | *.sublime-workspace | ||||||
|  | .env | ||||||
| .settings | .settings | ||||||
|  | adminuser.zcml | ||||||
|  |  | ||||||
							
								
								
									
										21
									
								
								LICENSE
									
										
									
									
									
										Normal file
									
								
							
							
						
						|  | @ -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. | ||||||
							
								
								
									
										222
									
								
								LICENSE.GPL
									
										
									
									
									
								
							
							
						
						|  | @ -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
									
								
							
							
						
						|  | @ -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
									
								
							
							
						
						|  | @ -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. | ||||||
							
								
								
									
										22
									
								
								__init__.py
									
										
									
									
									
								
							
							
						
						|  | @ -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$ |  | ||||||
| """ |  | ||||||
|  | @ -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 |  | ||||||
| 
 |  | ||||||
							
								
								
									
										14
									
								
								inst/bluebream/adminuser.zcml.in
									
										
									
									
									
										Normal 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> | ||||||
							
								
								
									
										11
									
								
								inst/bluebream/application.zcml
									
										
									
									
									
										Normal 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
									
								
							
							
						
						|  | @ -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
									
								
							
							
						
						|  | @ -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
									
								
							
							
						
						|  | @ -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) | ||||||
							
								
								
									
										4
									
								
								inst/bluebream/overrides.zcml
									
										
									
									
									
										Normal file
									
								
							
							
						
						|  | @ -0,0 +1,4 @@ | ||||||
|  | <configure xmlns="http://namespaces.zope.org/zope"> | ||||||
|  | 
 | ||||||
|  | </configure> | ||||||
|  | 
 | ||||||
							
								
								
									
										39
									
								
								inst/bluebream/zope.conf
									
										
									
									
									
										Normal 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> | ||||||
							
								
								
									
										14
									
								
								inst/loops/adminuser.zcml.in
									
										
									
									
									
										Normal 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> | ||||||
							
								
								
									
										33
									
								
								inst/loops/application.zcml
									
										
									
									
									
										Normal 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> | ||||||
							
								
								
									
										45
									
								
								inst/loops/config.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						|  | @ -0,0 +1,45 @@ | ||||||
|  | # 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, | ||||||
|  |     op_project_scope='urn:zitadel:iam:org:project:id:zitadel:aud', | ||||||
|  |     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), | ||||||
|  |     private_key_file=getenv('OIDC_SERVICE_USER_PRIVATE_KEY_FILE', '.private-key.json'), | ||||||
|  |     organization_id=getenv('OIDC_ORGANIZATION_ID', '12346'), | ||||||
|  |     project_id=getenv('OIDC_PROJECT_ID', None), | ||||||
|  | ) | ||||||
|  | 
 | ||||||
							
								
								
									
										12
									
								
								inst/loops/env.in
									
										
									
									
									
										Normal 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 | ||||||
							
								
								
									
										4
									
								
								inst/loops/overrides.zcml
									
										
									
									
									
										Normal file
									
								
							
							
						
						|  | @ -0,0 +1,4 @@ | ||||||
|  | <configure xmlns="http://namespaces.zope.org/zope"> | ||||||
|  | 
 | ||||||
|  | </configure> | ||||||
|  | 
 | ||||||
							
								
								
									
										7
									
								
								inst/loops/runserver.sh
									
										
									
									
									
										Executable 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
									
								
							
							
						
						|  | @ -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
									
								
							
							
						
						|  | @ -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()" | ||||||
|  | 
 | ||||||
|  | @ -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) |  | ||||||
|  | @ -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) |  | ||||||
|  | @ -1 +0,0 @@ | ||||||
| '''package loops.knowledge.qualification''' |  | ||||||
|  | @ -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) |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
|  | @ -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> |  | ||||||
|  | @ -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') |  | ||||||
| 
 |  | ||||||
|  | @ -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. |  | ||||||
|     """ |  | ||||||
| 
 |  | ||||||
|  | @ -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' |  | ||||||
| 
 |  | ||||||
|  | @ -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"> </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> |  | ||||||
|  | @ -23,7 +23,7 @@ with lower-level aspects like type or state management. | ||||||
| 
 | 
 | ||||||
| Let's also import some common stuff needed later. | Let's also import some common stuff needed later. | ||||||
| 
 | 
 | ||||||
|   >>> from loops.common import adapted |   >>> from loops.common import adapted, baseObject | ||||||
|   >>> from loops.setup import addAndConfigureObject |   >>> from loops.setup import addAndConfigureObject | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | @ -48,14 +48,14 @@ top-level loops container and a concept manager: | ||||||
|   >>> cc1 = Concept() |   >>> cc1 = Concept() | ||||||
|   >>> concepts['cc1'] = cc1 |   >>> concepts['cc1'] = cc1 | ||||||
|   >>> cc1.title |   >>> cc1.title | ||||||
|   u'' |   '' | ||||||
|   >>> loopsRoot.getLoopsUri(cc1) |   >>> loopsRoot.getLoopsUri(cc1) | ||||||
|   u'.loops/concepts/cc1' |   '.loops/concepts/cc1' | ||||||
| 
 | 
 | ||||||
|   >>> cc2 = Concept(u'Zope 3') |   >>> cc2 = Concept('Zope 3') | ||||||
|   >>> concepts['cc2'] = cc2 |   >>> concepts['cc2'] = cc2 | ||||||
|   >>> cc2.title |   >>> cc2.title | ||||||
|   u'Zope 3' |   'Zope 3' | ||||||
| 
 | 
 | ||||||
| Now we want to relate the second concept to the first one. | Now we want to relate the second concept to the first one. | ||||||
| 
 | 
 | ||||||
|  | @ -73,11 +73,11 @@ ConceptRelation): | ||||||
| We can now ask our concepts for their related child and parent concepts: | We can now ask our concepts for their related child and parent concepts: | ||||||
| 
 | 
 | ||||||
|   >>> [getName(c) for c in cc1.getChildren()] |   >>> [getName(c) for c in cc1.getChildren()] | ||||||
|   [u'cc2'] |   ['cc2'] | ||||||
|   >>> len(cc1.getParents()) |   >>> len(cc1.getParents()) | ||||||
|   0 |   0 | ||||||
|   >>> [getName(p) for p in cc2.getParents()] |   >>> [getName(p) for p in cc2.getParents()] | ||||||
|   [u'cc1'] |   ['cc1'] | ||||||
| 
 | 
 | ||||||
|   >>> len(cc2.getChildren()) |   >>> len(cc2.getChildren()) | ||||||
|   0 |   0 | ||||||
|  | @ -90,24 +90,24 @@ a special predicate 'hasType'. | ||||||
|   >>> typeObject = concepts['type'] |   >>> typeObject = concepts['type'] | ||||||
|   >>> typeObject.setConceptType(typeObject) |   >>> typeObject.setConceptType(typeObject) | ||||||
|   >>> typeObject.getConceptType().title |   >>> typeObject.getConceptType().title | ||||||
|   u'Type' |   'Type' | ||||||
| 
 | 
 | ||||||
|   >>> concepts['unknown'] = Concept(u'Unknown Type') |   >>> concepts['unknown'] = Concept('Unknown Type') | ||||||
|   >>> unknown = concepts['unknown'] |   >>> unknown = concepts['unknown'] | ||||||
|   >>> unknown.setConceptType(typeObject) |   >>> unknown.setConceptType(typeObject) | ||||||
|   >>> unknown.getConceptType().title |   >>> unknown.getConceptType().title | ||||||
|   u'Type' |   'Type' | ||||||
| 
 | 
 | ||||||
|   >>> cc1.setConceptType(unknown) |   >>> cc1.setConceptType(unknown) | ||||||
|   >>> cc1.getConceptType().title |   >>> cc1.getConceptType().title | ||||||
|   u'Unknown Type' |   'Unknown Type' | ||||||
| 
 | 
 | ||||||
|   >>> concepts['topic'] = Concept(u'Topic') |   >>> concepts['topic'] = Concept('Topic') | ||||||
|   >>> topic = concepts['topic'] |   >>> topic = concepts['topic'] | ||||||
|   >>> topic.setConceptType(typeObject) |   >>> topic.setConceptType(typeObject) | ||||||
|   >>> cc1.setConceptType(topic) |   >>> cc1.setConceptType(topic) | ||||||
|   >>> cc1.getConceptType().title |   >>> cc1.getConceptType().title | ||||||
|   u'Topic' |   'Topic' | ||||||
| 
 | 
 | ||||||
| We get a list of types using the ConceptTypeSourceList. | We get a list of types using the ConceptTypeSourceList. | ||||||
| In order for the type machinery to work we first have to provide a | In order for the type machinery to work we first have to provide a | ||||||
|  | @ -124,7 +124,7 @@ type manager. | ||||||
|   >>> from loops.concept import ConceptTypeSourceList |   >>> from loops.concept import ConceptTypeSourceList | ||||||
|   >>> types = ConceptTypeSourceList(cc1) |   >>> types = ConceptTypeSourceList(cc1) | ||||||
|   >>> sorted(t.title for t in types) |   >>> sorted(t.title for t in types) | ||||||
|   [u'Customer', u'Domain', u'Predicate', u'Topic', u'Type', u'Unknown Type'] |   ['Customer', 'Domain', 'Predicate', 'Topic', 'Type', 'Unknown Type'] | ||||||
| 
 | 
 | ||||||
| Using a PredicateSourceList we can retrieve a list of the available | Using a PredicateSourceList we can retrieve a list of the available | ||||||
| predicates. | predicates. | ||||||
|  | @ -136,7 +136,7 @@ Note that the 'hasType' predicate is suppressed from this list as the | ||||||
| corresponding relation is only assigned via the conceptType attribute: | corresponding relation is only assigned via the conceptType attribute: | ||||||
| 
 | 
 | ||||||
|   >>> sorted(t.title for t in predicates) |   >>> sorted(t.title for t in predicates) | ||||||
|   [u'subobject'] |   ['subobject'] | ||||||
| 
 | 
 | ||||||
| Concept Views | Concept Views | ||||||
| ------------- | ------------- | ||||||
|  | @ -146,7 +146,7 @@ Concept Views | ||||||
| 
 | 
 | ||||||
|   >>> children = list(view.children()) |   >>> children = list(view.children()) | ||||||
|   >>> [c.title for c in children] |   >>> [c.title for c in children] | ||||||
|   [u'Zope 3'] |   ['Zope 3'] | ||||||
| 
 | 
 | ||||||
| The token attribute provided with the items returned by the children() and | The token attribute provided with the items returned by the children() and | ||||||
| parents() methods identifies identifies not only the item itself but | parents() methods identifies identifies not only the item itself but | ||||||
|  | @ -154,19 +154,19 @@ also the relationship to the context object using a combination | ||||||
| of URIs to item and the predicate of the relationship: | of URIs to item and the predicate of the relationship: | ||||||
| 
 | 
 | ||||||
|   >>> [c.token for c in children] |   >>> [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 | There is also a concept configuration view that allows updating the | ||||||
| underlying context object: | underlying context object: | ||||||
| 
 | 
 | ||||||
|   >>> cc3 = Concept(u'loops for Zope 3') |   >>> cc3 = Concept('loops for Zope 3') | ||||||
|   >>> concepts['cc3'] = cc3 |   >>> concepts['cc3'] = cc3 | ||||||
|   >>> view = ConceptConfigureView(cc1, |   >>> view = ConceptConfigureView(cc1, | ||||||
|   ...           TestRequest(action='assign', tokens=['.loops/concepts/cc3'])) |   ...           TestRequest(action='assign', tokens=['.loops/concepts/cc3'])) | ||||||
|   >>> view.update() |   >>> view.update() | ||||||
|   True |   True | ||||||
|   >>> sorted(c.title for c in cc1.getChildren()) |   >>> sorted(c.title for c in cc1.getChildren()) | ||||||
|   [u'Zope 3', u'loops for Zope 3'] |   ['Zope 3', 'loops for Zope 3'] | ||||||
| 
 | 
 | ||||||
|   >>> input = {'action': 'remove', 'qualifier': 'children', |   >>> input = {'action': 'remove', 'qualifier': 'children', | ||||||
|   ...          'form.button.submit': 'Remove Chiildren', |   ...          'form.button.submit': 'Remove Chiildren', | ||||||
|  | @ -175,18 +175,18 @@ underlying context object: | ||||||
|   >>> view.update() |   >>> view.update() | ||||||
|   True |   True | ||||||
|   >>> sorted(c.title for c in cc1.getChildren()) |   >>> sorted(c.title for c in cc1.getChildren()) | ||||||
|   [u'loops for Zope 3'] |   ['loops for Zope 3'] | ||||||
| 
 | 
 | ||||||
| We can also create a new concept and assign it. | We can also create a new concept and assign it. | ||||||
| 
 | 
 | ||||||
|   >>> params = {'action': 'create', 'create.name': 'cc4', |   >>> params = {'action': 'create', 'create.name': 'cc4', | ||||||
|   ...           'create.title': u'New concept', |   ...           'create.title': 'New concept', | ||||||
|   ...           'create.type': '.loops/concepts/topic'} |   ...           'create.type': '.loops/concepts/topic'} | ||||||
|   >>> view = ConceptConfigureView(cc1, TestRequest(**params)) |   >>> view = ConceptConfigureView(cc1, TestRequest(**params)) | ||||||
|   >>> view.update() |   >>> view.update() | ||||||
|   True |   True | ||||||
|   >>> sorted(c.title for c in cc1.getChildren()) |   >>> sorted(c.title for c in cc1.getChildren()) | ||||||
|   [u'New concept', u'loops for Zope 3'] |   ['New concept', 'loops for Zope 3'] | ||||||
| 
 | 
 | ||||||
| The concept configuration view provides methods for displaying concept | The concept configuration view provides methods for displaying concept | ||||||
| types and predicates. | types and predicates. | ||||||
|  | @ -198,15 +198,15 @@ types and predicates. | ||||||
|   >>> component.provideAdapter(LoopsTerms, (IIterableSource, IBrowserRequest), ITerms) |   >>> component.provideAdapter(LoopsTerms, (IIterableSource, IBrowserRequest), ITerms) | ||||||
| 
 | 
 | ||||||
|   >>> sorted((t.title, t.token) for t in view.conceptTypes()) |   >>> sorted((t.title, t.token) for t in view.conceptTypes()) | ||||||
|   [(u'Customer', '.loops/concepts/customer'), |   [('Customer', '.loops/concepts/customer'), | ||||||
|    (u'Domain', '.loops/concepts/domain'), |    ('Domain', '.loops/concepts/domain'), | ||||||
|    (u'Predicate', '.loops/concepts/predicate'), |    ('Predicate', '.loops/concepts/predicate'), | ||||||
|    (u'Topic', '.loops/concepts/topic'), |    ('Topic', '.loops/concepts/topic'), | ||||||
|    (u'Type', '.loops/concepts/type'), |    ('Type', '.loops/concepts/type'), | ||||||
|    (u'Unknown Type', '.loops/concepts/unknown')] |    ('Unknown Type', '.loops/concepts/unknown')] | ||||||
| 
 | 
 | ||||||
|   >>> sorted((t.title, t.token) for t in view.predicates()) |   >>> sorted((t.title, t.token) for t in view.predicates()) | ||||||
|   [(u'subobject', '.loops/concepts/standard')] |   [('subobject', '.loops/concepts/standard')] | ||||||
| 
 | 
 | ||||||
| Index attributes adapter | Index attributes adapter | ||||||
| ------------------------ | ------------------------ | ||||||
|  | @ -214,10 +214,10 @@ Index attributes adapter | ||||||
|   >>> from loops.concept import IndexAttributes |   >>> from loops.concept import IndexAttributes | ||||||
|   >>> idx = IndexAttributes(cc2) |   >>> idx = IndexAttributes(cc2) | ||||||
|   >>> idx.text() |   >>> idx.text() | ||||||
|   u'cc2 Zope 3' |   'cc2 Zope 3' | ||||||
| 
 | 
 | ||||||
|   >>> idx.title() |   >>> idx.title() | ||||||
|   u'cc2 Zope 3' |   'cc2 Zope 3' | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| Resources and what they have to do with Concepts | Resources and what they have to do with Concepts | ||||||
|  | @ -233,27 +233,27 @@ A common type of resource is a document: | ||||||
| 
 | 
 | ||||||
|   >>> from loops.interfaces import IDocument |   >>> from loops.interfaces import IDocument | ||||||
|   >>> from loops.resource import Document |   >>> from loops.resource import Document | ||||||
|   >>> doc1 = Document(u'Zope Info') |   >>> doc1 = Document('Zope Info') | ||||||
|   >>> resources['doc1'] = doc1 |   >>> resources['doc1'] = doc1 | ||||||
|   >>> doc1.title |   >>> doc1.title | ||||||
|   u'Zope Info' |   'Zope Info' | ||||||
|   >>> doc1.data |   >>> doc1.data | ||||||
|   u'' |   '' | ||||||
|   >>> doc1.contentType |   >>> doc1.contentType | ||||||
|   u'' |   '' | ||||||
| 
 | 
 | ||||||
| We can also directly use Resource objects; these behave like files. | We can also directly use Resource objects; these behave like files. | ||||||
| In fact, by using resource types we can explicitly assign a resource | In fact, by using resource types we can explicitly assign a resource | ||||||
| the 'file' type, but we will use this feature later: | the 'file' type, but we will use this feature later: | ||||||
| 
 | 
 | ||||||
|   >>> img = Resource(u'A png Image') |   >>> img = Resource('A png Image') | ||||||
| 
 | 
 | ||||||
| For testing we use some simple files from the tests directory: | For testing we use some simple files from the tests directory: | ||||||
| 
 | 
 | ||||||
|   >>> from loops import tests |   >>> from loops import tests | ||||||
|   >>> import os |   >>> import os | ||||||
|   >>> path = os.path.join(*tests.__path__) |   >>> path = os.path.join(*tests.__path__) | ||||||
|   >>> img.data = open(os.path.join(path, 'test_icon.png')).read() |   >>> img.data = open(os.path.join(path, 'test_icon.png'), 'rb').read() | ||||||
|   >>> img.getSize() |   >>> img.getSize() | ||||||
|   381 |   381 | ||||||
|   >>> img.getImageSize() |   >>> img.getImageSize() | ||||||
|  | @ -261,8 +261,8 @@ For testing we use some simple files from the tests directory: | ||||||
|   >>> img.contentType |   >>> img.contentType | ||||||
|   'image/png' |   'image/png' | ||||||
| 
 | 
 | ||||||
|   >>> pdf = Resource(u'A pdf File') |   >>> pdf = Resource('A pdf File') | ||||||
|   >>> pdf.data = open(os.path.join(path, 'test.pdf')).read() |   >>> pdf.data = open(os.path.join(path, 'test.pdf'), 'rb').read() | ||||||
|   >>> pdf.getSize() |   >>> pdf.getSize() | ||||||
|   25862 |   25862 | ||||||
|   >>> pdf.getImageSize() |   >>> pdf.getImageSize() | ||||||
|  | @ -287,7 +287,7 @@ from concepts to resources: | ||||||
|   ...         'tokens': ['.loops/resources/doc1:.loops/concepts/standard']} |   ...         'tokens': ['.loops/resources/doc1:.loops/concepts/standard']} | ||||||
|   >>> view = ConceptConfigureView(cc1, TestRequest(form=form)) |   >>> view = ConceptConfigureView(cc1, TestRequest(form=form)) | ||||||
|   >>> [getName(r.context) for r in view.resources()] |   >>> [getName(r.context) for r in view.resources()] | ||||||
|   [u'doc1'] |   ['doc1'] | ||||||
|   >>> view.update() |   >>> view.update() | ||||||
|   True |   True | ||||||
|   >>> len(cc1.getResources()) |   >>> len(cc1.getResources()) | ||||||
|  | @ -316,10 +316,10 @@ Index attributes adapter | ||||||
|   >>> component.provideAdapter(FileAdapter, provides=IFile) |   >>> component.provideAdapter(FileAdapter, provides=IFile) | ||||||
|   >>> idx = IndexAttributes(doc1) |   >>> idx = IndexAttributes(doc1) | ||||||
|   >>> idx.text() |   >>> idx.text() | ||||||
|   u'' |   '' | ||||||
| 
 | 
 | ||||||
|   >>> idx.title() |   >>> idx.title() | ||||||
|   u'doc1 Zope Info' |   'doc1 Zope Info' | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| Views/Nodes: Menus, Menu Items, Listings, Pages, etc | Views/Nodes: Menus, Menu Items, Listings, Pages, etc | ||||||
|  | @ -343,14 +343,14 @@ The view manager has already been created during setup. | ||||||
| The view space is typically built up with nodes; a node may be a top-level | The view space is typically built up with nodes; a node may be a top-level | ||||||
| menu that may contain other nodes as menu or content items: | menu that may contain other nodes as menu or content items: | ||||||
| 
 | 
 | ||||||
|   >>> m1 = views['m1'] = Node(u'Menu') |   >>> m1 = views['m1'] = Node('Menu') | ||||||
|   >>> m11 = m1['m11'] = Node(u'Zope') |   >>> m11 = m1['m11'] = Node('Zope') | ||||||
|   >>> m111 = m11['m111'] = Node(u'Zope in General') |   >>> m111 = m11['m111'] = Node('Zope in General') | ||||||
|   >>> m112 = m11['m112'] = Node(u'Zope 3') |   >>> m112 = m11['m112'] = Node('Zope 3') | ||||||
|   >>> m112.title |   >>> m112.title | ||||||
|   u'Zope 3' |   'Zope 3' | ||||||
|   >>> m112.description |   >>> m112.description | ||||||
|   u'' |   '' | ||||||
| 
 | 
 | ||||||
| There are a few convienence methods for accessing parent and child nodes: | There are a few convienence methods for accessing parent and child nodes: | ||||||
| 
 | 
 | ||||||
|  | @ -359,7 +359,7 @@ There are a few convienence methods for accessing parent and child nodes: | ||||||
|   >>> m11.getParentNode() is m1 |   >>> m11.getParentNode() is m1 | ||||||
|   True |   True | ||||||
|   >>> [getName(child) for child in m11.getChildNodes()] |   >>> [getName(child) for child in m11.getChildNodes()] | ||||||
|   [u'm111', u'm112'] |   ['m111', 'm112'] | ||||||
| 
 | 
 | ||||||
| What is returned by these may be controlled by the nodeType attribute: | What is returned by these may be controlled by the nodeType attribute: | ||||||
| 
 | 
 | ||||||
|  | @ -444,13 +444,13 @@ Node Views | ||||||
|   >>> page = view.page |   >>> page = view.page | ||||||
|   >>> items = page.textItems |   >>> items = page.textItems | ||||||
|   >>> for item in items: |   >>> for item in items: | ||||||
|   ...     print item.url, item.editable |   ...     print(item.url, item.editable) | ||||||
|   http://127.0.0.1/loops/views/m1/m11/m112 False |   http://127.0.0.1/loops/views/m1/m11/m112 False | ||||||
| 
 | 
 | ||||||
|   >>> menu = view.menu |   >>> menu = view.menu | ||||||
|   >>> items = menu.menuItems |   >>> items = menu.menuItems | ||||||
|   >>> for item in items: |   >>> for item in items: | ||||||
|   ...     print item.url, view.selected(item) |   ...     print(item.url, view.selected(item)) | ||||||
|   http://127.0.0.1/loops/views/m1/m11 True |   http://127.0.0.1/loops/views/m1/m11 True | ||||||
| 
 | 
 | ||||||
| A NodeView provides an itemNum attribute that may be used to count elements | A NodeView provides an itemNum attribute that may be used to count elements | ||||||
|  | @ -493,14 +493,14 @@ view; these views we have to provide as multi-adapters: | ||||||
|   >>> len(tt) |   >>> len(tt) | ||||||
|   9 |   9 | ||||||
|   >>> sorted((t.token, t.title) for t in view.targetTypes())[1] |   >>> sorted((t.token, t.title) for t in view.targetTypes())[1] | ||||||
|   ('.loops/concepts/domain', u'Domain') |   ('.loops/concepts/domain', 'Domain') | ||||||
|   >>> view.update() |   >>> view.update() | ||||||
|   True |   True | ||||||
|   >>> sorted(resources.keys()) |   >>> sorted(resources.keys()) | ||||||
|   [u'd001.txt', u'd002.txt', u'd003.txt', u'doc1', u'm1.m11.m111'] |   ['d001.txt', 'd002.txt', 'd003.txt', 'doc1', 'm1.m11.m111'] | ||||||
| 
 | 
 | ||||||
|   >>> view.target.title, view.target.token |   >>> view.target.title, view.target.token | ||||||
|   ('New Resource', u'.loops/resources/m1.m11.m111') |   ('New Resource', '.loops/resources/m1.m11.m111') | ||||||
| 
 | 
 | ||||||
| A node object provides the targetSchema of its target: | A node object provides the targetSchema of its target: | ||||||
| 
 | 
 | ||||||
|  | @ -537,28 +537,28 @@ view for rendering.) | ||||||
|   >>> component.provideAdapter(LoopsType) |   >>> component.provideAdapter(LoopsType) | ||||||
|   >>> view = NodeView(m112, TestRequest()) |   >>> view = NodeView(m112, TestRequest()) | ||||||
|   >>> view.renderTarget() |   >>> view.renderTarget() | ||||||
|   u'<pre></pre>' |   '<pre></pre>' | ||||||
|   >>> doc1.data = u'Test data\n\nAnother paragraph' |   >>> doc1.data = 'Test data\n\nAnother paragraph' | ||||||
|   >>> view.renderTarget() |   >>> view.renderTarget() | ||||||
|   u'<pre>Test data\n\nAnother paragraph</pre>' |   '<pre>Test data\n\nAnother paragraph</pre>' | ||||||
| 
 | 
 | ||||||
|   >>> doc1.contentType = 'text/restructured' |   >>> doc1.contentType = 'text/restructured' | ||||||
|   >>> doc1.data = u'Test data\n\nAnother `paragraph <para>`_' |   >>> doc1.data = 'Test data\n\nAnother `paragraph <para>`_' | ||||||
| 
 | 
 | ||||||
|   >>> from loops.wiki.base import wikiLinksActive |   >>> from loops.wiki.base import wikiLinksActive | ||||||
|   >>> wikiLinksActive(loopsRoot) |   >>> wikiLinksActive(loopsRoot) | ||||||
|   False |   False | ||||||
| 
 | 
 | ||||||
|   >>> view.renderTarget() |   >>> view.renderTarget() | ||||||
|   u'<p>Test data</p>\n<p>Another <a class="reference external" href="para">paragraph</a></p>\n' |   '<p>Test data</p>\n<p>Another <a class="reference external" href="para">paragraph</a></p>\n' | ||||||
| 
 | 
 | ||||||
| u'<p>Test data</p>\n<p>Another <a class="reference create" | '<p>Test data</p>\n<p>Another <a class="reference create" | ||||||
|     href="http://127.0.0.1/loops/wiki/create.html?linkid=0000001">?paragraph</a></p>\n' |     href="http://127.0.0.1/loops/wiki/create.html?linkid=0000001">?paragraph</a></p>\n' | ||||||
| 
 | 
 | ||||||
|   >>> #links = loopsRoot.getRecordManager()['links'] |   >>> #links = loopsRoot.getRecordManager()['links'] | ||||||
|   >>> #links['0000001'] |   >>> #links['0000001'] | ||||||
| 
 | 
 | ||||||
| <Link ['42', 1, '', '... ...', u'para', None]: {}> | <Link ['42', 1, '', '... ...', 'para', None]: {}> | ||||||
| 
 | 
 | ||||||
| If the target object is removed from its container all references | If the target object is removed from its container all references | ||||||
| to it are removed as well. (To make this work we have to handle | to it are removed as well. (To make this work we have to handle | ||||||
|  | @ -632,7 +632,7 @@ Let's add some more nodes and reorder them: | ||||||
|   >>> m114 = Node() |   >>> m114 = Node() | ||||||
|   >>> m11['m114'] = m114 |   >>> m11['m114'] = m114 | ||||||
|   >>> m11.keys() |   >>> 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, | A special management view provides methods for moving objects down, up, | ||||||
| to the bottom, and to the top. | to the bottom, and to the top. | ||||||
|  | @ -641,10 +641,10 @@ to the bottom, and to the top. | ||||||
|   >>> view = OrderedContainerView(m11, TestRequest()) |   >>> view = OrderedContainerView(m11, TestRequest()) | ||||||
|   >>> view.move_bottom(('m113',)) |   >>> view.move_bottom(('m113',)) | ||||||
|   >>> m11.keys() |   >>> m11.keys() | ||||||
|   [u'm111', u'm112', u'm114', u'm113'] |   ['m111', 'm112', 'm114', 'm113'] | ||||||
|   >>> view.move_up(('m114',), 1) |   >>> view.move_up(('m114',), 1) | ||||||
|   >>> m11.keys() |   >>> m11.keys() | ||||||
|   [u'm111', u'm114', u'm112', u'm113'] |   ['m111', 'm114', 'm112', 'm113'] | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| Breadcrumbs | Breadcrumbs | ||||||
|  | @ -661,9 +661,9 @@ Breadcrumbs | ||||||
|   >>> view = NodeView(m114, request) |   >>> view = NodeView(m114, request) | ||||||
|   >>> request.annotations.setdefault('loops.view', {})['nodeView'] = view |   >>> request.annotations.setdefault('loops.view', {})['nodeView'] = view | ||||||
|   >>> view.breadcrumbs() |   >>> view.breadcrumbs() | ||||||
|   [{'url': 'http://127.0.0.1/loops/views/m1', 'label': u'Menu'}, |   [{'label': 'Menu', 'url': 'http://127.0.0.1/loops/views/m1'}, | ||||||
|    {'url': 'http://127.0.0.1/loops/views/m1/m11', 'label': u'Zope'}, |    {'label': 'Zope', 'url': 'http://127.0.0.1/loops/views/m1/m11'}, | ||||||
|    {'url': 'http://127.0.0.1/loops/views/m1/m11/m114', 'label': u''}] |    {'label': '', 'url': 'http://127.0.0.1/loops/views/m1/m11/m114'}] | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| End-user Forms and Special Views | End-user Forms and Special Views | ||||||
|  | @ -705,8 +705,8 @@ been created during setup. | ||||||
|   >>> custType = TypeConcept(customer) |   >>> custType = TypeConcept(customer) | ||||||
|   >>> custType.options |   >>> custType.options | ||||||
|   [] |   [] | ||||||
|   >>> cust1 = concepts['cust1'] = Concept(u'Zope Corporation') |   >>> cust1 = concepts['cust1'] = Concept('Zope Corporation') | ||||||
|   >>> cust2 = concepts['cust2'] = Concept(u'cyberconcepts') |   >>> cust2 = concepts['cust2'] = Concept('cyberconcepts') | ||||||
|   >>> for c in (cust1, cust2): c.conceptType = customer |   >>> for c in (cust1, cust2): c.conceptType = customer | ||||||
|   >>> custType.options = ('qualifier:assign',) |   >>> custType.options = ('qualifier:assign',) | ||||||
|   >>> ConceptType(cust1).qualifiers |   >>> ConceptType(cust1).qualifiers | ||||||
|  | @ -714,7 +714,7 @@ been created during setup. | ||||||
| 
 | 
 | ||||||
|   >>> form = CreateObjectForm(m112, TestRequest()) |   >>> form = CreateObjectForm(m112, TestRequest()) | ||||||
|   >>> form.presetTypesForAssignment |   >>> form.presetTypesForAssignment | ||||||
|   [{'token': 'loops:concept:customer', 'title': u'Customer'}] |   [{'title': 'Customer', 'token': 'loops:concept:customer'}] | ||||||
| 
 | 
 | ||||||
| If the node's target is a type concept we don't get any assignments because | If the node's target is a type concept we don't get any assignments because | ||||||
| it does not make much sense to assign resources or other concepts as | it does not make much sense to assign resources or other concepts as | ||||||
|  | @ -736,16 +736,18 @@ on data provided in this form: | ||||||
|   >>> note_tc = concepts['note'] |   >>> note_tc = concepts['note'] | ||||||
| 
 | 
 | ||||||
|   >>> component.provideAdapter(NameChooser) |   >>> component.provideAdapter(NameChooser) | ||||||
|   >>> request = TestRequest(form={'title': u'Test Note', |   >>> request = TestRequest(form={'title': 'Test Note', | ||||||
|   ...                             'form.type': u'.loops/concepts/note'}) |   ...                             'form.type': '.loops/concepts/note', | ||||||
|  |   ...                             'contentType': 'text/restructured', | ||||||
|  |   ...                             'linkUrl': 'http://'}) | ||||||
|   >>> view = NodeView(m112, request) |   >>> view = NodeView(m112, request) | ||||||
|   >>> cont = CreateObject(view, request) |   >>> cont = CreateObject(view, request) | ||||||
|   >>> cont.update() |   >>> cont.update() | ||||||
|   False |   False | ||||||
|   >>> sorted(resources.keys()) |   >>> sorted(resources.keys()) | ||||||
|   [...u'test_note'...] |   [...'test_note'...] | ||||||
|   >>> resources['test_note'].title |   >>> resources['test_note'].title | ||||||
|   u'Test Note' |   'Test Note' | ||||||
| 
 | 
 | ||||||
| If there is a concept selected in the combo box we assign this to the newly | If there is a concept selected in the combo box we assign this to the newly | ||||||
| created object: | created object: | ||||||
|  | @ -753,8 +755,8 @@ created object: | ||||||
|   >>> from loops import util |   >>> from loops import util | ||||||
|   >>> topicUid = util.getUidForObject(topic) |   >>> topicUid = util.getUidForObject(topic) | ||||||
|   >>> predicateUid = util.getUidForObject(concepts.getDefaultPredicate()) |   >>> predicateUid = util.getUidForObject(concepts.getDefaultPredicate()) | ||||||
|   >>> request = TestRequest(form={'title': u'Test Note', |   >>> request = TestRequest(form={'title': 'Test Note', | ||||||
|   ...                             'form.type': u'.loops/concepts/note', |   ...                             'form.type': '.loops/concepts/note', | ||||||
|   ...                             'form.assignments.selected': |   ...                             'form.assignments.selected': | ||||||
|   ...                                   [':'.join((topicUid, predicateUid))]}) |   ...                                   [':'.join((topicUid, predicateUid))]}) | ||||||
|   >>> view = NodeView(m112, request) |   >>> view = NodeView(m112, request) | ||||||
|  | @ -762,22 +764,22 @@ created object: | ||||||
|   >>> cont.update() |   >>> cont.update() | ||||||
|   False |   False | ||||||
|   >>> sorted(resources.keys()) |   >>> sorted(resources.keys()) | ||||||
|   [...u'test_note-2'...] |   [...'test_note-2'...] | ||||||
|   >>> note = resources['test_note-2'] |   >>> note = resources['test_note-2'] | ||||||
|   >>> sorted(t.__name__ for t in note.getConcepts()) |   >>> sorted(t.__name__ for t in note.getConcepts()) | ||||||
|   [u'note', u'topic'] |   ['note', 'topic'] | ||||||
| 
 | 
 | ||||||
| When creating an object its name may be automatically generated using the title | When creating an object its name may be automatically generated using the title | ||||||
| of the object. Let's make sure that the name chooser also handles special | of the object. Let's make sure that the name chooser also handles special | ||||||
| and possibly critcal cases: | and possibly critcal cases: | ||||||
| 
 | 
 | ||||||
|   >>> nc = NameChooser(resources) |   >>> nc = NameChooser(resources) | ||||||
|   >>> nc.chooseName(u'', Resource(u'abc: (cde)')) |   >>> nc.chooseName('', Resource('abc: (cde)')) | ||||||
|   u'abc__cde' |   'abc__cde' | ||||||
|   >>> nc.chooseName(u'', Resource(u'\xdcml\xe4ut')) |   >>> nc.chooseName('', Resource('\xdcml\xe4ut')) | ||||||
|   u'uemlaeut' |   'uemlaeut' | ||||||
|   >>> nc.chooseName(u'', Resource(u'A very very loooooong title')) |   >>> nc.chooseName('', Resource('A very very loooooong title')) | ||||||
|   u'a_title' |   'a_title' | ||||||
| 
 | 
 | ||||||
| Editing an Object | Editing an Object | ||||||
| ----------------- | ----------------- | ||||||
|  | @ -794,7 +796,7 @@ that in turns calls formlibs ``setUpWidgets()``. | ||||||
| The new technique uses the ``fields`` and ``data`` attributes... | The new technique uses the ``fields`` and ``data`` attributes... | ||||||
| 
 | 
 | ||||||
|   >>> for f in view.fields: |   >>> for f in view.fields: | ||||||
|   ...     print f.name, f.fieldType, f.required, f.vocabulary |   ...     print(f.name, f.fieldType, f.required, f.vocabulary) | ||||||
|   title textline True None |   title textline True None | ||||||
|   data textarea False None |   data textarea False None | ||||||
|   contentType dropdown True <...SimpleVocabulary object...> |   contentType dropdown True <...SimpleVocabulary object...> | ||||||
|  | @ -802,22 +804,22 @@ The new technique uses the ``fields`` and ``data`` attributes... | ||||||
|   linkText textline False None |   linkText textline False None | ||||||
| 
 | 
 | ||||||
|   >>> view.data |   >>> view.data | ||||||
|   {'linkUrl': u'http://', 'contentType': 'text/restructured', 'data': u'', |   {'title': 'Test Note', 'data': '', 'contentType': 'text/restructured',  | ||||||
|    'linkText': u'', 'title': u'Test Note'} |    'linkUrl': 'http://', 'linkText': ''} | ||||||
| 
 | 
 | ||||||
| The object is changed via a FormController adapter created for | The object is changed via a FormController adapter created for | ||||||
| a NodeView. | a NodeView. | ||||||
| 
 | 
 | ||||||
|   >>> form = dict( |   >>> form = dict( | ||||||
|   ...     title=u'Test Note - changed', |   ...     title='Test Note - changed', | ||||||
|   ...     contentType=u'text/plain',) |   ...     contentType='text/plain',) | ||||||
|   >>> request = TestRequest(form=form) |   >>> request = TestRequest(form=form) | ||||||
|   >>> view = NodeView(m112, request) |   >>> view = NodeView(m112, request) | ||||||
|   >>> cont = EditObject(view, request) |   >>> cont = EditObject(view, request) | ||||||
|   >>> cont.update() |   >>> cont.update() | ||||||
|   False |   False | ||||||
|   >>> resources['test_note'].title |   >>> resources['test_note'].title | ||||||
|   u'Test Note - changed' |   'Test Note - changed' | ||||||
| 
 | 
 | ||||||
| Virtual Targets | Virtual Targets | ||||||
| --------------- | --------------- | ||||||
|  | @ -881,13 +883,13 @@ informations about all parents of an object. | ||||||
| 
 | 
 | ||||||
|   >>> parents = m113.getAllParents() |   >>> parents = m113.getAllParents() | ||||||
|   >>> for p in parents: |   >>> for p in parents: | ||||||
|   ...     print p.object.title |   ...     print(p.object.title) | ||||||
|   Zope |   Zope | ||||||
|   Menu |   Menu | ||||||
| 
 | 
 | ||||||
|   >>> parents = resources['test_note'].getAllParents() |   >>> parents = resources['test_note'].getAllParents() | ||||||
|   >>> for p in parents: |   >>> for p in parents: | ||||||
|   ...     print p.object.title, len(p.relations) |   ...     print(p.object.title, len(p.relations)) | ||||||
|   Note 1 |   Note 1 | ||||||
|   Type 2 |   Type 2 | ||||||
| 
 | 
 | ||||||
|  | @ -913,6 +915,32 @@ relates ISO country codes with the full name of the country. | ||||||
|   >>> sorted(adapted(concepts['countries']).data.items()) |   >>> sorted(adapted(concepts['countries']).data.items()) | ||||||
|   [('at', ['Austria']), ('de', ['Germany'])] |   [('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 | Caching | ||||||
| ======= | ======= | ||||||
|  | @ -923,7 +951,7 @@ To be done... | ||||||
|   >>> obj = resources['test_note'] |   >>> obj = resources['test_note'] | ||||||
|   >>> cxObj = cached(obj) |   >>> cxObj = cached(obj) | ||||||
|   >>> [p.object.title for p in cxObj.getAllParents()] |   >>> [p.object.title for p in cxObj.getAllParents()] | ||||||
|   [u'Note', u'Type'] |   ['Note', 'Type'] | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| Security | Security | ||||||
|  | @ -932,6 +960,12 @@ Security | ||||||
|   >>> from loops.security.browser import admin, audit |   >>> from loops.security.browser import admin, audit | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | Paster Shell Utilities - Repair Scripts | ||||||
|  | ======================================= | ||||||
|  | 
 | ||||||
|  |   >>> from loops.repair.base import removeRecords | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
| Import/Export | Import/Export | ||||||
| ============= | ============= | ||||||
| 
 | 
 | ||||||
							
								
								
									
										17
									
								
								loops/__init__.py
									
										
									
									
									
										Normal 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 | ||||||
|  | 
 | ||||||
|  | @ -1,32 +1,13 @@ | ||||||
| # -*- coding: UTF-8 -*- | # loops.base | ||||||
| # -*- 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 |  | ||||||
| # |  | ||||||
| 
 | 
 | ||||||
| """ | """ Implementation of loops root object. | ||||||
| The loops container class. |  | ||||||
| """ | """ | ||||||
| 
 | 
 | ||||||
| from zope.app.container.btree import BTreeContainer | from zope.container.btree import BTreeContainer | ||||||
| from zope.app.folder.folder import Folder | from zope.site.folder import Folder | ||||||
| from zope.app.folder.interfaces import IFolder | from zope.site.interfaces import IFolder | ||||||
| from zope.traversing.api import getPath, traverse | from zope.traversing.api import getPath, traverse | ||||||
| from zope.interface import implements | from zope.interface import implementer | ||||||
| 
 | 
 | ||||||
| from cybertools.util.jeep import Jeep | from cybertools.util.jeep import Jeep | ||||||
| from loops.interfaces import ILoops | from loops.interfaces import ILoops | ||||||
|  | @ -34,17 +15,8 @@ from loops.interfaces import ILoops | ||||||
| loopsPrefix = '.loops' | loopsPrefix = '.loops' | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | @implementer(ILoops) | ||||||
| class Loops(Folder): | class Loops(Folder): | ||||||
| #class Loops(BTreeContainer): |  | ||||||
| 
 |  | ||||||
|     implements(ILoops) |  | ||||||
| 
 |  | ||||||
|     #def getSiteManager(self): |  | ||||||
|     #    return self.__parent__.getSiteManager() |  | ||||||
| 
 |  | ||||||
|     #@property |  | ||||||
|     #def _SampleContainer__data(self): |  | ||||||
|     #    return self.data |  | ||||||
| 
 | 
 | ||||||
|     _skinName = '' |     _skinName = '' | ||||||
|     def getSkinName(self): return self._skinName |     def getSkinName(self): return self._skinName | ||||||
|  | @ -74,10 +46,7 @@ class Loops(Folder): | ||||||
|         return self.get('records') |         return self.get('records') | ||||||
| 
 | 
 | ||||||
|     def getLoopsUri(self, obj): |     def getLoopsUri(self, obj): | ||||||
|         #return str(loopsPrefix + getPath(obj)[len(getPath(self)):]) |  | ||||||
|         uri = loopsPrefix + getPath(obj)[len(getPath(self)):] |         uri = loopsPrefix + getPath(obj)[len(getPath(self)):] | ||||||
|         #if isinstance(uri, unicode): |  | ||||||
|         #    uri = uri.encode('UTF-8') |  | ||||||
|         return uri |         return uri | ||||||
| 
 | 
 | ||||||
|     def loopsTraverse(self, uri): |     def loopsTraverse(self, uri): | ||||||
							
								
								
									
										68
									
								
								loops/bluebream.zcml
									
										
									
									
									
										Normal 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> | ||||||
|  | 
 | ||||||
|  | @ -1,26 +1,9 @@ | ||||||
| # | # loops.browser.action | ||||||
| #  Copyright (c) 2013 Helmut Merz helmutm@cy55.de |  | ||||||
| # |  | ||||||
| #  This program is free software; you can redistribute it and/or modify |  | ||||||
| #  it under the terms of the GNU General Public License as published by |  | ||||||
| #  the Free Software Foundation; either version 2 of the License, or |  | ||||||
| #  (at your option) any later version. |  | ||||||
| # |  | ||||||
| #  This program is distributed in the hope that it will be useful, |  | ||||||
| #  but WITHOUT ANY WARRANTY; without even the implied warranty of |  | ||||||
| #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the |  | ||||||
| #  GNU General Public License for more details. |  | ||||||
| # |  | ||||||
| #  You should have received a copy of the GNU General Public License |  | ||||||
| #  along with this program; if not, write to the Free Software |  | ||||||
| #  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA |  | ||||||
| # |  | ||||||
| 
 | 
 | ||||||
| """ | """ Base classes (sort of views) for action portlet items. | ||||||
| Base classes (sort of views) for action portlet items. |  | ||||||
| """ | """ | ||||||
| 
 | 
 | ||||||
| from urllib import urlencode | from urllib.parse import urlencode | ||||||
| from zope import component | from zope import component | ||||||
| from zope.app.pagetemplate import ViewPageTemplateFile | from zope.app.pagetemplate import ViewPageTemplateFile | ||||||
| from zope.cachedescriptors.property import Lazy | from zope.cachedescriptors.property import Lazy | ||||||
|  | @ -92,6 +75,8 @@ class DialogAction(Action): | ||||||
|             urlParams['fixed_type'] = 'yes' |             urlParams['fixed_type'] = 'yes' | ||||||
|         if self.viewTitle: |         if self.viewTitle: | ||||||
|             urlParams['view_title'] = self.viewTitle |             urlParams['view_title'] = self.viewTitle | ||||||
|  |         #for k, v in self.page.sortInfo.items(): | ||||||
|  |         #    urlParams['sortinfo_' + k] = v['fparam'] | ||||||
|         urlParams.update(self.addParams) |         urlParams.update(self.addParams) | ||||||
|         if self.target is not None: |         if self.target is not None: | ||||||
|             url = self.page.getUrlForTarget(self.target) |             url = self.page.getUrlForTarget(self.target) | ||||||
|  | @ -1,35 +1,18 @@ | ||||||
| # | # loops.browser.auth | ||||||
| #  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 |  | ||||||
| # |  | ||||||
| 
 | 
 | ||||||
| """ | """ Login, logout, unauthorized stuff. | ||||||
| Login, logout, unauthorized stuff. |  | ||||||
| """ | """ | ||||||
| 
 | 
 | ||||||
| from zope.app.exception.browser.unauthorized import Unauthorized as DefaultUnauth | from zope.app.exception.browser.unauthorized import Unauthorized as DefaultUnauth | ||||||
| from zope.app.security.interfaces import IAuthentication | from zope.authentication.interfaces import IAuthentication | ||||||
| from zope.app.security.interfaces import ILogout, IUnauthenticatedPrincipal | from zope.authentication.interfaces import ILogout, IUnauthenticatedPrincipal | ||||||
|  | from zope.browserpage import ViewPageTemplateFile | ||||||
|  | from zope.cachedescriptors.property import Lazy | ||||||
| from zope import component | from zope import component | ||||||
| from zope.interface import implements | from zope.interface import implementer | ||||||
| 
 | 
 | ||||||
| from loops.browser.concept import ConceptView | from loops.browser.concept import ConceptView | ||||||
| from loops.browser.node import NodeView | from loops.browser.node import NodeView | ||||||
| from zope.app.pagetemplate import ViewPageTemplateFile |  | ||||||
| from zope.cachedescriptors.property import Lazy |  | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| template = ViewPageTemplateFile('auth.pt') | template = ViewPageTemplateFile('auth.pt') | ||||||
|  | @ -57,9 +40,9 @@ class LoginForm(NodeView): | ||||||
|         return self |         return self | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | @implementer(ILogout) | ||||||
| class Logout(object): | class Logout(object): | ||||||
| 
 | 
 | ||||||
|     implements(ILogout) |  | ||||||
| 
 | 
 | ||||||
|     def __init__(self, context, request): |     def __init__(self, context, request): | ||||||
|         self.context = context |         self.context = context | ||||||
|  | @ -67,10 +50,11 @@ class Logout(object): | ||||||
| 
 | 
 | ||||||
|     def __call__(self): |     def __call__(self): | ||||||
|         nextUrl = self.request.get('nextURL') or self.request.URL[-1] |         nextUrl = self.request.get('nextURL') or self.request.URL[-1] | ||||||
|  |         nx = self.request.response.redirect(nextUrl) | ||||||
|         if not IUnauthenticatedPrincipal.providedBy(self.request.principal): |         if not IUnauthenticatedPrincipal.providedBy(self.request.principal): | ||||||
|             auth = component.getUtility(IAuthentication) |             auth = component.getUtility(IAuthentication) | ||||||
|             ILogout(auth).logout(self.request) |             ILogout(auth).logout(self.request) | ||||||
|         return self.request.response.redirect(nextUrl) |         return nx | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class Unauthorized(ConceptView): | class Unauthorized(ConceptView): | ||||||
|  | @ -1,46 +1,28 @@ | ||||||
| # | # loops.browser.common | ||||||
| #  Copyright (c) 2016 Helmut Merz helmutm@cy55.de |  | ||||||
| # |  | ||||||
| #  This program is free software; you can redistribute it and/or modify |  | ||||||
| #  it under the terms of the GNU General Public License as published by |  | ||||||
| #  the Free Software Foundation; either version 2 of the License, or |  | ||||||
| #  (at your option) any later version. |  | ||||||
| # |  | ||||||
| #  This program is distributed in the hope that it will be useful, |  | ||||||
| #  but WITHOUT ANY WARRANTY; without even the implied warranty of |  | ||||||
| #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the |  | ||||||
| #  GNU General Public License for more details. |  | ||||||
| # |  | ||||||
| #  You should have received a copy of the GNU General Public License |  | ||||||
| #  along with this program; if not, write to the Free Software |  | ||||||
| #  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA |  | ||||||
| # |  | ||||||
| 
 | 
 | ||||||
| """ | """ Common base class for loops browser view classes. | ||||||
| Common base class for loops browser view classes. |  | ||||||
| """ | """ | ||||||
| 
 | 
 | ||||||
| from cgi import parse_qs, parse_qsl |  | ||||||
| #import mimetypes   # use more specific assignments from cybertools.text | #import mimetypes   # use more specific assignments from cybertools.text | ||||||
| from datetime import datetime | from datetime import date, datetime | ||||||
| from logging import getLogger | from logging import getLogger | ||||||
| import re | import re | ||||||
| from time import strptime | from time import strptime | ||||||
| from urllib import urlencode | from urllib.parse import parse_qs, parse_qsl, urlencode | ||||||
| from zope import component | from zope import component | ||||||
| from zope.app.form.browser.interfaces import ITerms | from zope.authentication.interfaces import IAuthentication, IUnauthenticatedPrincipal | ||||||
| from zope.app.i18n.interfaces import ITranslationDomain | from zope.authentication.interfaces import IUnauthenticatedPrincipal | ||||||
| from zope.app.security.interfaces import IAuthentication, IUnauthenticatedPrincipal | from zope.authentication.interfaces import PrincipalLookupError | ||||||
| from zope.app.pagetemplate import ViewPageTemplateFile | from zope.browser.interfaces import ITerms | ||||||
| from zope.app.security.interfaces import IUnauthenticatedPrincipal | from zope.browserpage import ViewPageTemplateFile | ||||||
| from zope.app.security.interfaces import PrincipalLookupError |  | ||||||
| from zope.cachedescriptors.property import Lazy | from zope.cachedescriptors.property import Lazy | ||||||
| from zope.dottedname.resolve import resolve | from zope.dottedname.resolve import resolve | ||||||
| from zope.dublincore.interfaces import IZopeDublinCore | from zope.dublincore.interfaces import IZopeDublinCore | ||||||
| from zope.formlib import form | from zope.formlib import form | ||||||
| from zope.formlib.form import FormFields | from zope.formlib.form import FormFields | ||||||
| from zope.formlib.namedtemplate import NamedTemplate | from zope.formlib.namedtemplate import NamedTemplate | ||||||
| from zope.interface import Interface, implements | from zope.i18n.interfaces import ITranslationDomain | ||||||
|  | from zope.interface import Interface, implementer | ||||||
| from zope.proxy import removeAllProxies | from zope.proxy import removeAllProxies | ||||||
| from zope.publisher.browser import applySkin | from zope.publisher.browser import applySkin | ||||||
| from zope.publisher.interfaces.browser import IBrowserSkinType, IBrowserView | from zope.publisher.interfaces.browser import IBrowserSkinType, IBrowserView | ||||||
|  | @ -62,17 +44,21 @@ from cybertools.stateful.interfaces import IStateful | ||||||
| from cybertools.text import mimetypes | from cybertools.text import mimetypes | ||||||
| from cybertools.typology.interfaces import IType, ITypeManager | from cybertools.typology.interfaces import IType, ITypeManager | ||||||
| from cybertools.util.date import toLocalTime | from cybertools.util.date import toLocalTime | ||||||
|  | from cybertools.util.format import formatDate | ||||||
| from cybertools.util.jeep import Jeep | from cybertools.util.jeep import Jeep | ||||||
| from loops.browser.util import normalizeForUrl | from loops.browser.util import normalizeForUrl | ||||||
| from loops.common import adapted, baseObject | from loops.common import adapted, baseObject | ||||||
| from loops.config.base import DummyOptions | from loops.config.base import DummyOptions | ||||||
| from loops.i18n.browser import I18NView | from loops.i18n.browser import I18NView | ||||||
| from loops.interfaces import IResource, IView, INode, ITypeConcept | 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.tracking import access | ||||||
| from loops.organize.util import getRolesForPrincipal | from loops.organize.util import getRolesForPrincipal | ||||||
| from loops.resource import Resource | from loops.resource import Resource | ||||||
| from loops.security.common import checkPermission | from loops.security.common import checkPermission | ||||||
| from loops.security.common import canAccessObject, canListObject, canWriteObject | from loops.security.common import canAccessObject, canListObject, canWriteObject | ||||||
|  | from loops.security.common import canEditRestricted | ||||||
| from loops.type import ITypeConcept, LoopsTypeInfo | from loops.type import ITypeConcept, LoopsTypeInfo | ||||||
| from loops import util | from loops import util | ||||||
| from loops.util import _, saveRequest | from loops.util import _, saveRequest | ||||||
|  | @ -137,7 +123,58 @@ class EditForm(form.EditForm): | ||||||
|         return parentUrl + '/contents.html' |         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 = {} |     actions = {} | ||||||
|     portlet_actions = [] |     portlet_actions = [] | ||||||
|  | @ -146,6 +183,7 @@ class BaseView(GenericView, I18NView): | ||||||
|     icon = None |     icon = None | ||||||
|     modeName = 'view' |     modeName = 'view' | ||||||
|     isToplevel = False |     isToplevel = False | ||||||
|  |     isVisible = True | ||||||
| 
 | 
 | ||||||
|     def __init__(self, context, request): |     def __init__(self, context, request): | ||||||
|         context = baseObject(context) |         context = baseObject(context) | ||||||
|  | @ -163,6 +201,10 @@ class BaseView(GenericView, I18NView): | ||||||
|             pass |             pass | ||||||
|         saveRequest(request) |         saveRequest(request) | ||||||
| 
 | 
 | ||||||
|  |     def todayFormatted(self): | ||||||
|  |         return formatDate(date.today(), 'date', 'short', | ||||||
|  |                           self.languageInfo.language) | ||||||
|  | 
 | ||||||
|     def checkPermissions(self): |     def checkPermissions(self): | ||||||
|         return canAccessObject(self.context) |         return canAccessObject(self.context) | ||||||
| 
 | 
 | ||||||
|  | @ -214,6 +256,16 @@ class BaseView(GenericView, I18NView): | ||||||
|                 result.append(view) |                 result.append(view) | ||||||
|         return result |         return result | ||||||
| 
 | 
 | ||||||
|  |     @Lazy | ||||||
|  |     def urlParamString(self): | ||||||
|  |         return self.getUrlParamString() | ||||||
|  | 
 | ||||||
|  |     def getUrlParamString(self): | ||||||
|  |         qs = self.request.get('QUERY_STRING') | ||||||
|  |         if qs: | ||||||
|  |             return '?' + qs | ||||||
|  |         return '' | ||||||
|  | 
 | ||||||
|     @Lazy |     @Lazy | ||||||
|     def principalId(self): |     def principalId(self): | ||||||
|         principal = self.request.principal |         principal = self.request.principal | ||||||
|  | @ -347,6 +399,10 @@ class BaseView(GenericView, I18NView): | ||||||
|     def isPartOfPredicate(self): |     def isPartOfPredicate(self): | ||||||
|         return self.conceptManager.get('ispartof') |         return self.conceptManager.get('ispartof') | ||||||
| 
 | 
 | ||||||
|  |     @Lazy | ||||||
|  |     def queryTargetPredicate(self): | ||||||
|  |         return self.conceptManager.get('querytarget') | ||||||
|  | 
 | ||||||
|     @Lazy |     @Lazy | ||||||
|     def memberPredicate(self): |     def memberPredicate(self): | ||||||
|         return self.conceptManager.get('ismember') |         return self.conceptManager.get('ismember') | ||||||
|  | @ -395,6 +451,10 @@ class BaseView(GenericView, I18NView): | ||||||
|     def description(self): |     def description(self): | ||||||
|         return self.adapted.description |         return self.adapted.description | ||||||
| 
 | 
 | ||||||
|  |     @Lazy | ||||||
|  |     def tabTitle(self): | ||||||
|  |         return u'Info' | ||||||
|  | 
 | ||||||
|     @Lazy |     @Lazy | ||||||
|     def additionalInfos(self): |     def additionalInfos(self): | ||||||
|         return [] |         return [] | ||||||
|  | @ -747,6 +807,8 @@ class BaseView(GenericView, I18NView): | ||||||
|         return result |         return result | ||||||
| 
 | 
 | ||||||
|     def checkState(self): |     def checkState(self): | ||||||
|  |         if checkPermission('loops.ManageSite', self.context): | ||||||
|  |             return True | ||||||
|         if not self.allStates: |         if not self.allStates: | ||||||
|             return True |             return True | ||||||
|         for stf in self.allStates: |         for stf in self.allStates: | ||||||
|  | @ -821,6 +883,10 @@ class BaseView(GenericView, I18NView): | ||||||
|     def canAccessRestricted(self): |     def canAccessRestricted(self): | ||||||
|         return checkPermission('loops.ViewRestricted', self.context) |         return checkPermission('loops.ViewRestricted', self.context) | ||||||
| 
 | 
 | ||||||
|  |     @Lazy | ||||||
|  |     def canEditRestricted(self): | ||||||
|  |         return canEditRestricted(self.context) | ||||||
|  | 
 | ||||||
|     def openEditWindow(self, viewName='edit.html'): |     def openEditWindow(self, viewName='edit.html'): | ||||||
|         if self.editable: |         if self.editable: | ||||||
|             if checkPermission('loops.ManageSite', self.context): |             if checkPermission('loops.ManageSite', self.context): | ||||||
|  | @ -829,6 +895,7 @@ class BaseView(GenericView, I18NView): | ||||||
| 
 | 
 | ||||||
|     @Lazy |     @Lazy | ||||||
|     def xeditable(self): |     def xeditable(self): | ||||||
|  |         return False | ||||||
|         if self.typeOptions('no_external_edit'): |         if self.typeOptions('no_external_edit'): | ||||||
|             return False |             return False | ||||||
|         ct = getattr(self.context, 'contentType', '') |         ct = getattr(self.context, 'contentType', '') | ||||||
|  | @ -943,6 +1010,12 @@ class BaseView(GenericView, I18NView): | ||||||
|         jsCall = 'dojo.require("dojox.image.Lightbox");' |         jsCall = 'dojo.require("dojox.image.Lightbox");' | ||||||
|         self.controller.macros.register('js-execute', jsCall, jsCall=jsCall) |         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): |     def registerDojoFormAll(self): | ||||||
|         self.registerDojo() |         self.registerDojo() | ||||||
|         self.registerDojoEditor() |         self.registerDojoEditor() | ||||||
|  | @ -996,17 +1069,17 @@ class LoggedIn(object): | ||||||
|             params = parse_qsl(qs) |             params = parse_qsl(qs) | ||||||
|         params = [(k, v) for k, v in params if k != 'loops.messages.top:record'] |         params = [(k, v) for k, v in params if k != 'loops.messages.top:record'] | ||||||
|         params.append(('loops.messages.top:record', message.encode('UTF-8'))) |         params.append(('loops.messages.top:record', message.encode('UTF-8'))) | ||||||
|  |         url = url.encode('utf-8') | ||||||
|         return '%s?%s' % (url, urlencode(params)) |         return '%s?%s' % (url, urlencode(params)) | ||||||
| 
 | 
 | ||||||
| # vocabulary stuff | # vocabulary stuff | ||||||
| 
 | 
 | ||||||
|  | @implementer(ITerms) | ||||||
| class SimpleTerms(object): | class SimpleTerms(object): | ||||||
|     """ Provide the ITerms interface, e.g. for usage in selection |     """ Provide the ITerms interface, e.g. for usage in selection | ||||||
|         lists. |         lists. | ||||||
|     """ |     """ | ||||||
| 
 | 
 | ||||||
|     implements(ITerms) |  | ||||||
| 
 |  | ||||||
|     def __init__(self, source, request): |     def __init__(self, source, request): | ||||||
|         # the source parameter is a list of tuples (token, title). |         # the source parameter is a list of tuples (token, title). | ||||||
|         self.source = source |         self.source = source | ||||||
|  | @ -1021,13 +1094,12 @@ class SimpleTerms(object): | ||||||
|         return (token, self.terms[token]) |         return (token, self.terms[token]) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | @implementer(ITerms) | ||||||
| class LoopsTerms(object): | class LoopsTerms(object): | ||||||
|     """ Provide the ITerms interface, e.g. for usage in selection |     """ Provide the ITerms interface, e.g. for usage in selection | ||||||
|         lists. |         lists. | ||||||
|     """ |     """ | ||||||
| 
 | 
 | ||||||
|     implements(ITerms) |  | ||||||
| 
 |  | ||||||
|     def __init__(self, source, request): |     def __init__(self, source, request): | ||||||
|         # the source parameter is a view or adapter of a real context object: |         # the source parameter is a view or adapter of a real context object: | ||||||
|         self.source = source |         self.source = source | ||||||
|  | @ -1049,12 +1121,11 @@ class LoopsTerms(object): | ||||||
|         return self.loopsRoot.loopsTraverse(token) |         return self.loopsRoot.loopsTraverse(token) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | @implementer(ITerms) | ||||||
| class InterfaceTerms(object): | class InterfaceTerms(object): | ||||||
|     """ Provide the ITerms interface for source list of interfaces. |     """ Provide the ITerms interface for source list of interfaces. | ||||||
|     """ |     """ | ||||||
| 
 | 
 | ||||||
|     implements(ITerms) |  | ||||||
| 
 |  | ||||||
|     def __init__(self, source, request): |     def __init__(self, source, request): | ||||||
|         self.source = source |         self.source = source | ||||||
|         self.request = request |         self.request = request | ||||||
|  | @ -1,40 +1,22 @@ | ||||||
| # | # loops.browser.concept | ||||||
| #  Copyright (c) 2013 Helmut Merz helmutm@cy55.de |  | ||||||
| # |  | ||||||
| #  This program is free software; you can redistribute it and/or modify |  | ||||||
| #  it under the terms of the GNU General Public License as published by |  | ||||||
| #  the Free Software Foundation; either version 2 of the License, or |  | ||||||
| #  (at your option) any later version. |  | ||||||
| # |  | ||||||
| #  This program is distributed in the hope that it will be useful, |  | ||||||
| #  but WITHOUT ANY WARRANTY; without even the implied warranty of |  | ||||||
| #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the |  | ||||||
| #  GNU General Public License for more details. |  | ||||||
| # |  | ||||||
| #  You should have received a copy of the GNU General Public License |  | ||||||
| #  along with this program; if not, write to the Free Software |  | ||||||
| #  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA |  | ||||||
| # |  | ||||||
| 
 | 
 | ||||||
| """ | """ Definition of the concept view classes. | ||||||
| Definition of the concept view classes. |  | ||||||
| """ | """ | ||||||
| 
 | 
 | ||||||
| from itertools import groupby | from itertools import groupby | ||||||
| from zope import interface, component, schema | from zope import interface, component, schema | ||||||
| from zope.app.catalog.interfaces import ICatalog | from zope.authentication.interfaces import IUnauthenticatedPrincipal | ||||||
| from zope.lifecycleevent import ObjectCreatedEvent, ObjectModifiedEvent | from zope.browser.interfaces import ITerms | ||||||
| from zope.app.container.contained import ObjectRemovedEvent | from zope.browserpage import ViewPageTemplateFile | ||||||
| from zope.app.form.browser.interfaces import ITerms |  | ||||||
| from zope.app.form.interfaces import IDisplayWidget |  | ||||||
| from zope.app.pagetemplate import ViewPageTemplateFile |  | ||||||
| from zope.app.security.interfaces import IUnauthenticatedPrincipal |  | ||||||
| from zope.cachedescriptors.property import Lazy | from zope.cachedescriptors.property import Lazy | ||||||
|  | from zope.catalog.interfaces import ICatalog | ||||||
|  | from zope.container.contained import ObjectRemovedEvent | ||||||
| from zope.dottedname.resolve import resolve | from zope.dottedname.resolve import resolve | ||||||
| from zope.event import notify | from zope.event import notify | ||||||
| from zope.formlib.form import EditForm, FormFields, setUpEditWidgets | from zope.formlib.form import EditForm, FormFields, setUpEditWidgets | ||||||
|  | from zope.formlib.interfaces import IDisplayWidget | ||||||
| from zope.formlib.namedtemplate import NamedTemplate | from zope.formlib.namedtemplate import NamedTemplate | ||||||
| from zope.interface import implements | from zope.lifecycleevent import ObjectCreatedEvent, ObjectModifiedEvent | ||||||
| from zope.publisher.interfaces import BadRequest | from zope.publisher.interfaces import BadRequest | ||||||
| from zope.publisher.interfaces.browser import IBrowserRequest | from zope.publisher.interfaces.browser import IBrowserRequest | ||||||
| from zope.schema.interfaces import IIterableSource | from zope.schema.interfaces import IIterableSource | ||||||
|  | @ -254,18 +236,35 @@ class ConceptView(BaseView): | ||||||
|                 result.append(view) |                 result.append(view) | ||||||
|         return result |         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 |     @Lazy | ||||||
|     def adapted(self): |     def adapted(self): | ||||||
|         return adapted(self.context, self.languageInfo) |         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 |     @Lazy | ||||||
|     def targetUrl(self): |     def targetUrl(self): | ||||||
|         return self.nodeView.getUrlForTarget(self.context) |         return self.nodeView.getUrlForTarget(self.context) | ||||||
|  | @ -282,8 +281,17 @@ class ConceptView(BaseView): | ||||||
|     def breadcrumbsTitle(self): |     def breadcrumbsTitle(self): | ||||||
|         return self.title |         return self.title | ||||||
| 
 | 
 | ||||||
|  |     @Lazy | ||||||
|  |     def showInBreadcrumbs(self): | ||||||
|  |         return (self.options('show_in_breadcrumbs') or  | ||||||
|  |                 self.typeOptions('show_in_breadcrumbs')) | ||||||
|  | 
 | ||||||
|     @Lazy |     @Lazy | ||||||
|     def breadcrumbsParent(self): |     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 |         return None | ||||||
| 
 | 
 | ||||||
|     def getData(self, omit=('title', 'description')): |     def getData(self, omit=('title', 'description')): | ||||||
|  | @ -389,7 +397,8 @@ class ConceptView(BaseView): | ||||||
|     children = getChildren |     children = getChildren | ||||||
| 
 | 
 | ||||||
|     def childrenAlphaGroups(self, predicates=None): |     def childrenAlphaGroups(self, predicates=None): | ||||||
|         result = Jeep() |         #result = Jeep() | ||||||
|  |         result = {} | ||||||
|         rels = self.getChildren(predicates=predicates or [self.defaultPredicate], |         rels = self.getChildren(predicates=predicates or [self.defaultPredicate], | ||||||
|                                 topLevelOnly=False, sort=False) |                                 topLevelOnly=False, sort=False) | ||||||
|         rels = sorted(rels, key=lambda r: r.title.lower()) |         rels = sorted(rels, key=lambda r: r.title.lower()) | ||||||
|  | @ -436,7 +445,7 @@ class ConceptView(BaseView): | ||||||
| 
 | 
 | ||||||
|     def parents(self): |     def parents(self): | ||||||
|         rels = sorted(self.context.getParentRelations(), |         rels = sorted(self.context.getParentRelations(), | ||||||
|                       key=(lambda x: x.first.title and x.first.title.lower())) |                       key=(lambda x: x.first.title and x.first.title.lower() or '')) | ||||||
|         for r in rels: |         for r in rels: | ||||||
|             yield self.childViewFactory(r, self.request) |             yield self.childViewFactory(r, self.request) | ||||||
| 
 | 
 | ||||||
|  | @ -449,7 +458,7 @@ class ConceptView(BaseView): | ||||||
|                 if r.order != pos: |                 if r.order != pos: | ||||||
|                     r.order = pos |                     r.order = pos | ||||||
| 
 | 
 | ||||||
|     def getResources(self): |     def getResources(self, relView=None, sort='default'): | ||||||
|         form = self.request.form |         form = self.request.form | ||||||
|         #if form.get('loops.viewName') == 'index.html' and self.editable: |         #if form.get('loops.viewName') == 'index.html' and self.editable: | ||||||
|         if self.editable: |         if self.editable: | ||||||
|  | @ -458,13 +467,17 @@ class ConceptView(BaseView): | ||||||
|                 tokens = form.get('resources_tokens') |                 tokens = form.get('resources_tokens') | ||||||
|                 if tokens: |                 if tokens: | ||||||
|                     self.reorderResources(tokens) |                     self.reorderResources(tokens) | ||||||
|  |         if relView is None: | ||||||
|             from loops.browser.resource import ResourceRelationView |             from loops.browser.resource import ResourceRelationView | ||||||
|  |             relView = ResourceRelationView | ||||||
|         from loops.organize.personal.browser.filter import FilterView |         from loops.organize.personal.browser.filter import FilterView | ||||||
|         fv = FilterView(self.context, self.request) |         fv = FilterView(self.context, self.request) | ||||||
|         rels = self.context.getResourceRelations() |         rels = self.context.getResourceRelations(sort=sort) | ||||||
|         for r in rels: |         for r in rels: | ||||||
|             if fv.check(r.first): |             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): |     def resources(self): | ||||||
|         return self.getResources() |         return self.getResources() | ||||||
|  | @ -53,7 +53,7 @@ | ||||||
|     <h1 tal:define="tabview item/tabview|nothing" |     <h1 tal:define="tabview item/tabview|nothing" | ||||||
|         tal:attributes="ondblclick item/openEditWindow"> |         tal:attributes="ondblclick item/openEditWindow"> | ||||||
|       <a tal:omit-tag="python: level > 1" |       <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> |          tal:content="item/title">Title</a> | ||||||
|       <a title="Show tabular view" |       <a title="Show tabular view" | ||||||
|          i18n:attributes="title" |          i18n:attributes="title" | ||||||
|  | @ -367,4 +367,21 @@ | ||||||
| </metal:actions> | </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> | </html> | ||||||
|  | @ -125,7 +125,7 @@ | ||||||
| 
 | 
 | ||||||
|   <containerViews |   <containerViews | ||||||
|       for="loops.interfaces.ILoops" |       for="loops.interfaces.ILoops" | ||||||
|       index="zope.View" |       index="zope.ManageSite" | ||||||
|       contents="loops.ManageSite" |       contents="loops.ManageSite" | ||||||
|       add="loops.ManageSite" /> |       add="loops.ManageSite" /> | ||||||
| 
 | 
 | ||||||
|  | @ -365,7 +365,7 @@ | ||||||
| 
 | 
 | ||||||
|   <containerViews |   <containerViews | ||||||
|       for="loops.interfaces.IViewManager" |       for="loops.interfaces.IViewManager" | ||||||
|       index="zope.View" |       index="zope.ManageSite" | ||||||
|       add="loops.ManageSite" /> |       add="loops.ManageSite" /> | ||||||
| 
 | 
 | ||||||
|   <menuItem |   <menuItem | ||||||
|  | @ -571,6 +571,14 @@ | ||||||
|       factory="loops.browser.concept.TabbedPage" |       factory="loops.browser.concept.TabbedPage" | ||||||
|       permission="zope.View" /> |       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) --> |   <!-- dialogs/forms (end-user views) --> | ||||||
| 
 | 
 | ||||||
|   <page |   <page | ||||||
|  | @ -1,28 +1,8 @@ | ||||||
| # | # loops.browser.external | ||||||
| #  Copyright (c) 2008 Helmut Merz helmutm@cy55.de |  | ||||||
| # |  | ||||||
| #  This program is free software; you can redistribute it and/or modify |  | ||||||
| #  it under the terms of the GNU General Public License as published by |  | ||||||
| #  the Free Software Foundation; either version 2 of the License, or |  | ||||||
| #  (at your option) any later version. |  | ||||||
| # |  | ||||||
| #  This program is distributed in the hope that it will be useful, |  | ||||||
| #  but WITHOUT ANY WARRANTY; without even the implied warranty of |  | ||||||
| #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the |  | ||||||
| #  GNU General Public License for more details. |  | ||||||
| # |  | ||||||
| #  You should have received a copy of the GNU General Public License |  | ||||||
| #  along with this program; if not, write to the Free Software |  | ||||||
| #  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA |  | ||||||
| # |  | ||||||
| 
 | 
 | ||||||
| """ | """ view class(es) for import/export. | ||||||
| view class(es) for import/export. |  | ||||||
| 
 |  | ||||||
| $Id$ |  | ||||||
| """ | """ | ||||||
| 
 | 
 | ||||||
| from zope.interface import Interface, implements |  | ||||||
| from zope.app import zapi | from zope.app import zapi | ||||||
| from zope.app.pagetemplate.viewpagetemplatefile import ViewPageTemplateFile | from zope.app.pagetemplate.viewpagetemplatefile import ViewPageTemplateFile | ||||||
| from zope.cachedescriptors.property import Lazy | from zope.cachedescriptors.property import Lazy | ||||||
| Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB | 
| Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.7 KiB | 
|  | @ -1,41 +1,25 @@ | ||||||
| # | # loops.browser.form | ||||||
| #  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 |  | ||||||
| # |  | ||||||
| 
 | 
 | ||||||
| """ | """ 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 import component, interface, schema | ||||||
| from zope.component import adapts | from zope.component import adapts | ||||||
|  | from zope.container.contained import ObjectRemovedEvent | ||||||
| from zope.event import notify | from zope.event import notify | ||||||
| from zope.interface import Interface | from zope.interface import Interface | ||||||
| from zope.lifecycleevent import ObjectCreatedEvent, ObjectModifiedEvent | from zope.lifecycleevent import ObjectCreatedEvent, ObjectModifiedEvent | ||||||
| 
 | from zope.container.interfaces import INameChooser | ||||||
| from zope.app.container.interfaces import INameChooser | from zope.lifecycleevent import ObjectAddedEvent | ||||||
| from zope.app.container.contained import ObjectAddedEvent | from zope.browserpage import ViewPageTemplateFile | ||||||
| from zope.app.pagetemplate import ViewPageTemplateFile |  | ||||||
| from zope.cachedescriptors.property import Lazy | from zope.cachedescriptors.property import Lazy | ||||||
| from zope.contenttype import guess_content_type | from zope.contenttype import guess_content_type | ||||||
| from zope.publisher.browser import FileUpload | from zope.publisher.browser import FileUpload | ||||||
| from zope.publisher.interfaces import BadRequest | from zope.publisher.interfaces import BadRequest | ||||||
| from zope.security.interfaces import ForbiddenAttribute, Unauthorized | from zope.security.interfaces import ForbiddenAttribute, Unauthorized | ||||||
| from zope.security.proxy import isinstance, removeSecurityProxy | 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.ajax import innerHtml | ||||||
| from cybertools.browser.form import FormController | from cybertools.browser.form import FormController | ||||||
|  | @ -68,6 +52,25 @@ from loops.util import _ | ||||||
| from loops.versioning.interfaces import IVersionable | 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 | # forms | ||||||
| 
 | 
 | ||||||
| class ObjectForm(NodeView): | class ObjectForm(NodeView): | ||||||
|  | @ -162,7 +165,10 @@ class ObjectForm(NodeView): | ||||||
|                 field = self.schema.fields.get(k) |                 field = self.schema.fields.get(k) | ||||||
|                 if field: |                 if field: | ||||||
|                     fi = field.getFieldInstance(self.instance) |                     fi = field.getFieldInstance(self.instance) | ||||||
|                     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]) |                     #data[k] = toUnicode(form[k]) | ||||||
|         return data |         return data | ||||||
| 
 | 
 | ||||||
|  | @ -196,15 +202,37 @@ class ObjectForm(NodeView): | ||||||
|     def typeManager(self): |     def typeManager(self): | ||||||
|         return ITypeManager(self.target) |         return ITypeManager(self.target) | ||||||
| 
 | 
 | ||||||
|  |     @Lazy | ||||||
|  |     def targetType(self): | ||||||
|  |         return self.target.getType() | ||||||
|  | 
 | ||||||
|     @Lazy |     @Lazy | ||||||
|     def presetTypesForAssignment(self): |     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] |         assigned = [r.context.conceptType for r in self.assignments] | ||||||
|         types = [t for t in types if t.typeProvider not in assigned] |         types = [t for t in types if t.typeProvider not in assigned] | ||||||
|         return [dict(title=t.title, token=t.tokenForSearch) for t in types] |         return [dict(title=t.title, token=t.tokenForSearch) for t in types] | ||||||
| 
 | 
 | ||||||
|     def conceptsForType(self, token): |     def conceptsForType(self, token): | ||||||
|         result = ConceptQuery(self).query(type=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) |         fv = FilterView(self.context, self.request) | ||||||
|         result = fv.apply(result) |         result = fv.apply(result) | ||||||
|         result.sort(key=lambda x: x.title) |         result.sort(key=lambda x: x.title) | ||||||
|  | @ -288,8 +316,11 @@ class CreateObjectForm(ObjectForm): | ||||||
| 
 | 
 | ||||||
|     @Lazy |     @Lazy | ||||||
|     def defaultTypeToken(self): |     def defaultTypeToken(self): | ||||||
|         return (self.controller.params.get('form.create.defaultTypeToken') |         setting = self.controller.params.get('form.create.defaultTypeToken') | ||||||
|                 or '.loops/concepts/textdocument') |         if setting: | ||||||
|  |             return setting | ||||||
|  |         opt = self.globalOptions('form.create.default_type_token') | ||||||
|  |         return opt and opt[0] or '.loops/concepts/textdocument' | ||||||
| 
 | 
 | ||||||
|     @Lazy |     @Lazy | ||||||
|     def typeToken(self): |     def typeToken(self): | ||||||
|  | @ -310,10 +341,15 @@ class CreateObjectForm(ObjectForm): | ||||||
|         if typeToken: |         if typeToken: | ||||||
|             return self.loopsRoot.loopsTraverse(typeToken) |             return self.loopsRoot.loopsTraverse(typeToken) | ||||||
| 
 | 
 | ||||||
|  |     @Lazy | ||||||
|  |     def targetType(self): | ||||||
|  |         return self.typeConcept | ||||||
|  | 
 | ||||||
|     @Lazy |     @Lazy | ||||||
|     def adapted(self): |     def adapted(self): | ||||||
|         ad = self.typeInterface(Resource()) |         ad = self.typeInterface(Resource()) | ||||||
|         ad.storageName = 'unknown'  # hack for file objects: don't try to retrieve data |         ad.storageName = 'unknown'  # hack for file objects: don't try to retrieve data | ||||||
|  |         ad.__is_dummy__ = True | ||||||
|         ad.__type__ = adapted(self.typeConcept) |         ad.__type__ = adapted(self.typeConcept) | ||||||
|         return ad |         return ad | ||||||
| 
 | 
 | ||||||
|  | @ -423,6 +459,7 @@ class CreateConceptForm(CreateObjectForm): | ||||||
|             return c |             return c | ||||||
|         ad = ti(c) |         ad = ti(c) | ||||||
|         ad.__is_dummy__ = True |         ad.__is_dummy__ = True | ||||||
|  |         ad.__type__ = adapted(self.typeConcept) | ||||||
|         return ad |         return ad | ||||||
| 
 | 
 | ||||||
|     @Lazy |     @Lazy | ||||||
|  | @ -2,7 +2,7 @@ | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| <metal:info define-macro="object_info" | <metal:info define-macro="object_info" | ||||||
|             tal:define="item nocall:view/item"> |             tal:define="item nocall:view/targetItem"> | ||||||
|   <table class="object_info" width="400"> |   <table class="object_info" width="400"> | ||||||
|     <tr> |     <tr> | ||||||
|       <td colspan="2"><h2 i18n:translate="">Object Information</h2><br /></td> |       <td colspan="2"><h2 i18n:translate="">Object Information</h2><br /></td> | ||||||
|  | @ -52,7 +52,7 @@ | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| <metal:info define-macro="meta_info" | <metal:info define-macro="meta_info" | ||||||
|             tal:define="item nocall:view/item"> |             tal:define="item nocall:view/targetItem"> | ||||||
|   <table class="object_info" width="400"> |   <table class="object_info" width="400"> | ||||||
|     <tr> |     <tr> | ||||||
|       <td colspan="2"> |       <td colspan="2"> | ||||||
|  | @ -1,26 +1,9 @@ | ||||||
| # | # loops.browser.lobo.standard | ||||||
| #  Copyright (c) 2012 Helmut Merz helmutm@cy55.de |  | ||||||
| # |  | ||||||
| #  This program is free software; you can redistribute it and/or modify |  | ||||||
| #  it under the terms of the GNU General Public License as published by |  | ||||||
| #  the Free Software Foundation; either version 2 of the License, or |  | ||||||
| #  (at your option) any later version. |  | ||||||
| # |  | ||||||
| #  This program is distributed in the hope that it will be useful, |  | ||||||
| #  but WITHOUT ANY WARRANTY; without even the implied warranty of |  | ||||||
| #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the |  | ||||||
| #  GNU General Public License for more details. |  | ||||||
| # |  | ||||||
| #  You should have received a copy of the GNU General Public License |  | ||||||
| #  along with this program; if not, write to the Free Software |  | ||||||
| #  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA |  | ||||||
| # |  | ||||||
| 
 | 
 | ||||||
| """ | """ View classes for lobo (blueprint-based) layouts. | ||||||
| View classes for lobo (blueprint-based) layouts. |  | ||||||
| """ | """ | ||||||
| 
 | 
 | ||||||
| from cgi import parse_qs | from urllib.parse import parse_qs | ||||||
| from zope import interface, component | from zope import interface, component | ||||||
| from zope.app.pagetemplate import ViewPageTemplateFile | from zope.app.pagetemplate import ViewPageTemplateFile | ||||||
| from zope.cachedescriptors.property import Lazy | from zope.cachedescriptors.property import Lazy | ||||||
|  | @ -1,3 +1,4 @@ | ||||||
|  | # loops.browser.lobo.tests | ||||||
| 
 | 
 | ||||||
| import unittest, doctest | import unittest, doctest | ||||||
| from zope.interface.verify import verifyClass | from zope.interface.verify import verifyClass | ||||||
|  | @ -12,7 +13,7 @@ class Test(unittest.TestCase): | ||||||
| def test_suite(): | def test_suite(): | ||||||
|     flags = doctest.NORMALIZE_WHITESPACE | doctest.ELLIPSIS |     flags = doctest.NORMALIZE_WHITESPACE | doctest.ELLIPSIS | ||||||
|     return unittest.TestSuite(( |     return unittest.TestSuite(( | ||||||
|                 unittest.makeSuite(Test), |         unittest.TestLoader().loadTestsFromTestCase(Test), | ||||||
|         doctest.DocFileSuite('README.txt', optionflags=flags), |         doctest.DocFileSuite('README.txt', optionflags=flags), | ||||||
|         )) |         )) | ||||||
| 
 | 
 | ||||||
|  | @ -238,18 +238,21 @@ fieldset.box td { | ||||||
|     font-weight: bold; |     font-weight: bold; | ||||||
|     color: #444; |     color: #444; | ||||||
|     padding-top: 0.4em; |     padding-top: 0.4em; | ||||||
|  |     border-bottom: none; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| .content-4 h1, .content-3 h2, .content-2 h3, .content-1 h4, h4 { | .content-4 h1, .content-3 h2, .content-2 h3, .content-1 h4, h4 { | ||||||
|     font-size: 130%; |     font-size: 130%; | ||||||
|     font-weight: normal; |     font-weight: normal; | ||||||
|     padding-top: 0.3em; |     padding-top: 0.3em; | ||||||
|  |     border-bottom: none; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| .content-5 h1, .content-4 h2, .content-3 h3, content-2 h4, h5 { | .content-5 h1, .content-4 h2, .content-3 h3, content-2 h4, h5 { | ||||||
|     font-size: 120%; |     font-size: 120%; | ||||||
|     /* border: none; */ |     /* border: none; */ | ||||||
|     padding-top: 0.2em; |     padding-top: 0.2em; | ||||||
|  |     border-bottom: none; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| .box { | .box { | ||||||
|  | @ -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) { | function destroyWidgets(node) { | ||||||
|     dojo.forEach(dojo.query('[widgetId]', node), function(n) { |     dojo.forEach(dojo.query('[widgetId]', node), function(n) { | ||||||
|         w = dijit.byNode(n); |         w = dijit.byNode(n); | ||||||
|  | @ -103,7 +132,7 @@ function submitReplacing(targetId, formId, url) { | ||||||
|         mimetype: "text/html", |         mimetype: "text/html", | ||||||
|         load: function(response, ioArgs) { |         load: function(response, ioArgs) { | ||||||
|             replaceNode(response, targetId); |             replaceNode(response, targetId); | ||||||
|             return resonse; |             return response; | ||||||
|         } |         } | ||||||
|     }) |     }) | ||||||
| } | } | ||||||
|  | @ -115,7 +144,7 @@ function xhrSubmitPopup(formId, url) { | ||||||
|         mimetype: "text/html", |         mimetype: "text/html", | ||||||
|         load: function(response, ioArgs) { |         load: function(response, ioArgs) { | ||||||
|             window.close(); |             window.close(); | ||||||
|             return resonse; |             return response; | ||||||
|         } |         } | ||||||
|     }); |     }); | ||||||
| } | } | ||||||
| Before Width: | Height: | Size: 942 B After Width: | Height: | Size: 942 B | 
|  | @ -1,31 +1,11 @@ | ||||||
| # | # loops.browser.mobile.default | ||||||
| #  Copyright (c) 2011 Helmut Merz helmutm@cy55.de |  | ||||||
| # |  | ||||||
| #  This program is free software; you can redistribute it and/or modify |  | ||||||
| #  it under the terms of the GNU General Public License as published by |  | ||||||
| #  the Free Software Foundation; either version 2 of the License, or |  | ||||||
| #  (at your option) any later version. |  | ||||||
| # |  | ||||||
| #  This program is distributed in the hope that it will be useful, |  | ||||||
| #  but WITHOUT ANY WARRANTY; without even the implied warranty of |  | ||||||
| #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the |  | ||||||
| #  GNU General Public License for more details. |  | ||||||
| # |  | ||||||
| #  You should have received a copy of the GNU General Public License |  | ||||||
| #  along with this program; if not, write to the Free Software |  | ||||||
| #  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA |  | ||||||
| # |  | ||||||
| 
 | 
 | ||||||
| """ | """ Default layouts for the loops mobile skin. | ||||||
| Default layouts for the loops mobile skin. |  | ||||||
| 
 |  | ||||||
| $Id$ |  | ||||||
| """ | """ | ||||||
| 
 | 
 | ||||||
| from zope.app.pagetemplate import ViewPageTemplateFile | from zope.app.pagetemplate import ViewPageTemplateFile | ||||||
| from zope.cachedescriptors.property import Lazy | from zope.cachedescriptors.property import Lazy | ||||||
| from zope import component | from zope import component | ||||||
| from zope.interface import implements |  | ||||||
| 
 | 
 | ||||||
| from cybertools.browser.renderer import RendererFactory | from cybertools.browser.renderer import RendererFactory | ||||||
| from cybertools.composer.layout.base import Layout | from cybertools.composer.layout.base import Layout | ||||||
|  | @ -1,37 +1,20 @@ | ||||||
| # | # loops.browser.node | ||||||
| #  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 |  | ||||||
| # |  | ||||||
| 
 | 
 | ||||||
| """ | """ View class for Node objects. | ||||||
| View class for Node objects. |  | ||||||
| """ | """ | ||||||
| 
 | 
 | ||||||
| from logging import getLogger | from logging import getLogger | ||||||
| import urllib | from urllib.parse import urlencode, urlparse, urlunparse | ||||||
| from urlparse import urlparse, urlunparse | #from urlparse import urlparse, urlunparse | ||||||
| from zope import component, interface, schema |  | ||||||
| from zope.cachedescriptors.property import Lazy |  | ||||||
| from zope.annotation.interfaces import IAnnotations |  | ||||||
| from zope.app.catalog.interfaces import ICatalog |  | ||||||
| from zope.app.container.browser.contents import JustContents | from zope.app.container.browser.contents import JustContents | ||||||
| from zope.app.container.browser.adding import Adding | from zope.app.container.browser.adding import Adding | ||||||
| from zope.app.container.traversal import ItemTraverser | from zope import component, interface, schema | ||||||
| from zope.app.pagetemplate import ViewPageTemplateFile | from zope.annotation.interfaces import IAnnotations | ||||||
| from zope.app.security.interfaces import IUnauthenticatedPrincipal | from zope.authentication.interfaces import IUnauthenticatedPrincipal | ||||||
|  | from zope.browserpage import ViewPageTemplateFile | ||||||
|  | from zope.cachedescriptors.property import Lazy | ||||||
|  | from zope.catalog.interfaces import ICatalog | ||||||
|  | from zope.container.traversal import ItemTraverser | ||||||
| from zope.dottedname.resolve import resolve | from zope.dottedname.resolve import resolve | ||||||
| from zope.event import notify | from zope.event import notify | ||||||
| from zope.lifecycleevent import ObjectCreatedEvent, ObjectModifiedEvent | from zope.lifecycleevent import ObjectCreatedEvent, ObjectModifiedEvent | ||||||
|  | @ -86,10 +69,14 @@ class NodeView(BaseView): | ||||||
|         super(NodeView, self).__init__(context, request) |         super(NodeView, self).__init__(context, request) | ||||||
|         self.viewAnnotations.setdefault('nodeView', self) |         self.viewAnnotations.setdefault('nodeView', self) | ||||||
|         self.viewAnnotations.setdefault('node', self.context) |         self.viewAnnotations.setdefault('node', self.context) | ||||||
|         viewConfig = getViewConfiguration(context, request) |         self.setSkin(self.viewConfig.get('skinName')) | ||||||
|         self.setSkin(viewConfig.get('skinName')) |  | ||||||
| 
 | 
 | ||||||
|     def __call__(self, *args, **kw): |     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') |         tv = self.viewAnnotations.get('targetView') | ||||||
|         if tv is not None: |         if tv is not None: | ||||||
|             if tv.isToplevel: |             if tv.isToplevel: | ||||||
|  | @ -98,6 +85,29 @@ class NodeView(BaseView): | ||||||
|             self.controller.setMainPage() |             self.controller.setMainPage() | ||||||
|         return super(NodeView, self).__call__(*args, **kw) |         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 |     @Lazy | ||||||
|     def macro(self): |     def macro(self): | ||||||
|         return self.template.macros['content'] |         return self.template.macros['content'] | ||||||
|  | @ -115,7 +125,9 @@ class NodeView(BaseView): | ||||||
|             parts.extend(getParts(n)) |             parts.extend(getParts(n)) | ||||||
|         return parts |         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() |         result = super(NodeView, self).update() | ||||||
|         self.recordAccess() |         self.recordAccess() | ||||||
|         return result |         return result | ||||||
|  | @ -129,7 +141,7 @@ class NodeView(BaseView): | ||||||
|             return [] |             return [] | ||||||
|         menu = self.menu |         menu = self.menu | ||||||
|         data = [dict(label=menu.title, url=menu.url)] |         data = [dict(label=menu.title, url=menu.url)] | ||||||
|         menuItem = self.nearestMenuItem |         menuItem = self.getNearestMenuItem(all=True) | ||||||
|         if menuItem != menu.context: |         if menuItem != menu.context: | ||||||
|             data.append(dict(label=menuItem.title, |             data.append(dict(label=menuItem.title, | ||||||
|                              url=absoluteURL(menuItem, self.request))) |                              url=absoluteURL(menuItem, self.request))) | ||||||
|  | @ -140,6 +152,9 @@ class NodeView(BaseView): | ||||||
|                                     url=absoluteURL(p, self.request))) |                                     url=absoluteURL(p, self.request))) | ||||||
|         if self.virtualTarget: |         if self.virtualTarget: | ||||||
|             data.extend(self.virtualTarget.breadcrumbs()) |             data.extend(self.virtualTarget.breadcrumbs()) | ||||||
|  |         if data and not '?' in data[-1]['url']: | ||||||
|  |             if self.urlParamString: | ||||||
|  |                 data[-1]['url'] += self.urlParamString | ||||||
|         return data |         return data | ||||||
| 
 | 
 | ||||||
|     def viewModes(self): |     def viewModes(self): | ||||||
|  | @ -366,6 +381,10 @@ class NodeView(BaseView): | ||||||
|     def editable(self): |     def editable(self): | ||||||
|         return canWrite(self.context, 'body') |         return canWrite(self.context, 'body') | ||||||
| 
 | 
 | ||||||
|  |     def hasTopPage(self, name): | ||||||
|  |         page = self.topMenu.context.get(name) | ||||||
|  |         return page is not None | ||||||
|  | 
 | ||||||
|     # menu stuff |     # menu stuff | ||||||
| 
 | 
 | ||||||
|     @Lazy |     @Lazy | ||||||
|  | @ -411,8 +430,9 @@ class NodeView(BaseView): | ||||||
| 
 | 
 | ||||||
|     @Lazy |     @Lazy | ||||||
|     def menuItems(self): |     def menuItems(self): | ||||||
|         return [NodeView(child, self.request) |         items = [NodeView(child, self.request).view | ||||||
|                     for child in self.context.getMenuItems()] |                     for child in self.context.getMenuItems()] | ||||||
|  |         return [item for item in items if item.isVisible] | ||||||
| 
 | 
 | ||||||
|     @Lazy |     @Lazy | ||||||
|     def parents(self): |     def parents(self): | ||||||
|  | @ -420,10 +440,13 @@ class NodeView(BaseView): | ||||||
| 
 | 
 | ||||||
|     @Lazy |     @Lazy | ||||||
|     def nearestMenuItem(self): |     def nearestMenuItem(self): | ||||||
|  |         return self.getNearestMenuItem() | ||||||
|  | 
 | ||||||
|  |     def getNearestMenuItem(self, all=False): | ||||||
|         menu = self.menuObject |         menu = self.menuObject | ||||||
|         menuItem = None |         menuItem = None | ||||||
|         for p in [self.context] + self.parents: |         for p in [self.context] + self.parents: | ||||||
|             if not p.isMenuItem(): |             if not all and not p.isMenuItem(): | ||||||
|                 menuItem = None |                 menuItem = None | ||||||
|             elif menuItem is None: |             elif menuItem is None: | ||||||
|                 menuItem = p |                 menuItem = p | ||||||
|  | @ -439,7 +462,7 @@ class NodeView(BaseView): | ||||||
| 
 | 
 | ||||||
|     @Lazy |     @Lazy | ||||||
|     def logoutUrl(self): |     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) |         return '%s/logout.html?%s' % (self.menu.url, nextUrl) | ||||||
| 
 | 
 | ||||||
|     @Lazy |     @Lazy | ||||||
|  | @ -469,7 +492,7 @@ class NodeView(BaseView): | ||||||
|     def targetView(self, name='index.html', methodName='show'): |     def targetView(self, name='index.html', methodName='show'): | ||||||
|         if name == 'index.html':    # only when called for default view |         if name == 'index.html':    # only when called for default view | ||||||
|             tv = self.viewAnnotations.get('targetView') |             tv = self.viewAnnotations.get('targetView') | ||||||
|             if tv is not None: |             if tv is not None and callable(tv): | ||||||
|                 return tv() |                 return tv() | ||||||
|         if '?' in name: |         if '?' in name: | ||||||
|             name, params = name.split('?', 1) |             name, params = name.split('?', 1) | ||||||
|  | @ -567,12 +590,21 @@ class NodeView(BaseView): | ||||||
|         """ Return URL of given target view given as .XXX URL. |         """ Return URL of given target view given as .XXX URL. | ||||||
|         """ |         """ | ||||||
|         if isinstance(target, BaseView): |         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) |             return self.makeTargetUrl(self.url, target.uniqueId, target.title) | ||||||
|         else: |         else: | ||||||
|             target = baseObject(target) |             target = baseObject(target) | ||||||
|             return self.makeTargetUrl(self.url, util.getUidForObject(target), |             return self.makeTargetUrl(self.url, util.getUidForObject(target), | ||||||
|                                       target.title) |                                       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): |     def getActions(self, category='object', page=None, target=None): | ||||||
|         actions = [] |         actions = [] | ||||||
|         #self.registerDojo() |         #self.registerDojo() | ||||||
|  | @ -737,11 +769,11 @@ class InlineEdit(NodeView): | ||||||
|         if ti is not None: |         if ti is not None: | ||||||
|             target = ti(target) |             target = ti(target) | ||||||
|         data = self.request.form['editorContent'] |         data = self.request.form['editorContent'] | ||||||
|         if type(data) != unicode: |         if not isinstance(data, str): | ||||||
|             try: |             try: | ||||||
|                 data = data.decode('ISO-8859-15')  # IE hack |                 data = data.decode('ISO-8859-15')  # IE hack | ||||||
|             except UnicodeDecodeError: |             except UnicodeDecodeError: | ||||||
|                 print 'loops.browser.node.InlineEdit.save():', data |                 print('loops.browser.node.InlineEdit.save():', data) | ||||||
|                 return |                 return | ||||||
|         #    data = data.decode('UTF-8') |         #    data = data.decode('UTF-8') | ||||||
|         target.data = data |         target.data = data | ||||||
|  | @ -913,9 +945,9 @@ class NodeAdding(Adding): | ||||||
|         return info |         return info | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | @interface.implementer(IViewConfiguratorSchema) | ||||||
| class ViewPropertiesConfigurator(object): | class ViewPropertiesConfigurator(object): | ||||||
| 
 | 
 | ||||||
|     interface.implements(IViewConfiguratorSchema) |  | ||||||
|     component.adapts(INode) |     component.adapts(INode) | ||||||
| 
 | 
 | ||||||
|     def __init__(self, context): |     def __init__(self, context): | ||||||
|  | @ -976,7 +1008,8 @@ class NodeTraverser(ItemTraverser): | ||||||
|         if context.nodeType == 'menu': |         if context.nodeType == 'menu': | ||||||
|             setViewConfiguration(context, request) |             setViewConfiguration(context, request) | ||||||
|         if name == '.loops': |         if name == '.loops': | ||||||
|             return self.context.getLoopsRoot() |             name = self.getTargetUid(request) | ||||||
|  |             #return self.context.getLoopsRoot() | ||||||
|         if name.startswith('.'): |         if name.startswith('.'): | ||||||
|             name = self.cleanUpTraversalStack(request, name)[1:] |             name = self.cleanUpTraversalStack(request, name)[1:] | ||||||
|             target = self.getTarget(name) |             target = self.getTarget(name) | ||||||
|  | @ -1002,23 +1035,40 @@ class NodeTraverser(ItemTraverser): | ||||||
|                 return self.context |                 return self.context | ||||||
|         try: |         try: | ||||||
|             obj = super(NodeTraverser, self).publishTraverse(request, name) |             obj = super(NodeTraverser, self).publishTraverse(request, name) | ||||||
|         except NotFound, e: |         except NotFound: | ||||||
|             logger.warn('NodeTraverser: NotFound: URL = %s, name = %r' % |             logger.warn('NodeTraverser: NotFound: URL = %s, name = %r' % | ||||||
|                             (request.URL, name)) |                             (request.URL, name)) | ||||||
|             raise |             raise | ||||||
|         return obj |         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): |     def cleanUpTraversalStack(self, request, name): | ||||||
|         traversalStack = request._traversal_stack |         #traversalStack = request._traversal_stack | ||||||
|         while traversalStack and traversalStack[0].startswith('.'): |         #while traversalStack and traversalStack[0].startswith('.'): | ||||||
|             # skip obsolete target references in the url |             # skip obsolete target references in the url | ||||||
|             name = traversalStack.pop(0) |         #    name = traversalStack.pop(0) | ||||||
|         traversedNames = request._traversed_names |         traversedNames = request._traversed_names | ||||||
|         if traversedNames: |         for n in list(traversedNames): | ||||||
|             lastTraversed = traversedNames[-1] |             if n.startswith('.'): | ||||||
|             if lastTraversed.startswith('.') and lastTraversed != name: |                 # 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 |                 # 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 |         return name | ||||||
| 
 | 
 | ||||||
|     def getTarget(self, name): |     def getTarget(self, name): | ||||||
|  | @ -1036,7 +1086,9 @@ def setViewConfiguration(context, request): | ||||||
|     config = IViewConfiguratorSchema(context) |     config = IViewConfiguratorSchema(context) | ||||||
|     skinName = config.skinName |     skinName = config.skinName | ||||||
|     if not skinName: |     if not skinName: | ||||||
|         skinName = context.getLoopsRoot().skinName |         root = removeSecurityProxy(context.getLoopsRoot()) | ||||||
|  |         skinName = root.skinName | ||||||
|  |         #skinName = context.getLoopsRoot().skinName | ||||||
|     if skinName: |     if skinName: | ||||||
|         viewAnnotations['skinName'] = skinName |         viewAnnotations['skinName'] = skinName | ||||||
|     if config.options: |     if config.options: | ||||||
|  | @ -1055,12 +1107,12 @@ def getViewConfiguration(context, request): | ||||||
| class TestView(NodeView): | class TestView(NodeView): | ||||||
| 
 | 
 | ||||||
|     def __call__(self): |     def __call__(self): | ||||||
|         print '*** begin' |         print( '*** begin') | ||||||
|         for i in range(500): |         for i in range(500): | ||||||
|             #x = util.getObjectForUid('1994729849') |             #x = util.getObjectForUid('1994729849') | ||||||
|             x = util.getObjectForUid('2018653366') |             x = util.getObjectForUid('2018653366') | ||||||
|             self.c = list(x.getChildren()) |             self.c = list(x.getChildren()) | ||||||
|             #self.c = list(x.getChildren([self.defaultPredicate])) |             #self.c = list(x.getChildren([self.defaultPredicate])) | ||||||
|         print '*** end', len(self.c) |         print('*** end', len(self.c)) | ||||||
|         return 'done' |         return 'done' | ||||||
| 
 | 
 | ||||||
| Before Width: | Height: | Size: 942 B After Width: | Height: | Size: 942 B | 
|  | @ -41,17 +41,22 @@ | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| <metal:body define-macro="conceptbody"> | <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" |     <div class="content-1" id="1" | ||||||
|          tal:attributes="class string:content-$level; |          tal:attributes="class string:content-$level; | ||||||
|                          id string:${view/itemNum}.body; |                          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> |       <span tal:content="structure body">Node Body</span> | ||||||
|     </div> |     </div> | ||||||
|     <tal:concepts define="item nocall:item/targetObjectView; |     <div tal:define="item nocall:item/targetObjectView; | ||||||
|                      macro item/macro"> |                      macro item/macro"> | ||||||
|  |       <div tal:attributes="class string:content-$level; | ||||||
|  |                            id id;"> | ||||||
|         <div metal:use-macro="macro" /> |         <div metal:use-macro="macro" /> | ||||||
|     </tal:concepts> |       </div> | ||||||
|  |     </div> | ||||||
|   </tal:body> |   </tal:body> | ||||||
| </metal:body> | </metal:body> | ||||||
| 
 | 
 | ||||||
|  | @ -328,11 +333,12 @@ | ||||||
| <metal:login define-macro="login"> | <metal:login define-macro="login"> | ||||||
|     <div> |     <div> | ||||||
|       <a href="login.html" |       <a href="login.html" | ||||||
|  |          tal:attributes="href string:${view/topMenu/url}/login.html" | ||||||
|          i18n:translate="">Log in</a></div> |          i18n:translate="">Log in</a></div> | ||||||
|     <div tal:define="register python:view.globalOptions('provideLogin')" |     <div tal:define="register python:view.globalOptions('provideLogin')" | ||||||
|          tal:condition="register"> |          tal:condition="python:register and register != True"> | ||||||
|       <a tal:condition="python:register != True" |       <a tal:define="reg python:register[0]" | ||||||
|          tal:attributes="href python:register[0]" |          tal:attributes="href string:${view/topMenu/url}/$reg" | ||||||
|          i18n:translate="">Register new member</a></div> |          i18n:translate="">Register new member</a></div> | ||||||
| </metal:login> | </metal:login> | ||||||
| 
 | 
 | ||||||
|  | @ -18,10 +18,6 @@ | ||||||
|           <a href="#" |           <a href="#" | ||||||
|              tal:attributes="href string:${target/url}/@@configure.html" |              tal:attributes="href string:${target/url}/@@configure.html" | ||||||
|              tal:content="target/title">Document xy</a> |              tal:content="target/title">Document xy</a> | ||||||
|           <tal:xedit define="xeditObjectUrl target/url" |  | ||||||
|                      condition="target/xeditable"> |  | ||||||
|             <metal:xedit use-macro="views/xedit_macros/editLink" /> |  | ||||||
|           </tal:xedit> |  | ||||||
|         </tal:target> |         </tal:target> | ||||||
|       </div> |       </div> | ||||||
| 
 | 
 | ||||||
|  | @ -1,35 +1,18 @@ | ||||||
| # | # loops.browser.resource | ||||||
| #  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 |  | ||||||
| # |  | ||||||
| 
 | 
 | ||||||
| """ | """ 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.cachedescriptors.property import Lazy | ||||||
| from zope import component | from zope import component | ||||||
| from zope.app.catalog.interfaces import ICatalog | from zope.catalog.interfaces import ICatalog | ||||||
| from zope.app.container.interfaces import INameChooser | from zope.container.interfaces import INameChooser | ||||||
| from zope.app.form.browser.textwidgets import FileWidget |  | ||||||
| from zope.app.pagetemplate import ViewPageTemplateFile |  | ||||||
| from zope.app.security.interfaces import IUnauthenticatedPrincipal |  | ||||||
| from zope.formlib.form import FormFields | from zope.formlib.form import FormFields | ||||||
| from zope.formlib.interfaces import DISPLAY_UNWRITEABLE | from zope.formlib.interfaces import DISPLAY_UNWRITEABLE | ||||||
|  | from zope.formlib.textwidgets import FileWidget | ||||||
| from zope.proxy import removeAllProxies | from zope.proxy import removeAllProxies | ||||||
| from zope.schema.interfaces import IBytes | from zope.schema.interfaces import IBytes | ||||||
| from zope.security import canAccess, canWrite | from zope.security import canAccess, canWrite | ||||||
|  | @ -47,7 +30,7 @@ from loops.browser.common import EditForm, BaseView | ||||||
| from loops.browser.concept import BaseRelationView, ConceptRelationView | from loops.browser.concept import BaseRelationView, ConceptRelationView | ||||||
| from loops.browser.concept import ConceptConfigureView | from loops.browser.concept import ConceptConfigureView | ||||||
| from loops.browser.node import NodeView, node_macros | 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 IBaseResource, IDocument, ITextDocument | ||||||
| from loops.interfaces import IMediaAsset as legacy_IMediaAsset | from loops.interfaces import IMediaAsset as legacy_IMediaAsset | ||||||
| from loops.interfaces import ITypeConcept | from loops.interfaces import ITypeConcept | ||||||
|  | @ -196,6 +179,9 @@ class ResourceView(BaseView): | ||||||
|         context = self.context |         context = self.context | ||||||
|         ct = context.contentType |         ct = context.contentType | ||||||
|         response = self.request.response |         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) |         self.recordAccess('show', target=self.uniqueId) | ||||||
|         if ct.startswith('image/'): |         if ct.startswith('image/'): | ||||||
|             #response.setHeader('Cache-Control', 'public,max-age=86400') |             #response.setHeader('Cache-Control', 'public,max-age=86400') | ||||||
|  | @ -216,6 +202,16 @@ class ResourceView(BaseView): | ||||||
|             if filename is None: |             if filename is None: | ||||||
|                 filename = (adapted(self.context).localFilename or |                 filename = (adapted(self.context).localFilename or | ||||||
|                                 getName(self.context)) |                                 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'): |             if self.typeOptions('no_normalize_download_filename'): | ||||||
|                 filename = '"%s"' % filename |                 filename = '"%s"' % filename | ||||||
|             else: |             else: | ||||||
|  | @ -258,15 +254,21 @@ class ResourceView(BaseView): | ||||||
|             #wp = wiki.createPage(getName(self.context)) |             #wp = wiki.createPage(getName(self.context)) | ||||||
|             wp = wiki.addPage(LoopsWikiPage(self.context)) |             wp = wiki.addPage(LoopsWikiPage(self.context)) | ||||||
|             wp.text = text |             wp.text = text | ||||||
|             #print wp.wiki.getManager() |             #print(wp.wiki.getManager()) | ||||||
|             #return util.toUnicode(wp.render(self.request)) |             #return util.toUnicode(wp.render(self.request)) | ||||||
|         return super(ResourceView, self).renderText(text, contentType) |         return super(ResourceView, self).renderText(text, contentType) | ||||||
| 
 | 
 | ||||||
|  |     showMore = True | ||||||
|  | 
 | ||||||
|     def renderShortText(self): |     def renderShortText(self): | ||||||
|         return self.renderDescription() or self.createShortText(self.render()) |         return self.renderDescription() or self.createShortText(self.render()) | ||||||
| 
 | 
 | ||||||
|     def createShortText(self, text=None): |     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): |     def download(self): | ||||||
|         """ Force download, e.g. of a PDF file """ |         """ 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__))) |         r.append('meta_type:' + '.'.join((context.__module__, context.__class__.__name__))) | ||||||
|         auth = self.request.get('_auth') |         auth = self.request.get('_auth') | ||||||
|         if auth: |         if auth: | ||||||
|             print 'ExternalEditorView: auth = ', auth |             print('ExternalEditorView: auth = ', auth) | ||||||
|             if auth.endswith('\n'): |             if auth.endswith('\n'): | ||||||
|                 auth = auth[:-1] |                 auth = auth[:-1] | ||||||
|             r.append('auth:' + auth) |             r.append('auth:' + auth) | ||||||
|  | @ -471,4 +473,3 @@ class NoteView(DocumentView): | ||||||
|     def linkUrl(self): |     def linkUrl(self): | ||||||
|         ad = self.typeAdapter |         ad = self.typeAdapter | ||||||
|         return ad and ad.linkUrl or '' |         return ad and ad.linkUrl or '' | ||||||
| 
 |  | ||||||
|  | @ -10,7 +10,7 @@ | ||||||
|         <div metal:use-macro="views/node_macros/object_actions" /> |         <div metal:use-macro="views/node_macros/object_actions" /> | ||||||
|       </tal:actions> |       </tal:actions> | ||||||
|       <h1><a tal:omit-tag="python: level > 1" |       <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:content="item/title">Title</a></h1> | ||||||
|       <tal:desc define="description description|item/renderedDescription" |       <tal:desc define="description description|item/renderedDescription" | ||||||
|                 condition="description"> |                 condition="description"> | ||||||
|  | @ -51,7 +51,7 @@ | ||||||
|   <div tal:attributes="ondblclick python: item.openEditWindow('edit.html')"> |   <div tal:attributes="ondblclick python: item.openEditWindow('edit.html')"> | ||||||
|     <div metal:use-macro="views/node_macros/object_actions" /> |     <div metal:use-macro="views/node_macros/object_actions" /> | ||||||
|     <h1><a tal:omit-tag="python: level > 1" |     <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 /> |            tal:content="item/title">Title</a></h1><br /> | ||||||
|     <img tal:attributes="src |     <img tal:attributes="src | ||||||
|                 string:${view/url}/.${view/targetId}/view?version=this" /> |                 string:${view/url}/.${view/targetId}/view?version=this" /> | ||||||
|  | @ -72,6 +72,7 @@ | ||||||
|     <div> |     <div> | ||||||
|       <span class="button"> |       <span class="button"> | ||||||
|         <a i18n:translate="" |         <a i18n:translate="" | ||||||
|  |            target="_blank" | ||||||
|            tal:attributes="href |            tal:attributes="href | ||||||
|                 string:${view/virtualTargetUrl}/download.html?version=this"> |                 string:${view/virtualTargetUrl}/download.html?version=this"> | ||||||
|           Download |           Download | ||||||
|  | @ -96,6 +97,7 @@ | ||||||
|         </a> |         </a> | ||||||
|       </span> |       </span> | ||||||
|     </div> |     </div> | ||||||
|  |     <metal:custom define-slot="custom_info" /> | ||||||
|     <metal:fields use-macro="view/comment_macros/comments" /> |     <metal:fields use-macro="view/comment_macros/comments" /> | ||||||
|   </div> |   </div> | ||||||
| </metal:block> | </metal:block> | ||||||
|  | @ -1,6 +1,4 @@ | ||||||
| """ | # package loops.browser.skin | ||||||
| $Id$ |  | ||||||
| """ |  | ||||||
| 
 | 
 | ||||||
| from cybertools.browser.liquid import Liquid | from cybertools.browser.liquid import Liquid | ||||||
| from cybertools.browser.blue import Blue | from cybertools.browser.blue import Blue | ||||||