Compare commits
	
		
			216 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 | |||
| dd23787d27 | |||
| 0cd2e1f47d | |||
| 8723289204 | |||
| b471deef43 | |||
| c3079472de | |||
|   | b59248ee31 | ||
|   | 1b0f0e4859 | ||
| 082acf3fbc | |||
| 9ab3e61f9a | |||
| 1854903d0f | |||
| b4a8ae1ed1 | |||
| c5fe2044b5 | |||
| 9c2517a518 | |||
| dc5d913b99 | |||
| 814f10ae51 | |||
| dae9610e2e | |||
| 7567374e50 | |||
| 2012181382 | |||
| e1b1a82ee1 | |||
| 233d146587 | |||
| aa263eb635 | |||
|   | 66b616e5e1 | ||
| 7dcf4a9f4e | |||
| 181846b29a | |||
| 37d508cec2 | |||
| f8bca9e7d6 | |||
| 7a3cfbc175 | |||
| 63220a656f | |||
|   | fb924fdc03 | ||
| dc75d56951 | |||
| 8f12304074 | |||
| 30c13d57d7 | |||
| f9ba9d2115 | |||
| 4339f25ca3 | |||
| ccc9886113 | |||
|   | e27f4000f7 | ||
|   | c64f067d6e | ||
|   | fbacdd7465 | ||
| 0b5c50c100 | |||
| 4a18c42283 | |||
| 0915d04e30 | |||
| 24cd81e267 | |||
| 2e096a3682 | |||
| f69a43699c | |||
| 970775f847 | |||
|   | 1f4d246994 | ||
|   | fbf4efc40c | ||
| bd33c28d08 | |||
| babb222868 | |||
|   | 9ec755ecd3 | ||
| a3b649582c | |||
| 0b4fdef7b5 | |||
|   | 16b5665bfa | ||
| 357c660659 | |||
| 5c90a19859 | |||
| a2dca10e15 | |||
| ec28357ba7 | |||
| 4a0b31b34d | |||
| d09e2a9d0a | |||
| 019eef29a6 | |||
| c029cb2356 | |||
| e929a3154e | |||
| 57588f1e19 | |||
| 853782fb35 | |||
| 9f9df6599f | 
							
								
								
									
										6
									
								
								.gitignore
									
										
									
									
										vendored
									
									
								
							
							
						
						|  | @ -1,8 +1,14 @@ | |||
| *.pyc | ||||
| *.pyo | ||||
| *.swp | ||||
| dist/ | ||||
| var/ | ||||
| *.egg-info | ||||
| *.project | ||||
| *.pydevproject | ||||
| *.ropeproject | ||||
| *.sublime-project | ||||
| *.sublime-workspace | ||||
| .env | ||||
| .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 | ||||
|  | @ -1,61 +0,0 @@ | |||
| # | ||||
| #  Copyright (c) 2011 Helmut Merz helmutm@cy55.de | ||||
| # | ||||
| #  This program is free software; you can redistribute it and/or modify | ||||
| #  it under the terms of the GNU General Public License as published by | ||||
| #  the Free Software Foundation; either version 2 of the License, or | ||||
| #  (at your option) any later version. | ||||
| # | ||||
| #  This program is distributed in the hope that it will be useful, | ||||
| #  but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
| #  GNU General Public License for more details. | ||||
| # | ||||
| #  You should have received a copy of the GNU General Public License | ||||
| #  along with this program; if not, write to the Free Software | ||||
| #  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA | ||||
| # | ||||
| 
 | ||||
| """ | ||||
| $Id$ | ||||
| """ | ||||
| 
 | ||||
| from zope.app.security.interfaces import IAuthentication | ||||
| from zope.app.security.interfaces import ILogout, IUnauthenticatedPrincipal | ||||
| from zope import component | ||||
| from zope.interface import implements | ||||
| 
 | ||||
| from loops.browser.node import NodeView | ||||
| from zope.app.pagetemplate import ViewPageTemplateFile | ||||
| from zope.cachedescriptors.property import Lazy | ||||
| 
 | ||||
| 
 | ||||
| class LoginForm(NodeView): | ||||
| 
 | ||||
|     template = ViewPageTemplateFile('auth.pt') | ||||
| 
 | ||||
|     @Lazy | ||||
|     def macro(self): | ||||
|         return self.template.macros['login_form'] | ||||
| 
 | ||||
|     @Lazy | ||||
|     def item(self): | ||||
|         return self | ||||
| 
 | ||||
| 
 | ||||
| class Logout(object): | ||||
| 
 | ||||
|     implements(ILogout) | ||||
| 
 | ||||
|     def __init__(self, context, request): | ||||
|         self.context = context | ||||
|         self.request = request | ||||
| 
 | ||||
|     def __call__(self): | ||||
|         nextUrl = self.request.get('nextURL') or self.request.URL[-1] | ||||
|         if not IUnauthenticatedPrincipal.providedBy(self.request.principal): | ||||
|             auth = component.getUtility(IAuthentication) | ||||
|             ILogout(auth).logout(self.request) | ||||
|         return self.request.response.redirect(nextUrl) | ||||
| 
 | ||||
| 
 | ||||
|  | @ -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) | ||||
| 
 | ||||
| 
 | ||||
|  | @ -23,7 +23,7 @@ with lower-level aspects like type or state management. | |||
| 
 | ||||
| Let's also import some common stuff needed later. | ||||
| 
 | ||||
|   >>> from loops.common import adapted | ||||
|   >>> from loops.common import adapted, baseObject | ||||
|   >>> from loops.setup import addAndConfigureObject | ||||
| 
 | ||||
| 
 | ||||
|  | @ -48,14 +48,14 @@ top-level loops container and a concept manager: | |||
|   >>> cc1 = Concept() | ||||
|   >>> concepts['cc1'] = cc1 | ||||
|   >>> cc1.title | ||||
|   u'' | ||||
|   '' | ||||
|   >>> loopsRoot.getLoopsUri(cc1) | ||||
|   '.loops/concepts/cc1' | ||||
| 
 | ||||
|   >>> cc2 = Concept(u'Zope 3') | ||||
|   >>> cc2 = Concept('Zope 3') | ||||
|   >>> concepts['cc2'] = cc2 | ||||
|   >>> cc2.title | ||||
|   u'Zope 3' | ||||
|   'Zope 3' | ||||
| 
 | ||||
| Now we want to relate the second concept to the first one. | ||||
| 
 | ||||
|  | @ -73,11 +73,11 @@ ConceptRelation): | |||
| We can now ask our concepts for their related child and parent concepts: | ||||
| 
 | ||||
|   >>> [getName(c) for c in cc1.getChildren()] | ||||
|   [u'cc2'] | ||||
|   ['cc2'] | ||||
|   >>> len(cc1.getParents()) | ||||
|   0 | ||||
|   >>> [getName(p) for p in cc2.getParents()] | ||||
|   [u'cc1'] | ||||
|   ['cc1'] | ||||
| 
 | ||||
|   >>> len(cc2.getChildren()) | ||||
|   0 | ||||
|  | @ -90,24 +90,24 @@ a special predicate 'hasType'. | |||
|   >>> typeObject = concepts['type'] | ||||
|   >>> typeObject.setConceptType(typeObject) | ||||
|   >>> typeObject.getConceptType().title | ||||
|   u'Type' | ||||
|   'Type' | ||||
| 
 | ||||
|   >>> concepts['unknown'] = Concept(u'Unknown Type') | ||||
|   >>> concepts['unknown'] = Concept('Unknown Type') | ||||
|   >>> unknown = concepts['unknown'] | ||||
|   >>> unknown.setConceptType(typeObject) | ||||
|   >>> unknown.getConceptType().title | ||||
|   u'Type' | ||||
|   'Type' | ||||
| 
 | ||||
|   >>> cc1.setConceptType(unknown) | ||||
|   >>> cc1.getConceptType().title | ||||
|   u'Unknown Type' | ||||
|   'Unknown Type' | ||||
| 
 | ||||
|   >>> concepts['topic'] = Concept(u'Topic') | ||||
|   >>> concepts['topic'] = Concept('Topic') | ||||
|   >>> topic = concepts['topic'] | ||||
|   >>> topic.setConceptType(typeObject) | ||||
|   >>> cc1.setConceptType(topic) | ||||
|   >>> cc1.getConceptType().title | ||||
|   u'Topic' | ||||
|   'Topic' | ||||
| 
 | ||||
| We get a list of types using the ConceptTypeSourceList. | ||||
| In order for the type machinery to work we first have to provide a | ||||
|  | @ -124,7 +124,7 @@ type manager. | |||
|   >>> from loops.concept import ConceptTypeSourceList | ||||
|   >>> types = ConceptTypeSourceList(cc1) | ||||
|   >>> sorted(t.title for t in types) | ||||
|   [u'Customer', u'Domain', u'Predicate', u'Topic', u'Type', u'Unknown Type'] | ||||
|   ['Customer', 'Domain', 'Predicate', 'Topic', 'Type', 'Unknown Type'] | ||||
| 
 | ||||
| Using a PredicateSourceList we can retrieve a list of the available | ||||
| predicates. | ||||
|  | @ -136,7 +136,7 @@ Note that the 'hasType' predicate is suppressed from this list as the | |||
| corresponding relation is only assigned via the conceptType attribute: | ||||
| 
 | ||||
|   >>> sorted(t.title for t in predicates) | ||||
|   [u'subobject'] | ||||
|   ['subobject'] | ||||
| 
 | ||||
| Concept Views | ||||
| ------------- | ||||
|  | @ -146,7 +146,7 @@ Concept Views | |||
| 
 | ||||
|   >>> children = list(view.children()) | ||||
|   >>> [c.title for c in children] | ||||
|   [u'Zope 3'] | ||||
|   ['Zope 3'] | ||||
| 
 | ||||
| The token attribute provided with the items returned by the children() and | ||||
| parents() methods identifies identifies not only the item itself but | ||||
|  | @ -159,14 +159,14 @@ of URIs to item and the predicate of the relationship: | |||
| There is also a concept configuration view that allows updating the | ||||
| underlying context object: | ||||
| 
 | ||||
|   >>> cc3 = Concept(u'loops for Zope 3') | ||||
|   >>> cc3 = Concept('loops for Zope 3') | ||||
|   >>> concepts['cc3'] = cc3 | ||||
|   >>> view = ConceptConfigureView(cc1, | ||||
|   ...           TestRequest(action='assign', tokens=['.loops/concepts/cc3'])) | ||||
|   >>> view.update() | ||||
|   True | ||||
|   >>> sorted(c.title for c in cc1.getChildren()) | ||||
|   [u'Zope 3', u'loops for Zope 3'] | ||||
|   ['Zope 3', 'loops for Zope 3'] | ||||
| 
 | ||||
|   >>> input = {'action': 'remove', 'qualifier': 'children', | ||||
|   ...          'form.button.submit': 'Remove Chiildren', | ||||
|  | @ -175,18 +175,18 @@ underlying context object: | |||
|   >>> view.update() | ||||
|   True | ||||
|   >>> sorted(c.title for c in cc1.getChildren()) | ||||
|   [u'loops for Zope 3'] | ||||
|   ['loops for Zope 3'] | ||||
| 
 | ||||
| We can also create a new concept and assign it. | ||||
| 
 | ||||
|   >>> params = {'action': 'create', 'create.name': 'cc4', | ||||
|   ...           'create.title': u'New concept', | ||||
|   ...           'create.title': 'New concept', | ||||
|   ...           'create.type': '.loops/concepts/topic'} | ||||
|   >>> view = ConceptConfigureView(cc1, TestRequest(**params)) | ||||
|   >>> view.update() | ||||
|   True | ||||
|   >>> sorted(c.title for c in cc1.getChildren()) | ||||
|   [u'New concept', u'loops for Zope 3'] | ||||
|   ['New concept', 'loops for Zope 3'] | ||||
| 
 | ||||
| The concept configuration view provides methods for displaying concept | ||||
| types and predicates. | ||||
|  | @ -198,15 +198,15 @@ types and predicates. | |||
|   >>> component.provideAdapter(LoopsTerms, (IIterableSource, IBrowserRequest), ITerms) | ||||
| 
 | ||||
|   >>> sorted((t.title, t.token) for t in view.conceptTypes()) | ||||
|   [(u'Customer', '.loops/concepts/customer'), | ||||
|    (u'Domain', '.loops/concepts/domain'), | ||||
|    (u'Predicate', '.loops/concepts/predicate'), | ||||
|    (u'Topic', '.loops/concepts/topic'), | ||||
|    (u'Type', '.loops/concepts/type'), | ||||
|    (u'Unknown Type', '.loops/concepts/unknown')] | ||||
|   [('Customer', '.loops/concepts/customer'), | ||||
|    ('Domain', '.loops/concepts/domain'), | ||||
|    ('Predicate', '.loops/concepts/predicate'), | ||||
|    ('Topic', '.loops/concepts/topic'), | ||||
|    ('Type', '.loops/concepts/type'), | ||||
|    ('Unknown Type', '.loops/concepts/unknown')] | ||||
| 
 | ||||
|   >>> sorted((t.title, t.token) for t in view.predicates()) | ||||
|   [(u'subobject', '.loops/concepts/standard')] | ||||
|   [('subobject', '.loops/concepts/standard')] | ||||
| 
 | ||||
| Index attributes adapter | ||||
| ------------------------ | ||||
|  | @ -214,10 +214,10 @@ Index attributes adapter | |||
|   >>> from loops.concept import IndexAttributes | ||||
|   >>> idx = IndexAttributes(cc2) | ||||
|   >>> idx.text() | ||||
|   u'cc2 Zope 3' | ||||
|   'cc2 Zope 3' | ||||
| 
 | ||||
|   >>> idx.title() | ||||
|   u'cc2 Zope 3' | ||||
|   'cc2 Zope 3' | ||||
| 
 | ||||
| 
 | ||||
| Resources and what they have to do with Concepts | ||||
|  | @ -233,27 +233,27 @@ A common type of resource is a document: | |||
| 
 | ||||
|   >>> from loops.interfaces import IDocument | ||||
|   >>> from loops.resource import Document | ||||
|   >>> doc1 = Document(u'Zope Info') | ||||
|   >>> doc1 = Document('Zope Info') | ||||
|   >>> resources['doc1'] = doc1 | ||||
|   >>> doc1.title | ||||
|   u'Zope Info' | ||||
|   'Zope Info' | ||||
|   >>> doc1.data | ||||
|   u'' | ||||
|   '' | ||||
|   >>> doc1.contentType | ||||
|   u'' | ||||
|   '' | ||||
| 
 | ||||
| We can also directly use Resource objects; these behave like files. | ||||
| In fact, by using resource types we can explicitly assign a resource | ||||
| the 'file' type, but we will use this feature later: | ||||
| 
 | ||||
|   >>> img = Resource(u'A png Image') | ||||
|   >>> img = Resource('A png Image') | ||||
| 
 | ||||
| For testing we use some simple files from the tests directory: | ||||
| 
 | ||||
|   >>> from loops import tests | ||||
|   >>> import os | ||||
|   >>> path = os.path.join(*tests.__path__) | ||||
|   >>> img.data = open(os.path.join(path, 'test_icon.png')).read() | ||||
|   >>> img.data = open(os.path.join(path, 'test_icon.png'), 'rb').read() | ||||
|   >>> img.getSize() | ||||
|   381 | ||||
|   >>> img.getImageSize() | ||||
|  | @ -261,8 +261,8 @@ For testing we use some simple files from the tests directory: | |||
|   >>> img.contentType | ||||
|   'image/png' | ||||
| 
 | ||||
|   >>> pdf = Resource(u'A pdf File') | ||||
|   >>> pdf.data = open(os.path.join(path, 'test.pdf')).read() | ||||
|   >>> pdf = Resource('A pdf File') | ||||
|   >>> pdf.data = open(os.path.join(path, 'test.pdf'), 'rb').read() | ||||
|   >>> pdf.getSize() | ||||
|   25862 | ||||
|   >>> pdf.getImageSize() | ||||
|  | @ -287,7 +287,7 @@ from concepts to resources: | |||
|   ...         'tokens': ['.loops/resources/doc1:.loops/concepts/standard']} | ||||
|   >>> view = ConceptConfigureView(cc1, TestRequest(form=form)) | ||||
|   >>> [getName(r.context) for r in view.resources()] | ||||
|   [u'doc1'] | ||||
|   ['doc1'] | ||||
|   >>> view.update() | ||||
|   True | ||||
|   >>> len(cc1.getResources()) | ||||
|  | @ -316,10 +316,10 @@ Index attributes adapter | |||
|   >>> component.provideAdapter(FileAdapter, provides=IFile) | ||||
|   >>> idx = IndexAttributes(doc1) | ||||
|   >>> idx.text() | ||||
|   u'' | ||||
|   '' | ||||
| 
 | ||||
|   >>> idx.title() | ||||
|   u'doc1 Zope Info' | ||||
|   'doc1 Zope Info' | ||||
| 
 | ||||
| 
 | ||||
| Views/Nodes: Menus, Menu Items, Listings, Pages, etc | ||||
|  | @ -343,14 +343,14 @@ The view manager has already been created during setup. | |||
| The view space is typically built up with nodes; a node may be a top-level | ||||
| menu that may contain other nodes as menu or content items: | ||||
| 
 | ||||
|   >>> m1 = views['m1'] = Node(u'Menu') | ||||
|   >>> m11 = m1['m11'] = Node(u'Zope') | ||||
|   >>> m111 = m11['m111'] = Node(u'Zope in General') | ||||
|   >>> m112 = m11['m112'] = Node(u'Zope 3') | ||||
|   >>> m1 = views['m1'] = Node('Menu') | ||||
|   >>> m11 = m1['m11'] = Node('Zope') | ||||
|   >>> m111 = m11['m111'] = Node('Zope in General') | ||||
|   >>> m112 = m11['m112'] = Node('Zope 3') | ||||
|   >>> m112.title | ||||
|   u'Zope 3' | ||||
|   'Zope 3' | ||||
|   >>> m112.description | ||||
|   u'' | ||||
|   '' | ||||
| 
 | ||||
| There are a few convienence methods for accessing parent and child nodes: | ||||
| 
 | ||||
|  | @ -359,7 +359,7 @@ There are a few convienence methods for accessing parent and child nodes: | |||
|   >>> m11.getParentNode() is m1 | ||||
|   True | ||||
|   >>> [getName(child) for child in m11.getChildNodes()] | ||||
|   [u'm111', u'm112'] | ||||
|   ['m111', 'm112'] | ||||
| 
 | ||||
| What is returned by these may be controlled by the nodeType attribute: | ||||
| 
 | ||||
|  | @ -444,13 +444,13 @@ Node Views | |||
|   >>> page = view.page | ||||
|   >>> items = page.textItems | ||||
|   >>> for item in items: | ||||
|   ...     print item.url, item.editable | ||||
|   ...     print(item.url, item.editable) | ||||
|   http://127.0.0.1/loops/views/m1/m11/m112 False | ||||
| 
 | ||||
|   >>> menu = view.menu | ||||
|   >>> items = menu.menuItems | ||||
|   >>> for item in items: | ||||
|   ...     print item.url, view.selected(item) | ||||
|   ...     print(item.url, view.selected(item)) | ||||
|   http://127.0.0.1/loops/views/m1/m11 True | ||||
| 
 | ||||
| A NodeView provides an itemNum attribute that may be used to count elements | ||||
|  | @ -493,11 +493,11 @@ view; these views we have to provide as multi-adapters: | |||
|   >>> len(tt) | ||||
|   9 | ||||
|   >>> sorted((t.token, t.title) for t in view.targetTypes())[1] | ||||
|   ('.loops/concepts/domain', u'Domain') | ||||
|   ('.loops/concepts/domain', 'Domain') | ||||
|   >>> view.update() | ||||
|   True | ||||
|   >>> sorted(resources.keys()) | ||||
|   [u'd001.txt', u'd002.txt', u'd003.txt', u'doc1', u'm1.m11.m111'] | ||||
|   ['d001.txt', 'd002.txt', 'd003.txt', 'doc1', 'm1.m11.m111'] | ||||
| 
 | ||||
|   >>> view.target.title, view.target.token | ||||
|   ('New Resource', '.loops/resources/m1.m11.m111') | ||||
|  | @ -537,28 +537,28 @@ view for rendering.) | |||
|   >>> component.provideAdapter(LoopsType) | ||||
|   >>> view = NodeView(m112, TestRequest()) | ||||
|   >>> view.renderTarget() | ||||
|   u'<pre></pre>' | ||||
|   >>> doc1.data = u'Test data\n\nAnother paragraph' | ||||
|   '<pre></pre>' | ||||
|   >>> doc1.data = 'Test data\n\nAnother paragraph' | ||||
|   >>> view.renderTarget() | ||||
|   u'<pre>Test data\n\nAnother paragraph</pre>' | ||||
|   '<pre>Test data\n\nAnother paragraph</pre>' | ||||
| 
 | ||||
|   >>> doc1.contentType = 'text/restructured' | ||||
|   >>> doc1.data = u'Test data\n\nAnother `paragraph <para>`_' | ||||
|   >>> doc1.data = 'Test data\n\nAnother `paragraph <para>`_' | ||||
| 
 | ||||
|   >>> from loops.wiki.base import wikiLinksActive | ||||
|   >>> wikiLinksActive(loopsRoot) | ||||
|   False | ||||
| 
 | ||||
|   >>> view.renderTarget() | ||||
|   u'<p>Test data</p>\n<p>Another <a class="reference external" href="para">paragraph</a></p>\n' | ||||
|   '<p>Test data</p>\n<p>Another <a class="reference external" href="para">paragraph</a></p>\n' | ||||
| 
 | ||||
| u'<p>Test data</p>\n<p>Another <a class="reference create" | ||||
| '<p>Test data</p>\n<p>Another <a class="reference create" | ||||
|     href="http://127.0.0.1/loops/wiki/create.html?linkid=0000001">?paragraph</a></p>\n' | ||||
| 
 | ||||
|   >>> #links = loopsRoot.getRecordManager()['links'] | ||||
|   >>> #links['0000001'] | ||||
| 
 | ||||
| <Link ['42', 1, '', '... ...', u'para', None]: {}> | ||||
| <Link ['42', 1, '', '... ...', 'para', None]: {}> | ||||
| 
 | ||||
| If the target object is removed from its container all references | ||||
| to it are removed as well. (To make this work we have to handle | ||||
|  | @ -661,9 +661,9 @@ Breadcrumbs | |||
|   >>> view = NodeView(m114, request) | ||||
|   >>> request.annotations.setdefault('loops.view', {})['nodeView'] = view | ||||
|   >>> view.breadcrumbs() | ||||
|   [{'url': 'http://127.0.0.1/loops/views/m1', 'label': u'Menu'}, | ||||
|    {'url': 'http://127.0.0.1/loops/views/m1/m11', 'label': u'Zope'}, | ||||
|    {'url': 'http://127.0.0.1/loops/views/m1/m11/m114', 'label': u''}] | ||||
|   [{'label': 'Menu', 'url': 'http://127.0.0.1/loops/views/m1'}, | ||||
|    {'label': 'Zope', 'url': 'http://127.0.0.1/loops/views/m1/m11'}, | ||||
|    {'label': '', 'url': 'http://127.0.0.1/loops/views/m1/m11/m114'}] | ||||
| 
 | ||||
| 
 | ||||
| End-user Forms and Special Views | ||||
|  | @ -705,8 +705,8 @@ been created during setup. | |||
|   >>> custType = TypeConcept(customer) | ||||
|   >>> custType.options | ||||
|   [] | ||||
|   >>> cust1 = concepts['cust1'] = Concept(u'Zope Corporation') | ||||
|   >>> cust2 = concepts['cust2'] = Concept(u'cyberconcepts') | ||||
|   >>> cust1 = concepts['cust1'] = Concept('Zope Corporation') | ||||
|   >>> cust2 = concepts['cust2'] = Concept('cyberconcepts') | ||||
|   >>> for c in (cust1, cust2): c.conceptType = customer | ||||
|   >>> custType.options = ('qualifier:assign',) | ||||
|   >>> ConceptType(cust1).qualifiers | ||||
|  | @ -714,7 +714,7 @@ been created during setup. | |||
| 
 | ||||
|   >>> form = CreateObjectForm(m112, TestRequest()) | ||||
|   >>> form.presetTypesForAssignment | ||||
|   [{'token': 'loops:concept:customer', 'title': u'Customer'}] | ||||
|   [{'title': 'Customer', 'token': 'loops:concept:customer'}] | ||||
| 
 | ||||
| If the node's target is a type concept we don't get any assignments because | ||||
| it does not make much sense to assign resources or other concepts as | ||||
|  | @ -736,18 +736,18 @@ on data provided in this form: | |||
|   >>> note_tc = concepts['note'] | ||||
| 
 | ||||
|   >>> component.provideAdapter(NameChooser) | ||||
|   >>> request = TestRequest(form={'title': u'Test Note', | ||||
|   ...                             'form.type': u'.loops/concepts/note', | ||||
|   ...                             'contentType': u'text/restructured', | ||||
|   ...                             'linkUrl': u'http://'}) | ||||
|   >>> request = TestRequest(form={'title': 'Test Note', | ||||
|   ...                             'form.type': '.loops/concepts/note', | ||||
|   ...                             'contentType': 'text/restructured', | ||||
|   ...                             'linkUrl': 'http://'}) | ||||
|   >>> view = NodeView(m112, request) | ||||
|   >>> cont = CreateObject(view, request) | ||||
|   >>> cont.update() | ||||
|   False | ||||
|   >>> sorted(resources.keys()) | ||||
|   [...u'test_note'...] | ||||
|   [...'test_note'...] | ||||
|   >>> resources['test_note'].title | ||||
|   u'Test Note' | ||||
|   'Test Note' | ||||
| 
 | ||||
| If there is a concept selected in the combo box we assign this to the newly | ||||
| created object: | ||||
|  | @ -755,8 +755,8 @@ created object: | |||
|   >>> from loops import util | ||||
|   >>> topicUid = util.getUidForObject(topic) | ||||
|   >>> predicateUid = util.getUidForObject(concepts.getDefaultPredicate()) | ||||
|   >>> request = TestRequest(form={'title': u'Test Note', | ||||
|   ...                             'form.type': u'.loops/concepts/note', | ||||
|   >>> request = TestRequest(form={'title': 'Test Note', | ||||
|   ...                             'form.type': '.loops/concepts/note', | ||||
|   ...                             'form.assignments.selected': | ||||
|   ...                                   [':'.join((topicUid, predicateUid))]}) | ||||
|   >>> view = NodeView(m112, request) | ||||
|  | @ -764,22 +764,22 @@ created object: | |||
|   >>> cont.update() | ||||
|   False | ||||
|   >>> sorted(resources.keys()) | ||||
|   [...u'test_note-2'...] | ||||
|   [...'test_note-2'...] | ||||
|   >>> note = resources['test_note-2'] | ||||
|   >>> sorted(t.__name__ for t in note.getConcepts()) | ||||
|   [u'note', u'topic'] | ||||
|   ['note', 'topic'] | ||||
| 
 | ||||
| When creating an object its name may be automatically generated using the title | ||||
| of the object. Let's make sure that the name chooser also handles special | ||||
| and possibly critcal cases: | ||||
| 
 | ||||
|   >>> nc = NameChooser(resources) | ||||
|   >>> nc.chooseName(u'', Resource(u'abc: (cde)')) | ||||
|   u'abc__cde' | ||||
|   >>> nc.chooseName(u'', Resource(u'\xdcml\xe4ut')) | ||||
|   u'uemlaeut' | ||||
|   >>> nc.chooseName(u'', Resource(u'A very very loooooong title')) | ||||
|   u'a_title' | ||||
|   >>> nc.chooseName('', Resource('abc: (cde)')) | ||||
|   'abc__cde' | ||||
|   >>> nc.chooseName('', Resource('\xdcml\xe4ut')) | ||||
|   'uemlaeut' | ||||
|   >>> nc.chooseName('', Resource('A very very loooooong title')) | ||||
|   'a_title' | ||||
| 
 | ||||
| Editing an Object | ||||
| ----------------- | ||||
|  | @ -796,7 +796,7 @@ that in turns calls formlibs ``setUpWidgets()``. | |||
| The new technique uses the ``fields`` and ``data`` attributes... | ||||
| 
 | ||||
|   >>> for f in view.fields: | ||||
|   ...     print f.name, f.fieldType, f.required, f.vocabulary | ||||
|   ...     print(f.name, f.fieldType, f.required, f.vocabulary) | ||||
|   title textline True None | ||||
|   data textarea False None | ||||
|   contentType dropdown True <...SimpleVocabulary object...> | ||||
|  | @ -804,22 +804,22 @@ The new technique uses the ``fields`` and ``data`` attributes... | |||
|   linkText textline False None | ||||
| 
 | ||||
|   >>> view.data | ||||
|   {'linkUrl': u'http://', 'contentType': u'text/restructured', 'data': u'', | ||||
|    'linkText': u'', 'title': u'Test Note'} | ||||
|   {'title': 'Test Note', 'data': '', 'contentType': 'text/restructured',  | ||||
|    'linkUrl': 'http://', 'linkText': ''} | ||||
| 
 | ||||
| The object is changed via a FormController adapter created for | ||||
| a NodeView. | ||||
| 
 | ||||
|   >>> form = dict( | ||||
|   ...     title=u'Test Note - changed', | ||||
|   ...     contentType=u'text/plain',) | ||||
|   ...     title='Test Note - changed', | ||||
|   ...     contentType='text/plain',) | ||||
|   >>> request = TestRequest(form=form) | ||||
|   >>> view = NodeView(m112, request) | ||||
|   >>> cont = EditObject(view, request) | ||||
|   >>> cont.update() | ||||
|   False | ||||
|   >>> resources['test_note'].title | ||||
|   u'Test Note - changed' | ||||
|   'Test Note - changed' | ||||
| 
 | ||||
| Virtual Targets | ||||
| --------------- | ||||
|  | @ -883,13 +883,13 @@ informations about all parents of an object. | |||
| 
 | ||||
|   >>> parents = m113.getAllParents() | ||||
|   >>> for p in parents: | ||||
|   ...     print p.object.title | ||||
|   ...     print(p.object.title) | ||||
|   Zope | ||||
|   Menu | ||||
| 
 | ||||
|   >>> parents = resources['test_note'].getAllParents() | ||||
|   >>> for p in parents: | ||||
|   ...     print p.object.title, len(p.relations) | ||||
|   ...     print(p.object.title, len(p.relations)) | ||||
|   Note 1 | ||||
|   Type 2 | ||||
| 
 | ||||
|  | @ -916,10 +916,30 @@ relates ISO country codes with the full name of the country. | |||
|   [('at', ['Austria']), ('de', ['Germany'])] | ||||
| 
 | ||||
|   >>> countries.dataAsRecords() | ||||
|   [{'value': 'Austria', 'key': 'at'}, {'value': 'Germany', 'key': 'de'}] | ||||
|   [{'key': 'at', 'value': 'Austria'}, {'key': 'de', 'value': 'Germany'}] | ||||
| 
 | ||||
|   >>> countries.getRowsByValue('value', 'Germany') | ||||
|   [{'value': 'Germany', 'key': 'de'}] | ||||
|   [{'key': 'de', 'value': 'Germany'}] | ||||
| 
 | ||||
| The ``recordstable`` type is a variation of this datable type that contains | ||||
| a simple list of records - without a key column. A record in this  type is a | ||||
| dictionary with the field name as key and the field value as value. | ||||
| 
 | ||||
|   >>> from loops.table import IRecordsTable, RecordsTable | ||||
|   >>> component.provideAdapter(RecordsTable, provides=IRecordsTable) | ||||
| 
 | ||||
|   >>> drType = addAndConfigureObject(concepts, Concept, 'recordstable', | ||||
|   ...                   title='Records Table', conceptType=concepts['type'], | ||||
|   ...                   typeInterface=IRecordsTable) | ||||
| 
 | ||||
| We just reuse the existing ``countries`` table and convert it to a records table. | ||||
| 
 | ||||
|   >>> baseObject(countries).setType(drType) | ||||
| 
 | ||||
|   >>> countries = adapted(concepts['countries']) | ||||
| 
 | ||||
|   >>> countries.data | ||||
|   [{'key': 'at', 'value': 'Austria'}, {'key': 'de', 'value': 'Germany'}] | ||||
| 
 | ||||
| 
 | ||||
| Caching | ||||
|  | @ -931,7 +951,7 @@ To be done... | |||
|   >>> obj = resources['test_note'] | ||||
|   >>> cxObj = cached(obj) | ||||
|   >>> [p.object.title for p in cxObj.getAllParents()] | ||||
|   [u'Note', u'Type'] | ||||
|   ['Note', 'Type'] | ||||
| 
 | ||||
| 
 | ||||
| Security | ||||
|  | @ -1,30 +1,13 @@ | |||
| # | ||||
| #  Copyright (c) 2017 Helmut Merz helmutm@cy55.de | ||||
| # | ||||
| #  This program is free software; you can redistribute it and/or modify | ||||
| #  it under the terms of the GNU General Public License as published by | ||||
| #  the Free Software Foundation; either version 2 of the License, or | ||||
| #  (at your option) any later version. | ||||
| # | ||||
| #  This program is distributed in the hope that it will be useful, | ||||
| #  but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
| #  GNU General Public License for more details. | ||||
| # | ||||
| #  You should have received a copy of the GNU General Public License | ||||
| #  along with this program; if not, write to the Free Software | ||||
| #  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA | ||||
| # | ||||
| # loops.base | ||||
| 
 | ||||
| """ | ||||
| Implementation of loops root object. | ||||
| """ Implementation of loops root object. | ||||
| """ | ||||
| 
 | ||||
| from zope.app.container.btree import BTreeContainer | ||||
| from zope.app.folder.folder import Folder | ||||
| from zope.app.folder.interfaces import IFolder | ||||
| from zope.container.btree import BTreeContainer | ||||
| from zope.site.folder import Folder | ||||
| from zope.site.interfaces import IFolder | ||||
| from zope.traversing.api import getPath, traverse | ||||
| from zope.interface import implements | ||||
| from zope.interface import implementer | ||||
| 
 | ||||
| from cybertools.util.jeep import Jeep | ||||
| from loops.interfaces import ILoops | ||||
|  | @ -32,17 +15,8 @@ from loops.interfaces import ILoops | |||
| loopsPrefix = '.loops' | ||||
| 
 | ||||
| 
 | ||||
| @implementer(ILoops) | ||||
| class Loops(Folder): | ||||
| #class Loops(BTreeContainer): | ||||
| 
 | ||||
|     implements(ILoops) | ||||
| 
 | ||||
|     #def getSiteManager(self): | ||||
|     #    return self.__parent__.getSiteManager() | ||||
| 
 | ||||
|     #@property | ||||
|     #def _SampleContainer__data(self): | ||||
|     #    return self.data | ||||
| 
 | ||||
|     _skinName = '' | ||||
|     def getSkinName(self): return self._skinName | ||||
|  | @ -72,7 +46,8 @@ class Loops(Folder): | |||
|         return self.get('records') | ||||
| 
 | ||||
|     def getLoopsUri(self, obj): | ||||
|         return str(loopsPrefix + getPath(obj)[len(getPath(self)):]) | ||||
|         uri = loopsPrefix + getPath(obj)[len(getPath(self)):] | ||||
|         return uri | ||||
| 
 | ||||
|     def loopsTraverse(self, uri): | ||||
|         prefix = loopsPrefix + '/' | ||||
							
								
								
									
										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 @@ | |||
| # | ||||
| #  Copyright (c) 2013 Helmut Merz helmutm@cy55.de | ||||
| # | ||||
| #  This program is free software; you can redistribute it and/or modify | ||||
| #  it under the terms of the GNU General Public License as published by | ||||
| #  the Free Software Foundation; either version 2 of the License, or | ||||
| #  (at your option) any later version. | ||||
| # | ||||
| #  This program is distributed in the hope that it will be useful, | ||||
| #  but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
| #  GNU General Public License for more details. | ||||
| # | ||||
| #  You should have received a copy of the GNU General Public License | ||||
| #  along with this program; if not, write to the Free Software | ||||
| #  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA | ||||
| # | ||||
| # loops.browser.action | ||||
| 
 | ||||
| """ | ||||
| Base classes (sort of views) for action portlet items. | ||||
| """ Base classes (sort of views) for action portlet items. | ||||
| """ | ||||
| 
 | ||||
| from urllib import urlencode | ||||
| from urllib.parse import urlencode | ||||
| from zope import component | ||||
| from zope.app.pagetemplate import ViewPageTemplateFile | ||||
| from zope.cachedescriptors.property import Lazy | ||||
							
								
								
									
										79
									
								
								loops/browser/auth.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						|  | @ -0,0 +1,79 @@ | |||
| # loops.browser.auth | ||||
| 
 | ||||
| """ Login, logout, unauthorized stuff. | ||||
| """ | ||||
| 
 | ||||
| from zope.app.exception.browser.unauthorized import Unauthorized as DefaultUnauth | ||||
| from zope.authentication.interfaces import IAuthentication | ||||
| from zope.authentication.interfaces import ILogout, IUnauthenticatedPrincipal | ||||
| from zope.browserpage import ViewPageTemplateFile | ||||
| from zope.cachedescriptors.property import Lazy | ||||
| from zope import component | ||||
| from zope.interface import implementer | ||||
| 
 | ||||
| from loops.browser.concept import ConceptView | ||||
| from loops.browser.node import NodeView | ||||
| 
 | ||||
| 
 | ||||
| template = ViewPageTemplateFile('auth.pt') | ||||
| 
 | ||||
| 
 | ||||
| class LoginConcept(ConceptView): | ||||
| 
 | ||||
|     template = template | ||||
| 
 | ||||
|     @Lazy | ||||
|     def macro(self): | ||||
|         return self.template.macros['login_form'] | ||||
| 
 | ||||
| 
 | ||||
| class LoginForm(NodeView): | ||||
| 
 | ||||
|     template = template | ||||
| 
 | ||||
|     @Lazy | ||||
|     def macro(self): | ||||
|         return self.template.macros['login_form'] | ||||
| 
 | ||||
|     @Lazy | ||||
|     def item(self): | ||||
|         return self | ||||
| 
 | ||||
| 
 | ||||
| @implementer(ILogout) | ||||
| class Logout(object): | ||||
| 
 | ||||
| 
 | ||||
|     def __init__(self, context, request): | ||||
|         self.context = context | ||||
|         self.request = request | ||||
| 
 | ||||
|     def __call__(self): | ||||
|         nextUrl = self.request.get('nextURL') or self.request.URL[-1] | ||||
|         nx = self.request.response.redirect(nextUrl) | ||||
|         if not IUnauthenticatedPrincipal.providedBy(self.request.principal): | ||||
|             auth = component.getUtility(IAuthentication) | ||||
|             ILogout(auth).logout(self.request) | ||||
|         return nx | ||||
| 
 | ||||
| 
 | ||||
| class Unauthorized(ConceptView): | ||||
| 
 | ||||
|     isTopLevel = True | ||||
| 
 | ||||
|     def __init__(self, context, request): | ||||
|         self.context = context | ||||
|         self.request = request | ||||
| 
 | ||||
|     def __call__(self): | ||||
|         response = self.request.response | ||||
|         response.setStatus(403) | ||||
|         # make sure that squid does not keep the response in the cache | ||||
|         response.setHeader('Expires', 'Mon, 26 Jul 1997 05:00:00 GMT') | ||||
|         response.setHeader('Cache-Control', 'no-store, no-cache, must-revalidate') | ||||
|         response.setHeader('Pragma', 'no-cache') | ||||
|         if self.nodeView is None: | ||||
|             v = DefaultUnauth(self.context, self.request) | ||||
|             return v() | ||||
|         url = self.nodeView.topMenu.url | ||||
|         response.redirect(url + '/unauthorized') | ||||
|  | @ -1,45 +1,28 @@ | |||
| # | ||||
| #  Copyright (c) 2016 Helmut Merz helmutm@cy55.de | ||||
| # | ||||
| #  This program is free software; you can redistribute it and/or modify | ||||
| #  it under the terms of the GNU General Public License as published by | ||||
| #  the Free Software Foundation; either version 2 of the License, or | ||||
| #  (at your option) any later version. | ||||
| # | ||||
| #  This program is distributed in the hope that it will be useful, | ||||
| #  but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
| #  GNU General Public License for more details. | ||||
| # | ||||
| #  You should have received a copy of the GNU General Public License | ||||
| #  along with this program; if not, write to the Free Software | ||||
| #  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA | ||||
| # | ||||
| # loops.browser.common | ||||
| 
 | ||||
| """ | ||||
| Common base class for loops browser view classes. | ||||
| """ Common base class for loops browser view classes. | ||||
| """ | ||||
| 
 | ||||
| from cgi import parse_qs, parse_qsl | ||||
| #import mimetypes   # use more specific assignments from cybertools.text | ||||
| from datetime import date, datetime | ||||
| from logging import getLogger | ||||
| import re | ||||
| from time import strptime | ||||
| from urllib import urlencode | ||||
| from urllib.parse import parse_qs, parse_qsl, urlencode | ||||
| from zope import component | ||||
| from zope.app.form.browser.interfaces import ITerms | ||||
| from zope.app.i18n.interfaces import ITranslationDomain | ||||
| from zope.app.security.interfaces import IAuthentication, IUnauthenticatedPrincipal | ||||
| from zope.app.pagetemplate import ViewPageTemplateFile | ||||
| from zope.app.security.interfaces import IUnauthenticatedPrincipal | ||||
| from zope.app.security.interfaces import PrincipalLookupError | ||||
| from zope.authentication.interfaces import IAuthentication, IUnauthenticatedPrincipal | ||||
| from zope.authentication.interfaces import IUnauthenticatedPrincipal | ||||
| from zope.authentication.interfaces import PrincipalLookupError | ||||
| from zope.browser.interfaces import ITerms | ||||
| from zope.browserpage import ViewPageTemplateFile | ||||
| from zope.cachedescriptors.property import Lazy | ||||
| from zope.dottedname.resolve import resolve | ||||
| from zope.dublincore.interfaces import IZopeDublinCore | ||||
| from zope.formlib import form | ||||
| from zope.formlib.form import FormFields | ||||
| from zope.formlib.namedtemplate import NamedTemplate | ||||
| from zope.interface import Interface, implements | ||||
| from zope.i18n.interfaces import ITranslationDomain | ||||
| from zope.interface import Interface, implementer | ||||
| from zope.proxy import removeAllProxies | ||||
| from zope.publisher.browser import applySkin | ||||
| from zope.publisher.interfaces.browser import IBrowserSkinType, IBrowserView | ||||
|  | @ -98,16 +81,20 @@ class NameField(schema.ASCIILine): | |||
| class ViewMode(object): | ||||
| 
 | ||||
|     def __init__(self, name='view', title=None, url=None, active=False, | ||||
|                  description=u''): | ||||
|                  description=u'', subViewModes=Jeep()): | ||||
|         self.name = name | ||||
|         self.title = title | ||||
|         self.url = url | ||||
|         self.active = active | ||||
|         self.description = description | ||||
|         self.subViewModes = subViewModes | ||||
| 
 | ||||
|     @property | ||||
|     def cssClass(self): | ||||
|         return self.active and u'active' or u'inactive' | ||||
|         result = self.active and u'active' or u'inactive' | ||||
|         if self.subViewModes: | ||||
|             result += u' sub-modes' | ||||
|         return result | ||||
| 
 | ||||
| 
 | ||||
| class IAddForm(Interface): | ||||
|  | @ -205,6 +192,10 @@ class BaseView(GenericView, I18NView, SortableMixin): | |||
|         self.context = removeSecurityProxy(context) | ||||
|         try: | ||||
|             if not self.checkPermissions(): | ||||
|                 logger = getLogger('loops.browser.common-153') | ||||
|                 principal = request.principal and request.principal.id | ||||
|                 msg = 'Unauthorized: %s, %s' % (self.contextInfo, principal) | ||||
|                 logger.warn(msg) | ||||
|                 raise Unauthorized(str(self.contextInfo)) | ||||
|         except ForbiddenAttribute:  # ignore when testing | ||||
|             pass | ||||
|  | @ -904,6 +895,7 @@ class BaseView(GenericView, I18NView, SortableMixin): | |||
| 
 | ||||
|     @Lazy | ||||
|     def xeditable(self): | ||||
|         return False | ||||
|         if self.typeOptions('no_external_edit'): | ||||
|             return False | ||||
|         ct = getattr(self.context, 'contentType', '') | ||||
|  | @ -952,9 +944,9 @@ class BaseView(GenericView, I18NView, SortableMixin): | |||
|                     djConfig='parseOnLoad: true, usePlainJson: true, ' | ||||
|                              #'isDebug: true, ' | ||||
|                              'locale: "%s"' % self.languageInfo.language) | ||||
|         jsCall = ('dojo.require("dojo.parser"); ' | ||||
|                   'dojo.registerModulePath("jocy", "/@@/cybertools.jocy"); ' | ||||
|                   'dojo.require("jocy.data");') | ||||
|         jsCall = ('dojo.require("dojo.parser"); ') | ||||
|                   #'dojo.registerModulePath("jocy", "/@@/cybertools.jocy"); ' | ||||
|                   #'dojo.require("jocy.data");') | ||||
|         cm.register('js-execute', 'dojo_registration', jsCall=jsCall) | ||||
|         cm.register('css', identifier='Lightbox.css', position=0, | ||||
|                     resourceName='ajax.dojo/dojox/image/resources/Lightbox.css', | ||||
|  | @ -1082,13 +1074,12 @@ class LoggedIn(object): | |||
| 
 | ||||
| # vocabulary stuff | ||||
| 
 | ||||
| @implementer(ITerms) | ||||
| class SimpleTerms(object): | ||||
|     """ Provide the ITerms interface, e.g. for usage in selection | ||||
|         lists. | ||||
|     """ | ||||
| 
 | ||||
|     implements(ITerms) | ||||
| 
 | ||||
|     def __init__(self, source, request): | ||||
|         # the source parameter is a list of tuples (token, title). | ||||
|         self.source = source | ||||
|  | @ -1103,13 +1094,12 @@ class SimpleTerms(object): | |||
|         return (token, self.terms[token]) | ||||
| 
 | ||||
| 
 | ||||
| @implementer(ITerms) | ||||
| class LoopsTerms(object): | ||||
|     """ Provide the ITerms interface, e.g. for usage in selection | ||||
|         lists. | ||||
|     """ | ||||
| 
 | ||||
|     implements(ITerms) | ||||
| 
 | ||||
|     def __init__(self, source, request): | ||||
|         # the source parameter is a view or adapter of a real context object: | ||||
|         self.source = source | ||||
|  | @ -1131,12 +1121,11 @@ class LoopsTerms(object): | |||
|         return self.loopsRoot.loopsTraverse(token) | ||||
| 
 | ||||
| 
 | ||||
| @implementer(ITerms) | ||||
| class InterfaceTerms(object): | ||||
|     """ Provide the ITerms interface for source list of interfaces. | ||||
|     """ | ||||
| 
 | ||||
|     implements(ITerms) | ||||
| 
 | ||||
|     def __init__(self, source, request): | ||||
|         self.source = source | ||||
|         self.request = request | ||||
|  | @ -1,40 +1,22 @@ | |||
| # | ||||
| #  Copyright (c) 2016 Helmut Merz helmutm@cy55.de | ||||
| # | ||||
| #  This program is free software; you can redistribute it and/or modify | ||||
| #  it under the terms of the GNU General Public License as published by | ||||
| #  the Free Software Foundation; either version 2 of the License, or | ||||
| #  (at your option) any later version. | ||||
| # | ||||
| #  This program is distributed in the hope that it will be useful, | ||||
| #  but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
| #  GNU General Public License for more details. | ||||
| # | ||||
| #  You should have received a copy of the GNU General Public License | ||||
| #  along with this program; if not, write to the Free Software | ||||
| #  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA | ||||
| # | ||||
| # loops.browser.concept | ||||
| 
 | ||||
| """ | ||||
| Definition of the concept view classes. | ||||
| """ Definition of the concept view classes. | ||||
| """ | ||||
| 
 | ||||
| from itertools import groupby | ||||
| from zope import interface, component, schema | ||||
| from zope.app.catalog.interfaces import ICatalog | ||||
| from zope.lifecycleevent import ObjectCreatedEvent, ObjectModifiedEvent | ||||
| from zope.app.container.contained import ObjectRemovedEvent | ||||
| from zope.app.form.browser.interfaces import ITerms | ||||
| from zope.app.form.interfaces import IDisplayWidget | ||||
| from zope.app.pagetemplate import ViewPageTemplateFile | ||||
| from zope.app.security.interfaces import IUnauthenticatedPrincipal | ||||
| from zope.authentication.interfaces import IUnauthenticatedPrincipal | ||||
| from zope.browser.interfaces import ITerms | ||||
| from zope.browserpage import ViewPageTemplateFile | ||||
| from zope.cachedescriptors.property import Lazy | ||||
| from zope.catalog.interfaces import ICatalog | ||||
| from zope.container.contained import ObjectRemovedEvent | ||||
| from zope.dottedname.resolve import resolve | ||||
| from zope.event import notify | ||||
| from zope.formlib.form import EditForm, FormFields, setUpEditWidgets | ||||
| from zope.formlib.interfaces import IDisplayWidget | ||||
| from zope.formlib.namedtemplate import NamedTemplate | ||||
| from zope.interface import implements | ||||
| from zope.lifecycleevent import ObjectCreatedEvent, ObjectModifiedEvent | ||||
| from zope.publisher.interfaces import BadRequest | ||||
| from zope.publisher.interfaces.browser import IBrowserRequest | ||||
| from zope.schema.interfaces import IIterableSource | ||||
|  | @ -308,7 +290,7 @@ class ConceptView(BaseView): | |||
|     def breadcrumbsParent(self): | ||||
|         for p in self.context.getParents([self.defaultPredicate]): | ||||
|             view = self.nodeView.getViewForTarget(p) | ||||
|             if view.showInBreadcrumbs: | ||||
|             if view is not None and view.showInBreadcrumbs: | ||||
|                 return view | ||||
|         return None | ||||
| 
 | ||||
|  | @ -463,7 +445,7 @@ class ConceptView(BaseView): | |||
| 
 | ||||
|     def parents(self): | ||||
|         rels = sorted(self.context.getParentRelations(), | ||||
|                       key=(lambda x: x.first.title and x.first.title.lower())) | ||||
|                       key=(lambda x: x.first.title and x.first.title.lower() or '')) | ||||
|         for r in rels: | ||||
|             yield self.childViewFactory(r, self.request) | ||||
| 
 | ||||
|  | @ -10,7 +10,9 @@ | |||
| 
 | ||||
| <metal:data define-macro="conceptdata"> | ||||
|   <div tal:attributes="class string:content-$level;"> | ||||
|       <tal:block condition="not:title_shown|python:False"> | ||||
|         <metal:block use-macro="view/concept_macros/concepttitle"/> | ||||
|       </tal:block> | ||||
|       <metal:slot define-slot="fields"> | ||||
|         <metal:block use-macro="view/concept_macros/conceptfields" /> | ||||
|       </metal:slot> | ||||
|  | @ -34,16 +34,18 @@ | |||
| 
 | ||||
|   <!-- login/logout --> | ||||
| 
 | ||||
|   <page for="loops.interfaces.INode" | ||||
|   <!--<page for="loops.interfaces.INode" | ||||
|         name="login.html" | ||||
|         class="loops.browser.auth.LoginForm" | ||||
|         permission="zope.View" /> | ||||
|         permission="zope.View" />--> | ||||
| 
 | ||||
|   <page for="loops.interfaces.INode" | ||||
|         name="logout.html" | ||||
|         class="loops.browser.auth.Logout" | ||||
|         permission="zope.View" /> | ||||
| 
 | ||||
|   <!-- see also view/adapter "login.html" in section "query views" --> | ||||
| 
 | ||||
|   <!-- macros --> | ||||
| 
 | ||||
|   <page | ||||
|  | @ -537,6 +539,14 @@ | |||
| 
 | ||||
|   <!-- query views --> | ||||
| 
 | ||||
|   <!--<zope:adapter | ||||
|       name="login.html" | ||||
|       for="loops.interfaces.IConcept | ||||
|            zope.publisher.interfaces.browser.IBrowserRequest" | ||||
|       provides="zope.interface.Interface" | ||||
|       factory="loops.browser.auth.LoginConcept" | ||||
|       permission="zope.View" />--> | ||||
| 
 | ||||
|   <zope:adapter | ||||
|       name="list_children.html" | ||||
|       for="loops.interfaces.IConcept | ||||
|  | @ -1,28 +1,8 @@ | |||
| # | ||||
| #  Copyright (c) 2008 Helmut Merz helmutm@cy55.de | ||||
| # | ||||
| #  This program is free software; you can redistribute it and/or modify | ||||
| #  it under the terms of the GNU General Public License as published by | ||||
| #  the Free Software Foundation; either version 2 of the License, or | ||||
| #  (at your option) any later version. | ||||
| # | ||||
| #  This program is distributed in the hope that it will be useful, | ||||
| #  but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
| #  GNU General Public License for more details. | ||||
| # | ||||
| #  You should have received a copy of the GNU General Public License | ||||
| #  along with this program; if not, write to the Free Software | ||||
| #  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA | ||||
| # | ||||
| # loops.browser.external | ||||
| 
 | ||||
| """ | ||||
| view class(es) for import/export. | ||||
| 
 | ||||
| $Id$ | ||||
| """ view class(es) for import/export. | ||||
| """ | ||||
| 
 | ||||
| from zope.interface import Interface, implements | ||||
| from zope.app import zapi | ||||
| from zope.app.pagetemplate.viewpagetemplatefile import ViewPageTemplateFile | ||||
| from zope.cachedescriptors.property import Lazy | ||||
| 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,35 +1,18 @@ | |||
| # | ||||
| #  Copyright (c) 2017 Helmut Merz helmutm@cy55.de | ||||
| # | ||||
| #  This program is free software; you can redistribute it and/or modify | ||||
| #  it under the terms of the GNU General Public License as published by | ||||
| #  the Free Software Foundation; either version 2 of the License, or | ||||
| #  (at your option) any later version. | ||||
| # | ||||
| #  This program is distributed in the hope that it will be useful, | ||||
| #  but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
| #  GNU General Public License for more details. | ||||
| # | ||||
| #  You should have received a copy of the GNU General Public License | ||||
| #  along with this program; if not, write to the Free Software | ||||
| #  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA | ||||
| # | ||||
| # loops.browser.form | ||||
| 
 | ||||
| """ | ||||
| Classes for form presentation and processing. | ||||
| """ Classes for form presentation and processing. | ||||
| """ | ||||
| 
 | ||||
| from urllib import urlencode, unquote_plus | ||||
| from zope.app.container.contained import ObjectRemovedEvent | ||||
| from urllib.parse import urlencode, unquote_plus | ||||
| from zope import component, interface, schema | ||||
| from zope.component import adapts | ||||
| from zope.container.contained import ObjectRemovedEvent | ||||
| from zope.event import notify | ||||
| from zope.interface import Interface | ||||
| from zope.lifecycleevent import ObjectCreatedEvent, ObjectModifiedEvent | ||||
| from zope.app.container.interfaces import INameChooser | ||||
| from zope.app.container.contained import ObjectAddedEvent | ||||
| from zope.app.pagetemplate import ViewPageTemplateFile | ||||
| from zope.container.interfaces import INameChooser | ||||
| from zope.lifecycleevent import ObjectAddedEvent | ||||
| from zope.browserpage import ViewPageTemplateFile | ||||
| from zope.cachedescriptors.property import Lazy | ||||
| from zope.contenttype import guess_content_type | ||||
| from zope.publisher.browser import FileUpload | ||||
|  | @ -182,7 +165,9 @@ class ObjectForm(NodeView): | |||
|                 field = self.schema.fields.get(k) | ||||
|                 if field: | ||||
|                     fi = field.getFieldInstance(self.instance) | ||||
|                     input = unquote_plus(form[k]) | ||||
|                     input = form[k] | ||||
|                     if isinstance(input, str): | ||||
|                         input = unquote_plus(input) | ||||
|                     data[k] = fi.marshall(fi.unmarshall(input)) | ||||
|                     #data[k] = toUnicode(form[k]) | ||||
|         return data | ||||
|  | @ -364,6 +349,7 @@ class CreateObjectForm(ObjectForm): | |||
|     def adapted(self): | ||||
|         ad = self.typeInterface(Resource()) | ||||
|         ad.storageName = 'unknown'  # hack for file objects: don't try to retrieve data | ||||
|         ad.__is_dummy__ = True | ||||
|         ad.__type__ = adapted(self.typeConcept) | ||||
|         return ad | ||||
| 
 | ||||
|  | @ -1,26 +1,9 @@ | |||
| # | ||||
| #  Copyright (c) 2012 Helmut Merz helmutm@cy55.de | ||||
| # | ||||
| #  This program is free software; you can redistribute it and/or modify | ||||
| #  it under the terms of the GNU General Public License as published by | ||||
| #  the Free Software Foundation; either version 2 of the License, or | ||||
| #  (at your option) any later version. | ||||
| # | ||||
| #  This program is distributed in the hope that it will be useful, | ||||
| #  but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
| #  GNU General Public License for more details. | ||||
| # | ||||
| #  You should have received a copy of the GNU General Public License | ||||
| #  along with this program; if not, write to the Free Software | ||||
| #  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA | ||||
| # | ||||
| # loops.browser.lobo.standard | ||||
| 
 | ||||
| """ | ||||
| View classes for lobo (blueprint-based) layouts. | ||||
| """ View classes for lobo (blueprint-based) layouts. | ||||
| """ | ||||
| 
 | ||||
| from cgi import parse_qs | ||||
| from urllib.parse import parse_qs | ||||
| from zope import interface, component | ||||
| from zope.app.pagetemplate import ViewPageTemplateFile | ||||
| from zope.cachedescriptors.property import Lazy | ||||
|  | @ -1,3 +1,4 @@ | |||
| # loops.browser.lobo.tests | ||||
| 
 | ||||
| import unittest, doctest | ||||
| from zope.interface.verify import verifyClass | ||||
|  | @ -12,7 +13,7 @@ class Test(unittest.TestCase): | |||
| def test_suite(): | ||||
|     flags = doctest.NORMALIZE_WHITESPACE | doctest.ELLIPSIS | ||||
|     return unittest.TestSuite(( | ||||
|                 unittest.makeSuite(Test), | ||||
|         unittest.TestLoader().loadTestsFromTestCase(Test), | ||||
|         doctest.DocFileSuite('README.txt', optionflags=flags), | ||||
|         )) | ||||
| 
 | ||||
| Before Width: | Height: | Size: 942 B After Width: | Height: | Size: 942 B | 
|  | @ -1,31 +1,11 @@ | |||
| # | ||||
| #  Copyright (c) 2011 Helmut Merz helmutm@cy55.de | ||||
| # | ||||
| #  This program is free software; you can redistribute it and/or modify | ||||
| #  it under the terms of the GNU General Public License as published by | ||||
| #  the Free Software Foundation; either version 2 of the License, or | ||||
| #  (at your option) any later version. | ||||
| # | ||||
| #  This program is distributed in the hope that it will be useful, | ||||
| #  but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
| #  GNU General Public License for more details. | ||||
| # | ||||
| #  You should have received a copy of the GNU General Public License | ||||
| #  along with this program; if not, write to the Free Software | ||||
| #  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA | ||||
| # | ||||
| # loops.browser.mobile.default | ||||
| 
 | ||||
| """ | ||||
| Default layouts for the loops mobile skin. | ||||
| 
 | ||||
| $Id$ | ||||
| """ Default layouts for the loops mobile skin. | ||||
| """ | ||||
| 
 | ||||
| from zope.app.pagetemplate import ViewPageTemplateFile | ||||
| from zope.cachedescriptors.property import Lazy | ||||
| from zope import component | ||||
| from zope.interface import implements | ||||
| 
 | ||||
| from cybertools.browser.renderer import RendererFactory | ||||
| from cybertools.composer.layout.base import Layout | ||||
|  | @ -1,35 +1,20 @@ | |||
| # | ||||
| #  Copyright (c) 2017 Helmut Merz helmutm@cy55.de | ||||
| # | ||||
| #  This program is free software; you can redistribute it and/or modify | ||||
| #  it under the terms of the GNU General Public License as published by | ||||
| #  the Free Software Foundation; either version 2 of the License, or | ||||
| #  (at your option) any later version. | ||||
| # | ||||
| #  This program is distributed in the hope that it will be useful, | ||||
| #  but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
| #  GNU General Public License for more details. | ||||
| # | ||||
| #  You should have received a copy of the GNU General Public License | ||||
| #  along with this program; if not, write to the Free Software | ||||
| #  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA | ||||
| # | ||||
| # loops.browser.node | ||||
| 
 | ||||
| """ | ||||
| View class for Node objects. | ||||
| """ View class for Node objects. | ||||
| """ | ||||
| 
 | ||||
| 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 logging import getLogger | ||||
| from urllib.parse import urlencode, urlparse, urlunparse | ||||
| #from urlparse import urlparse, urlunparse | ||||
| from zope.app.container.browser.contents import JustContents | ||||
| from zope.app.container.browser.adding import Adding | ||||
| from zope.app.container.traversal import ItemTraverser | ||||
| from zope.app.pagetemplate import ViewPageTemplateFile | ||||
| from zope.app.security.interfaces import IUnauthenticatedPrincipal | ||||
| from zope import component, interface, schema | ||||
| from zope.annotation.interfaces import IAnnotations | ||||
| from zope.authentication.interfaces import IUnauthenticatedPrincipal | ||||
| from zope.browserpage import ViewPageTemplateFile | ||||
| from zope.cachedescriptors.property import Lazy | ||||
| from zope.catalog.interfaces import ICatalog | ||||
| from zope.container.traversal import ItemTraverser | ||||
| from zope.dottedname.resolve import resolve | ||||
| from zope.event import notify | ||||
| from zope.lifecycleevent import ObjectCreatedEvent, ObjectModifiedEvent | ||||
|  | @ -37,6 +22,7 @@ from zope.lifecycleevent import Attributes | |||
| from zope.formlib.form import Form, FormFields | ||||
| from zope.proxy import removeAllProxies | ||||
| from zope.publisher.defaultview import getDefaultViewName | ||||
| from zope.publisher.interfaces import NotFound | ||||
| from zope.security import canAccess, canWrite, checkPermission | ||||
| from zope.security.proxy import removeSecurityProxy | ||||
| from zope.traversing.api import getParent, getParents, getPath | ||||
|  | @ -65,6 +51,7 @@ from loops import util | |||
| from loops.util import _ | ||||
| from loops.versioning.util import getVersion | ||||
| 
 | ||||
| logger = getLogger('loops.browser.node') | ||||
| 
 | ||||
| node_macros = ViewPageTemplateFile('node_macros.pt') | ||||
| info_macros = ViewPageTemplateFile('info.pt') | ||||
|  | @ -473,6 +460,11 @@ class NodeView(BaseView): | |||
|     def active(self, item): | ||||
|         return item.context == self.context or item.context in self.parents | ||||
| 
 | ||||
|     @Lazy | ||||
|     def logoutUrl(self): | ||||
|         nextUrl = urlencode(dict(nextUrl=self.menu.url)) | ||||
|         return '%s/logout.html?%s' % (self.menu.url, nextUrl) | ||||
| 
 | ||||
|     @Lazy | ||||
|     def authenticationMethod(self): | ||||
|         return self.viewAnnotations.get('auth_method') or 'standard' | ||||
|  | @ -589,7 +581,7 @@ class NodeView(BaseView): | |||
|     @Lazy | ||||
|     def typeProvider(self): | ||||
|         if self.virtualTargetObject is not None: | ||||
|             return IType(self.virtualTargetObject).typeProvider | ||||
|             return IType(baseObject(self.virtualTargetObject)).typeProvider | ||||
|         return None | ||||
| 
 | ||||
|     # target viewing and editing support | ||||
|  | @ -777,11 +769,11 @@ class InlineEdit(NodeView): | |||
|         if ti is not None: | ||||
|             target = ti(target) | ||||
|         data = self.request.form['editorContent'] | ||||
|         if type(data) != unicode: | ||||
|         if not isinstance(data, str): | ||||
|             try: | ||||
|                 data = data.decode('ISO-8859-15')  # IE hack | ||||
|             except UnicodeDecodeError: | ||||
|                 print 'loops.browser.node.InlineEdit.save():', data | ||||
|                 print('loops.browser.node.InlineEdit.save():', data) | ||||
|                 return | ||||
|         #    data = data.decode('UTF-8') | ||||
|         target.data = data | ||||
|  | @ -953,9 +945,9 @@ class NodeAdding(Adding): | |||
|         return info | ||||
| 
 | ||||
| 
 | ||||
| @interface.implementer(IViewConfiguratorSchema) | ||||
| class ViewPropertiesConfigurator(object): | ||||
| 
 | ||||
|     interface.implements(IViewConfiguratorSchema) | ||||
|     component.adapts(INode) | ||||
| 
 | ||||
|     def __init__(self, context): | ||||
|  | @ -1041,7 +1033,12 @@ class NodeTraverser(ItemTraverser): | |||
|                 viewAnnotations['targetView'] = view | ||||
|                 view.logInfo('NodeTraverser:targetView = %r' % view) | ||||
|                 return self.context | ||||
|         try: | ||||
|             obj = super(NodeTraverser, self).publishTraverse(request, name) | ||||
|         except NotFound: | ||||
|             logger.warn('NodeTraverser: NotFound: URL = %s, name = %r' % | ||||
|                             (request.URL, name)) | ||||
|             raise | ||||
|         return obj | ||||
| 
 | ||||
|     def getTargetUid(self, request): | ||||
|  | @ -1089,7 +1086,9 @@ def setViewConfiguration(context, request): | |||
|     config = IViewConfiguratorSchema(context) | ||||
|     skinName = config.skinName | ||||
|     if not skinName: | ||||
|         skinName = context.getLoopsRoot().skinName | ||||
|         root = removeSecurityProxy(context.getLoopsRoot()) | ||||
|         skinName = root.skinName | ||||
|         #skinName = context.getLoopsRoot().skinName | ||||
|     if skinName: | ||||
|         viewAnnotations['skinName'] = skinName | ||||
|     if config.options: | ||||
|  | @ -1108,12 +1107,12 @@ def getViewConfiguration(context, request): | |||
| class TestView(NodeView): | ||||
| 
 | ||||
|     def __call__(self): | ||||
|         print '*** begin' | ||||
|         print( '*** begin') | ||||
|         for i in range(500): | ||||
|             #x = util.getObjectForUid('1994729849') | ||||
|             x = util.getObjectForUid('2018653366') | ||||
|             self.c = list(x.getChildren()) | ||||
|             #self.c = list(x.getChildren([self.defaultPredicate])) | ||||
|         print '*** end', len(self.c) | ||||
|         print('*** end', len(self.c)) | ||||
|         return 'done' | ||||
| 
 | ||||
| Before Width: | Height: | Size: 942 B After Width: | Height: | Size: 942 B | 
|  | @ -267,19 +267,28 @@ | |||
| 
 | ||||
| 
 | ||||
| <metal:actions define-macro="view_modes"> | ||||
|   <div style="margin-bottom: 10px" | ||||
|   <ul class="view-modes" | ||||
|       tal:define="viewModes view/viewModes" | ||||
|       tal:condition="viewModes"> | ||||
|     <ul class="view-modes"> | ||||
|     <li tal:repeat="mode viewModes" | ||||
|         tal:attributes="class mode/cssClass"> | ||||
|       <a tal:attributes="href mode/url; | ||||
|                          title mode/description" | ||||
|          tal:content="mode/title" | ||||
|          i18n:translate="" /> | ||||
|       <ul class="sub-view-modes" | ||||
|           tal:define="subModes mode/subViewModes|nothing" | ||||
|           tal:condition="subModes"> | ||||
|         <li tal:repeat="mode subModes" | ||||
|             tal:attributes="class mode/cssClass"> | ||||
|           <a tal:attributes="href mode/url; | ||||
|                              title mode/description;" | ||||
|              tal:content="mode/title" | ||||
|              i18n:translate=""></a> | ||||
|         </li> | ||||
|       </ul> | ||||
|     </li> | ||||
|   </ul> | ||||
|   </div> | ||||
| </metal:actions> | ||||
| 
 | ||||
| 
 | ||||
|  | @ -335,8 +344,7 @@ | |||
| 
 | ||||
| 
 | ||||
| <metal:actions define-macro="personal"> | ||||
|     <div><a href="logout.html?nextURL=login.html" | ||||
|             tal:attributes="href string:logout.html?nextURL=${view/menu/url}" | ||||
|     <div><a tal:attributes="href view/logoutUrl" | ||||
|             i18n:translate="">Log out</a></div> | ||||
|     <tal:actions repeat="action python:view.getAllowedActions('personal')"> | ||||
|       <metal:action use-macro="action/macro" /> | ||||
|  | @ -18,10 +18,6 @@ | |||
|           <a href="#" | ||||
|              tal:attributes="href string:${target/url}/@@configure.html" | ||||
|              tal:content="target/title">Document xy</a> | ||||
|           <tal:xedit define="xeditObjectUrl target/url" | ||||
|                      condition="target/xeditable"> | ||||
|             <metal:xedit use-macro="views/xedit_macros/editLink" /> | ||||
|           </tal:xedit> | ||||
|         </tal:target> | ||||
|       </div> | ||||
| 
 | ||||
|  | @ -52,7 +52,7 @@ | |||
|                      tal:attributes="value item/token" /> | ||||
|             </td> | ||||
|             <td> | ||||
|               <a tal:content="item/title" | ||||
|               <a tal:content="item/title|string:???" | ||||
|                  tal:attributes="href string:${item/url}/@@SelectedManagementView.html"> | ||||
|                 Title | ||||
|               </a> | ||||
|  | @ -1,36 +1,18 @@ | |||
| # | ||||
| #  Copyright (c) 2015 Helmut Merz helmutm@cy55.de | ||||
| # | ||||
| #  This program is free software; you can redistribute it and/or modify | ||||
| #  it under the terms of the GNU General Public License as published by | ||||
| #  the Free Software Foundation; either version 2 of the License, or | ||||
| #  (at your option) any later version. | ||||
| # | ||||
| #  This program is distributed in the hope that it will be useful, | ||||
| #  but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
| #  GNU General Public License for more details. | ||||
| # | ||||
| #  You should have received a copy of the GNU General Public License | ||||
| #  along with this program; if not, write to the Free Software | ||||
| #  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA | ||||
| # | ||||
| # loops.browser.resource | ||||
| 
 | ||||
| """ | ||||
| View class for resource objects. | ||||
| """ View class for resource objects. | ||||
| """ | ||||
| 
 | ||||
| import os.path | ||||
| import urllib | ||||
| from zope.authentication.interfaces import IUnauthenticatedPrincipal | ||||
| from zope.browserpage import ViewPageTemplateFile | ||||
| from zope.cachedescriptors.property import Lazy | ||||
| from zope import component | ||||
| from zope.app.catalog.interfaces import ICatalog | ||||
| from zope.app.container.interfaces import INameChooser | ||||
| from zope.app.form.browser.textwidgets import FileWidget | ||||
| from zope.app.pagetemplate import ViewPageTemplateFile | ||||
| from zope.app.security.interfaces import IUnauthenticatedPrincipal | ||||
| from zope.catalog.interfaces import ICatalog | ||||
| from zope.container.interfaces import INameChooser | ||||
| from zope.formlib.form import FormFields | ||||
| from zope.formlib.interfaces import DISPLAY_UNWRITEABLE | ||||
| from zope.formlib.textwidgets import FileWidget | ||||
| from zope.proxy import removeAllProxies | ||||
| from zope.schema.interfaces import IBytes | ||||
| from zope.security import canAccess, canWrite | ||||
|  | @ -272,7 +254,7 @@ class ResourceView(BaseView): | |||
|             #wp = wiki.createPage(getName(self.context)) | ||||
|             wp = wiki.addPage(LoopsWikiPage(self.context)) | ||||
|             wp.text = text | ||||
|             #print wp.wiki.getManager() | ||||
|             #print(wp.wiki.getManager()) | ||||
|             #return util.toUnicode(wp.render(self.request)) | ||||
|         return super(ResourceView, self).renderText(text, contentType) | ||||
| 
 | ||||
|  | @ -467,7 +449,7 @@ class ExternalEditorView(ExternalEditorView, BaseView): | |||
|         r.append('meta_type:' + '.'.join((context.__module__, context.__class__.__name__))) | ||||
|         auth = self.request.get('_auth') | ||||
|         if auth: | ||||
|             print 'ExternalEditorView: auth = ', auth | ||||
|             print('ExternalEditorView: auth = ', auth) | ||||
|             if auth.endswith('\n'): | ||||
|                 auth = auth[:-1] | ||||
|             r.append('auth:' + auth) | ||||
|  | @ -72,6 +72,7 @@ | |||
|     <div> | ||||
|       <span class="button"> | ||||
|         <a i18n:translate="" | ||||
|            target="_blank" | ||||
|            tal:attributes="href | ||||
|                 string:${view/virtualTargetUrl}/download.html?version=this"> | ||||
|           Download | ||||
|  | @ -25,6 +25,7 @@ | |||
|     </div> | ||||
| 
 | ||||
|     <div id="content" class="span-6" | ||||
|          tal:attributes="class content_class|string:span-6" | ||||
|          metal:define-macro="content"> | ||||
|       <metal:breadcrumbs define-slot="breadcrumbs"> | ||||
|         <metal:tabs use-macro="views/node_macros/breadcrumbs" /> | ||||
|  | @ -37,7 +38,9 @@ | |||
|              tal:condition="msg" | ||||
|              tal:content="msg" /> | ||||
|       </metal:message> | ||||
|       <metal:tabs define-slot="view_modes"> | ||||
|         <metal:tabs use-macro="views/node_macros/view_modes" /> | ||||
|       </metal:tabs> | ||||
|       <metal:content define-slot="content"> | ||||
|         <tal:content define="item nocall:view/item; | ||||
|                              level level|python: 1; | ||||
|  | @ -48,13 +51,18 @@ | |||
|       </metal:content> | ||||
|     </div> | ||||
| 
 | ||||
|     <div id="portlets" class="span-2 last"> | ||||
|     <div id="portlets" class="span-2 last" | ||||
|          tal:attributes="class portlets_class|string:span-2 last"> | ||||
|       <metal:portlet define-slot="portlet-left" > | ||||
|         <tal:portlet repeat="macro controller/macros/portlet_left"> | ||||
|           <metal:portlet use-macro="macro" /> | ||||
|         </tal:portlet> | ||||
|       </metal:portlet> | ||||
|       <metal:portlet define-slot="portlet-right" > | ||||
|         <tal:portlet repeat="macro controller/macros/portlet_right"> | ||||
|           <metal:portlet use-macro="macro" /> | ||||
|         </tal:portlet> | ||||
|       </metal:portlet> | ||||
|     </div> | ||||
| 
 | ||||
|     <div id="footer" class="footer clear" | ||||
| Before Width: | Height: | Size: 580 B After Width: | Height: | Size: 580 B |