Compare commits
404 commits
Author | SHA1 | Date | |
---|---|---|---|
a72553c2de | |||
1d264fc54f | |||
80c83d5c9f | |||
77fedaaeaa | |||
636b209e9a | |||
520c89f4b2 | |||
b6c93302b9 | |||
0340992932 | |||
ed5e560ba4 | |||
aa0443d0b5 | |||
0de7ef2550 | |||
bbc277e81d | |||
c3efe7a6f9 | |||
8d2f185d17 | |||
ec88df3405 | |||
678ed53ab5 | |||
72d7fdd05f | |||
eddb58c794 | |||
8a578b46a8 | |||
78c8b196bf | |||
2b5a5ec65a | |||
b8ce799b12 | |||
c0fc7fd464 | |||
e01a7362f9 | |||
74a3f9210b | |||
95ed826629 | |||
22efffa11b | |||
0f2232648c | |||
14ea59a307 | |||
8d7cda5ec0 | |||
cfa079de0d | |||
dc87e32a63 | |||
2a689a871b | |||
76e189ac45 | |||
0e180bb229 | |||
98f023195b | |||
5dcc7d2fc6 | |||
2cf3569fcb | |||
6169a2d728 | |||
ffa5c8701e | |||
48a51c54b1 | |||
74ce78dae9 | |||
f2a737e0a8 | |||
6ae2590f50 | |||
1da71c72a9 | |||
010106406d | |||
992b5c012d | |||
bf1fda008c | |||
5e5e9aedfc | |||
956a6d01b3 | |||
52ac41a82f | |||
04a9d9ced0 | |||
509000a996 | |||
ce7015b224 | |||
53be77b5b9 | |||
42d24a5c3f | |||
494612235e | |||
ca70050bec | |||
7a9be5d79b | |||
98bd9b0a46 | |||
7b3c7c182e | |||
77aaad1aa0 | |||
4041841c65 | |||
cb994dc238 | |||
baedf02d78 | |||
6efe2b7a46 | |||
d5a8068261 | |||
73ac0af54e | |||
3c82ec9fdc | |||
81fef0e1d5 | |||
31886012ce | |||
086868d5b7 | |||
f51f3d4f25 | |||
536903f3d8 | |||
75fdced678 | |||
a739aed66a | |||
92bc00e134 | |||
fb9d6991cd | |||
bea7ed0254 | |||
bece8863ef | |||
16a8bcf5c3 | |||
2dbbf977d4 | |||
23623aeb3d | |||
70a93f56d9 | |||
ec87bcd682 | |||
3d3e221a5c | |||
4635a00caf | |||
64ad40fb96 | |||
b4b93b122e | |||
5c4b7fd730 | |||
380d7e7b59 | |||
1cd9908afe | |||
6994d923a2 | |||
997bffc286 | |||
81aa9d4e1e | |||
45d568b77b | |||
4343dc517f | |||
a2ccb8a35b | |||
8e81bab3bf | |||
466044eb77 | |||
a65c138949 | |||
2c22df9c1d | |||
506e539c2d | |||
781521ca88 | |||
6d39f9f354 | |||
e3359db4ca | |||
c9c537af23 | |||
![]() |
1cf6bd3c06 | ||
![]() |
d4edd8ee7a | ||
![]() |
4b54cadcac | ||
8e94c5971e | |||
![]() |
aab2d6c955 | ||
![]() |
34c9d24ffa | ||
1504375a25 | |||
346d041be4 | |||
4b4a804cb7 | |||
2ddf3e03e7 | |||
fecac89a84 | |||
4f710c15d6 | |||
522e40f242 | |||
61a146a43d | |||
31b81d10e3 | |||
347e19a2c6 | |||
ebcbab1a6c | |||
ebacd14134 | |||
d07f3b85a9 | |||
9d9cfd6fc7 | |||
0cd2e1f47d | |||
8723289204 | |||
b471deef43 | |||
3263672934 | |||
bbd1d06792 | |||
8bd69b12fd | |||
fb94aacaa8 | |||
ff0e19a72c | |||
937f2ee8b0 | |||
afd4416eaa | |||
a296370484 | |||
4b5680d969 | |||
590cffaa35 | |||
d282e3c93a | |||
e35fafad81 | |||
de43ab03ca | |||
a13784e93e | |||
8c09a0e73d | |||
4b49fda269 | |||
8c29a3e7d4 | |||
3b37723cd4 | |||
cfc32d7ebd | |||
ef8a47c1a1 | |||
516eda33d1 | |||
![]() |
7f7a2af25f | ||
d863305b94 | |||
6a685187d5 | |||
6ca85b21db | |||
6fd47572f2 | |||
5d02a5ee03 | |||
536f204a03 | |||
efb2c8ba71 | |||
34926dde8c | |||
b7c702f17d | |||
b3677ea635 | |||
421c21cdec | |||
e51ebdb28c | |||
4cfdebec77 | |||
dd96f01a32 | |||
5083b2edc2 | |||
9670ace28c | |||
96e7c20a56 | |||
a91e1b0c4c | |||
3115b4f19e | |||
6261831340 | |||
5f78698e06 | |||
7b0fe5aaf2 | |||
5ffb1edfbc | |||
![]() |
214fb2dc0f | ||
![]() |
df3ae0179b | ||
0dc20ea52f | |||
3b11a72a81 | |||
2720b0444c | |||
![]() |
040997b322 | ||
![]() |
4fb3fa54ec | ||
949a543aa9 | |||
597a8d48ef | |||
65ad427413 | |||
9bf69e88b3 | |||
7cb0cf0d95 | |||
d4e22b7f79 | |||
9e9390cff3 | |||
a8f70fd498 | |||
17eafd7895 | |||
97e7537c4c | |||
4ddddc0933 | |||
01ab785c0d | |||
85dfd815dc | |||
428c772ea9 | |||
6c21581c7e | |||
b14e59e29c | |||
fa1170444d | |||
e27556b98b | |||
6c6c0791fa | |||
dda87b0e79 | |||
b0139ccb13 | |||
89863c9fde | |||
22d1d44560 | |||
665c0271fd | |||
f12361daa4 | |||
635a4e2568 | |||
8e9b4267f2 | |||
1d84f5e509 | |||
8eab09cd44 | |||
f5731c1e3f | |||
5f8ed47165 | |||
5b2b28da19 | |||
608b75f1c8 | |||
689bd983bc | |||
acef9b683e | |||
1d7f01dccb | |||
841eb204eb | |||
17f15429c8 | |||
354dfeda7f | |||
db4f7d15a9 | |||
![]() |
e2c1e67843 | ||
b87dee5c35 | |||
711488a412 | |||
f4a5b78700 | |||
7d979a5749 | |||
396d4727dc | |||
cd65a3f7b5 | |||
e272675498 | |||
4051c2f9a5 | |||
b05c9c5851 | |||
8080342620 | |||
f1b7e01cbc | |||
3990b710c6 | |||
1800fe7c9e | |||
2c512a1ff7 | |||
c7c98b12db | |||
11f859b71e | |||
b70b522137 | |||
6db71366c6 | |||
4f481ce79b | |||
773ad4e1eb | |||
bd1f12ffa0 | |||
f64b60f3b5 | |||
07bb68ae9d | |||
2ae8a60b18 | |||
b8f485d2d3 | |||
0cec511a9f | |||
f2cf265d0c | |||
48caf96670 | |||
48e45941b8 | |||
fc81b49d44 | |||
9df059e77a | |||
9587a86f32 | |||
a45bf52fe7 | |||
085192de21 | |||
bdb10523a4 | |||
11123b16fa | |||
0ec131e1a8 | |||
08d8993e5a | |||
69e529ce39 | |||
e308b8b34f | |||
bbd87c5707 | |||
d834ec2e16 | |||
8256a4efea | |||
146b1c78aa | |||
dbc91c7e6f | |||
![]() |
9b74079bca | ||
e56b0c9863 | |||
5fd52269af | |||
70e53daadf | |||
3c1a5ccdf4 | |||
5bf2906c51 | |||
a3716050e9 | |||
acc104cf95 | |||
2e76910134 | |||
a97ea1b5d1 | |||
4833ebd54b | |||
4c13744a0c | |||
b34b5705e0 | |||
77bf66996a | |||
0555329e86 | |||
070cb50a54 | |||
aef3d2e139 | |||
c970507c85 | |||
9c71572be8 | |||
a02ccde5ff | |||
86421f8cc4 | |||
144e6e5412 | |||
38d74bcb21 | |||
ae789e117b | |||
f935a0efb7 | |||
2ea0689a05 | |||
57aee009b6 | |||
328c1fbaaf | |||
5d4a74c528 | |||
adbdb2840b | |||
09b75367a7 | |||
2c548a3df6 | |||
8d66ee3830 | |||
c3b9e1b665 | |||
7a0ddb1e0d | |||
0936181bf6 | |||
67fe55056f | |||
05b13e154f | |||
58b4db66cc | |||
3470750fc7 | |||
0e28cefc2d | |||
e871eafcaf | |||
7ba0bec7f7 | |||
6a96d72b22 | |||
2567347f62 | |||
c49f87aef5 | |||
396e17c0dc | |||
60604cd38b | |||
c06785b98b | |||
0bac3ecc13 | |||
e70bbb941b | |||
335217081c | |||
a3e0cac006 | |||
9c09222762 | |||
3bbda09f84 | |||
7113369fca | |||
0316bafffc | |||
d317612187 | |||
ef61a836a4 | |||
4c449d5dc4 | |||
82645fb574 | |||
74eb054bc9 | |||
4a67756cf8 | |||
81dde67338 | |||
f6f266acb5 | |||
a565d750e3 | |||
c824c0bdb5 | |||
c4ce28e239 | |||
beff6ef2dd | |||
2c112703a3 | |||
d05b3cd544 | |||
e16f3e5ccf | |||
1716990d26 | |||
bc0949842e | |||
1cec746423 | |||
a13f2218db | |||
075badc55a | |||
0ca62cbe91 | |||
f3d595a974 | |||
b1df34621e | |||
bd85bfbcdc | |||
40d368602d | |||
53d9a5b3a4 | |||
5c0d46193f | |||
7e975dadfb | |||
71cdde4308 | |||
7e9a68bde1 | |||
e5a2cfafa7 | |||
93dcb301f9 | |||
f0248b2ec8 | |||
11a7546506 | |||
58b43637d1 | |||
c3922747bb | |||
e5b4e9800e | |||
d5c851a5de | |||
50b45b2c56 | |||
e675a95871 | |||
527c2f445d | |||
e20feb5851 | |||
1a8550995b | |||
6dde146189 | |||
2f74920692 | |||
17af65c461 | |||
ed996977f9 | |||
95a510c759 | |||
391bc395f0 | |||
0c043434e2 | |||
3a6d6a7ddd | |||
5f81ecfed2 | |||
49b7ef0382 | |||
61484aea70 | |||
cb2ac10524 | |||
6ff8e8211a | |||
11f3218ea4 | |||
ba0fc064d0 | |||
30a7d430d7 | |||
33687724bd | |||
f9bce78d81 | |||
a306e11cec | |||
66bf217ee8 | |||
2fc7709c56 | |||
0dee3c28aa | |||
21ac74f764 | |||
8ab637c402 | |||
fab93d8ceb | |||
2a532dba86 | |||
2d2240244e | |||
f34dc4a59c | |||
6a91788d9e | |||
a47b6a02a0 | |||
61cfff0f91 | |||
e0f30d7a96 | |||
8ce1eb7222 | |||
6e901de066 | |||
f8849ee393 | |||
8495eea8ad |
7
.gitignore
vendored
|
@ -1,7 +1,14 @@
|
||||||
*.pyc
|
*.pyc
|
||||||
*.pyo
|
*.pyo
|
||||||
|
*.swp
|
||||||
|
dist/
|
||||||
|
var/
|
||||||
|
*.egg-info
|
||||||
*.project
|
*.project
|
||||||
*.pydevproject
|
*.pydevproject
|
||||||
|
*.ropeproject
|
||||||
*.sublime-project
|
*.sublime-project
|
||||||
*.sublime-workspace
|
*.sublime-workspace
|
||||||
|
.env
|
||||||
.settings
|
.settings
|
||||||
|
adminuser.zcml
|
||||||
|
|
21
LICENSE
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (C) 2023 cyberconcepts.org team
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
222
LICENSE.GPL
|
@ -1,222 +0,0 @@
|
||||||
GNU GENERAL PUBLIC LICENSE
|
|
||||||
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
|
||||||
|
|
||||||
0. This License applies to any program or other work which contains
|
|
||||||
a notice placed by the copyright holder saying it may be distributed
|
|
||||||
under the terms of this General Public License. The "Program", below,
|
|
||||||
refers to any such program or work, and a "work based on the Program"
|
|
||||||
means either the Program or any derivative work under copyright law:
|
|
||||||
that is to say, a work containing the Program or a portion of it,
|
|
||||||
either verbatim or with modifications and/or translated into another
|
|
||||||
language. (Hereinafter, translation is included without limitation in
|
|
||||||
the term "modification".) Each licensee is addressed as "you".
|
|
||||||
|
|
||||||
Activities other than copying, distribution and modification are not
|
|
||||||
covered by this License; they are outside its scope. The act of
|
|
||||||
running the Program is not restricted, and the output from the Program
|
|
||||||
is covered only if its contents constitute a work based on the
|
|
||||||
Program (independent of having been made by running the Program).
|
|
||||||
Whether that is true depends on what the Program does.
|
|
||||||
|
|
||||||
1. You may copy and distribute verbatim copies of the Program's
|
|
||||||
source code as you receive it, in any medium, provided that you
|
|
||||||
conspicuously and appropriately publish on each copy an appropriate
|
|
||||||
copyright notice and disclaimer of warranty; keep intact all the
|
|
||||||
notices that refer to this License and to the absence of any warranty;
|
|
||||||
and give any other recipients of the Program a copy of this License
|
|
||||||
along with the Program.
|
|
||||||
|
|
||||||
You may charge a fee for the physical act of transferring a copy, and
|
|
||||||
you may at your option offer warranty protection in exchange for a fee.
|
|
||||||
|
|
||||||
2. You may modify your copy or copies of the Program or any portion
|
|
||||||
of it, thus forming a work based on the Program, and copy and
|
|
||||||
distribute such modifications or work under the terms of Section 1
|
|
||||||
above, provided that you also meet all of these conditions:
|
|
||||||
|
|
||||||
a) You must cause the modified files to carry prominent notices
|
|
||||||
stating that you changed the files and the date of any change.
|
|
||||||
|
|
||||||
b) You must cause any work that you distribute or publish, that in
|
|
||||||
whole or in part contains or is derived from the Program or any
|
|
||||||
part thereof, to be licensed as a whole at no charge to all third
|
|
||||||
parties under the terms of this License.
|
|
||||||
|
|
||||||
c) If the modified program normally reads commands interactively
|
|
||||||
when run, you must cause it, when started running for such
|
|
||||||
interactive use in the most ordinary way, to print or display an
|
|
||||||
announcement including an appropriate copyright notice and a
|
|
||||||
notice that there is no warranty (or else, saying that you provide
|
|
||||||
a warranty) and that users may redistribute the program under
|
|
||||||
these conditions, and telling the user how to view a copy of this
|
|
||||||
License. (Exception: if the Program itself is interactive but
|
|
||||||
does not normally print such an announcement, your work based on
|
|
||||||
the Program is not required to print an announcement.)
|
|
||||||
|
|
||||||
These requirements apply to the modified work as a whole. If
|
|
||||||
identifiable sections of that work are not derived from the Program,
|
|
||||||
and can be reasonably considered independent and separate works in
|
|
||||||
themselves, then this License, and its terms, do not apply to those
|
|
||||||
sections when you distribute them as separate works. But when you
|
|
||||||
distribute the same sections as part of a whole which is a work based
|
|
||||||
on the Program, the distribution of the whole must be on the terms of
|
|
||||||
this License, whose permissions for other licensees extend to the
|
|
||||||
entire whole, and thus to each and every part regardless of who wrote it.
|
|
||||||
|
|
||||||
Thus, it is not the intent of this section to claim rights or contest
|
|
||||||
your rights to work written entirely by you; rather, the intent is to
|
|
||||||
exercise the right to control the distribution of derivative or
|
|
||||||
collective works based on the Program.
|
|
||||||
|
|
||||||
In addition, mere aggregation of another work not based on the Program
|
|
||||||
with the Program (or with a work based on the Program) on a volume of
|
|
||||||
a storage or distribution medium does not bring the other work under
|
|
||||||
the scope of this License.
|
|
||||||
|
|
||||||
3. You may copy and distribute the Program (or a work based on it,
|
|
||||||
under Section 2) in object code or executable form under the terms of
|
|
||||||
Sections 1 and 2 above provided that you also do one of the following:
|
|
||||||
|
|
||||||
a) Accompany it with the complete corresponding machine-readable
|
|
||||||
source code, which must be distributed under the terms of Sections
|
|
||||||
1 and 2 above on a medium customarily used for software interchange; or,
|
|
||||||
|
|
||||||
b) Accompany it with a written offer, valid for at least three
|
|
||||||
years, to give any third party, for a charge no more than your
|
|
||||||
cost of physically performing source distribution, a complete
|
|
||||||
machine-readable copy of the corresponding source code, to be
|
|
||||||
distributed under the terms of Sections 1 and 2 above on a medium
|
|
||||||
customarily used for software interchange; or,
|
|
||||||
|
|
||||||
c) Accompany it with the information you received as to the offer
|
|
||||||
to distribute corresponding source code. (This alternative is
|
|
||||||
allowed only for noncommercial distribution and only if you
|
|
||||||
received the program in object code or executable form with such
|
|
||||||
an offer, in accord with Subsection b above.)
|
|
||||||
|
|
||||||
The source code for a work means the preferred form of the work for
|
|
||||||
making modifications to it. For an executable work, complete source
|
|
||||||
code means all the source code for all modules it contains, plus any
|
|
||||||
associated interface definition files, plus the scripts used to
|
|
||||||
control compilation and installation of the executable. However, as a
|
|
||||||
special exception, the source code distributed need not include
|
|
||||||
anything that is normally distributed (in either source or binary
|
|
||||||
form) with the major components (compiler, kernel, and so on) of the
|
|
||||||
operating system on which the executable runs, unless that component
|
|
||||||
itself accompanies the executable.
|
|
||||||
|
|
||||||
If distribution of executable or object code is made by offering
|
|
||||||
access to copy from a designated place, then offering equivalent
|
|
||||||
access to copy the source code from the same place counts as
|
|
||||||
distribution of the source code, even though third parties are not
|
|
||||||
compelled to copy the source along with the object code.
|
|
||||||
|
|
||||||
4. You may not copy, modify, sublicense, or distribute the Program
|
|
||||||
except as expressly provided under this License. Any attempt
|
|
||||||
otherwise to copy, modify, sublicense or distribute the Program is
|
|
||||||
void, and will automatically terminate your rights under this License.
|
|
||||||
However, parties who have received copies, or rights, from you under
|
|
||||||
this License will not have their licenses terminated so long as such
|
|
||||||
parties remain in full compliance.
|
|
||||||
|
|
||||||
5. You are not required to accept this License, since you have not
|
|
||||||
signed it. However, nothing else grants you permission to modify or
|
|
||||||
distribute the Program or its derivative works. These actions are
|
|
||||||
prohibited by law if you do not accept this License. Therefore, by
|
|
||||||
modifying or distributing the Program (or any work based on the
|
|
||||||
Program), you indicate your acceptance of this License to do so, and
|
|
||||||
all its terms and conditions for copying, distributing or modifying
|
|
||||||
the Program or works based on it.
|
|
||||||
|
|
||||||
6. Each time you redistribute the Program (or any work based on the
|
|
||||||
Program), the recipient automatically receives a license from the
|
|
||||||
original licensor to copy, distribute or modify the Program subject to
|
|
||||||
these terms and conditions. You may not impose any further
|
|
||||||
restrictions on the recipients' exercise of the rights granted herein.
|
|
||||||
You are not responsible for enforcing compliance by third parties to
|
|
||||||
this License.
|
|
||||||
|
|
||||||
7. If, as a consequence of a court judgment or allegation of patent
|
|
||||||
infringement or for any other reason (not limited to patent issues),
|
|
||||||
conditions are imposed on you (whether by court order, agreement or
|
|
||||||
otherwise) that contradict the conditions of this License, they do not
|
|
||||||
excuse you from the conditions of this License. If you cannot
|
|
||||||
distribute so as to satisfy simultaneously your obligations under this
|
|
||||||
License and any other pertinent obligations, then as a consequence you
|
|
||||||
may not distribute the Program at all. For example, if a patent
|
|
||||||
license would not permit royalty-free redistribution of the Program by
|
|
||||||
all those who receive copies directly or indirectly through you, then
|
|
||||||
the only way you could satisfy both it and this License would be to
|
|
||||||
refrain entirely from distribution of the Program.
|
|
||||||
|
|
||||||
If any portion of this section is held invalid or unenforceable under
|
|
||||||
any particular circumstance, the balance of the section is intended to
|
|
||||||
apply and the section as a whole is intended to apply in other
|
|
||||||
circumstances.
|
|
||||||
|
|
||||||
It is not the purpose of this section to induce you to infringe any
|
|
||||||
patents or other property right claims or to contest validity of any
|
|
||||||
such claims; this section has the sole purpose of protecting the
|
|
||||||
integrity of the free software distribution system, which is
|
|
||||||
implemented by public license practices. Many people have made
|
|
||||||
generous contributions to the wide range of software distributed
|
|
||||||
through that system in reliance on consistent application of that
|
|
||||||
system; it is up to the author/donor to decide if he or she is willing
|
|
||||||
to distribute software through any other system and a licensee cannot
|
|
||||||
impose that choice.
|
|
||||||
|
|
||||||
This section is intended to make thoroughly clear what is believed to
|
|
||||||
be a consequence of the rest of this License.
|
|
||||||
|
|
||||||
8. If the distribution and/or use of the Program is restricted in
|
|
||||||
certain countries either by patents or by copyrighted interfaces, the
|
|
||||||
original copyright holder who places the Program under this License
|
|
||||||
may add an explicit geographical distribution limitation excluding
|
|
||||||
those countries, so that distribution is permitted only in or among
|
|
||||||
countries not thus excluded. In such case, this License incorporates
|
|
||||||
the limitation as if written in the body of this License.
|
|
||||||
|
|
||||||
9. The Free Software Foundation may publish revised and/or new versions
|
|
||||||
of the General Public License from time to time. Such new versions will
|
|
||||||
be similar in spirit to the present version, but may differ in detail to
|
|
||||||
address new problems or concerns.
|
|
||||||
|
|
||||||
Each version is given a distinguishing version number. If the Program
|
|
||||||
specifies a version number of this License which applies to it and "any
|
|
||||||
later version", you have the option of following the terms and conditions
|
|
||||||
either of that version or of any later version published by the Free
|
|
||||||
Software Foundation. If the Program does not specify a version number of
|
|
||||||
this License, you may choose any version ever published by the Free Software
|
|
||||||
Foundation.
|
|
||||||
|
|
||||||
10. If you wish to incorporate parts of the Program into other free
|
|
||||||
programs whose distribution conditions are different, write to the author
|
|
||||||
to ask for permission. For software which is copyrighted by the Free
|
|
||||||
Software Foundation, write to the Free Software Foundation; we sometimes
|
|
||||||
make exceptions for this. Our decision will be guided by the two goals
|
|
||||||
of preserving the free status of all derivatives of our free software and
|
|
||||||
of promoting the sharing and reuse of software generally.
|
|
||||||
|
|
||||||
NO WARRANTY
|
|
||||||
|
|
||||||
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
|
|
||||||
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
|
|
||||||
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
|
|
||||||
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
|
|
||||||
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
|
||||||
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
|
|
||||||
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
|
|
||||||
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
|
|
||||||
REPAIR OR CORRECTION.
|
|
||||||
|
|
||||||
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
|
||||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
|
|
||||||
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
|
|
||||||
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
|
|
||||||
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
|
|
||||||
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
|
|
||||||
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
|
|
||||||
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
|
|
||||||
POSSIBILITY OF SUCH DAMAGES.
|
|
||||||
|
|
||||||
END OF TERMS AND CONDITIONS
|
|
11
MANIFEST.in
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
global-include *.cfg
|
||||||
|
global-include *.css *.js
|
||||||
|
global-include *.gif *.jpg *.png
|
||||||
|
global-include *.dmp
|
||||||
|
global-include *.md *.txt
|
||||||
|
global-include *.mo *.po *.pot
|
||||||
|
global-include *.pdf
|
||||||
|
global-include *.pt
|
||||||
|
global-include *.zcml
|
||||||
|
|
||||||
|
graft loops/integrator/testdata
|
7
README.md
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
# Introduction
|
||||||
|
|
||||||
|
This is the main part of the code of the semantic
|
||||||
|
web application platform *loops*, based on
|
||||||
|
Zope 3 / bluebream.
|
||||||
|
|
||||||
|
More information: see https://www.cyberconcepts.org.
|
22
__init__.py
|
@ -1,22 +0,0 @@
|
||||||
#
|
|
||||||
# Copyright (c) 2008 Helmut Merz helmutm@cy55.de
|
|
||||||
#
|
|
||||||
# This program is free software; you can redistribute it and/or modify
|
|
||||||
# it under the terms of the GNU General Public License as published by
|
|
||||||
# the Free Software Foundation; either version 2 of the License, or
|
|
||||||
# (at your option) any later version.
|
|
||||||
#
|
|
||||||
# This program is distributed in the hope that it will be useful,
|
|
||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
# GNU General Public License for more details.
|
|
||||||
#
|
|
||||||
# You should have received a copy of the GNU General Public License
|
|
||||||
# along with this program; if not, write to the Free Software
|
|
||||||
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
||||||
#
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
$Id$
|
|
||||||
"""
|
|
19
config.py
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
# loops/config.py
|
||||||
|
# (used for testing only)
|
||||||
|
|
||||||
|
from dotenv import load_dotenv
|
||||||
|
from os import getenv
|
||||||
|
|
||||||
|
load_dotenv()
|
||||||
|
|
||||||
|
server_port = getenv('SERVER_PORT', '8099')
|
||||||
|
|
||||||
|
app_factory = zope_app_factory
|
||||||
|
|
||||||
|
# storage settings
|
||||||
|
dbengine = 'postgresql+psycopg'
|
||||||
|
dbname = getenv('DBNAME', 'demo')
|
||||||
|
dbuser = getenv('DBUSER', 'demo')
|
||||||
|
dbpassword = getenv('DBPASSWORD', 'secret')
|
||||||
|
dbschema = getenv('DBSCHEMA', 'demo')
|
||||||
|
|
|
@ -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>
|
41
inst/loops/config.py
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
# loops/inst/loops/config.py
|
||||||
|
|
||||||
|
from dotenv import load_dotenv
|
||||||
|
from os import getenv
|
||||||
|
|
||||||
|
load_dotenv()
|
||||||
|
|
||||||
|
server_id = getenv('SERVER_ID')
|
||||||
|
zope_conf = getenv('ZOPE_CONF', 'zope.conf')
|
||||||
|
server_port = getenv('SERVER_PORT',
|
||||||
|
server_id and getenv(f'SERVER_PORT_{server_id}')) or '8080'
|
||||||
|
base_url = getenv('BASE_URL',
|
||||||
|
server_id and getenv(f'BASE_URL_{server_id}')) or 'https://test.example.com'
|
||||||
|
|
||||||
|
shell_pw = (getenv('SHELL_PW', 'dummy'))
|
||||||
|
loops_path = (getenv('LOOPS_PATH', 'loops/demo'))
|
||||||
|
|
||||||
|
# storage settings
|
||||||
|
from scopes.storage.db.postgres import StorageFactory
|
||||||
|
dbengine = getenv('DBENGINE', 'postgresql+psycopg')
|
||||||
|
dbname = getenv('DBNAME', 'demo')
|
||||||
|
dbuser = getenv('DBUSER', 'demo')
|
||||||
|
dbpassword = getenv('DBPASSWORD', 'secret')
|
||||||
|
dbschema = getenv('DBSCHEMA', 'demo')
|
||||||
|
|
||||||
|
# OpenID Connect (OIDC, e.g. via zitadel) authentication settings
|
||||||
|
oidc_provider = getenv('OIDC_PROVIDER', '') #'https://instance1-abcdef.zitadel.cloud')
|
||||||
|
oidc_client_id = getenv('OIDC_CLIENT_ID', '12345')
|
||||||
|
oidc_params = dict(
|
||||||
|
op_config_url=oidc_provider + '/.well-known/openid-configuration',
|
||||||
|
op_uris=None,
|
||||||
|
op_keys=None,
|
||||||
|
callback_url=getenv('OIDC_CALLBACK_URL', base_url + '/auth_callback'),
|
||||||
|
client_id=oidc_client_id,
|
||||||
|
principal_prefix=getenv('OIDC_PRINCIPAL_PREFIX', 'loops.'),
|
||||||
|
cookie_name=getenv('OIDC_COOKIE_NAME', 'oidc_' + oidc_client_id),
|
||||||
|
cookie_domain=getenv('OIDC_COOKIE_DOMAIN', None),
|
||||||
|
cookie_lifetime=getenv('OIDC_COOKIE_LIFETIME', '86400'),
|
||||||
|
cookie_crypt=getenv('OIDC_COOKIE_CRYPT', None)
|
||||||
|
)
|
||||||
|
|
12
inst/loops/env.in
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
# loops/inst/loops/.env
|
||||||
|
|
||||||
|
SERVER_ID=0
|
||||||
|
SERVER_PORT_0=8800
|
||||||
|
|
||||||
|
SHELL_PW=dummy
|
||||||
|
LOOPS_PATH=demo
|
||||||
|
|
||||||
|
DBNAME=loops
|
||||||
|
DBUSER=demouser
|
||||||
|
DBPASSWORD=dummy
|
||||||
|
DBSCHEMA=demo
|
4
inst/loops/overrides.zcml
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
<configure xmlns="http://namespaces.zope.org/zope">
|
||||||
|
|
||||||
|
</configure>
|
||||||
|
|
7
inst/loops/runserver.sh
Executable file
|
@ -0,0 +1,7 @@
|
||||||
|
# inst/loops/runserver.sh
|
||||||
|
set -a
|
||||||
|
|
||||||
|
# use environment variables for instance-specific configuration:
|
||||||
|
#SERVER_ID=0
|
||||||
|
|
||||||
|
python -c "from loops.server.main import main; main()"
|
39
inst/loops/zope.conf
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
# loops/inst/bluebream/zope.conf
|
||||||
|
# main zope configuration file for deployment
|
||||||
|
|
||||||
|
# Identify the component configuration used to define the site:
|
||||||
|
site-definition application.zcml
|
||||||
|
|
||||||
|
<zodb>
|
||||||
|
|
||||||
|
<filestorage>
|
||||||
|
path var/filestorage/Data.$(SERVER_ID).fs
|
||||||
|
blob-dir var/blob.$(SERVER_ID)
|
||||||
|
</filestorage>
|
||||||
|
|
||||||
|
# Uncomment this if you want to connect to a ZEO server instead:
|
||||||
|
# <zeoclient>
|
||||||
|
# server $(ZEO_SERVER)
|
||||||
|
# storage 1
|
||||||
|
# # ZEO client cache, in bytes
|
||||||
|
# cache-size $(ZEO_CACHE_SIZE)
|
||||||
|
# # Uncomment to have a persistent disk cache
|
||||||
|
# #client zeo1$(SERVER_ID)
|
||||||
|
# </zeoclient>
|
||||||
|
</zodb>
|
||||||
|
|
||||||
|
<eventlog>
|
||||||
|
# This sets up logging to both a file and to standard output (STDOUT).
|
||||||
|
# The "path" setting can be a relative or absolute filesystem path or
|
||||||
|
# the tokens STDOUT or STDERR.
|
||||||
|
|
||||||
|
<logfile>
|
||||||
|
path var/log/loops-$(SERVER_ID).log
|
||||||
|
formatter zope.exceptions.log.Formatter
|
||||||
|
</logfile>
|
||||||
|
|
||||||
|
<logfile>
|
||||||
|
path STDOUT
|
||||||
|
formatter zope.exceptions.log.Formatter
|
||||||
|
</logfile>
|
||||||
|
</eventlog>
|
9
inst/loops/zshell.sh
Executable file
|
@ -0,0 +1,9 @@
|
||||||
|
# inst/loops/zshell.sh
|
||||||
|
set -a
|
||||||
|
|
||||||
|
# use environment variables for instance-specific configuration:
|
||||||
|
#SERVER_ID=0
|
||||||
|
#LOOPS_PATH=sites/mysite
|
||||||
|
|
||||||
|
python -ic "from loops.server import psu; psu.setup()"
|
||||||
|
|
|
@ -1,54 +0,0 @@
|
||||||
#
|
|
||||||
# Copyright (c) 2008 Helmut Merz helmutm@cy55.de
|
|
||||||
#
|
|
||||||
# This program is free software; you can redistribute it and/or modify
|
|
||||||
# it under the terms of the GNU General Public License as published by
|
|
||||||
# the Free Software Foundation; either version 2 of the License, or
|
|
||||||
# (at your option) any later version.
|
|
||||||
#
|
|
||||||
# This program is distributed in the hope that it will be useful,
|
|
||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
# GNU General Public License for more details.
|
|
||||||
#
|
|
||||||
# You should have received a copy of the GNU General Public License
|
|
||||||
# along with this program; if not, write to the Free Software
|
|
||||||
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
||||||
#
|
|
||||||
|
|
||||||
"""
|
|
||||||
Access to external objects.
|
|
||||||
|
|
||||||
$Id$
|
|
||||||
"""
|
|
||||||
|
|
||||||
import os, re
|
|
||||||
|
|
||||||
from zope import component
|
|
||||||
from zope.cachedescriptors.property import Lazy
|
|
||||||
from zope.component import adapts
|
|
||||||
from zope.interface import implements
|
|
||||||
|
|
||||||
from cybertools.integrator.interfaces import IContainerFactory
|
|
||||||
from loops.common import AdapterBase, adapted
|
|
||||||
from loops.integrator.content.interfaces import IExternalAccess
|
|
||||||
from loops.interfaces import IConcept
|
|
||||||
from loops.type import TypeInterfaceSourceList
|
|
||||||
|
|
||||||
|
|
||||||
TypeInterfaceSourceList.typeInterfaces += (IExternalAccess,)
|
|
||||||
|
|
||||||
|
|
||||||
class ExternalAccess(AdapterBase):
|
|
||||||
""" A concept adapter for accessing external collection.
|
|
||||||
"""
|
|
||||||
|
|
||||||
implements(IExternalAccess)
|
|
||||||
adapts(IConcept)
|
|
||||||
|
|
||||||
_contextAttributes = list(IExternalAccess) + list(IConcept)
|
|
||||||
|
|
||||||
def __call__(self):
|
|
||||||
factory = component.getUtility(IContainerFactory, self.providerName)
|
|
||||||
address = os.path.join(self.baseAddress, self.address or '')
|
|
||||||
return factory(address, __parent__=self.context)
|
|
|
@ -1,45 +0,0 @@
|
||||||
#
|
|
||||||
# Copyright (c) 2009 Helmut Merz helmutm@cy55.de
|
|
||||||
#
|
|
||||||
# This program is free software; you can redistribute it and/or modify
|
|
||||||
# it under the terms of the GNU General Public License as published by
|
|
||||||
# the Free Software Foundation; either version 2 of the License, or
|
|
||||||
# (at your option) any later version.
|
|
||||||
#
|
|
||||||
# This program is distributed in the hope that it will be useful,
|
|
||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
# GNU General Public License for more details.
|
|
||||||
#
|
|
||||||
# You should have received a copy of the GNU General Public License
|
|
||||||
# along with this program; if not, write to the Free Software
|
|
||||||
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
||||||
#
|
|
||||||
|
|
||||||
"""
|
|
||||||
Adapter for mail resources.
|
|
||||||
|
|
||||||
$Id$
|
|
||||||
"""
|
|
||||||
|
|
||||||
from zope.cachedescriptors.property import Lazy
|
|
||||||
from zope import component
|
|
||||||
from zope.component import adapts
|
|
||||||
from zope.interface import implements
|
|
||||||
|
|
||||||
from loops.integrator.mail.interfaces import IMailResource
|
|
||||||
from loops.resource import TextDocumentAdapter
|
|
||||||
from loops.type import TypeInterfaceSourceList
|
|
||||||
|
|
||||||
|
|
||||||
TypeInterfaceSourceList.typeInterfaces += (IMailResource,)
|
|
||||||
|
|
||||||
|
|
||||||
class MailResource(TextDocumentAdapter):
|
|
||||||
""" A concept adapter for accessing a mail collection.
|
|
||||||
May delegate access to a named utility.
|
|
||||||
"""
|
|
||||||
|
|
||||||
implements(IMailResource)
|
|
||||||
|
|
||||||
_contextAttributes = list(IMailResource)
|
|
|
@ -1 +0,0 @@
|
||||||
'''package loops.knowledge.qualification'''
|
|
|
@ -1,43 +0,0 @@
|
||||||
#
|
|
||||||
# Copyright (c) 2013 Helmut Merz helmutm@cy55.de
|
|
||||||
#
|
|
||||||
# This program is free software; you can redistribute it and/or modify
|
|
||||||
# it under the terms of the GNU General Public License as published by
|
|
||||||
# the Free Software Foundation; either version 2 of the License, or
|
|
||||||
# (at your option) any later version.
|
|
||||||
#
|
|
||||||
# This program is distributed in the hope that it will be useful,
|
|
||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
# GNU General Public License for more details.
|
|
||||||
#
|
|
||||||
# You should have received a copy of the GNU General Public License
|
|
||||||
# along with this program; if not, write to the Free Software
|
|
||||||
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
||||||
#
|
|
||||||
|
|
||||||
"""
|
|
||||||
Controlling qualification activities of persons.
|
|
||||||
|
|
||||||
Central part of CCM competence and certification management framework.
|
|
||||||
"""
|
|
||||||
|
|
||||||
from zope.component import adapts
|
|
||||||
from zope.interface import implementer, implements
|
|
||||||
|
|
||||||
from loops.common import AdapterBase
|
|
||||||
from loops.interfaces import IConcept
|
|
||||||
from loops.knowledge.qualification.interfaces import ICompetence
|
|
||||||
from loops.type import TypeInterfaceSourceList
|
|
||||||
|
|
||||||
|
|
||||||
TypeInterfaceSourceList.typeInterfaces += (ICompetence,)
|
|
||||||
|
|
||||||
|
|
||||||
class Competence(AdapterBase):
|
|
||||||
|
|
||||||
implements(ICompetence)
|
|
||||||
|
|
||||||
_contextAttributes = list(ICompetence)
|
|
||||||
|
|
||||||
|
|
|
@ -1,18 +0,0 @@
|
||||||
<configure
|
|
||||||
xmlns:zope="http://namespaces.zope.org/zope"
|
|
||||||
xmlns:browser="http://namespaces.zope.org/browser"
|
|
||||||
i18n_domain="loops">
|
|
||||||
|
|
||||||
<zope:adapter
|
|
||||||
factory="loops.knowledge.qualification.base.Competence"
|
|
||||||
trusted="True" />
|
|
||||||
<zope:class class="loops.knowledge.qualification.base.Competence">
|
|
||||||
<require permission="zope.View"
|
|
||||||
interface="loops.knowledge.qualification.interfaces.ICompetence" />
|
|
||||||
<require permission="zope.ManageContent"
|
|
||||||
set_schema="loops.knowledge.qualification.interfaces.ICompetence" />
|
|
||||||
</zope:class>
|
|
||||||
|
|
||||||
<!-- views -->
|
|
||||||
|
|
||||||
</configure>
|
|
|
@ -1,179 +0,0 @@
|
||||||
#
|
|
||||||
# Copyright (c) 2013 Helmut Merz helmutm@cy55.de
|
|
||||||
#
|
|
||||||
# This program is free software; you can redistribute it and/or modify
|
|
||||||
# it under the terms of the GNU General Public License as published by
|
|
||||||
# the Free Software Foundation; either version 2 of the License, or
|
|
||||||
# (at your option) any later version.
|
|
||||||
#
|
|
||||||
# This program is distributed in the hope that it will be useful,
|
|
||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
# GNU General Public License for more details.
|
|
||||||
#
|
|
||||||
# You should have received a copy of the GNU General Public License
|
|
||||||
# along with this program; if not, write to the Free Software
|
|
||||||
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
||||||
#
|
|
||||||
|
|
||||||
"""
|
|
||||||
Definition of view classes and other browser related stuff for
|
|
||||||
surveys and self-assessments.
|
|
||||||
"""
|
|
||||||
|
|
||||||
import csv
|
|
||||||
from cStringIO import StringIO
|
|
||||||
from zope.app.pagetemplate import ViewPageTemplateFile
|
|
||||||
from zope.cachedescriptors.property import Lazy
|
|
||||||
from zope.i18n import translate
|
|
||||||
|
|
||||||
from cybertools.knowledge.survey.questionnaire import Response
|
|
||||||
from cybertools.util.date import formatTimeStamp
|
|
||||||
from loops.browser.concept import ConceptView
|
|
||||||
from loops.browser.node import NodeView
|
|
||||||
from loops.common import adapted
|
|
||||||
from loops.knowledge.survey.response import Responses
|
|
||||||
from loops.organize.party import getPersonForUser
|
|
||||||
from loops.util import getObjectForUid
|
|
||||||
from loops.util import _
|
|
||||||
|
|
||||||
|
|
||||||
template = ViewPageTemplateFile('view_macros.pt')
|
|
||||||
|
|
||||||
class SurveyView(ConceptView):
|
|
||||||
|
|
||||||
data = None
|
|
||||||
errors = None
|
|
||||||
|
|
||||||
@Lazy
|
|
||||||
def macro(self):
|
|
||||||
self.registerDojo()
|
|
||||||
return template.macros['survey']
|
|
||||||
|
|
||||||
@Lazy
|
|
||||||
def tabview(self):
|
|
||||||
if self.editable:
|
|
||||||
return 'index.html'
|
|
||||||
|
|
||||||
def results(self):
|
|
||||||
result = []
|
|
||||||
response = None
|
|
||||||
form = self.request.form
|
|
||||||
if 'submit' in form:
|
|
||||||
self.data = {}
|
|
||||||
response = Response(self.adapted, None)
|
|
||||||
for key, value in form.items():
|
|
||||||
if key.startswith('question_'):
|
|
||||||
uid = key[len('question_'):]
|
|
||||||
question = adapted(self.getObjectForUid(uid))
|
|
||||||
if value != 'none':
|
|
||||||
value = int(value)
|
|
||||||
self.data[uid] = value
|
|
||||||
response.values[question] = value
|
|
||||||
Responses(self.context).save(self.data)
|
|
||||||
self.errors = self.check(response)
|
|
||||||
if self.errors:
|
|
||||||
return []
|
|
||||||
if response is not None:
|
|
||||||
result = response.getGroupedResult()
|
|
||||||
return [dict(category=r[0].title, text=r[1].text,
|
|
||||||
score=int(round(r[2] * 100)))
|
|
||||||
for r in result]
|
|
||||||
|
|
||||||
def check(self, response):
|
|
||||||
errors = []
|
|
||||||
values = response.values
|
|
||||||
for qu in self.adapted.questions:
|
|
||||||
if qu.required and qu not in values:
|
|
||||||
errors.append('Please answer the obligatory questions.')
|
|
||||||
break
|
|
||||||
qugroups = {}
|
|
||||||
for qugroup in self.adapted.questionGroups:
|
|
||||||
qugroups[qugroup] = 0
|
|
||||||
for qu in values:
|
|
||||||
qugroups[qu.questionGroup] += 1
|
|
||||||
for qugroup, count in qugroups.items():
|
|
||||||
minAnswers = qugroup.minAnswers
|
|
||||||
if minAnswers in (u'', None):
|
|
||||||
minAnswers = len(qugroup.questions)
|
|
||||||
if count < minAnswers:
|
|
||||||
errors.append('Please answer the minimum number of questions.')
|
|
||||||
break
|
|
||||||
return errors
|
|
||||||
|
|
||||||
def getInfoText(self, qugroup):
|
|
||||||
lang = self.languageInfo.language
|
|
||||||
text = qugroup.description
|
|
||||||
info = None
|
|
||||||
if qugroup.minAnswers in (u'', None):
|
|
||||||
info = translate(_(u'Please answer all questions.'), target_language=lang)
|
|
||||||
elif qugroup.minAnswers > 0:
|
|
||||||
info = translate(_(u'Please answer at least $minAnswers questions.',
|
|
||||||
mapping=dict(minAnswers=qugroup.minAnswers)),
|
|
||||||
target_language=lang)
|
|
||||||
if info:
|
|
||||||
text = u'<i>%s</i><br />(%s)' % (text, info)
|
|
||||||
return text
|
|
||||||
|
|
||||||
def getValues(self, question):
|
|
||||||
setting = None
|
|
||||||
if self.data is None:
|
|
||||||
self.data = Responses(self.context).load()
|
|
||||||
if self.data:
|
|
||||||
setting = self.data.get(question.uid)
|
|
||||||
noAnswer = [dict(value='none', checked=(setting == None),
|
|
||||||
radio=(not question.required))]
|
|
||||||
return noAnswer + [dict(value=i, checked=(setting == i), radio=True)
|
|
||||||
for i in reversed(range(question.answerRange))]
|
|
||||||
|
|
||||||
|
|
||||||
class SurveyCsvExport(NodeView):
|
|
||||||
|
|
||||||
encoding = 'ISO8859-15'
|
|
||||||
|
|
||||||
def encode(self, text):
|
|
||||||
text.encode(self.encoding)
|
|
||||||
|
|
||||||
@Lazy
|
|
||||||
def questions(self):
|
|
||||||
result = []
|
|
||||||
for idx1, qug in enumerate(adapted(self.virtualTargetObject).questionGroups):
|
|
||||||
for idx2, qu in enumerate(qug.questions):
|
|
||||||
result.append((idx1, idx2, qug, qu))
|
|
||||||
return result
|
|
||||||
|
|
||||||
@Lazy
|
|
||||||
def columns(self):
|
|
||||||
infoCols = ['Name', 'Timestamp']
|
|
||||||
dataCols = ['%02i-%02i' % (item[0], item[1]) for item in self.questions]
|
|
||||||
return infoCols + dataCols
|
|
||||||
|
|
||||||
def getRows(self):
|
|
||||||
for tr in Responses(self.virtualTargetObject).getAllTracks():
|
|
||||||
p = adapted(getObjectForUid(tr.userName))
|
|
||||||
name = p and p.title or u'???'
|
|
||||||
ts = formatTimeStamp(tr.timeStamp)
|
|
||||||
cells = [tr.data.get(qu.uid, -1)
|
|
||||||
for (idx1, idx2, qug, qu) in self.questions]
|
|
||||||
yield [name, ts] + cells
|
|
||||||
|
|
||||||
def __call__(self):
|
|
||||||
f = StringIO()
|
|
||||||
writer = csv.writer(f, delimiter=',')
|
|
||||||
writer.writerow(self.columns)
|
|
||||||
for row in self.getRows():
|
|
||||||
writer.writerow(row)
|
|
||||||
text = f.getvalue()
|
|
||||||
self.setDownloadHeader(text)
|
|
||||||
return text
|
|
||||||
|
|
||||||
def setDownloadHeader(self, text):
|
|
||||||
response = self.request.response
|
|
||||||
filename = 'survey_data.csv'
|
|
||||||
response.setHeader('Content-Disposition',
|
|
||||||
'attachment; filename=%s' % filename)
|
|
||||||
response.setHeader('Cache-Control', '')
|
|
||||||
response.setHeader('Pragma', '')
|
|
||||||
response.setHeader('Content-Length', len(text))
|
|
||||||
response.setHeader('Content-Type', 'text/csv')
|
|
||||||
|
|
|
@ -1,100 +0,0 @@
|
||||||
#
|
|
||||||
# Copyright (c) 2013 Helmut Merz helmutm@cy55.de
|
|
||||||
#
|
|
||||||
# This program is free software; you can redistribute it and/or modify
|
|
||||||
# it under the terms of the GNU General Public License as published by
|
|
||||||
# the Free Software Foundation; either version 2 of the License, or
|
|
||||||
# (at your option) any later version.
|
|
||||||
#
|
|
||||||
# This program is distributed in the hope that it will be useful,
|
|
||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
# GNU General Public License for more details.
|
|
||||||
#
|
|
||||||
# You should have received a copy of the GNU General Public License
|
|
||||||
# along with this program; if not, write to the Free Software
|
|
||||||
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
||||||
#
|
|
||||||
|
|
||||||
"""
|
|
||||||
Interfaces for surveys used in knowledge management.
|
|
||||||
"""
|
|
||||||
|
|
||||||
from zope.interface import Interface, Attribute
|
|
||||||
from zope import interface, component, schema
|
|
||||||
|
|
||||||
from cybertools.knowledge.survey import interfaces
|
|
||||||
from loops.interfaces import IConceptSchema, ILoopsAdapter
|
|
||||||
from loops.util import _
|
|
||||||
|
|
||||||
|
|
||||||
class IQuestionnaire(IConceptSchema, interfaces.IQuestionnaire):
|
|
||||||
""" A collection of questions for setting up a survey.
|
|
||||||
"""
|
|
||||||
|
|
||||||
defaultAnswerRange = schema.Int(
|
|
||||||
title=_(u'Answer Range'),
|
|
||||||
description=_(u'Number of items (answer options) to select from.'),
|
|
||||||
default=4,
|
|
||||||
required=True)
|
|
||||||
|
|
||||||
feedbackHeader = schema.Text(
|
|
||||||
title=_(u'Feedback Header'),
|
|
||||||
description=_(u'Text that will appear at the top of the feedback page.'),
|
|
||||||
default=u'',
|
|
||||||
missing_value=u'',
|
|
||||||
required=False)
|
|
||||||
|
|
||||||
feedbackFooter = schema.Text(
|
|
||||||
title=_(u'Feedback Footer'),
|
|
||||||
description=_(u'Text that will appear at the end of the feedback page.'),
|
|
||||||
default=u'',
|
|
||||||
missing_value=u'',
|
|
||||||
required=False)
|
|
||||||
|
|
||||||
|
|
||||||
class IQuestionGroup(IConceptSchema, interfaces.IQuestionGroup):
|
|
||||||
""" A group of questions within a questionnaire.
|
|
||||||
"""
|
|
||||||
|
|
||||||
minAnswers = schema.Int(
|
|
||||||
title=_(u'Minimum Number of Answers'),
|
|
||||||
description=_(u'Minumum number of questions that have to be answered. '
|
|
||||||
'Empty means all questions have to be answered.'),
|
|
||||||
default=None,
|
|
||||||
required=False)
|
|
||||||
|
|
||||||
|
|
||||||
class IQuestion(IConceptSchema, interfaces.IQuestion):
|
|
||||||
""" A single question within a questionnaire.
|
|
||||||
"""
|
|
||||||
|
|
||||||
required = schema.Bool(
|
|
||||||
title=_(u'Required'),
|
|
||||||
description=_(u'Question must be answered.'),
|
|
||||||
default=False,
|
|
||||||
required=False)
|
|
||||||
|
|
||||||
revertAnswerOptions = schema.Bool(
|
|
||||||
title=_(u'Negative'),
|
|
||||||
description=_(u'Value inversion: High selection means low value.'),
|
|
||||||
default=False,
|
|
||||||
required=False)
|
|
||||||
|
|
||||||
|
|
||||||
class IFeedbackItem(IConceptSchema, interfaces.IFeedbackItem):
|
|
||||||
""" Some text (e.g. a recommendation) or some other kind of information
|
|
||||||
that may be deduced from the res)ponses to a questionnaire.
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
class IResponse(interfaces.IResponse):
|
|
||||||
""" A set of response values given to the questions of a questionnaire
|
|
||||||
by a single person or party.
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
class IResponses(Interface):
|
|
||||||
""" A container or manager of survey responses.
|
|
||||||
"""
|
|
||||||
|
|
|
@ -1,62 +0,0 @@
|
||||||
#
|
|
||||||
# Copyright (c) 2013 Helmut Merz helmutm@cy55.de
|
|
||||||
#
|
|
||||||
# This program is free software; you can redistribute it and/or modify
|
|
||||||
# it under the terms of the GNU General Public License as published by
|
|
||||||
# the Free Software Foundation; either version 2 of the License, or
|
|
||||||
# (at your option) any later version.
|
|
||||||
#
|
|
||||||
# This program is distributed in the hope that it will be useful,
|
|
||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
# GNU General Public License for more details.
|
|
||||||
#
|
|
||||||
# You should have received a copy of the GNU General Public License
|
|
||||||
# along with this program; if not, write to the Free Software
|
|
||||||
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
||||||
#
|
|
||||||
|
|
||||||
"""
|
|
||||||
Handling survey responses.
|
|
||||||
"""
|
|
||||||
|
|
||||||
from zope.component import adapts
|
|
||||||
from zope.interface import implements
|
|
||||||
|
|
||||||
from cybertools.tracking.btree import Track
|
|
||||||
from cybertools.tracking.interfaces import ITrackingStorage
|
|
||||||
from loops.knowledge.survey.interfaces import IResponse, IResponses
|
|
||||||
from loops.organize.tracking.base import BaseRecordManager
|
|
||||||
|
|
||||||
|
|
||||||
class Responses(BaseRecordManager):
|
|
||||||
|
|
||||||
implements(IResponses)
|
|
||||||
|
|
||||||
storageName = 'survey_responses'
|
|
||||||
|
|
||||||
def __init__(self, context):
|
|
||||||
self.context = context
|
|
||||||
|
|
||||||
def save(self, data):
|
|
||||||
if self.personId:
|
|
||||||
self.storage.saveUserTrack(self.uid, 0, self.personId, data,
|
|
||||||
update=True, overwrite=True)
|
|
||||||
|
|
||||||
def load(self):
|
|
||||||
if self.personId:
|
|
||||||
tracks = self.storage.getUserTracks(self.uid, 0, self.personId)
|
|
||||||
if tracks:
|
|
||||||
return tracks[0].data
|
|
||||||
return {}
|
|
||||||
|
|
||||||
def getAllTracks(self):
|
|
||||||
return self.storage.query(taskId=self.uid)
|
|
||||||
|
|
||||||
|
|
||||||
class Response(Track):
|
|
||||||
|
|
||||||
implements(IResponse)
|
|
||||||
|
|
||||||
typeName = 'Response'
|
|
||||||
|
|
|
@ -1,100 +0,0 @@
|
||||||
<!-- ZPT macros for loops.knowledge.survey views -->
|
|
||||||
<html i18n:domain="loops">
|
|
||||||
|
|
||||||
|
|
||||||
<metal:block define-macro="survey"
|
|
||||||
tal:define="feedback item/results;
|
|
||||||
errors item/errors">
|
|
||||||
<metal:title use-macro="item/conceptMacros/concepttitle_only" />
|
|
||||||
<tal:description condition="not:feedback">
|
|
||||||
<metal:title use-macro="item/conceptMacros/conceptdescription" />
|
|
||||||
</tal:description>
|
|
||||||
<div tal:condition="feedback">
|
|
||||||
<h3 i18n:translate="">Feedback</h3>
|
|
||||||
<div tal:define="header item/adapted/feedbackHeader"
|
|
||||||
tal:condition="header"
|
|
||||||
tal:content="structure python:item.renderText(header, 'text/restructured')" />
|
|
||||||
<table class="listing">
|
|
||||||
<tr>
|
|
||||||
<th i18n:translate="">Category</th>
|
|
||||||
<th i18n:translate="">Response</th>
|
|
||||||
<th i18n:translate="">%</th>
|
|
||||||
</tr>
|
|
||||||
<tr tal:repeat="fbitem feedback">
|
|
||||||
<td tal:content="fbitem/category" />
|
|
||||||
<td tal:content="fbitem/text" />
|
|
||||||
<td tal:content="fbitem/score" />
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
<div class="button" id="show_questionnaire">
|
|
||||||
<a href="" onclick="back(); return false"
|
|
||||||
i18n:translate="">
|
|
||||||
Back to Questionnaire</a>
|
|
||||||
<br />
|
|
||||||
</div>
|
|
||||||
<div tal:define="footer item/adapted/feedbackFooter"
|
|
||||||
tal:condition="footer"
|
|
||||||
tal:content="structure python:item.renderText(footer, 'text/restructured')" />
|
|
||||||
</div>
|
|
||||||
<div id="questionnaire"
|
|
||||||
tal:condition="not:feedback">
|
|
||||||
<h3 i18n:translate="">Questionnaire</h3>
|
|
||||||
<div class="error"
|
|
||||||
tal:condition="errors">
|
|
||||||
<div tal:repeat="error errors">
|
|
||||||
<span i18n:translate=""
|
|
||||||
tal:content="error" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<form method="post">
|
|
||||||
<table class="listing">
|
|
||||||
<tal:qugroup repeat="qugroup item/adapted/questionGroups">
|
|
||||||
<tr><td colspan="6"> </td></tr>
|
|
||||||
<tr class="vpad">
|
|
||||||
<td tal:define="infoText python:item.getInfoText(qugroup)">
|
|
||||||
<b tal:content="qugroup/title" />
|
|
||||||
<div class="infotext"
|
|
||||||
tal:condition="infoText">
|
|
||||||
<span tal:content="structure infoText" />
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
<td style="text-align: center"
|
|
||||||
i18n:translate="">No answer</td>
|
|
||||||
<td colspan="2"
|
|
||||||
i18n:translate="">Fully applies</td>
|
|
||||||
<td colspan="2"
|
|
||||||
style="text-align: right"
|
|
||||||
i18n:translate="">Does not apply</td>
|
|
||||||
</tr>
|
|
||||||
<tr class="vpad"
|
|
||||||
tal:repeat="question qugroup/questions">
|
|
||||||
<td tal:content="question/text" />
|
|
||||||
<td style="white-space: nowrap; text-align: center"
|
|
||||||
tal:repeat="value python:item.getValues(question)">
|
|
||||||
<input type="radio"
|
|
||||||
i18n:attributes="title"
|
|
||||||
tal:condition="value/radio"
|
|
||||||
tal:attributes="
|
|
||||||
name string:question_${question/uid};
|
|
||||||
value value/value;
|
|
||||||
checked value/checked;
|
|
||||||
title string:survey_value_${value/value}" />
|
|
||||||
<span tal:condition="not:value/radio"
|
|
||||||
title="Obligatory question, must be answered"
|
|
||||||
i18n:attributes="title">***
|
|
||||||
</span>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</tal:qugroup>
|
|
||||||
</table>
|
|
||||||
<input type="submit" name="submit" value="Evaluate Questionnaire"
|
|
||||||
i18n:attributes="value" />
|
|
||||||
<input type="button" name="reset_responses" value="Reset Responses Entered"
|
|
||||||
i18n:attributes="value"
|
|
||||||
onclick="setRadioButtons('none'); return false" />
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</metal:block>
|
|
||||||
|
|
||||||
|
|
||||||
</html>
|
|
|
@ -23,7 +23,7 @@ with lower-level aspects like type or state management.
|
||||||
|
|
||||||
Let's also import some common stuff needed later.
|
Let's also import some common stuff needed later.
|
||||||
|
|
||||||
>>> from loops.common import adapted
|
>>> from loops.common import adapted, baseObject
|
||||||
>>> from loops.setup import addAndConfigureObject
|
>>> from loops.setup import addAndConfigureObject
|
||||||
|
|
||||||
|
|
||||||
|
@ -48,14 +48,14 @@ top-level loops container and a concept manager:
|
||||||
>>> cc1 = Concept()
|
>>> cc1 = Concept()
|
||||||
>>> concepts['cc1'] = cc1
|
>>> concepts['cc1'] = cc1
|
||||||
>>> cc1.title
|
>>> cc1.title
|
||||||
u''
|
''
|
||||||
>>> loopsRoot.getLoopsUri(cc1)
|
>>> loopsRoot.getLoopsUri(cc1)
|
||||||
u'.loops/concepts/cc1'
|
'.loops/concepts/cc1'
|
||||||
|
|
||||||
>>> cc2 = Concept(u'Zope 3')
|
>>> cc2 = Concept('Zope 3')
|
||||||
>>> concepts['cc2'] = cc2
|
>>> concepts['cc2'] = cc2
|
||||||
>>> cc2.title
|
>>> cc2.title
|
||||||
u'Zope 3'
|
'Zope 3'
|
||||||
|
|
||||||
Now we want to relate the second concept to the first one.
|
Now we want to relate the second concept to the first one.
|
||||||
|
|
||||||
|
@ -73,11 +73,11 @@ ConceptRelation):
|
||||||
We can now ask our concepts for their related child and parent concepts:
|
We can now ask our concepts for their related child and parent concepts:
|
||||||
|
|
||||||
>>> [getName(c) for c in cc1.getChildren()]
|
>>> [getName(c) for c in cc1.getChildren()]
|
||||||
[u'cc2']
|
['cc2']
|
||||||
>>> len(cc1.getParents())
|
>>> len(cc1.getParents())
|
||||||
0
|
0
|
||||||
>>> [getName(p) for p in cc2.getParents()]
|
>>> [getName(p) for p in cc2.getParents()]
|
||||||
[u'cc1']
|
['cc1']
|
||||||
|
|
||||||
>>> len(cc2.getChildren())
|
>>> len(cc2.getChildren())
|
||||||
0
|
0
|
||||||
|
@ -90,24 +90,24 @@ a special predicate 'hasType'.
|
||||||
>>> typeObject = concepts['type']
|
>>> typeObject = concepts['type']
|
||||||
>>> typeObject.setConceptType(typeObject)
|
>>> typeObject.setConceptType(typeObject)
|
||||||
>>> typeObject.getConceptType().title
|
>>> typeObject.getConceptType().title
|
||||||
u'Type'
|
'Type'
|
||||||
|
|
||||||
>>> concepts['unknown'] = Concept(u'Unknown Type')
|
>>> concepts['unknown'] = Concept('Unknown Type')
|
||||||
>>> unknown = concepts['unknown']
|
>>> unknown = concepts['unknown']
|
||||||
>>> unknown.setConceptType(typeObject)
|
>>> unknown.setConceptType(typeObject)
|
||||||
>>> unknown.getConceptType().title
|
>>> unknown.getConceptType().title
|
||||||
u'Type'
|
'Type'
|
||||||
|
|
||||||
>>> cc1.setConceptType(unknown)
|
>>> cc1.setConceptType(unknown)
|
||||||
>>> cc1.getConceptType().title
|
>>> cc1.getConceptType().title
|
||||||
u'Unknown Type'
|
'Unknown Type'
|
||||||
|
|
||||||
>>> concepts['topic'] = Concept(u'Topic')
|
>>> concepts['topic'] = Concept('Topic')
|
||||||
>>> topic = concepts['topic']
|
>>> topic = concepts['topic']
|
||||||
>>> topic.setConceptType(typeObject)
|
>>> topic.setConceptType(typeObject)
|
||||||
>>> cc1.setConceptType(topic)
|
>>> cc1.setConceptType(topic)
|
||||||
>>> cc1.getConceptType().title
|
>>> cc1.getConceptType().title
|
||||||
u'Topic'
|
'Topic'
|
||||||
|
|
||||||
We get a list of types using the ConceptTypeSourceList.
|
We get a list of types using the ConceptTypeSourceList.
|
||||||
In order for the type machinery to work we first have to provide a
|
In order for the type machinery to work we first have to provide a
|
||||||
|
@ -124,7 +124,7 @@ type manager.
|
||||||
>>> from loops.concept import ConceptTypeSourceList
|
>>> from loops.concept import ConceptTypeSourceList
|
||||||
>>> types = ConceptTypeSourceList(cc1)
|
>>> types = ConceptTypeSourceList(cc1)
|
||||||
>>> sorted(t.title for t in types)
|
>>> sorted(t.title for t in types)
|
||||||
[u'Customer', u'Domain', u'Predicate', u'Topic', u'Type', u'Unknown Type']
|
['Customer', 'Domain', 'Predicate', 'Topic', 'Type', 'Unknown Type']
|
||||||
|
|
||||||
Using a PredicateSourceList we can retrieve a list of the available
|
Using a PredicateSourceList we can retrieve a list of the available
|
||||||
predicates.
|
predicates.
|
||||||
|
@ -136,7 +136,7 @@ Note that the 'hasType' predicate is suppressed from this list as the
|
||||||
corresponding relation is only assigned via the conceptType attribute:
|
corresponding relation is only assigned via the conceptType attribute:
|
||||||
|
|
||||||
>>> sorted(t.title for t in predicates)
|
>>> sorted(t.title for t in predicates)
|
||||||
[u'subobject']
|
['subobject']
|
||||||
|
|
||||||
Concept Views
|
Concept Views
|
||||||
-------------
|
-------------
|
||||||
|
@ -146,7 +146,7 @@ Concept Views
|
||||||
|
|
||||||
>>> children = list(view.children())
|
>>> children = list(view.children())
|
||||||
>>> [c.title for c in children]
|
>>> [c.title for c in children]
|
||||||
[u'Zope 3']
|
['Zope 3']
|
||||||
|
|
||||||
The token attribute provided with the items returned by the children() and
|
The token attribute provided with the items returned by the children() and
|
||||||
parents() methods identifies identifies not only the item itself but
|
parents() methods identifies identifies not only the item itself but
|
||||||
|
@ -154,19 +154,19 @@ also the relationship to the context object using a combination
|
||||||
of URIs to item and the predicate of the relationship:
|
of URIs to item and the predicate of the relationship:
|
||||||
|
|
||||||
>>> [c.token for c in children]
|
>>> [c.token for c in children]
|
||||||
[u'.loops/concepts/cc2:.loops/concepts/standard']
|
['.loops/concepts/cc2:.loops/concepts/standard']
|
||||||
|
|
||||||
There is also a concept configuration view that allows updating the
|
There is also a concept configuration view that allows updating the
|
||||||
underlying context object:
|
underlying context object:
|
||||||
|
|
||||||
>>> cc3 = Concept(u'loops for Zope 3')
|
>>> cc3 = Concept('loops for Zope 3')
|
||||||
>>> concepts['cc3'] = cc3
|
>>> concepts['cc3'] = cc3
|
||||||
>>> view = ConceptConfigureView(cc1,
|
>>> view = ConceptConfigureView(cc1,
|
||||||
... TestRequest(action='assign', tokens=['.loops/concepts/cc3']))
|
... TestRequest(action='assign', tokens=['.loops/concepts/cc3']))
|
||||||
>>> view.update()
|
>>> view.update()
|
||||||
True
|
True
|
||||||
>>> sorted(c.title for c in cc1.getChildren())
|
>>> sorted(c.title for c in cc1.getChildren())
|
||||||
[u'Zope 3', u'loops for Zope 3']
|
['Zope 3', 'loops for Zope 3']
|
||||||
|
|
||||||
>>> input = {'action': 'remove', 'qualifier': 'children',
|
>>> input = {'action': 'remove', 'qualifier': 'children',
|
||||||
... 'form.button.submit': 'Remove Chiildren',
|
... 'form.button.submit': 'Remove Chiildren',
|
||||||
|
@ -175,18 +175,18 @@ underlying context object:
|
||||||
>>> view.update()
|
>>> view.update()
|
||||||
True
|
True
|
||||||
>>> sorted(c.title for c in cc1.getChildren())
|
>>> sorted(c.title for c in cc1.getChildren())
|
||||||
[u'loops for Zope 3']
|
['loops for Zope 3']
|
||||||
|
|
||||||
We can also create a new concept and assign it.
|
We can also create a new concept and assign it.
|
||||||
|
|
||||||
>>> params = {'action': 'create', 'create.name': 'cc4',
|
>>> params = {'action': 'create', 'create.name': 'cc4',
|
||||||
... 'create.title': u'New concept',
|
... 'create.title': 'New concept',
|
||||||
... 'create.type': '.loops/concepts/topic'}
|
... 'create.type': '.loops/concepts/topic'}
|
||||||
>>> view = ConceptConfigureView(cc1, TestRequest(**params))
|
>>> view = ConceptConfigureView(cc1, TestRequest(**params))
|
||||||
>>> view.update()
|
>>> view.update()
|
||||||
True
|
True
|
||||||
>>> sorted(c.title for c in cc1.getChildren())
|
>>> sorted(c.title for c in cc1.getChildren())
|
||||||
[u'New concept', u'loops for Zope 3']
|
['New concept', 'loops for Zope 3']
|
||||||
|
|
||||||
The concept configuration view provides methods for displaying concept
|
The concept configuration view provides methods for displaying concept
|
||||||
types and predicates.
|
types and predicates.
|
||||||
|
@ -198,15 +198,15 @@ types and predicates.
|
||||||
>>> component.provideAdapter(LoopsTerms, (IIterableSource, IBrowserRequest), ITerms)
|
>>> component.provideAdapter(LoopsTerms, (IIterableSource, IBrowserRequest), ITerms)
|
||||||
|
|
||||||
>>> sorted((t.title, t.token) for t in view.conceptTypes())
|
>>> sorted((t.title, t.token) for t in view.conceptTypes())
|
||||||
[(u'Customer', '.loops/concepts/customer'),
|
[('Customer', '.loops/concepts/customer'),
|
||||||
(u'Domain', '.loops/concepts/domain'),
|
('Domain', '.loops/concepts/domain'),
|
||||||
(u'Predicate', '.loops/concepts/predicate'),
|
('Predicate', '.loops/concepts/predicate'),
|
||||||
(u'Topic', '.loops/concepts/topic'),
|
('Topic', '.loops/concepts/topic'),
|
||||||
(u'Type', '.loops/concepts/type'),
|
('Type', '.loops/concepts/type'),
|
||||||
(u'Unknown Type', '.loops/concepts/unknown')]
|
('Unknown Type', '.loops/concepts/unknown')]
|
||||||
|
|
||||||
>>> sorted((t.title, t.token) for t in view.predicates())
|
>>> sorted((t.title, t.token) for t in view.predicates())
|
||||||
[(u'subobject', '.loops/concepts/standard')]
|
[('subobject', '.loops/concepts/standard')]
|
||||||
|
|
||||||
Index attributes adapter
|
Index attributes adapter
|
||||||
------------------------
|
------------------------
|
||||||
|
@ -214,10 +214,10 @@ Index attributes adapter
|
||||||
>>> from loops.concept import IndexAttributes
|
>>> from loops.concept import IndexAttributes
|
||||||
>>> idx = IndexAttributes(cc2)
|
>>> idx = IndexAttributes(cc2)
|
||||||
>>> idx.text()
|
>>> idx.text()
|
||||||
u'cc2 Zope 3'
|
'cc2 Zope 3'
|
||||||
|
|
||||||
>>> idx.title()
|
>>> idx.title()
|
||||||
u'cc2 Zope 3'
|
'cc2 Zope 3'
|
||||||
|
|
||||||
|
|
||||||
Resources and what they have to do with Concepts
|
Resources and what they have to do with Concepts
|
||||||
|
@ -233,27 +233,27 @@ A common type of resource is a document:
|
||||||
|
|
||||||
>>> from loops.interfaces import IDocument
|
>>> from loops.interfaces import IDocument
|
||||||
>>> from loops.resource import Document
|
>>> from loops.resource import Document
|
||||||
>>> doc1 = Document(u'Zope Info')
|
>>> doc1 = Document('Zope Info')
|
||||||
>>> resources['doc1'] = doc1
|
>>> resources['doc1'] = doc1
|
||||||
>>> doc1.title
|
>>> doc1.title
|
||||||
u'Zope Info'
|
'Zope Info'
|
||||||
>>> doc1.data
|
>>> doc1.data
|
||||||
u''
|
''
|
||||||
>>> doc1.contentType
|
>>> doc1.contentType
|
||||||
u''
|
''
|
||||||
|
|
||||||
We can also directly use Resource objects; these behave like files.
|
We can also directly use Resource objects; these behave like files.
|
||||||
In fact, by using resource types we can explicitly assign a resource
|
In fact, by using resource types we can explicitly assign a resource
|
||||||
the 'file' type, but we will use this feature later:
|
the 'file' type, but we will use this feature later:
|
||||||
|
|
||||||
>>> img = Resource(u'A png Image')
|
>>> img = Resource('A png Image')
|
||||||
|
|
||||||
For testing we use some simple files from the tests directory:
|
For testing we use some simple files from the tests directory:
|
||||||
|
|
||||||
>>> from loops import tests
|
>>> from loops import tests
|
||||||
>>> import os
|
>>> import os
|
||||||
>>> path = os.path.join(*tests.__path__)
|
>>> path = os.path.join(*tests.__path__)
|
||||||
>>> img.data = open(os.path.join(path, 'test_icon.png')).read()
|
>>> img.data = open(os.path.join(path, 'test_icon.png'), 'rb').read()
|
||||||
>>> img.getSize()
|
>>> img.getSize()
|
||||||
381
|
381
|
||||||
>>> img.getImageSize()
|
>>> img.getImageSize()
|
||||||
|
@ -261,8 +261,8 @@ For testing we use some simple files from the tests directory:
|
||||||
>>> img.contentType
|
>>> img.contentType
|
||||||
'image/png'
|
'image/png'
|
||||||
|
|
||||||
>>> pdf = Resource(u'A pdf File')
|
>>> pdf = Resource('A pdf File')
|
||||||
>>> pdf.data = open(os.path.join(path, 'test.pdf')).read()
|
>>> pdf.data = open(os.path.join(path, 'test.pdf'), 'rb').read()
|
||||||
>>> pdf.getSize()
|
>>> pdf.getSize()
|
||||||
25862
|
25862
|
||||||
>>> pdf.getImageSize()
|
>>> pdf.getImageSize()
|
||||||
|
@ -287,7 +287,7 @@ from concepts to resources:
|
||||||
... 'tokens': ['.loops/resources/doc1:.loops/concepts/standard']}
|
... 'tokens': ['.loops/resources/doc1:.loops/concepts/standard']}
|
||||||
>>> view = ConceptConfigureView(cc1, TestRequest(form=form))
|
>>> view = ConceptConfigureView(cc1, TestRequest(form=form))
|
||||||
>>> [getName(r.context) for r in view.resources()]
|
>>> [getName(r.context) for r in view.resources()]
|
||||||
[u'doc1']
|
['doc1']
|
||||||
>>> view.update()
|
>>> view.update()
|
||||||
True
|
True
|
||||||
>>> len(cc1.getResources())
|
>>> len(cc1.getResources())
|
||||||
|
@ -316,10 +316,10 @@ Index attributes adapter
|
||||||
>>> component.provideAdapter(FileAdapter, provides=IFile)
|
>>> component.provideAdapter(FileAdapter, provides=IFile)
|
||||||
>>> idx = IndexAttributes(doc1)
|
>>> idx = IndexAttributes(doc1)
|
||||||
>>> idx.text()
|
>>> idx.text()
|
||||||
u''
|
''
|
||||||
|
|
||||||
>>> idx.title()
|
>>> idx.title()
|
||||||
u'doc1 Zope Info'
|
'doc1 Zope Info'
|
||||||
|
|
||||||
|
|
||||||
Views/Nodes: Menus, Menu Items, Listings, Pages, etc
|
Views/Nodes: Menus, Menu Items, Listings, Pages, etc
|
||||||
|
@ -343,14 +343,14 @@ The view manager has already been created during setup.
|
||||||
The view space is typically built up with nodes; a node may be a top-level
|
The view space is typically built up with nodes; a node may be a top-level
|
||||||
menu that may contain other nodes as menu or content items:
|
menu that may contain other nodes as menu or content items:
|
||||||
|
|
||||||
>>> m1 = views['m1'] = Node(u'Menu')
|
>>> m1 = views['m1'] = Node('Menu')
|
||||||
>>> m11 = m1['m11'] = Node(u'Zope')
|
>>> m11 = m1['m11'] = Node('Zope')
|
||||||
>>> m111 = m11['m111'] = Node(u'Zope in General')
|
>>> m111 = m11['m111'] = Node('Zope in General')
|
||||||
>>> m112 = m11['m112'] = Node(u'Zope 3')
|
>>> m112 = m11['m112'] = Node('Zope 3')
|
||||||
>>> m112.title
|
>>> m112.title
|
||||||
u'Zope 3'
|
'Zope 3'
|
||||||
>>> m112.description
|
>>> m112.description
|
||||||
u''
|
''
|
||||||
|
|
||||||
There are a few convienence methods for accessing parent and child nodes:
|
There are a few convienence methods for accessing parent and child nodes:
|
||||||
|
|
||||||
|
@ -359,7 +359,7 @@ There are a few convienence methods for accessing parent and child nodes:
|
||||||
>>> m11.getParentNode() is m1
|
>>> m11.getParentNode() is m1
|
||||||
True
|
True
|
||||||
>>> [getName(child) for child in m11.getChildNodes()]
|
>>> [getName(child) for child in m11.getChildNodes()]
|
||||||
[u'm111', u'm112']
|
['m111', 'm112']
|
||||||
|
|
||||||
What is returned by these may be controlled by the nodeType attribute:
|
What is returned by these may be controlled by the nodeType attribute:
|
||||||
|
|
||||||
|
@ -444,13 +444,13 @@ Node Views
|
||||||
>>> page = view.page
|
>>> page = view.page
|
||||||
>>> items = page.textItems
|
>>> items = page.textItems
|
||||||
>>> for item in items:
|
>>> for item in items:
|
||||||
... print item.url, item.editable
|
... print(item.url, item.editable)
|
||||||
http://127.0.0.1/loops/views/m1/m11/m112 False
|
http://127.0.0.1/loops/views/m1/m11/m112 False
|
||||||
|
|
||||||
>>> menu = view.menu
|
>>> menu = view.menu
|
||||||
>>> items = menu.menuItems
|
>>> items = menu.menuItems
|
||||||
>>> for item in items:
|
>>> for item in items:
|
||||||
... print item.url, view.selected(item)
|
... print(item.url, view.selected(item))
|
||||||
http://127.0.0.1/loops/views/m1/m11 True
|
http://127.0.0.1/loops/views/m1/m11 True
|
||||||
|
|
||||||
A NodeView provides an itemNum attribute that may be used to count elements
|
A NodeView provides an itemNum attribute that may be used to count elements
|
||||||
|
@ -493,14 +493,14 @@ view; these views we have to provide as multi-adapters:
|
||||||
>>> len(tt)
|
>>> len(tt)
|
||||||
9
|
9
|
||||||
>>> sorted((t.token, t.title) for t in view.targetTypes())[1]
|
>>> sorted((t.token, t.title) for t in view.targetTypes())[1]
|
||||||
('.loops/concepts/domain', u'Domain')
|
('.loops/concepts/domain', 'Domain')
|
||||||
>>> view.update()
|
>>> view.update()
|
||||||
True
|
True
|
||||||
>>> sorted(resources.keys())
|
>>> sorted(resources.keys())
|
||||||
[u'd001.txt', u'd002.txt', u'd003.txt', u'doc1', u'm1.m11.m111']
|
['d001.txt', 'd002.txt', 'd003.txt', 'doc1', 'm1.m11.m111']
|
||||||
|
|
||||||
>>> view.target.title, view.target.token
|
>>> view.target.title, view.target.token
|
||||||
('New Resource', u'.loops/resources/m1.m11.m111')
|
('New Resource', '.loops/resources/m1.m11.m111')
|
||||||
|
|
||||||
A node object provides the targetSchema of its target:
|
A node object provides the targetSchema of its target:
|
||||||
|
|
||||||
|
@ -537,28 +537,28 @@ view for rendering.)
|
||||||
>>> component.provideAdapter(LoopsType)
|
>>> component.provideAdapter(LoopsType)
|
||||||
>>> view = NodeView(m112, TestRequest())
|
>>> view = NodeView(m112, TestRequest())
|
||||||
>>> view.renderTarget()
|
>>> view.renderTarget()
|
||||||
u'<pre></pre>'
|
'<pre></pre>'
|
||||||
>>> doc1.data = u'Test data\n\nAnother paragraph'
|
>>> doc1.data = 'Test data\n\nAnother paragraph'
|
||||||
>>> view.renderTarget()
|
>>> view.renderTarget()
|
||||||
u'<pre>Test data\n\nAnother paragraph</pre>'
|
'<pre>Test data\n\nAnother paragraph</pre>'
|
||||||
|
|
||||||
>>> doc1.contentType = 'text/restructured'
|
>>> doc1.contentType = 'text/restructured'
|
||||||
>>> doc1.data = u'Test data\n\nAnother `paragraph <para>`_'
|
>>> doc1.data = 'Test data\n\nAnother `paragraph <para>`_'
|
||||||
|
|
||||||
>>> from loops.wiki.base import wikiLinksActive
|
>>> from loops.wiki.base import wikiLinksActive
|
||||||
>>> wikiLinksActive(loopsRoot)
|
>>> wikiLinksActive(loopsRoot)
|
||||||
False
|
False
|
||||||
|
|
||||||
>>> view.renderTarget()
|
>>> view.renderTarget()
|
||||||
u'<p>Test data</p>\n<p>Another <a class="reference external" href="para">paragraph</a></p>\n'
|
'<p>Test data</p>\n<p>Another <a class="reference external" href="para">paragraph</a></p>\n'
|
||||||
|
|
||||||
u'<p>Test data</p>\n<p>Another <a class="reference create"
|
'<p>Test data</p>\n<p>Another <a class="reference create"
|
||||||
href="http://127.0.0.1/loops/wiki/create.html?linkid=0000001">?paragraph</a></p>\n'
|
href="http://127.0.0.1/loops/wiki/create.html?linkid=0000001">?paragraph</a></p>\n'
|
||||||
|
|
||||||
>>> #links = loopsRoot.getRecordManager()['links']
|
>>> #links = loopsRoot.getRecordManager()['links']
|
||||||
>>> #links['0000001']
|
>>> #links['0000001']
|
||||||
|
|
||||||
<Link ['42', 1, '', '... ...', u'para', None]: {}>
|
<Link ['42', 1, '', '... ...', 'para', None]: {}>
|
||||||
|
|
||||||
If the target object is removed from its container all references
|
If the target object is removed from its container all references
|
||||||
to it are removed as well. (To make this work we have to handle
|
to it are removed as well. (To make this work we have to handle
|
||||||
|
@ -632,7 +632,7 @@ Let's add some more nodes and reorder them:
|
||||||
>>> m114 = Node()
|
>>> m114 = Node()
|
||||||
>>> m11['m114'] = m114
|
>>> m11['m114'] = m114
|
||||||
>>> m11.keys()
|
>>> m11.keys()
|
||||||
[u'm111', u'm112', u'm113', u'm114']
|
['m111', 'm112', 'm113', 'm114']
|
||||||
|
|
||||||
A special management view provides methods for moving objects down, up,
|
A special management view provides methods for moving objects down, up,
|
||||||
to the bottom, and to the top.
|
to the bottom, and to the top.
|
||||||
|
@ -641,10 +641,10 @@ to the bottom, and to the top.
|
||||||
>>> view = OrderedContainerView(m11, TestRequest())
|
>>> view = OrderedContainerView(m11, TestRequest())
|
||||||
>>> view.move_bottom(('m113',))
|
>>> view.move_bottom(('m113',))
|
||||||
>>> m11.keys()
|
>>> m11.keys()
|
||||||
[u'm111', u'm112', u'm114', u'm113']
|
['m111', 'm112', 'm114', 'm113']
|
||||||
>>> view.move_up(('m114',), 1)
|
>>> view.move_up(('m114',), 1)
|
||||||
>>> m11.keys()
|
>>> m11.keys()
|
||||||
[u'm111', u'm114', u'm112', u'm113']
|
['m111', 'm114', 'm112', 'm113']
|
||||||
|
|
||||||
|
|
||||||
Breadcrumbs
|
Breadcrumbs
|
||||||
|
@ -661,9 +661,9 @@ Breadcrumbs
|
||||||
>>> view = NodeView(m114, request)
|
>>> view = NodeView(m114, request)
|
||||||
>>> request.annotations.setdefault('loops.view', {})['nodeView'] = view
|
>>> request.annotations.setdefault('loops.view', {})['nodeView'] = view
|
||||||
>>> view.breadcrumbs()
|
>>> view.breadcrumbs()
|
||||||
[{'url': 'http://127.0.0.1/loops/views/m1', 'label': u'Menu'},
|
[{'label': 'Menu', 'url': 'http://127.0.0.1/loops/views/m1'},
|
||||||
{'url': 'http://127.0.0.1/loops/views/m1/m11', 'label': u'Zope'},
|
{'label': 'Zope', 'url': 'http://127.0.0.1/loops/views/m1/m11'},
|
||||||
{'url': 'http://127.0.0.1/loops/views/m1/m11/m114', 'label': u''}]
|
{'label': '', 'url': 'http://127.0.0.1/loops/views/m1/m11/m114'}]
|
||||||
|
|
||||||
|
|
||||||
End-user Forms and Special Views
|
End-user Forms and Special Views
|
||||||
|
@ -705,8 +705,8 @@ been created during setup.
|
||||||
>>> custType = TypeConcept(customer)
|
>>> custType = TypeConcept(customer)
|
||||||
>>> custType.options
|
>>> custType.options
|
||||||
[]
|
[]
|
||||||
>>> cust1 = concepts['cust1'] = Concept(u'Zope Corporation')
|
>>> cust1 = concepts['cust1'] = Concept('Zope Corporation')
|
||||||
>>> cust2 = concepts['cust2'] = Concept(u'cyberconcepts')
|
>>> cust2 = concepts['cust2'] = Concept('cyberconcepts')
|
||||||
>>> for c in (cust1, cust2): c.conceptType = customer
|
>>> for c in (cust1, cust2): c.conceptType = customer
|
||||||
>>> custType.options = ('qualifier:assign',)
|
>>> custType.options = ('qualifier:assign',)
|
||||||
>>> ConceptType(cust1).qualifiers
|
>>> ConceptType(cust1).qualifiers
|
||||||
|
@ -714,7 +714,7 @@ been created during setup.
|
||||||
|
|
||||||
>>> form = CreateObjectForm(m112, TestRequest())
|
>>> form = CreateObjectForm(m112, TestRequest())
|
||||||
>>> form.presetTypesForAssignment
|
>>> form.presetTypesForAssignment
|
||||||
[{'token': 'loops:concept:customer', 'title': u'Customer'}]
|
[{'title': 'Customer', 'token': 'loops:concept:customer'}]
|
||||||
|
|
||||||
If the node's target is a type concept we don't get any assignments because
|
If the node's target is a type concept we don't get any assignments because
|
||||||
it does not make much sense to assign resources or other concepts as
|
it does not make much sense to assign resources or other concepts as
|
||||||
|
@ -736,16 +736,18 @@ on data provided in this form:
|
||||||
>>> note_tc = concepts['note']
|
>>> note_tc = concepts['note']
|
||||||
|
|
||||||
>>> component.provideAdapter(NameChooser)
|
>>> component.provideAdapter(NameChooser)
|
||||||
>>> request = TestRequest(form={'title': u'Test Note',
|
>>> request = TestRequest(form={'title': 'Test Note',
|
||||||
... 'form.type': u'.loops/concepts/note'})
|
... 'form.type': '.loops/concepts/note',
|
||||||
|
... 'contentType': 'text/restructured',
|
||||||
|
... 'linkUrl': 'http://'})
|
||||||
>>> view = NodeView(m112, request)
|
>>> view = NodeView(m112, request)
|
||||||
>>> cont = CreateObject(view, request)
|
>>> cont = CreateObject(view, request)
|
||||||
>>> cont.update()
|
>>> cont.update()
|
||||||
False
|
False
|
||||||
>>> sorted(resources.keys())
|
>>> sorted(resources.keys())
|
||||||
[...u'test_note'...]
|
[...'test_note'...]
|
||||||
>>> resources['test_note'].title
|
>>> resources['test_note'].title
|
||||||
u'Test Note'
|
'Test Note'
|
||||||
|
|
||||||
If there is a concept selected in the combo box we assign this to the newly
|
If there is a concept selected in the combo box we assign this to the newly
|
||||||
created object:
|
created object:
|
||||||
|
@ -753,8 +755,8 @@ created object:
|
||||||
>>> from loops import util
|
>>> from loops import util
|
||||||
>>> topicUid = util.getUidForObject(topic)
|
>>> topicUid = util.getUidForObject(topic)
|
||||||
>>> predicateUid = util.getUidForObject(concepts.getDefaultPredicate())
|
>>> predicateUid = util.getUidForObject(concepts.getDefaultPredicate())
|
||||||
>>> request = TestRequest(form={'title': u'Test Note',
|
>>> request = TestRequest(form={'title': 'Test Note',
|
||||||
... 'form.type': u'.loops/concepts/note',
|
... 'form.type': '.loops/concepts/note',
|
||||||
... 'form.assignments.selected':
|
... 'form.assignments.selected':
|
||||||
... [':'.join((topicUid, predicateUid))]})
|
... [':'.join((topicUid, predicateUid))]})
|
||||||
>>> view = NodeView(m112, request)
|
>>> view = NodeView(m112, request)
|
||||||
|
@ -762,22 +764,22 @@ created object:
|
||||||
>>> cont.update()
|
>>> cont.update()
|
||||||
False
|
False
|
||||||
>>> sorted(resources.keys())
|
>>> sorted(resources.keys())
|
||||||
[...u'test_note-2'...]
|
[...'test_note-2'...]
|
||||||
>>> note = resources['test_note-2']
|
>>> note = resources['test_note-2']
|
||||||
>>> sorted(t.__name__ for t in note.getConcepts())
|
>>> sorted(t.__name__ for t in note.getConcepts())
|
||||||
[u'note', u'topic']
|
['note', 'topic']
|
||||||
|
|
||||||
When creating an object its name may be automatically generated using the title
|
When creating an object its name may be automatically generated using the title
|
||||||
of the object. Let's make sure that the name chooser also handles special
|
of the object. Let's make sure that the name chooser also handles special
|
||||||
and possibly critcal cases:
|
and possibly critcal cases:
|
||||||
|
|
||||||
>>> nc = NameChooser(resources)
|
>>> nc = NameChooser(resources)
|
||||||
>>> nc.chooseName(u'', Resource(u'abc: (cde)'))
|
>>> nc.chooseName('', Resource('abc: (cde)'))
|
||||||
u'abc__cde'
|
'abc__cde'
|
||||||
>>> nc.chooseName(u'', Resource(u'\xdcml\xe4ut'))
|
>>> nc.chooseName('', Resource('\xdcml\xe4ut'))
|
||||||
u'uemlaeut'
|
'uemlaeut'
|
||||||
>>> nc.chooseName(u'', Resource(u'A very very loooooong title'))
|
>>> nc.chooseName('', Resource('A very very loooooong title'))
|
||||||
u'a_title'
|
'a_title'
|
||||||
|
|
||||||
Editing an Object
|
Editing an Object
|
||||||
-----------------
|
-----------------
|
||||||
|
@ -794,7 +796,7 @@ that in turns calls formlibs ``setUpWidgets()``.
|
||||||
The new technique uses the ``fields`` and ``data`` attributes...
|
The new technique uses the ``fields`` and ``data`` attributes...
|
||||||
|
|
||||||
>>> for f in view.fields:
|
>>> for f in view.fields:
|
||||||
... print f.name, f.fieldType, f.required, f.vocabulary
|
... print(f.name, f.fieldType, f.required, f.vocabulary)
|
||||||
title textline True None
|
title textline True None
|
||||||
data textarea False None
|
data textarea False None
|
||||||
contentType dropdown True <...SimpleVocabulary object...>
|
contentType dropdown True <...SimpleVocabulary object...>
|
||||||
|
@ -802,22 +804,22 @@ The new technique uses the ``fields`` and ``data`` attributes...
|
||||||
linkText textline False None
|
linkText textline False None
|
||||||
|
|
||||||
>>> view.data
|
>>> view.data
|
||||||
{'linkUrl': u'http://', 'contentType': 'text/restructured', 'data': u'',
|
{'title': 'Test Note', 'data': '', 'contentType': 'text/restructured',
|
||||||
'linkText': u'', 'title': u'Test Note'}
|
'linkUrl': 'http://', 'linkText': ''}
|
||||||
|
|
||||||
The object is changed via a FormController adapter created for
|
The object is changed via a FormController adapter created for
|
||||||
a NodeView.
|
a NodeView.
|
||||||
|
|
||||||
>>> form = dict(
|
>>> form = dict(
|
||||||
... title=u'Test Note - changed',
|
... title='Test Note - changed',
|
||||||
... contentType=u'text/plain',)
|
... contentType='text/plain',)
|
||||||
>>> request = TestRequest(form=form)
|
>>> request = TestRequest(form=form)
|
||||||
>>> view = NodeView(m112, request)
|
>>> view = NodeView(m112, request)
|
||||||
>>> cont = EditObject(view, request)
|
>>> cont = EditObject(view, request)
|
||||||
>>> cont.update()
|
>>> cont.update()
|
||||||
False
|
False
|
||||||
>>> resources['test_note'].title
|
>>> resources['test_note'].title
|
||||||
u'Test Note - changed'
|
'Test Note - changed'
|
||||||
|
|
||||||
Virtual Targets
|
Virtual Targets
|
||||||
---------------
|
---------------
|
||||||
|
@ -881,13 +883,13 @@ informations about all parents of an object.
|
||||||
|
|
||||||
>>> parents = m113.getAllParents()
|
>>> parents = m113.getAllParents()
|
||||||
>>> for p in parents:
|
>>> for p in parents:
|
||||||
... print p.object.title
|
... print(p.object.title)
|
||||||
Zope
|
Zope
|
||||||
Menu
|
Menu
|
||||||
|
|
||||||
>>> parents = resources['test_note'].getAllParents()
|
>>> parents = resources['test_note'].getAllParents()
|
||||||
>>> for p in parents:
|
>>> for p in parents:
|
||||||
... print p.object.title, len(p.relations)
|
... print(p.object.title, len(p.relations))
|
||||||
Note 1
|
Note 1
|
||||||
Type 2
|
Type 2
|
||||||
|
|
||||||
|
@ -913,6 +915,32 @@ relates ISO country codes with the full name of the country.
|
||||||
>>> sorted(adapted(concepts['countries']).data.items())
|
>>> sorted(adapted(concepts['countries']).data.items())
|
||||||
[('at', ['Austria']), ('de', ['Germany'])]
|
[('at', ['Austria']), ('de', ['Germany'])]
|
||||||
|
|
||||||
|
>>> countries.dataAsRecords()
|
||||||
|
[{'key': 'at', 'value': 'Austria'}, {'key': 'de', 'value': 'Germany'}]
|
||||||
|
|
||||||
|
>>> countries.getRowsByValue('value', 'Germany')
|
||||||
|
[{'key': 'de', 'value': 'Germany'}]
|
||||||
|
|
||||||
|
The ``recordstable`` type is a variation of this datable type that contains
|
||||||
|
a simple list of records - without a key column. A record in this type is a
|
||||||
|
dictionary with the field name as key and the field value as value.
|
||||||
|
|
||||||
|
>>> from loops.table import IRecordsTable, RecordsTable
|
||||||
|
>>> component.provideAdapter(RecordsTable, provides=IRecordsTable)
|
||||||
|
|
||||||
|
>>> drType = addAndConfigureObject(concepts, Concept, 'recordstable',
|
||||||
|
... title='Records Table', conceptType=concepts['type'],
|
||||||
|
... typeInterface=IRecordsTable)
|
||||||
|
|
||||||
|
We just reuse the existing ``countries`` table and convert it to a records table.
|
||||||
|
|
||||||
|
>>> baseObject(countries).setType(drType)
|
||||||
|
|
||||||
|
>>> countries = adapted(concepts['countries'])
|
||||||
|
|
||||||
|
>>> countries.data
|
||||||
|
[{'key': 'at', 'value': 'Austria'}, {'key': 'de', 'value': 'Germany'}]
|
||||||
|
|
||||||
|
|
||||||
Caching
|
Caching
|
||||||
=======
|
=======
|
||||||
|
@ -923,7 +951,7 @@ To be done...
|
||||||
>>> obj = resources['test_note']
|
>>> obj = resources['test_note']
|
||||||
>>> cxObj = cached(obj)
|
>>> cxObj = cached(obj)
|
||||||
>>> [p.object.title for p in cxObj.getAllParents()]
|
>>> [p.object.title for p in cxObj.getAllParents()]
|
||||||
[u'Note', u'Type']
|
['Note', 'Type']
|
||||||
|
|
||||||
|
|
||||||
Security
|
Security
|
||||||
|
@ -932,6 +960,12 @@ Security
|
||||||
>>> from loops.security.browser import admin, audit
|
>>> from loops.security.browser import admin, audit
|
||||||
|
|
||||||
|
|
||||||
|
Paster Shell Utilities - Repair Scripts
|
||||||
|
=======================================
|
||||||
|
|
||||||
|
>>> from loops.repair.base import removeRecords
|
||||||
|
|
||||||
|
|
||||||
Import/Export
|
Import/Export
|
||||||
=============
|
=============
|
||||||
|
|
17
loops/__init__.py
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
# package loops
|
||||||
|
|
||||||
|
# intid monkey patch for avoiding ForbiddenAttribute error
|
||||||
|
|
||||||
|
from zope import component
|
||||||
|
from zope.intid.interfaces import IIntIds
|
||||||
|
from zope import intid
|
||||||
|
from zope.security.proxy import removeSecurityProxy
|
||||||
|
|
||||||
|
def queryId(self, ob, default=None):
|
||||||
|
try:
|
||||||
|
return self.getId(removeSecurityProxy(ob))
|
||||||
|
except KeyError:
|
||||||
|
return default
|
||||||
|
|
||||||
|
intid.IntIds.queryId = queryId
|
||||||
|
|
|
@ -1,32 +1,13 @@
|
||||||
# -*- coding: UTF-8 -*-
|
# loops.base
|
||||||
# -*- Mode: Python; py-indent-offset: 4 -*-
|
|
||||||
#
|
|
||||||
# Copyright (c) 2019 Helmut Merz helmutm@cy55.de
|
|
||||||
#
|
|
||||||
# This program is free software; you can redistribute it and/or modify
|
|
||||||
# it under the terms of the GNU General Public License as published by
|
|
||||||
# the Free Software Foundation; either version 2 of the License, or
|
|
||||||
# (at your option) any later version.
|
|
||||||
#
|
|
||||||
# This program is distributed in the hope that it will be useful,
|
|
||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
# GNU General Public License for more details.
|
|
||||||
#
|
|
||||||
# You should have received a copy of the GNU General Public License
|
|
||||||
# along with this program; if not, write to the Free Software
|
|
||||||
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
||||||
#
|
|
||||||
|
|
||||||
"""
|
""" Implementation of loops root object.
|
||||||
The loops container class.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from zope.app.container.btree import BTreeContainer
|
from zope.container.btree import BTreeContainer
|
||||||
from zope.app.folder.folder import Folder
|
from zope.site.folder import Folder
|
||||||
from zope.app.folder.interfaces import IFolder
|
from zope.site.interfaces import IFolder
|
||||||
from zope.traversing.api import getPath, traverse
|
from zope.traversing.api import getPath, traverse
|
||||||
from zope.interface import implements
|
from zope.interface import implementer
|
||||||
|
|
||||||
from cybertools.util.jeep import Jeep
|
from cybertools.util.jeep import Jeep
|
||||||
from loops.interfaces import ILoops
|
from loops.interfaces import ILoops
|
||||||
|
@ -34,17 +15,8 @@ from loops.interfaces import ILoops
|
||||||
loopsPrefix = '.loops'
|
loopsPrefix = '.loops'
|
||||||
|
|
||||||
|
|
||||||
|
@implementer(ILoops)
|
||||||
class Loops(Folder):
|
class Loops(Folder):
|
||||||
#class Loops(BTreeContainer):
|
|
||||||
|
|
||||||
implements(ILoops)
|
|
||||||
|
|
||||||
#def getSiteManager(self):
|
|
||||||
# return self.__parent__.getSiteManager()
|
|
||||||
|
|
||||||
#@property
|
|
||||||
#def _SampleContainer__data(self):
|
|
||||||
# return self.data
|
|
||||||
|
|
||||||
_skinName = ''
|
_skinName = ''
|
||||||
def getSkinName(self): return self._skinName
|
def getSkinName(self): return self._skinName
|
||||||
|
@ -74,10 +46,7 @@ class Loops(Folder):
|
||||||
return self.get('records')
|
return self.get('records')
|
||||||
|
|
||||||
def getLoopsUri(self, obj):
|
def getLoopsUri(self, obj):
|
||||||
#return str(loopsPrefix + getPath(obj)[len(getPath(self)):])
|
|
||||||
uri = loopsPrefix + getPath(obj)[len(getPath(self)):]
|
uri = loopsPrefix + getPath(obj)[len(getPath(self)):]
|
||||||
#if isinstance(uri, unicode):
|
|
||||||
# uri = uri.encode('UTF-8')
|
|
||||||
return uri
|
return uri
|
||||||
|
|
||||||
def loopsTraverse(self, uri):
|
def loopsTraverse(self, uri):
|
68
loops/bluebream.zcml
Normal file
|
@ -0,0 +1,68 @@
|
||||||
|
<configure
|
||||||
|
xmlns="http://namespaces.zope.org/zope"
|
||||||
|
xmlns:browser="http://namespaces.zope.org/browser">
|
||||||
|
|
||||||
|
<include package="zope.component" file="meta.zcml" />
|
||||||
|
<include package="zope.security" file="meta.zcml" />
|
||||||
|
<include package="zope.publisher" file="meta.zcml" />
|
||||||
|
<include package="zope.i18n" file="meta.zcml" />
|
||||||
|
<include package="zope.browserresource" file="meta.zcml" />
|
||||||
|
<include package="zope.browsermenu" file="meta.zcml" />
|
||||||
|
<include package="zope.browserpage" file="meta.zcml" />
|
||||||
|
<include package="zope.securitypolicy" file="meta.zcml" />
|
||||||
|
<include package="zope.principalregistry" file="meta.zcml" />
|
||||||
|
<include package="zope.app.publication" file="meta.zcml" />
|
||||||
|
<include package="zope.app.form.browser" file="meta.zcml" />
|
||||||
|
<include package="zope.app.container.browser" file="meta.zcml" />
|
||||||
|
|
||||||
|
<include package="zope.browserresource" />
|
||||||
|
<include package="zope.copypastemove" />
|
||||||
|
<include package="zope.publisher" />
|
||||||
|
<include package="zope.component" />
|
||||||
|
<include package="zope.traversing" />
|
||||||
|
<include package="zope.location" />
|
||||||
|
<include package="zope.site" />
|
||||||
|
<include package="zope.annotation" />
|
||||||
|
<include package="zope.principalregistry" />
|
||||||
|
<include package="zope.container" />
|
||||||
|
<include package="zope.componentvocabulary" />
|
||||||
|
<include package="zope.formlib" />
|
||||||
|
<include package="zope.app.appsetup" />
|
||||||
|
<include package="zope.app.security" />
|
||||||
|
<include package="zope.app.publication" />
|
||||||
|
<include package="zope.app.form.browser" />
|
||||||
|
<include package="zope.app.basicskin" />
|
||||||
|
<include package="zope.browsermenu" />
|
||||||
|
<include package="zope.authentication" />
|
||||||
|
<include package="zope.securitypolicy" />
|
||||||
|
<include package="zope.login" />
|
||||||
|
<include package="zope.session" />
|
||||||
|
<include package="zope.error" />
|
||||||
|
<include package="zope.app.zcmlfiles" file="menus.zcml" />
|
||||||
|
<include package="zope.app.authentication" />
|
||||||
|
<include package="zope.app.security.browser" />
|
||||||
|
<include package="zope.app.catalog" />
|
||||||
|
<include package="zope.traversing.browser" />
|
||||||
|
<include package="zope.browserpage" />
|
||||||
|
<include package="zope.app.schema" />
|
||||||
|
<include package="zope.app.http" />
|
||||||
|
<include package="zope.keyreference" />
|
||||||
|
<include package="zope.intid" />
|
||||||
|
<include package="zope.contentprovider" />
|
||||||
|
<include package="zope.i18n" />
|
||||||
|
<include package="zope.catalog" />
|
||||||
|
<include package="zope.dublincore.browser" />
|
||||||
|
|
||||||
|
<include package="zope.app.zcmlfiles" />
|
||||||
|
<include package="zope.app.i18n" />
|
||||||
|
<include package="zope.app.intid" />
|
||||||
|
<include package="zope.app.renderer" />
|
||||||
|
<include package="zope.app.session" />
|
||||||
|
<include package="zope.sendmail" file="meta.zcml" />
|
||||||
|
|
||||||
|
<browser:defaultView
|
||||||
|
for="zope.container.interfaces.IContainer"
|
||||||
|
name="index.html" />
|
||||||
|
|
||||||
|
</configure>
|
||||||
|
|
|
@ -1,26 +1,9 @@
|
||||||
#
|
# loops.browser.action
|
||||||
# Copyright (c) 2013 Helmut Merz helmutm@cy55.de
|
|
||||||
#
|
|
||||||
# This program is free software; you can redistribute it and/or modify
|
|
||||||
# it under the terms of the GNU General Public License as published by
|
|
||||||
# the Free Software Foundation; either version 2 of the License, or
|
|
||||||
# (at your option) any later version.
|
|
||||||
#
|
|
||||||
# This program is distributed in the hope that it will be useful,
|
|
||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
# GNU General Public License for more details.
|
|
||||||
#
|
|
||||||
# You should have received a copy of the GNU General Public License
|
|
||||||
# along with this program; if not, write to the Free Software
|
|
||||||
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
||||||
#
|
|
||||||
|
|
||||||
"""
|
""" Base classes (sort of views) for action portlet items.
|
||||||
Base classes (sort of views) for action portlet items.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from urllib import urlencode
|
from urllib.parse import urlencode
|
||||||
from zope import component
|
from zope import component
|
||||||
from zope.app.pagetemplate import ViewPageTemplateFile
|
from zope.app.pagetemplate import ViewPageTemplateFile
|
||||||
from zope.cachedescriptors.property import Lazy
|
from zope.cachedescriptors.property import Lazy
|
||||||
|
@ -92,6 +75,8 @@ class DialogAction(Action):
|
||||||
urlParams['fixed_type'] = 'yes'
|
urlParams['fixed_type'] = 'yes'
|
||||||
if self.viewTitle:
|
if self.viewTitle:
|
||||||
urlParams['view_title'] = self.viewTitle
|
urlParams['view_title'] = self.viewTitle
|
||||||
|
#for k, v in self.page.sortInfo.items():
|
||||||
|
# urlParams['sortinfo_' + k] = v['fparam']
|
||||||
urlParams.update(self.addParams)
|
urlParams.update(self.addParams)
|
||||||
if self.target is not None:
|
if self.target is not None:
|
||||||
url = self.page.getUrlForTarget(self.target)
|
url = self.page.getUrlForTarget(self.target)
|
|
@ -1,35 +1,18 @@
|
||||||
#
|
# loops.browser.auth
|
||||||
# Copyright (c) 2015 Helmut Merz helmutm@cy55.de
|
|
||||||
#
|
|
||||||
# This program is free software; you can redistribute it and/or modify
|
|
||||||
# it under the terms of the GNU General Public License as published by
|
|
||||||
# the Free Software Foundation; either version 2 of the License, or
|
|
||||||
# (at your option) any later version.
|
|
||||||
#
|
|
||||||
# This program is distributed in the hope that it will be useful,
|
|
||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
# GNU General Public License for more details.
|
|
||||||
#
|
|
||||||
# You should have received a copy of the GNU General Public License
|
|
||||||
# along with this program; if not, write to the Free Software
|
|
||||||
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
||||||
#
|
|
||||||
|
|
||||||
"""
|
""" Login, logout, unauthorized stuff.
|
||||||
Login, logout, unauthorized stuff.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from zope.app.exception.browser.unauthorized import Unauthorized as DefaultUnauth
|
from zope.app.exception.browser.unauthorized import Unauthorized as DefaultUnauth
|
||||||
from zope.app.security.interfaces import IAuthentication
|
from zope.authentication.interfaces import IAuthentication
|
||||||
from zope.app.security.interfaces import ILogout, IUnauthenticatedPrincipal
|
from zope.authentication.interfaces import ILogout, IUnauthenticatedPrincipal
|
||||||
|
from zope.browserpage import ViewPageTemplateFile
|
||||||
|
from zope.cachedescriptors.property import Lazy
|
||||||
from zope import component
|
from zope import component
|
||||||
from zope.interface import implements
|
from zope.interface import implementer
|
||||||
|
|
||||||
from loops.browser.concept import ConceptView
|
from loops.browser.concept import ConceptView
|
||||||
from loops.browser.node import NodeView
|
from loops.browser.node import NodeView
|
||||||
from zope.app.pagetemplate import ViewPageTemplateFile
|
|
||||||
from zope.cachedescriptors.property import Lazy
|
|
||||||
|
|
||||||
|
|
||||||
template = ViewPageTemplateFile('auth.pt')
|
template = ViewPageTemplateFile('auth.pt')
|
||||||
|
@ -57,9 +40,9 @@ class LoginForm(NodeView):
|
||||||
return self
|
return self
|
||||||
|
|
||||||
|
|
||||||
|
@implementer(ILogout)
|
||||||
class Logout(object):
|
class Logout(object):
|
||||||
|
|
||||||
implements(ILogout)
|
|
||||||
|
|
||||||
def __init__(self, context, request):
|
def __init__(self, context, request):
|
||||||
self.context = context
|
self.context = context
|
|
@ -1,46 +1,28 @@
|
||||||
#
|
# loops.browser.common
|
||||||
# Copyright (c) 2016 Helmut Merz helmutm@cy55.de
|
|
||||||
#
|
|
||||||
# This program is free software; you can redistribute it and/or modify
|
|
||||||
# it under the terms of the GNU General Public License as published by
|
|
||||||
# the Free Software Foundation; either version 2 of the License, or
|
|
||||||
# (at your option) any later version.
|
|
||||||
#
|
|
||||||
# This program is distributed in the hope that it will be useful,
|
|
||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
# GNU General Public License for more details.
|
|
||||||
#
|
|
||||||
# You should have received a copy of the GNU General Public License
|
|
||||||
# along with this program; if not, write to the Free Software
|
|
||||||
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
||||||
#
|
|
||||||
|
|
||||||
"""
|
""" Common base class for loops browser view classes.
|
||||||
Common base class for loops browser view classes.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from cgi import parse_qs, parse_qsl
|
|
||||||
#import mimetypes # use more specific assignments from cybertools.text
|
#import mimetypes # use more specific assignments from cybertools.text
|
||||||
from datetime import datetime
|
from datetime import date, datetime
|
||||||
from logging import getLogger
|
from logging import getLogger
|
||||||
import re
|
import re
|
||||||
from time import strptime
|
from time import strptime
|
||||||
from urllib import urlencode
|
from urllib.parse import parse_qs, parse_qsl, urlencode
|
||||||
from zope import component
|
from zope import component
|
||||||
from zope.app.form.browser.interfaces import ITerms
|
from zope.authentication.interfaces import IAuthentication, IUnauthenticatedPrincipal
|
||||||
from zope.app.i18n.interfaces import ITranslationDomain
|
from zope.authentication.interfaces import IUnauthenticatedPrincipal
|
||||||
from zope.app.security.interfaces import IAuthentication, IUnauthenticatedPrincipal
|
from zope.authentication.interfaces import PrincipalLookupError
|
||||||
from zope.app.pagetemplate import ViewPageTemplateFile
|
from zope.browser.interfaces import ITerms
|
||||||
from zope.app.security.interfaces import IUnauthenticatedPrincipal
|
from zope.browserpage import ViewPageTemplateFile
|
||||||
from zope.app.security.interfaces import PrincipalLookupError
|
|
||||||
from zope.cachedescriptors.property import Lazy
|
from zope.cachedescriptors.property import Lazy
|
||||||
from zope.dottedname.resolve import resolve
|
from zope.dottedname.resolve import resolve
|
||||||
from zope.dublincore.interfaces import IZopeDublinCore
|
from zope.dublincore.interfaces import IZopeDublinCore
|
||||||
from zope.formlib import form
|
from zope.formlib import form
|
||||||
from zope.formlib.form import FormFields
|
from zope.formlib.form import FormFields
|
||||||
from zope.formlib.namedtemplate import NamedTemplate
|
from zope.formlib.namedtemplate import NamedTemplate
|
||||||
from zope.interface import Interface, implements
|
from zope.i18n.interfaces import ITranslationDomain
|
||||||
|
from zope.interface import Interface, implementer
|
||||||
from zope.proxy import removeAllProxies
|
from zope.proxy import removeAllProxies
|
||||||
from zope.publisher.browser import applySkin
|
from zope.publisher.browser import applySkin
|
||||||
from zope.publisher.interfaces.browser import IBrowserSkinType, IBrowserView
|
from zope.publisher.interfaces.browser import IBrowserSkinType, IBrowserView
|
||||||
|
@ -62,17 +44,21 @@ from cybertools.stateful.interfaces import IStateful
|
||||||
from cybertools.text import mimetypes
|
from cybertools.text import mimetypes
|
||||||
from cybertools.typology.interfaces import IType, ITypeManager
|
from cybertools.typology.interfaces import IType, ITypeManager
|
||||||
from cybertools.util.date import toLocalTime
|
from cybertools.util.date import toLocalTime
|
||||||
|
from cybertools.util.format import formatDate
|
||||||
from cybertools.util.jeep import Jeep
|
from cybertools.util.jeep import Jeep
|
||||||
from loops.browser.util import normalizeForUrl
|
from loops.browser.util import normalizeForUrl
|
||||||
from loops.common import adapted, baseObject
|
from loops.common import adapted, baseObject
|
||||||
from loops.config.base import DummyOptions
|
from loops.config.base import DummyOptions
|
||||||
from loops.i18n.browser import I18NView
|
from loops.i18n.browser import I18NView
|
||||||
from loops.interfaces import IResource, IView, INode, ITypeConcept
|
from loops.interfaces import IResource, IView, INode, ITypeConcept
|
||||||
|
from loops.organize.personal import favorite
|
||||||
|
from loops.organize.party import getPersonForUser
|
||||||
from loops.organize.tracking import access
|
from loops.organize.tracking import access
|
||||||
from loops.organize.util import getRolesForPrincipal
|
from loops.organize.util import getRolesForPrincipal
|
||||||
from loops.resource import Resource
|
from loops.resource import Resource
|
||||||
from loops.security.common import checkPermission
|
from loops.security.common import checkPermission
|
||||||
from loops.security.common import canAccessObject, canListObject, canWriteObject
|
from loops.security.common import canAccessObject, canListObject, canWriteObject
|
||||||
|
from loops.security.common import canEditRestricted
|
||||||
from loops.type import ITypeConcept, LoopsTypeInfo
|
from loops.type import ITypeConcept, LoopsTypeInfo
|
||||||
from loops import util
|
from loops import util
|
||||||
from loops.util import _, saveRequest
|
from loops.util import _, saveRequest
|
||||||
|
@ -137,7 +123,58 @@ class EditForm(form.EditForm):
|
||||||
return parentUrl + '/contents.html'
|
return parentUrl + '/contents.html'
|
||||||
|
|
||||||
|
|
||||||
class BaseView(GenericView, I18NView):
|
class SortableMixin(object):
|
||||||
|
|
||||||
|
@Lazy
|
||||||
|
def sortInfo(self):
|
||||||
|
result = {}
|
||||||
|
for k, v in self.request.form.items():
|
||||||
|
if k.startswith('sortinfo_'):
|
||||||
|
tableName = k[len('sortinfo_'):]
|
||||||
|
if ',' in v:
|
||||||
|
fn, dir = v.split(',')
|
||||||
|
else:
|
||||||
|
fn = v
|
||||||
|
dir = 'asc'
|
||||||
|
result[tableName] = dict(
|
||||||
|
colName=fn, ascending=(dir=='asc'), fparam=v)
|
||||||
|
result = favorite.updateSortInfo(getPersonForUser(
|
||||||
|
self.context, self.request), self.target, result)
|
||||||
|
return result
|
||||||
|
|
||||||
|
def isSortableColumn(self, tableName, colName):
|
||||||
|
return False # overwrite in subclass
|
||||||
|
|
||||||
|
def getSortUrl(self, tableName, colName):
|
||||||
|
url = str(self.request.URL)
|
||||||
|
paramChar = '?' in url and '&' or '?'
|
||||||
|
si = self.sortInfo.get(tableName)
|
||||||
|
if si is not None and si.get('colName') == colName:
|
||||||
|
dir = si['ascending'] and 'desc' or 'asc'
|
||||||
|
else:
|
||||||
|
dir = 'asc'
|
||||||
|
return '%s%ssortinfo_%s=%s,%s' % (url, paramChar, tableName, colName, dir)
|
||||||
|
|
||||||
|
def getSortParams(self, tableName):
|
||||||
|
url = str(self.request.URL)
|
||||||
|
paramChar = '?' in url and '&' or '?'
|
||||||
|
si = self.sortInfo.get(tableName)
|
||||||
|
if si is not None:
|
||||||
|
colName = si['colName']
|
||||||
|
dir = si['ascending'] and 'asc' or 'desc'
|
||||||
|
return '%ssortinfo_%s=%s,%s' % (paramChar, tableName, colName, dir)
|
||||||
|
return ''
|
||||||
|
|
||||||
|
def getSortImage(self, tableName, colName):
|
||||||
|
si = self.sortInfo.get(tableName)
|
||||||
|
if si is not None and si.get('colName') == colName:
|
||||||
|
if si['ascending']:
|
||||||
|
return '/@@/cybertools.icons/arrowdown.gif'
|
||||||
|
else:
|
||||||
|
return '/@@/cybertools.icons/arrowup.gif'
|
||||||
|
|
||||||
|
|
||||||
|
class BaseView(GenericView, I18NView, SortableMixin):
|
||||||
|
|
||||||
actions = {}
|
actions = {}
|
||||||
portlet_actions = []
|
portlet_actions = []
|
||||||
|
@ -146,6 +183,7 @@ class BaseView(GenericView, I18NView):
|
||||||
icon = None
|
icon = None
|
||||||
modeName = 'view'
|
modeName = 'view'
|
||||||
isToplevel = False
|
isToplevel = False
|
||||||
|
isVisible = True
|
||||||
|
|
||||||
def __init__(self, context, request):
|
def __init__(self, context, request):
|
||||||
context = baseObject(context)
|
context = baseObject(context)
|
||||||
|
@ -163,6 +201,10 @@ class BaseView(GenericView, I18NView):
|
||||||
pass
|
pass
|
||||||
saveRequest(request)
|
saveRequest(request)
|
||||||
|
|
||||||
|
def todayFormatted(self):
|
||||||
|
return formatDate(date.today(), 'date', 'short',
|
||||||
|
self.languageInfo.language)
|
||||||
|
|
||||||
def checkPermissions(self):
|
def checkPermissions(self):
|
||||||
return canAccessObject(self.context)
|
return canAccessObject(self.context)
|
||||||
|
|
||||||
|
@ -214,6 +256,16 @@ class BaseView(GenericView, I18NView):
|
||||||
result.append(view)
|
result.append(view)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
@Lazy
|
||||||
|
def urlParamString(self):
|
||||||
|
return self.getUrlParamString()
|
||||||
|
|
||||||
|
def getUrlParamString(self):
|
||||||
|
qs = self.request.get('QUERY_STRING')
|
||||||
|
if qs:
|
||||||
|
return '?' + qs
|
||||||
|
return ''
|
||||||
|
|
||||||
@Lazy
|
@Lazy
|
||||||
def principalId(self):
|
def principalId(self):
|
||||||
principal = self.request.principal
|
principal = self.request.principal
|
||||||
|
@ -347,6 +399,10 @@ class BaseView(GenericView, I18NView):
|
||||||
def isPartOfPredicate(self):
|
def isPartOfPredicate(self):
|
||||||
return self.conceptManager.get('ispartof')
|
return self.conceptManager.get('ispartof')
|
||||||
|
|
||||||
|
@Lazy
|
||||||
|
def queryTargetPredicate(self):
|
||||||
|
return self.conceptManager.get('querytarget')
|
||||||
|
|
||||||
@Lazy
|
@Lazy
|
||||||
def memberPredicate(self):
|
def memberPredicate(self):
|
||||||
return self.conceptManager.get('ismember')
|
return self.conceptManager.get('ismember')
|
||||||
|
@ -395,6 +451,10 @@ class BaseView(GenericView, I18NView):
|
||||||
def description(self):
|
def description(self):
|
||||||
return self.adapted.description
|
return self.adapted.description
|
||||||
|
|
||||||
|
@Lazy
|
||||||
|
def tabTitle(self):
|
||||||
|
return u'Info'
|
||||||
|
|
||||||
@Lazy
|
@Lazy
|
||||||
def additionalInfos(self):
|
def additionalInfos(self):
|
||||||
return []
|
return []
|
||||||
|
@ -747,6 +807,8 @@ class BaseView(GenericView, I18NView):
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def checkState(self):
|
def checkState(self):
|
||||||
|
if checkPermission('loops.ManageSite', self.context):
|
||||||
|
return True
|
||||||
if not self.allStates:
|
if not self.allStates:
|
||||||
return True
|
return True
|
||||||
for stf in self.allStates:
|
for stf in self.allStates:
|
||||||
|
@ -821,6 +883,10 @@ class BaseView(GenericView, I18NView):
|
||||||
def canAccessRestricted(self):
|
def canAccessRestricted(self):
|
||||||
return checkPermission('loops.ViewRestricted', self.context)
|
return checkPermission('loops.ViewRestricted', self.context)
|
||||||
|
|
||||||
|
@Lazy
|
||||||
|
def canEditRestricted(self):
|
||||||
|
return canEditRestricted(self.context)
|
||||||
|
|
||||||
def openEditWindow(self, viewName='edit.html'):
|
def openEditWindow(self, viewName='edit.html'):
|
||||||
if self.editable:
|
if self.editable:
|
||||||
if checkPermission('loops.ManageSite', self.context):
|
if checkPermission('loops.ManageSite', self.context):
|
||||||
|
@ -829,6 +895,7 @@ class BaseView(GenericView, I18NView):
|
||||||
|
|
||||||
@Lazy
|
@Lazy
|
||||||
def xeditable(self):
|
def xeditable(self):
|
||||||
|
return False
|
||||||
if self.typeOptions('no_external_edit'):
|
if self.typeOptions('no_external_edit'):
|
||||||
return False
|
return False
|
||||||
ct = getattr(self.context, 'contentType', '')
|
ct = getattr(self.context, 'contentType', '')
|
||||||
|
@ -943,6 +1010,12 @@ class BaseView(GenericView, I18NView):
|
||||||
jsCall = 'dojo.require("dojox.image.Lightbox");'
|
jsCall = 'dojo.require("dojox.image.Lightbox");'
|
||||||
self.controller.macros.register('js-execute', jsCall, jsCall=jsCall)
|
self.controller.macros.register('js-execute', jsCall, jsCall=jsCall)
|
||||||
|
|
||||||
|
def registerDojoComboBox(self):
|
||||||
|
self.registerDojo()
|
||||||
|
jsCall = ('dojo.require("dijit.form.ComboBox");')
|
||||||
|
self.controller.macros.register('js-execute',
|
||||||
|
'dojo.require.ComboBox', jsCall=jsCall)
|
||||||
|
|
||||||
def registerDojoFormAll(self):
|
def registerDojoFormAll(self):
|
||||||
self.registerDojo()
|
self.registerDojo()
|
||||||
self.registerDojoEditor()
|
self.registerDojoEditor()
|
||||||
|
@ -996,17 +1069,17 @@ class LoggedIn(object):
|
||||||
params = parse_qsl(qs)
|
params = parse_qsl(qs)
|
||||||
params = [(k, v) for k, v in params if k != 'loops.messages.top:record']
|
params = [(k, v) for k, v in params if k != 'loops.messages.top:record']
|
||||||
params.append(('loops.messages.top:record', message.encode('UTF-8')))
|
params.append(('loops.messages.top:record', message.encode('UTF-8')))
|
||||||
|
url = url.encode('utf-8')
|
||||||
return '%s?%s' % (url, urlencode(params))
|
return '%s?%s' % (url, urlencode(params))
|
||||||
|
|
||||||
# vocabulary stuff
|
# vocabulary stuff
|
||||||
|
|
||||||
|
@implementer(ITerms)
|
||||||
class SimpleTerms(object):
|
class SimpleTerms(object):
|
||||||
""" Provide the ITerms interface, e.g. for usage in selection
|
""" Provide the ITerms interface, e.g. for usage in selection
|
||||||
lists.
|
lists.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
implements(ITerms)
|
|
||||||
|
|
||||||
def __init__(self, source, request):
|
def __init__(self, source, request):
|
||||||
# the source parameter is a list of tuples (token, title).
|
# the source parameter is a list of tuples (token, title).
|
||||||
self.source = source
|
self.source = source
|
||||||
|
@ -1021,13 +1094,12 @@ class SimpleTerms(object):
|
||||||
return (token, self.terms[token])
|
return (token, self.terms[token])
|
||||||
|
|
||||||
|
|
||||||
|
@implementer(ITerms)
|
||||||
class LoopsTerms(object):
|
class LoopsTerms(object):
|
||||||
""" Provide the ITerms interface, e.g. for usage in selection
|
""" Provide the ITerms interface, e.g. for usage in selection
|
||||||
lists.
|
lists.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
implements(ITerms)
|
|
||||||
|
|
||||||
def __init__(self, source, request):
|
def __init__(self, source, request):
|
||||||
# the source parameter is a view or adapter of a real context object:
|
# the source parameter is a view or adapter of a real context object:
|
||||||
self.source = source
|
self.source = source
|
||||||
|
@ -1049,12 +1121,11 @@ class LoopsTerms(object):
|
||||||
return self.loopsRoot.loopsTraverse(token)
|
return self.loopsRoot.loopsTraverse(token)
|
||||||
|
|
||||||
|
|
||||||
|
@implementer(ITerms)
|
||||||
class InterfaceTerms(object):
|
class InterfaceTerms(object):
|
||||||
""" Provide the ITerms interface for source list of interfaces.
|
""" Provide the ITerms interface for source list of interfaces.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
implements(ITerms)
|
|
||||||
|
|
||||||
def __init__(self, source, request):
|
def __init__(self, source, request):
|
||||||
self.source = source
|
self.source = source
|
||||||
self.request = request
|
self.request = request
|
|
@ -1,40 +1,22 @@
|
||||||
#
|
# loops.browser.concept
|
||||||
# Copyright (c) 2013 Helmut Merz helmutm@cy55.de
|
|
||||||
#
|
|
||||||
# This program is free software; you can redistribute it and/or modify
|
|
||||||
# it under the terms of the GNU General Public License as published by
|
|
||||||
# the Free Software Foundation; either version 2 of the License, or
|
|
||||||
# (at your option) any later version.
|
|
||||||
#
|
|
||||||
# This program is distributed in the hope that it will be useful,
|
|
||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
# GNU General Public License for more details.
|
|
||||||
#
|
|
||||||
# You should have received a copy of the GNU General Public License
|
|
||||||
# along with this program; if not, write to the Free Software
|
|
||||||
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
||||||
#
|
|
||||||
|
|
||||||
"""
|
""" Definition of the concept view classes.
|
||||||
Definition of the concept view classes.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from itertools import groupby
|
from itertools import groupby
|
||||||
from zope import interface, component, schema
|
from zope import interface, component, schema
|
||||||
from zope.app.catalog.interfaces import ICatalog
|
from zope.authentication.interfaces import IUnauthenticatedPrincipal
|
||||||
from zope.lifecycleevent import ObjectCreatedEvent, ObjectModifiedEvent
|
from zope.browser.interfaces import ITerms
|
||||||
from zope.app.container.contained import ObjectRemovedEvent
|
from zope.browserpage import ViewPageTemplateFile
|
||||||
from zope.app.form.browser.interfaces import ITerms
|
|
||||||
from zope.app.form.interfaces import IDisplayWidget
|
|
||||||
from zope.app.pagetemplate import ViewPageTemplateFile
|
|
||||||
from zope.app.security.interfaces import IUnauthenticatedPrincipal
|
|
||||||
from zope.cachedescriptors.property import Lazy
|
from zope.cachedescriptors.property import Lazy
|
||||||
|
from zope.catalog.interfaces import ICatalog
|
||||||
|
from zope.container.contained import ObjectRemovedEvent
|
||||||
from zope.dottedname.resolve import resolve
|
from zope.dottedname.resolve import resolve
|
||||||
from zope.event import notify
|
from zope.event import notify
|
||||||
from zope.formlib.form import EditForm, FormFields, setUpEditWidgets
|
from zope.formlib.form import EditForm, FormFields, setUpEditWidgets
|
||||||
|
from zope.formlib.interfaces import IDisplayWidget
|
||||||
from zope.formlib.namedtemplate import NamedTemplate
|
from zope.formlib.namedtemplate import NamedTemplate
|
||||||
from zope.interface import implements
|
from zope.lifecycleevent import ObjectCreatedEvent, ObjectModifiedEvent
|
||||||
from zope.publisher.interfaces import BadRequest
|
from zope.publisher.interfaces import BadRequest
|
||||||
from zope.publisher.interfaces.browser import IBrowserRequest
|
from zope.publisher.interfaces.browser import IBrowserRequest
|
||||||
from zope.schema.interfaces import IIterableSource
|
from zope.schema.interfaces import IIterableSource
|
||||||
|
@ -254,18 +236,35 @@ class ConceptView(BaseView):
|
||||||
result.append(view)
|
result.append(view)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
def viewModes(self):
|
||||||
|
modes = Jeep()
|
||||||
|
current = self.request.form.get('loops.viewName')
|
||||||
|
parts = (self.options('view_tabs') or
|
||||||
|
self.typeOptions('view_tabs') or [])
|
||||||
|
if not parts:
|
||||||
|
return modes
|
||||||
|
activeMode = None
|
||||||
|
for p in parts:
|
||||||
|
view = component.queryMultiAdapter(
|
||||||
|
(self.adapted, self.request), name=p)
|
||||||
|
if view is None:
|
||||||
|
view = component.queryMultiAdapter(
|
||||||
|
(self.context, self.request), name=p)
|
||||||
|
if view is None:
|
||||||
|
continue
|
||||||
|
active = (activeMode is None and p == current)
|
||||||
|
if active:
|
||||||
|
activeMode = p
|
||||||
|
url = '%s?loops.viewName=%s' % (self.targetUrl, p)
|
||||||
|
modes.append(ViewMode(p, view.tabTitle, url, active))
|
||||||
|
if activeMode is None:
|
||||||
|
modes[0].active = True
|
||||||
|
return modes
|
||||||
|
|
||||||
@Lazy
|
@Lazy
|
||||||
def adapted(self):
|
def adapted(self):
|
||||||
return adapted(self.context, self.languageInfo)
|
return adapted(self.context, self.languageInfo)
|
||||||
|
|
||||||
@Lazy
|
|
||||||
def title(self):
|
|
||||||
return self.adapted.title or getName(self.context)
|
|
||||||
|
|
||||||
@Lazy
|
|
||||||
def description(self):
|
|
||||||
return self.adapted.description
|
|
||||||
|
|
||||||
@Lazy
|
@Lazy
|
||||||
def targetUrl(self):
|
def targetUrl(self):
|
||||||
return self.nodeView.getUrlForTarget(self.context)
|
return self.nodeView.getUrlForTarget(self.context)
|
||||||
|
@ -282,8 +281,17 @@ class ConceptView(BaseView):
|
||||||
def breadcrumbsTitle(self):
|
def breadcrumbsTitle(self):
|
||||||
return self.title
|
return self.title
|
||||||
|
|
||||||
|
@Lazy
|
||||||
|
def showInBreadcrumbs(self):
|
||||||
|
return (self.options('show_in_breadcrumbs') or
|
||||||
|
self.typeOptions('show_in_breadcrumbs'))
|
||||||
|
|
||||||
@Lazy
|
@Lazy
|
||||||
def breadcrumbsParent(self):
|
def breadcrumbsParent(self):
|
||||||
|
for p in self.context.getParents([self.defaultPredicate]):
|
||||||
|
view = self.nodeView.getViewForTarget(p)
|
||||||
|
if view is not None and view.showInBreadcrumbs:
|
||||||
|
return view
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def getData(self, omit=('title', 'description')):
|
def getData(self, omit=('title', 'description')):
|
||||||
|
@ -389,7 +397,8 @@ class ConceptView(BaseView):
|
||||||
children = getChildren
|
children = getChildren
|
||||||
|
|
||||||
def childrenAlphaGroups(self, predicates=None):
|
def childrenAlphaGroups(self, predicates=None):
|
||||||
result = Jeep()
|
#result = Jeep()
|
||||||
|
result = {}
|
||||||
rels = self.getChildren(predicates=predicates or [self.defaultPredicate],
|
rels = self.getChildren(predicates=predicates or [self.defaultPredicate],
|
||||||
topLevelOnly=False, sort=False)
|
topLevelOnly=False, sort=False)
|
||||||
rels = sorted(rels, key=lambda r: r.title.lower())
|
rels = sorted(rels, key=lambda r: r.title.lower())
|
||||||
|
@ -436,7 +445,7 @@ class ConceptView(BaseView):
|
||||||
|
|
||||||
def parents(self):
|
def parents(self):
|
||||||
rels = sorted(self.context.getParentRelations(),
|
rels = sorted(self.context.getParentRelations(),
|
||||||
key=(lambda x: x.first.title and x.first.title.lower()))
|
key=(lambda x: x.first.title and x.first.title.lower() or ''))
|
||||||
for r in rels:
|
for r in rels:
|
||||||
yield self.childViewFactory(r, self.request)
|
yield self.childViewFactory(r, self.request)
|
||||||
|
|
||||||
|
@ -449,7 +458,7 @@ class ConceptView(BaseView):
|
||||||
if r.order != pos:
|
if r.order != pos:
|
||||||
r.order = pos
|
r.order = pos
|
||||||
|
|
||||||
def getResources(self):
|
def getResources(self, relView=None, sort='default'):
|
||||||
form = self.request.form
|
form = self.request.form
|
||||||
#if form.get('loops.viewName') == 'index.html' and self.editable:
|
#if form.get('loops.viewName') == 'index.html' and self.editable:
|
||||||
if self.editable:
|
if self.editable:
|
||||||
|
@ -458,13 +467,17 @@ class ConceptView(BaseView):
|
||||||
tokens = form.get('resources_tokens')
|
tokens = form.get('resources_tokens')
|
||||||
if tokens:
|
if tokens:
|
||||||
self.reorderResources(tokens)
|
self.reorderResources(tokens)
|
||||||
|
if relView is None:
|
||||||
from loops.browser.resource import ResourceRelationView
|
from loops.browser.resource import ResourceRelationView
|
||||||
|
relView = ResourceRelationView
|
||||||
from loops.organize.personal.browser.filter import FilterView
|
from loops.organize.personal.browser.filter import FilterView
|
||||||
fv = FilterView(self.context, self.request)
|
fv = FilterView(self.context, self.request)
|
||||||
rels = self.context.getResourceRelations()
|
rels = self.context.getResourceRelations(sort=sort)
|
||||||
for r in rels:
|
for r in rels:
|
||||||
if fv.check(r.first):
|
if fv.check(r.first):
|
||||||
yield ResourceRelationView(r, self.request, contextIsSecond=True)
|
view = relView(r, self.request, contextIsSecond=True)
|
||||||
|
if view.checkState():
|
||||||
|
yield view
|
||||||
|
|
||||||
def resources(self):
|
def resources(self):
|
||||||
return self.getResources()
|
return self.getResources()
|
|
@ -53,7 +53,7 @@
|
||||||
<h1 tal:define="tabview item/tabview|nothing"
|
<h1 tal:define="tabview item/tabview|nothing"
|
||||||
tal:attributes="ondblclick item/openEditWindow">
|
tal:attributes="ondblclick item/openEditWindow">
|
||||||
<a tal:omit-tag="python: level > 1"
|
<a tal:omit-tag="python: level > 1"
|
||||||
tal:attributes="href request/URL"
|
tal:attributes="href string:${view/requestUrl}${item/urlParamString}"
|
||||||
tal:content="item/title">Title</a>
|
tal:content="item/title">Title</a>
|
||||||
<a title="Show tabular view"
|
<a title="Show tabular view"
|
||||||
i18n:attributes="title"
|
i18n:attributes="title"
|
||||||
|
@ -367,4 +367,21 @@
|
||||||
</metal:actions>
|
</metal:actions>
|
||||||
|
|
||||||
|
|
||||||
|
<metal:sortable define-macro="sortable_column_header"
|
||||||
|
tal:define="tableName tableName|nothing">
|
||||||
|
<a title="tooltip_sort_column"
|
||||||
|
tal:define="colName col/name"
|
||||||
|
tal:omit-tag="python:not item.isSortableColumn(tableName, colName)"
|
||||||
|
tal:attributes="href python:item.getSortUrl(tableName, colName)"
|
||||||
|
i18n:attributes="title">
|
||||||
|
<span tal:content="col/title"
|
||||||
|
tal:attributes="class col/cssClass|nothing"
|
||||||
|
i18n:translate="" />
|
||||||
|
<img tal:define="src python:item.getSortImage(tableName, colName)"
|
||||||
|
tal:condition="src"
|
||||||
|
tal:attributes="src src" />
|
||||||
|
</a>
|
||||||
|
</metal:sortable>
|
||||||
|
|
||||||
|
|
||||||
</html>
|
</html>
|
|
@ -125,7 +125,7 @@
|
||||||
|
|
||||||
<containerViews
|
<containerViews
|
||||||
for="loops.interfaces.ILoops"
|
for="loops.interfaces.ILoops"
|
||||||
index="zope.View"
|
index="zope.ManageSite"
|
||||||
contents="loops.ManageSite"
|
contents="loops.ManageSite"
|
||||||
add="loops.ManageSite" />
|
add="loops.ManageSite" />
|
||||||
|
|
||||||
|
@ -365,7 +365,7 @@
|
||||||
|
|
||||||
<containerViews
|
<containerViews
|
||||||
for="loops.interfaces.IViewManager"
|
for="loops.interfaces.IViewManager"
|
||||||
index="zope.View"
|
index="zope.ManageSite"
|
||||||
add="loops.ManageSite" />
|
add="loops.ManageSite" />
|
||||||
|
|
||||||
<menuItem
|
<menuItem
|
||||||
|
@ -571,6 +571,14 @@
|
||||||
factory="loops.browser.concept.TabbedPage"
|
factory="loops.browser.concept.TabbedPage"
|
||||||
permission="zope.View" />
|
permission="zope.View" />
|
||||||
|
|
||||||
|
<!-- delete object action -->
|
||||||
|
|
||||||
|
<page
|
||||||
|
name="delete_object"
|
||||||
|
for="loops.interfaces.INode"
|
||||||
|
class="loops.browser.form.DeleteObject"
|
||||||
|
permission="zope.ManageContent" />
|
||||||
|
|
||||||
<!-- dialogs/forms (end-user views) -->
|
<!-- dialogs/forms (end-user views) -->
|
||||||
|
|
||||||
<page
|
<page
|
|
@ -1,28 +1,8 @@
|
||||||
#
|
# loops.browser.external
|
||||||
# Copyright (c) 2008 Helmut Merz helmutm@cy55.de
|
|
||||||
#
|
|
||||||
# This program is free software; you can redistribute it and/or modify
|
|
||||||
# it under the terms of the GNU General Public License as published by
|
|
||||||
# the Free Software Foundation; either version 2 of the License, or
|
|
||||||
# (at your option) any later version.
|
|
||||||
#
|
|
||||||
# This program is distributed in the hope that it will be useful,
|
|
||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
# GNU General Public License for more details.
|
|
||||||
#
|
|
||||||
# You should have received a copy of the GNU General Public License
|
|
||||||
# along with this program; if not, write to the Free Software
|
|
||||||
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
||||||
#
|
|
||||||
|
|
||||||
"""
|
""" view class(es) for import/export.
|
||||||
view class(es) for import/export.
|
|
||||||
|
|
||||||
$Id$
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from zope.interface import Interface, implements
|
|
||||||
from zope.app import zapi
|
from zope.app import zapi
|
||||||
from zope.app.pagetemplate.viewpagetemplatefile import ViewPageTemplateFile
|
from zope.app.pagetemplate.viewpagetemplatefile import ViewPageTemplateFile
|
||||||
from zope.cachedescriptors.property import Lazy
|
from zope.cachedescriptors.property import Lazy
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB |
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.7 KiB |
|
@ -1,41 +1,25 @@
|
||||||
#
|
# loops.browser.form
|
||||||
# Copyright (c) 2012 Helmut Merz helmutm@cy55.de
|
|
||||||
#
|
|
||||||
# This program is free software; you can redistribute it and/or modify
|
|
||||||
# it under the terms of the GNU General Public License as published by
|
|
||||||
# the Free Software Foundation; either version 2 of the License, or
|
|
||||||
# (at your option) any later version.
|
|
||||||
#
|
|
||||||
# This program is distributed in the hope that it will be useful,
|
|
||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
# GNU General Public License for more details.
|
|
||||||
#
|
|
||||||
# You should have received a copy of the GNU General Public License
|
|
||||||
# along with this program; if not, write to the Free Software
|
|
||||||
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
||||||
#
|
|
||||||
|
|
||||||
"""
|
""" Classes for form presentation and processing.
|
||||||
Classes for form presentation and processing.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
from urllib.parse import urlencode, unquote_plus
|
||||||
from zope import component, interface, schema
|
from zope import component, interface, schema
|
||||||
from zope.component import adapts
|
from zope.component import adapts
|
||||||
|
from zope.container.contained import ObjectRemovedEvent
|
||||||
from zope.event import notify
|
from zope.event import notify
|
||||||
from zope.interface import Interface
|
from zope.interface import Interface
|
||||||
from zope.lifecycleevent import ObjectCreatedEvent, ObjectModifiedEvent
|
from zope.lifecycleevent import ObjectCreatedEvent, ObjectModifiedEvent
|
||||||
|
from zope.container.interfaces import INameChooser
|
||||||
from zope.app.container.interfaces import INameChooser
|
from zope.lifecycleevent import ObjectAddedEvent
|
||||||
from zope.app.container.contained import ObjectAddedEvent
|
from zope.browserpage import ViewPageTemplateFile
|
||||||
from zope.app.pagetemplate import ViewPageTemplateFile
|
|
||||||
from zope.cachedescriptors.property import Lazy
|
from zope.cachedescriptors.property import Lazy
|
||||||
from zope.contenttype import guess_content_type
|
from zope.contenttype import guess_content_type
|
||||||
from zope.publisher.browser import FileUpload
|
from zope.publisher.browser import FileUpload
|
||||||
from zope.publisher.interfaces import BadRequest
|
from zope.publisher.interfaces import BadRequest
|
||||||
from zope.security.interfaces import ForbiddenAttribute, Unauthorized
|
from zope.security.interfaces import ForbiddenAttribute, Unauthorized
|
||||||
from zope.security.proxy import isinstance, removeSecurityProxy
|
from zope.security.proxy import isinstance, removeSecurityProxy
|
||||||
from zope.traversing.api import getName
|
from zope.traversing.api import getName, getParent
|
||||||
|
|
||||||
from cybertools.ajax import innerHtml
|
from cybertools.ajax import innerHtml
|
||||||
from cybertools.browser.form import FormController
|
from cybertools.browser.form import FormController
|
||||||
|
@ -68,6 +52,25 @@ from loops.util import _
|
||||||
from loops.versioning.interfaces import IVersionable
|
from loops.versioning.interfaces import IVersionable
|
||||||
|
|
||||||
|
|
||||||
|
# delete object
|
||||||
|
|
||||||
|
class DeleteObject(NodeView):
|
||||||
|
|
||||||
|
isTopLevel = True
|
||||||
|
|
||||||
|
def __call__(self):
|
||||||
|
# todo: check permission; check security code
|
||||||
|
form = self.request.form
|
||||||
|
obj = util.getObjectForUid(form['uid'])
|
||||||
|
container = getParent(obj)
|
||||||
|
notify(ObjectRemovedEvent(obj))
|
||||||
|
del container[getName(obj)]
|
||||||
|
message = 'The object requested has been deleted.'
|
||||||
|
params = [('loops.message', message.encode('UTF-8'))]
|
||||||
|
nextUrl = '%s?%s' % (self.request.URL[-1], urlencode(params))
|
||||||
|
return self.request.response.redirect(nextUrl)
|
||||||
|
|
||||||
|
|
||||||
# forms
|
# forms
|
||||||
|
|
||||||
class ObjectForm(NodeView):
|
class ObjectForm(NodeView):
|
||||||
|
@ -162,7 +165,10 @@ class ObjectForm(NodeView):
|
||||||
field = self.schema.fields.get(k)
|
field = self.schema.fields.get(k)
|
||||||
if field:
|
if field:
|
||||||
fi = field.getFieldInstance(self.instance)
|
fi = field.getFieldInstance(self.instance)
|
||||||
data[k] = fi.marshall(fi.unmarshall(form[k]))
|
input = form[k]
|
||||||
|
if isinstance(input, str):
|
||||||
|
input = unquote_plus(input)
|
||||||
|
data[k] = fi.marshall(fi.unmarshall(input))
|
||||||
#data[k] = toUnicode(form[k])
|
#data[k] = toUnicode(form[k])
|
||||||
return data
|
return data
|
||||||
|
|
||||||
|
@ -196,15 +202,37 @@ class ObjectForm(NodeView):
|
||||||
def typeManager(self):
|
def typeManager(self):
|
||||||
return ITypeManager(self.target)
|
return ITypeManager(self.target)
|
||||||
|
|
||||||
|
@Lazy
|
||||||
|
def targetType(self):
|
||||||
|
return self.target.getType()
|
||||||
|
|
||||||
@Lazy
|
@Lazy
|
||||||
def presetTypesForAssignment(self):
|
def presetTypesForAssignment(self):
|
||||||
types = list(self.typeManager.listTypes(include=('assign',)))
|
types = []
|
||||||
|
tn = getName(self.targetType)
|
||||||
|
for t in self.typeManager.listTypes(include=('assign',)):
|
||||||
|
# check if type is appropriate for the object to be created
|
||||||
|
opt = IOptions(adapted(t.context))('qualifier_assign_to')
|
||||||
|
#print '***', t.context.__name__, opt, tn
|
||||||
|
if not opt or tn in opt:
|
||||||
|
types.append(t)
|
||||||
assigned = [r.context.conceptType for r in self.assignments]
|
assigned = [r.context.conceptType for r in self.assignments]
|
||||||
types = [t for t in types if t.typeProvider not in assigned]
|
types = [t for t in types if t.typeProvider not in assigned]
|
||||||
return [dict(title=t.title, token=t.tokenForSearch) for t in types]
|
return [dict(title=t.title, token=t.tokenForSearch) for t in types]
|
||||||
|
|
||||||
def conceptsForType(self, token):
|
def conceptsForType(self, token):
|
||||||
result = ConceptQuery(self).query(type=token)
|
result = ConceptQuery(self).query(type=token)
|
||||||
|
# check typeOption: include only matching instances
|
||||||
|
include = []
|
||||||
|
type = self.conceptManager[token.split(':')[-1]]
|
||||||
|
#print '###', token, repr(type)
|
||||||
|
opt = IOptions(adapted(type))('qualifier_assign_check_parents')
|
||||||
|
if opt:
|
||||||
|
for p in self.target.getAllParents([self.defaultPredicate]):
|
||||||
|
for c in p.object.getChildren([self.defaultPredicate]):
|
||||||
|
include.append(c)
|
||||||
|
if include:
|
||||||
|
result = [c for c in result if c in include]
|
||||||
fv = FilterView(self.context, self.request)
|
fv = FilterView(self.context, self.request)
|
||||||
result = fv.apply(result)
|
result = fv.apply(result)
|
||||||
result.sort(key=lambda x: x.title)
|
result.sort(key=lambda x: x.title)
|
||||||
|
@ -288,8 +316,11 @@ class CreateObjectForm(ObjectForm):
|
||||||
|
|
||||||
@Lazy
|
@Lazy
|
||||||
def defaultTypeToken(self):
|
def defaultTypeToken(self):
|
||||||
return (self.controller.params.get('form.create.defaultTypeToken')
|
setting = self.controller.params.get('form.create.defaultTypeToken')
|
||||||
or '.loops/concepts/textdocument')
|
if setting:
|
||||||
|
return setting
|
||||||
|
opt = self.globalOptions('form.create.default_type_token')
|
||||||
|
return opt and opt[0] or '.loops/concepts/textdocument'
|
||||||
|
|
||||||
@Lazy
|
@Lazy
|
||||||
def typeToken(self):
|
def typeToken(self):
|
||||||
|
@ -310,10 +341,15 @@ class CreateObjectForm(ObjectForm):
|
||||||
if typeToken:
|
if typeToken:
|
||||||
return self.loopsRoot.loopsTraverse(typeToken)
|
return self.loopsRoot.loopsTraverse(typeToken)
|
||||||
|
|
||||||
|
@Lazy
|
||||||
|
def targetType(self):
|
||||||
|
return self.typeConcept
|
||||||
|
|
||||||
@Lazy
|
@Lazy
|
||||||
def adapted(self):
|
def adapted(self):
|
||||||
ad = self.typeInterface(Resource())
|
ad = self.typeInterface(Resource())
|
||||||
ad.storageName = 'unknown' # hack for file objects: don't try to retrieve data
|
ad.storageName = 'unknown' # hack for file objects: don't try to retrieve data
|
||||||
|
ad.__is_dummy__ = True
|
||||||
ad.__type__ = adapted(self.typeConcept)
|
ad.__type__ = adapted(self.typeConcept)
|
||||||
return ad
|
return ad
|
||||||
|
|
||||||
|
@ -423,6 +459,7 @@ class CreateConceptForm(CreateObjectForm):
|
||||||
return c
|
return c
|
||||||
ad = ti(c)
|
ad = ti(c)
|
||||||
ad.__is_dummy__ = True
|
ad.__is_dummy__ = True
|
||||||
|
ad.__type__ = adapted(self.typeConcept)
|
||||||
return ad
|
return ad
|
||||||
|
|
||||||
@Lazy
|
@Lazy
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
|
|
||||||
<metal:info define-macro="object_info"
|
<metal:info define-macro="object_info"
|
||||||
tal:define="item nocall:view/item">
|
tal:define="item nocall:view/targetItem">
|
||||||
<table class="object_info" width="400">
|
<table class="object_info" width="400">
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="2"><h2 i18n:translate="">Object Information</h2><br /></td>
|
<td colspan="2"><h2 i18n:translate="">Object Information</h2><br /></td>
|
||||||
|
@ -52,7 +52,7 @@
|
||||||
|
|
||||||
|
|
||||||
<metal:info define-macro="meta_info"
|
<metal:info define-macro="meta_info"
|
||||||
tal:define="item nocall:view/item">
|
tal:define="item nocall:view/targetItem">
|
||||||
<table class="object_info" width="400">
|
<table class="object_info" width="400">
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="2">
|
<td colspan="2">
|
|
@ -1,26 +1,9 @@
|
||||||
#
|
# loops.browser.lobo.standard
|
||||||
# Copyright (c) 2012 Helmut Merz helmutm@cy55.de
|
|
||||||
#
|
|
||||||
# This program is free software; you can redistribute it and/or modify
|
|
||||||
# it under the terms of the GNU General Public License as published by
|
|
||||||
# the Free Software Foundation; either version 2 of the License, or
|
|
||||||
# (at your option) any later version.
|
|
||||||
#
|
|
||||||
# This program is distributed in the hope that it will be useful,
|
|
||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
# GNU General Public License for more details.
|
|
||||||
#
|
|
||||||
# You should have received a copy of the GNU General Public License
|
|
||||||
# along with this program; if not, write to the Free Software
|
|
||||||
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
||||||
#
|
|
||||||
|
|
||||||
"""
|
""" View classes for lobo (blueprint-based) layouts.
|
||||||
View classes for lobo (blueprint-based) layouts.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from cgi import parse_qs
|
from urllib.parse import parse_qs
|
||||||
from zope import interface, component
|
from zope import interface, component
|
||||||
from zope.app.pagetemplate import ViewPageTemplateFile
|
from zope.app.pagetemplate import ViewPageTemplateFile
|
||||||
from zope.cachedescriptors.property import Lazy
|
from zope.cachedescriptors.property import Lazy
|
|
@ -1,3 +1,4 @@
|
||||||
|
# loops.browser.lobo.tests
|
||||||
|
|
||||||
import unittest, doctest
|
import unittest, doctest
|
||||||
from zope.interface.verify import verifyClass
|
from zope.interface.verify import verifyClass
|
||||||
|
@ -12,7 +13,7 @@ class Test(unittest.TestCase):
|
||||||
def test_suite():
|
def test_suite():
|
||||||
flags = doctest.NORMALIZE_WHITESPACE | doctest.ELLIPSIS
|
flags = doctest.NORMALIZE_WHITESPACE | doctest.ELLIPSIS
|
||||||
return unittest.TestSuite((
|
return unittest.TestSuite((
|
||||||
unittest.makeSuite(Test),
|
unittest.TestLoader().loadTestsFromTestCase(Test),
|
||||||
doctest.DocFileSuite('README.txt', optionflags=flags),
|
doctest.DocFileSuite('README.txt', optionflags=flags),
|
||||||
))
|
))
|
||||||
|
|
|
@ -238,18 +238,21 @@ fieldset.box td {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
color: #444;
|
color: #444;
|
||||||
padding-top: 0.4em;
|
padding-top: 0.4em;
|
||||||
|
border-bottom: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.content-4 h1, .content-3 h2, .content-2 h3, .content-1 h4, h4 {
|
.content-4 h1, .content-3 h2, .content-2 h3, .content-1 h4, h4 {
|
||||||
font-size: 130%;
|
font-size: 130%;
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
padding-top: 0.3em;
|
padding-top: 0.3em;
|
||||||
|
border-bottom: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.content-5 h1, .content-4 h2, .content-3 h3, content-2 h4, h5 {
|
.content-5 h1, .content-4 h2, .content-3 h3, content-2 h4, h5 {
|
||||||
font-size: 120%;
|
font-size: 120%;
|
||||||
/* border: none; */
|
/* border: none; */
|
||||||
padding-top: 0.2em;
|
padding-top: 0.2em;
|
||||||
|
border-bottom: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.box {
|
.box {
|
|
@ -47,6 +47,35 @@ function showIfIn(node, conditions) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function setIfIn(node, conditions) {
|
||||||
|
dojo.forEach(conditions, function(cond) {
|
||||||
|
if (node.value == cond[0]) {
|
||||||
|
target = dijit.byId(cond[1]);
|
||||||
|
target.setValue(cond[2]);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function setIf(node, cond, acts) {
|
||||||
|
if (node.value == cond) {
|
||||||
|
dojo.forEach(acts, function(act) {
|
||||||
|
target = dijit.byId(act[0]);
|
||||||
|
target.setValue(act[1]);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function setIfN(node, conds, acts) {
|
||||||
|
dojo.forEach(conds, function(cond) {
|
||||||
|
if (node.value == cond) {
|
||||||
|
dojo.forEach(acts, function(act) {
|
||||||
|
target = dijit.byId(act[0]);
|
||||||
|
target.setValue(act[1]);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
function destroyWidgets(node) {
|
function destroyWidgets(node) {
|
||||||
dojo.forEach(dojo.query('[widgetId]', node), function(n) {
|
dojo.forEach(dojo.query('[widgetId]', node), function(n) {
|
||||||
w = dijit.byNode(n);
|
w = dijit.byNode(n);
|
||||||
|
@ -103,7 +132,7 @@ function submitReplacing(targetId, formId, url) {
|
||||||
mimetype: "text/html",
|
mimetype: "text/html",
|
||||||
load: function(response, ioArgs) {
|
load: function(response, ioArgs) {
|
||||||
replaceNode(response, targetId);
|
replaceNode(response, targetId);
|
||||||
return resonse;
|
return response;
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -115,7 +144,7 @@ function xhrSubmitPopup(formId, url) {
|
||||||
mimetype: "text/html",
|
mimetype: "text/html",
|
||||||
load: function(response, ioArgs) {
|
load: function(response, ioArgs) {
|
||||||
window.close();
|
window.close();
|
||||||
return resonse;
|
return response;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
Before Width: | Height: | Size: 942 B After Width: | Height: | Size: 942 B |
|
@ -1,31 +1,11 @@
|
||||||
#
|
# loops.browser.mobile.default
|
||||||
# Copyright (c) 2011 Helmut Merz helmutm@cy55.de
|
|
||||||
#
|
|
||||||
# This program is free software; you can redistribute it and/or modify
|
|
||||||
# it under the terms of the GNU General Public License as published by
|
|
||||||
# the Free Software Foundation; either version 2 of the License, or
|
|
||||||
# (at your option) any later version.
|
|
||||||
#
|
|
||||||
# This program is distributed in the hope that it will be useful,
|
|
||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
# GNU General Public License for more details.
|
|
||||||
#
|
|
||||||
# You should have received a copy of the GNU General Public License
|
|
||||||
# along with this program; if not, write to the Free Software
|
|
||||||
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
||||||
#
|
|
||||||
|
|
||||||
"""
|
""" Default layouts for the loops mobile skin.
|
||||||
Default layouts for the loops mobile skin.
|
|
||||||
|
|
||||||
$Id$
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from zope.app.pagetemplate import ViewPageTemplateFile
|
from zope.app.pagetemplate import ViewPageTemplateFile
|
||||||
from zope.cachedescriptors.property import Lazy
|
from zope.cachedescriptors.property import Lazy
|
||||||
from zope import component
|
from zope import component
|
||||||
from zope.interface import implements
|
|
||||||
|
|
||||||
from cybertools.browser.renderer import RendererFactory
|
from cybertools.browser.renderer import RendererFactory
|
||||||
from cybertools.composer.layout.base import Layout
|
from cybertools.composer.layout.base import Layout
|
|
@ -1,37 +1,20 @@
|
||||||
#
|
# loops.browser.node
|
||||||
# Copyright (c) 2016 Helmut Merz helmutm@cy55.de
|
|
||||||
#
|
|
||||||
# This program is free software; you can redistribute it and/or modify
|
|
||||||
# it under the terms of the GNU General Public License as published by
|
|
||||||
# the Free Software Foundation; either version 2 of the License, or
|
|
||||||
# (at your option) any later version.
|
|
||||||
#
|
|
||||||
# This program is distributed in the hope that it will be useful,
|
|
||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
# GNU General Public License for more details.
|
|
||||||
#
|
|
||||||
# You should have received a copy of the GNU General Public License
|
|
||||||
# along with this program; if not, write to the Free Software
|
|
||||||
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
||||||
#
|
|
||||||
|
|
||||||
"""
|
""" View class for Node objects.
|
||||||
View class for Node objects.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from logging import getLogger
|
from logging import getLogger
|
||||||
import urllib
|
from urllib.parse import urlencode, urlparse, urlunparse
|
||||||
from urlparse import urlparse, urlunparse
|
#from urlparse import urlparse, urlunparse
|
||||||
from zope import component, interface, schema
|
|
||||||
from zope.cachedescriptors.property import Lazy
|
|
||||||
from zope.annotation.interfaces import IAnnotations
|
|
||||||
from zope.app.catalog.interfaces import ICatalog
|
|
||||||
from zope.app.container.browser.contents import JustContents
|
from zope.app.container.browser.contents import JustContents
|
||||||
from zope.app.container.browser.adding import Adding
|
from zope.app.container.browser.adding import Adding
|
||||||
from zope.app.container.traversal import ItemTraverser
|
from zope import component, interface, schema
|
||||||
from zope.app.pagetemplate import ViewPageTemplateFile
|
from zope.annotation.interfaces import IAnnotations
|
||||||
from zope.app.security.interfaces import IUnauthenticatedPrincipal
|
from zope.authentication.interfaces import IUnauthenticatedPrincipal
|
||||||
|
from zope.browserpage import ViewPageTemplateFile
|
||||||
|
from zope.cachedescriptors.property import Lazy
|
||||||
|
from zope.catalog.interfaces import ICatalog
|
||||||
|
from zope.container.traversal import ItemTraverser
|
||||||
from zope.dottedname.resolve import resolve
|
from zope.dottedname.resolve import resolve
|
||||||
from zope.event import notify
|
from zope.event import notify
|
||||||
from zope.lifecycleevent import ObjectCreatedEvent, ObjectModifiedEvent
|
from zope.lifecycleevent import ObjectCreatedEvent, ObjectModifiedEvent
|
||||||
|
@ -86,10 +69,14 @@ class NodeView(BaseView):
|
||||||
super(NodeView, self).__init__(context, request)
|
super(NodeView, self).__init__(context, request)
|
||||||
self.viewAnnotations.setdefault('nodeView', self)
|
self.viewAnnotations.setdefault('nodeView', self)
|
||||||
self.viewAnnotations.setdefault('node', self.context)
|
self.viewAnnotations.setdefault('node', self.context)
|
||||||
viewConfig = getViewConfiguration(context, request)
|
self.setSkin(self.viewConfig.get('skinName'))
|
||||||
self.setSkin(viewConfig.get('skinName'))
|
|
||||||
|
|
||||||
def __call__(self, *args, **kw):
|
def __call__(self, *args, **kw):
|
||||||
|
if self.nodeType == 'raw':
|
||||||
|
vn = self.context.viewName
|
||||||
|
if vn:
|
||||||
|
self.request.response.setHeader('content-type', vn)
|
||||||
|
return self.context.body
|
||||||
tv = self.viewAnnotations.get('targetView')
|
tv = self.viewAnnotations.get('targetView')
|
||||||
if tv is not None:
|
if tv is not None:
|
||||||
if tv.isToplevel:
|
if tv.isToplevel:
|
||||||
|
@ -98,6 +85,29 @@ class NodeView(BaseView):
|
||||||
self.controller.setMainPage()
|
self.controller.setMainPage()
|
||||||
return super(NodeView, self).__call__(*args, **kw)
|
return super(NodeView, self).__call__(*args, **kw)
|
||||||
|
|
||||||
|
@Lazy
|
||||||
|
def viewConfig(self):
|
||||||
|
return getViewConfiguration(self.context, self.request)
|
||||||
|
|
||||||
|
@Lazy
|
||||||
|
def viewConfigOptions(self):
|
||||||
|
result = {}
|
||||||
|
for opt in self.viewConfig.get('options') or []:
|
||||||
|
if ':' in opt:
|
||||||
|
k, v = opt.split(':', 1)
|
||||||
|
result[k] = v.split(',')
|
||||||
|
else:
|
||||||
|
result[opt] = True
|
||||||
|
return result
|
||||||
|
|
||||||
|
@Lazy
|
||||||
|
def copyright(self):
|
||||||
|
cr = self.viewConfigOptions.get('copyright')
|
||||||
|
if cr:
|
||||||
|
return cr[0]
|
||||||
|
cr = self.globalOptions('copyright')
|
||||||
|
return cr and cr[0] or 'cyberconcepts.org team'
|
||||||
|
|
||||||
@Lazy
|
@Lazy
|
||||||
def macro(self):
|
def macro(self):
|
||||||
return self.template.macros['content']
|
return self.template.macros['content']
|
||||||
|
@ -115,7 +125,9 @@ class NodeView(BaseView):
|
||||||
parts.extend(getParts(n))
|
parts.extend(getParts(n))
|
||||||
return parts
|
return parts
|
||||||
|
|
||||||
def update(self):
|
def update(self, topLevel=True):
|
||||||
|
if topLevel and self.view != self:
|
||||||
|
return self.view.update(False)
|
||||||
result = super(NodeView, self).update()
|
result = super(NodeView, self).update()
|
||||||
self.recordAccess()
|
self.recordAccess()
|
||||||
return result
|
return result
|
||||||
|
@ -129,7 +141,7 @@ class NodeView(BaseView):
|
||||||
return []
|
return []
|
||||||
menu = self.menu
|
menu = self.menu
|
||||||
data = [dict(label=menu.title, url=menu.url)]
|
data = [dict(label=menu.title, url=menu.url)]
|
||||||
menuItem = self.nearestMenuItem
|
menuItem = self.getNearestMenuItem(all=True)
|
||||||
if menuItem != menu.context:
|
if menuItem != menu.context:
|
||||||
data.append(dict(label=menuItem.title,
|
data.append(dict(label=menuItem.title,
|
||||||
url=absoluteURL(menuItem, self.request)))
|
url=absoluteURL(menuItem, self.request)))
|
||||||
|
@ -140,6 +152,9 @@ class NodeView(BaseView):
|
||||||
url=absoluteURL(p, self.request)))
|
url=absoluteURL(p, self.request)))
|
||||||
if self.virtualTarget:
|
if self.virtualTarget:
|
||||||
data.extend(self.virtualTarget.breadcrumbs())
|
data.extend(self.virtualTarget.breadcrumbs())
|
||||||
|
if data and not '?' in data[-1]['url']:
|
||||||
|
if self.urlParamString:
|
||||||
|
data[-1]['url'] += self.urlParamString
|
||||||
return data
|
return data
|
||||||
|
|
||||||
def viewModes(self):
|
def viewModes(self):
|
||||||
|
@ -366,6 +381,10 @@ class NodeView(BaseView):
|
||||||
def editable(self):
|
def editable(self):
|
||||||
return canWrite(self.context, 'body')
|
return canWrite(self.context, 'body')
|
||||||
|
|
||||||
|
def hasTopPage(self, name):
|
||||||
|
page = self.topMenu.context.get(name)
|
||||||
|
return page is not None
|
||||||
|
|
||||||
# menu stuff
|
# menu stuff
|
||||||
|
|
||||||
@Lazy
|
@Lazy
|
||||||
|
@ -411,8 +430,9 @@ class NodeView(BaseView):
|
||||||
|
|
||||||
@Lazy
|
@Lazy
|
||||||
def menuItems(self):
|
def menuItems(self):
|
||||||
return [NodeView(child, self.request)
|
items = [NodeView(child, self.request).view
|
||||||
for child in self.context.getMenuItems()]
|
for child in self.context.getMenuItems()]
|
||||||
|
return [item for item in items if item.isVisible]
|
||||||
|
|
||||||
@Lazy
|
@Lazy
|
||||||
def parents(self):
|
def parents(self):
|
||||||
|
@ -420,10 +440,13 @@ class NodeView(BaseView):
|
||||||
|
|
||||||
@Lazy
|
@Lazy
|
||||||
def nearestMenuItem(self):
|
def nearestMenuItem(self):
|
||||||
|
return self.getNearestMenuItem()
|
||||||
|
|
||||||
|
def getNearestMenuItem(self, all=False):
|
||||||
menu = self.menuObject
|
menu = self.menuObject
|
||||||
menuItem = None
|
menuItem = None
|
||||||
for p in [self.context] + self.parents:
|
for p in [self.context] + self.parents:
|
||||||
if not p.isMenuItem():
|
if not all and not p.isMenuItem():
|
||||||
menuItem = None
|
menuItem = None
|
||||||
elif menuItem is None:
|
elif menuItem is None:
|
||||||
menuItem = p
|
menuItem = p
|
||||||
|
@ -439,7 +462,7 @@ class NodeView(BaseView):
|
||||||
|
|
||||||
@Lazy
|
@Lazy
|
||||||
def logoutUrl(self):
|
def logoutUrl(self):
|
||||||
nextUrl = urllib.urlencode(dict(nextUrl=self.menu.url))
|
nextUrl = urlencode(dict(nextUrl=self.menu.url))
|
||||||
return '%s/logout.html?%s' % (self.menu.url, nextUrl)
|
return '%s/logout.html?%s' % (self.menu.url, nextUrl)
|
||||||
|
|
||||||
@Lazy
|
@Lazy
|
||||||
|
@ -469,7 +492,7 @@ class NodeView(BaseView):
|
||||||
def targetView(self, name='index.html', methodName='show'):
|
def targetView(self, name='index.html', methodName='show'):
|
||||||
if name == 'index.html': # only when called for default view
|
if name == 'index.html': # only when called for default view
|
||||||
tv = self.viewAnnotations.get('targetView')
|
tv = self.viewAnnotations.get('targetView')
|
||||||
if tv is not None:
|
if tv is not None and callable(tv):
|
||||||
return tv()
|
return tv()
|
||||||
if '?' in name:
|
if '?' in name:
|
||||||
name, params = name.split('?', 1)
|
name, params = name.split('?', 1)
|
||||||
|
@ -567,12 +590,21 @@ class NodeView(BaseView):
|
||||||
""" Return URL of given target view given as .XXX URL.
|
""" Return URL of given target view given as .XXX URL.
|
||||||
"""
|
"""
|
||||||
if isinstance(target, BaseView):
|
if isinstance(target, BaseView):
|
||||||
|
miu = self.getMenuItemUrlForTarget(target.context)
|
||||||
|
if miu is not None:
|
||||||
|
return miu
|
||||||
return self.makeTargetUrl(self.url, target.uniqueId, target.title)
|
return self.makeTargetUrl(self.url, target.uniqueId, target.title)
|
||||||
else:
|
else:
|
||||||
target = baseObject(target)
|
target = baseObject(target)
|
||||||
return self.makeTargetUrl(self.url, util.getUidForObject(target),
|
return self.makeTargetUrl(self.url, util.getUidForObject(target),
|
||||||
target.title)
|
target.title)
|
||||||
|
|
||||||
|
def getMenuItemUrlForTarget(self, tobj):
|
||||||
|
for node in tobj.getClients():
|
||||||
|
if node.nodeType == 'page' and node.getMenu() == self.menuObject:
|
||||||
|
return absoluteURL(node, self.request)
|
||||||
|
|
||||||
|
|
||||||
def getActions(self, category='object', page=None, target=None):
|
def getActions(self, category='object', page=None, target=None):
|
||||||
actions = []
|
actions = []
|
||||||
#self.registerDojo()
|
#self.registerDojo()
|
||||||
|
@ -737,11 +769,11 @@ class InlineEdit(NodeView):
|
||||||
if ti is not None:
|
if ti is not None:
|
||||||
target = ti(target)
|
target = ti(target)
|
||||||
data = self.request.form['editorContent']
|
data = self.request.form['editorContent']
|
||||||
if type(data) != unicode:
|
if not isinstance(data, str):
|
||||||
try:
|
try:
|
||||||
data = data.decode('ISO-8859-15') # IE hack
|
data = data.decode('ISO-8859-15') # IE hack
|
||||||
except UnicodeDecodeError:
|
except UnicodeDecodeError:
|
||||||
print 'loops.browser.node.InlineEdit.save():', data
|
print('loops.browser.node.InlineEdit.save():', data)
|
||||||
return
|
return
|
||||||
# data = data.decode('UTF-8')
|
# data = data.decode('UTF-8')
|
||||||
target.data = data
|
target.data = data
|
||||||
|
@ -913,9 +945,9 @@ class NodeAdding(Adding):
|
||||||
return info
|
return info
|
||||||
|
|
||||||
|
|
||||||
|
@interface.implementer(IViewConfiguratorSchema)
|
||||||
class ViewPropertiesConfigurator(object):
|
class ViewPropertiesConfigurator(object):
|
||||||
|
|
||||||
interface.implements(IViewConfiguratorSchema)
|
|
||||||
component.adapts(INode)
|
component.adapts(INode)
|
||||||
|
|
||||||
def __init__(self, context):
|
def __init__(self, context):
|
||||||
|
@ -976,7 +1008,8 @@ class NodeTraverser(ItemTraverser):
|
||||||
if context.nodeType == 'menu':
|
if context.nodeType == 'menu':
|
||||||
setViewConfiguration(context, request)
|
setViewConfiguration(context, request)
|
||||||
if name == '.loops':
|
if name == '.loops':
|
||||||
return self.context.getLoopsRoot()
|
name = self.getTargetUid(request)
|
||||||
|
#return self.context.getLoopsRoot()
|
||||||
if name.startswith('.'):
|
if name.startswith('.'):
|
||||||
name = self.cleanUpTraversalStack(request, name)[1:]
|
name = self.cleanUpTraversalStack(request, name)[1:]
|
||||||
target = self.getTarget(name)
|
target = self.getTarget(name)
|
||||||
|
@ -1002,23 +1035,40 @@ class NodeTraverser(ItemTraverser):
|
||||||
return self.context
|
return self.context
|
||||||
try:
|
try:
|
||||||
obj = super(NodeTraverser, self).publishTraverse(request, name)
|
obj = super(NodeTraverser, self).publishTraverse(request, name)
|
||||||
except NotFound, e:
|
except NotFound:
|
||||||
logger.warn('NodeTraverser: NotFound: URL = %s, name = %r' %
|
logger.warn('NodeTraverser: NotFound: URL = %s, name = %r' %
|
||||||
(request.URL, name))
|
(request.URL, name))
|
||||||
raise
|
raise
|
||||||
return obj
|
return obj
|
||||||
|
|
||||||
|
def getTargetUid(self, request):
|
||||||
|
parent = self.context.getLoopsRoot()
|
||||||
|
stack = request._traversal_stack
|
||||||
|
for i in range(2):
|
||||||
|
name = stack.pop()
|
||||||
|
obj = parent.get(name)
|
||||||
|
if not obj:
|
||||||
|
return name
|
||||||
|
parent = obj
|
||||||
|
return '.' + util.getUidForObject(obj)
|
||||||
|
|
||||||
def cleanUpTraversalStack(self, request, name):
|
def cleanUpTraversalStack(self, request, name):
|
||||||
traversalStack = request._traversal_stack
|
#traversalStack = request._traversal_stack
|
||||||
while traversalStack and traversalStack[0].startswith('.'):
|
#while traversalStack and traversalStack[0].startswith('.'):
|
||||||
# skip obsolete target references in the url
|
# skip obsolete target references in the url
|
||||||
name = traversalStack.pop(0)
|
# name = traversalStack.pop(0)
|
||||||
traversedNames = request._traversed_names
|
traversedNames = request._traversed_names
|
||||||
if traversedNames:
|
for n in list(traversedNames):
|
||||||
lastTraversed = traversedNames[-1]
|
if n.startswith('.'):
|
||||||
if lastTraversed.startswith('.') and lastTraversed != name:
|
# remove obsolete target refs
|
||||||
|
traversedNames.remove(n)
|
||||||
|
#if traversedNames:
|
||||||
|
# lastTraversed = traversedNames[-1]
|
||||||
|
# if lastTraversed.startswith('.') and lastTraversed != name:
|
||||||
# let <base .../> tag show the current object
|
# let <base .../> tag show the current object
|
||||||
traversedNames[-1] = name
|
# traversedNames[-1] = name
|
||||||
|
# let <base .../> tag show the current object
|
||||||
|
traversedNames.append(name)
|
||||||
return name
|
return name
|
||||||
|
|
||||||
def getTarget(self, name):
|
def getTarget(self, name):
|
||||||
|
@ -1036,7 +1086,9 @@ def setViewConfiguration(context, request):
|
||||||
config = IViewConfiguratorSchema(context)
|
config = IViewConfiguratorSchema(context)
|
||||||
skinName = config.skinName
|
skinName = config.skinName
|
||||||
if not skinName:
|
if not skinName:
|
||||||
skinName = context.getLoopsRoot().skinName
|
root = removeSecurityProxy(context.getLoopsRoot())
|
||||||
|
skinName = root.skinName
|
||||||
|
#skinName = context.getLoopsRoot().skinName
|
||||||
if skinName:
|
if skinName:
|
||||||
viewAnnotations['skinName'] = skinName
|
viewAnnotations['skinName'] = skinName
|
||||||
if config.options:
|
if config.options:
|
||||||
|
@ -1055,12 +1107,12 @@ def getViewConfiguration(context, request):
|
||||||
class TestView(NodeView):
|
class TestView(NodeView):
|
||||||
|
|
||||||
def __call__(self):
|
def __call__(self):
|
||||||
print '*** begin'
|
print( '*** begin')
|
||||||
for i in range(500):
|
for i in range(500):
|
||||||
#x = util.getObjectForUid('1994729849')
|
#x = util.getObjectForUid('1994729849')
|
||||||
x = util.getObjectForUid('2018653366')
|
x = util.getObjectForUid('2018653366')
|
||||||
self.c = list(x.getChildren())
|
self.c = list(x.getChildren())
|
||||||
#self.c = list(x.getChildren([self.defaultPredicate]))
|
#self.c = list(x.getChildren([self.defaultPredicate]))
|
||||||
print '*** end', len(self.c)
|
print('*** end', len(self.c))
|
||||||
return 'done'
|
return 'done'
|
||||||
|
|
Before Width: | Height: | Size: 942 B After Width: | Height: | Size: 942 B |
|
@ -41,17 +41,22 @@
|
||||||
|
|
||||||
|
|
||||||
<metal:body define-macro="conceptbody">
|
<metal:body define-macro="conceptbody">
|
||||||
<tal:body define="body item/body;">
|
<tal:body define="body item/body;
|
||||||
|
itemNum view/itemNum;
|
||||||
|
id string:$itemNum.body">
|
||||||
<div class="content-1" id="1"
|
<div class="content-1" id="1"
|
||||||
tal:attributes="class string:content-$level;
|
tal:attributes="class string:content-$level;
|
||||||
id string:${view/itemNum}.body;
|
id string:${view/itemNum}.body;
|
||||||
ondblclick python:item.openEditWindow('configure.html')">
|
ondblclick python:item.openEditWindow('configure.html')">
|
||||||
<span tal:content="structure body">Node Body</span>
|
<span tal:content="structure body">Node Body</span>
|
||||||
</div>
|
</div>
|
||||||
<tal:concepts define="item nocall:item/targetObjectView;
|
<div tal:define="item nocall:item/targetObjectView;
|
||||||
macro item/macro">
|
macro item/macro">
|
||||||
|
<div tal:attributes="class string:content-$level;
|
||||||
|
id id;">
|
||||||
<div metal:use-macro="macro" />
|
<div metal:use-macro="macro" />
|
||||||
</tal:concepts>
|
</div>
|
||||||
|
</div>
|
||||||
</tal:body>
|
</tal:body>
|
||||||
</metal:body>
|
</metal:body>
|
||||||
|
|
||||||
|
@ -328,11 +333,12 @@
|
||||||
<metal:login define-macro="login">
|
<metal:login define-macro="login">
|
||||||
<div>
|
<div>
|
||||||
<a href="login.html"
|
<a href="login.html"
|
||||||
|
tal:attributes="href string:${view/topMenu/url}/login.html"
|
||||||
i18n:translate="">Log in</a></div>
|
i18n:translate="">Log in</a></div>
|
||||||
<div tal:define="register python:view.globalOptions('provideLogin')"
|
<div tal:define="register python:view.globalOptions('provideLogin')"
|
||||||
tal:condition="register">
|
tal:condition="python:register and register != True">
|
||||||
<a tal:condition="python:register != True"
|
<a tal:define="reg python:register[0]"
|
||||||
tal:attributes="href python:register[0]"
|
tal:attributes="href string:${view/topMenu/url}/$reg"
|
||||||
i18n:translate="">Register new member</a></div>
|
i18n:translate="">Register new member</a></div>
|
||||||
</metal:login>
|
</metal:login>
|
||||||
|
|
|
@ -18,10 +18,6 @@
|
||||||
<a href="#"
|
<a href="#"
|
||||||
tal:attributes="href string:${target/url}/@@configure.html"
|
tal:attributes="href string:${target/url}/@@configure.html"
|
||||||
tal:content="target/title">Document xy</a>
|
tal:content="target/title">Document xy</a>
|
||||||
<tal:xedit define="xeditObjectUrl target/url"
|
|
||||||
condition="target/xeditable">
|
|
||||||
<metal:xedit use-macro="views/xedit_macros/editLink" />
|
|
||||||
</tal:xedit>
|
|
||||||
</tal:target>
|
</tal:target>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,35 +1,18 @@
|
||||||
#
|
# loops.browser.resource
|
||||||
# Copyright (c) 2014 Helmut Merz helmutm@cy55.de
|
|
||||||
#
|
|
||||||
# This program is free software; you can redistribute it and/or modify
|
|
||||||
# it under the terms of the GNU General Public License as published by
|
|
||||||
# the Free Software Foundation; either version 2 of the License, or
|
|
||||||
# (at your option) any later version.
|
|
||||||
#
|
|
||||||
# This program is distributed in the hope that it will be useful,
|
|
||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
# GNU General Public License for more details.
|
|
||||||
#
|
|
||||||
# You should have received a copy of the GNU General Public License
|
|
||||||
# along with this program; if not, write to the Free Software
|
|
||||||
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
||||||
#
|
|
||||||
|
|
||||||
"""
|
""" View class for resource objects.
|
||||||
View class for resource objects.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import urllib
|
import os.path
|
||||||
|
from zope.authentication.interfaces import IUnauthenticatedPrincipal
|
||||||
|
from zope.browserpage import ViewPageTemplateFile
|
||||||
from zope.cachedescriptors.property import Lazy
|
from zope.cachedescriptors.property import Lazy
|
||||||
from zope import component
|
from zope import component
|
||||||
from zope.app.catalog.interfaces import ICatalog
|
from zope.catalog.interfaces import ICatalog
|
||||||
from zope.app.container.interfaces import INameChooser
|
from zope.container.interfaces import INameChooser
|
||||||
from zope.app.form.browser.textwidgets import FileWidget
|
|
||||||
from zope.app.pagetemplate import ViewPageTemplateFile
|
|
||||||
from zope.app.security.interfaces import IUnauthenticatedPrincipal
|
|
||||||
from zope.formlib.form import FormFields
|
from zope.formlib.form import FormFields
|
||||||
from zope.formlib.interfaces import DISPLAY_UNWRITEABLE
|
from zope.formlib.interfaces import DISPLAY_UNWRITEABLE
|
||||||
|
from zope.formlib.textwidgets import FileWidget
|
||||||
from zope.proxy import removeAllProxies
|
from zope.proxy import removeAllProxies
|
||||||
from zope.schema.interfaces import IBytes
|
from zope.schema.interfaces import IBytes
|
||||||
from zope.security import canAccess, canWrite
|
from zope.security import canAccess, canWrite
|
||||||
|
@ -47,7 +30,7 @@ from loops.browser.common import EditForm, BaseView
|
||||||
from loops.browser.concept import BaseRelationView, ConceptRelationView
|
from loops.browser.concept import BaseRelationView, ConceptRelationView
|
||||||
from loops.browser.concept import ConceptConfigureView
|
from loops.browser.concept import ConceptConfigureView
|
||||||
from loops.browser.node import NodeView, node_macros
|
from loops.browser.node import NodeView, node_macros
|
||||||
from loops.common import adapted, NameChooser, normalizeName
|
from loops.common import adapted, baseObject, NameChooser, normalizeName
|
||||||
from loops.interfaces import IBaseResource, IDocument, ITextDocument
|
from loops.interfaces import IBaseResource, IDocument, ITextDocument
|
||||||
from loops.interfaces import IMediaAsset as legacy_IMediaAsset
|
from loops.interfaces import IMediaAsset as legacy_IMediaAsset
|
||||||
from loops.interfaces import ITypeConcept
|
from loops.interfaces import ITypeConcept
|
||||||
|
@ -196,6 +179,9 @@ class ResourceView(BaseView):
|
||||||
context = self.context
|
context = self.context
|
||||||
ct = context.contentType
|
ct = context.contentType
|
||||||
response = self.request.response
|
response = self.request.response
|
||||||
|
if self.typeOptions('x_robots_tag_header', None) is not None:
|
||||||
|
tagVal = ', '.join(self.typeOptions('x_robots_tag_header'))
|
||||||
|
response.setHeader('X-Robots-Tag', tagVal)
|
||||||
self.recordAccess('show', target=self.uniqueId)
|
self.recordAccess('show', target=self.uniqueId)
|
||||||
if ct.startswith('image/'):
|
if ct.startswith('image/'):
|
||||||
#response.setHeader('Cache-Control', 'public,max-age=86400')
|
#response.setHeader('Cache-Control', 'public,max-age=86400')
|
||||||
|
@ -216,6 +202,16 @@ class ResourceView(BaseView):
|
||||||
if filename is None:
|
if filename is None:
|
||||||
filename = (adapted(self.context).localFilename or
|
filename = (adapted(self.context).localFilename or
|
||||||
getName(self.context))
|
getName(self.context))
|
||||||
|
if self.typeOptions('use_title_for_download_filename'):
|
||||||
|
base, ext = os.path.splitext(filename)
|
||||||
|
filename = context.title
|
||||||
|
vr = IVersionable(baseObject(context))
|
||||||
|
if len(vr.versions) > 0:
|
||||||
|
filename = vr.generateName(filename, ext, vr.versionId)
|
||||||
|
else:
|
||||||
|
if not filename.endswith(ext):
|
||||||
|
filename += ext
|
||||||
|
filename = filename.encode('UTF-8')
|
||||||
if self.typeOptions('no_normalize_download_filename'):
|
if self.typeOptions('no_normalize_download_filename'):
|
||||||
filename = '"%s"' % filename
|
filename = '"%s"' % filename
|
||||||
else:
|
else:
|
||||||
|
@ -258,15 +254,21 @@ class ResourceView(BaseView):
|
||||||
#wp = wiki.createPage(getName(self.context))
|
#wp = wiki.createPage(getName(self.context))
|
||||||
wp = wiki.addPage(LoopsWikiPage(self.context))
|
wp = wiki.addPage(LoopsWikiPage(self.context))
|
||||||
wp.text = text
|
wp.text = text
|
||||||
#print wp.wiki.getManager()
|
#print(wp.wiki.getManager())
|
||||||
#return util.toUnicode(wp.render(self.request))
|
#return util.toUnicode(wp.render(self.request))
|
||||||
return super(ResourceView, self).renderText(text, contentType)
|
return super(ResourceView, self).renderText(text, contentType)
|
||||||
|
|
||||||
|
showMore = True
|
||||||
|
|
||||||
def renderShortText(self):
|
def renderShortText(self):
|
||||||
return self.renderDescription() or self.createShortText(self.render())
|
return self.renderDescription() or self.createShortText(self.render())
|
||||||
|
|
||||||
def createShortText(self, text=None):
|
def createShortText(self, text=None):
|
||||||
return extractFirstPart(text or self.render())
|
text = (text or self.render()).strip()
|
||||||
|
shortText = extractFirstPart(text)
|
||||||
|
if shortText == text:
|
||||||
|
self.showMore = False
|
||||||
|
return shortText
|
||||||
|
|
||||||
def download(self):
|
def download(self):
|
||||||
""" Force download, e.g. of a PDF file """
|
""" Force download, e.g. of a PDF file """
|
||||||
|
@ -447,7 +449,7 @@ class ExternalEditorView(ExternalEditorView, BaseView):
|
||||||
r.append('meta_type:' + '.'.join((context.__module__, context.__class__.__name__)))
|
r.append('meta_type:' + '.'.join((context.__module__, context.__class__.__name__)))
|
||||||
auth = self.request.get('_auth')
|
auth = self.request.get('_auth')
|
||||||
if auth:
|
if auth:
|
||||||
print 'ExternalEditorView: auth = ', auth
|
print('ExternalEditorView: auth = ', auth)
|
||||||
if auth.endswith('\n'):
|
if auth.endswith('\n'):
|
||||||
auth = auth[:-1]
|
auth = auth[:-1]
|
||||||
r.append('auth:' + auth)
|
r.append('auth:' + auth)
|
||||||
|
@ -471,4 +473,3 @@ class NoteView(DocumentView):
|
||||||
def linkUrl(self):
|
def linkUrl(self):
|
||||||
ad = self.typeAdapter
|
ad = self.typeAdapter
|
||||||
return ad and ad.linkUrl or ''
|
return ad and ad.linkUrl or ''
|
||||||
|
|
|
@ -10,7 +10,7 @@
|
||||||
<div metal:use-macro="views/node_macros/object_actions" />
|
<div metal:use-macro="views/node_macros/object_actions" />
|
||||||
</tal:actions>
|
</tal:actions>
|
||||||
<h1><a tal:omit-tag="python: level > 1"
|
<h1><a tal:omit-tag="python: level > 1"
|
||||||
tal:attributes="href request/URL"
|
tal:attributes="href view/requestUrl"
|
||||||
tal:content="item/title">Title</a></h1>
|
tal:content="item/title">Title</a></h1>
|
||||||
<tal:desc define="description description|item/renderedDescription"
|
<tal:desc define="description description|item/renderedDescription"
|
||||||
condition="description">
|
condition="description">
|
||||||
|
@ -51,7 +51,7 @@
|
||||||
<div tal:attributes="ondblclick python: item.openEditWindow('edit.html')">
|
<div tal:attributes="ondblclick python: item.openEditWindow('edit.html')">
|
||||||
<div metal:use-macro="views/node_macros/object_actions" />
|
<div metal:use-macro="views/node_macros/object_actions" />
|
||||||
<h1><a tal:omit-tag="python: level > 1"
|
<h1><a tal:omit-tag="python: level > 1"
|
||||||
tal:attributes="href request/URL"
|
tal:attributes="href view/requestUrl"
|
||||||
tal:content="item/title">Title</a></h1><br />
|
tal:content="item/title">Title</a></h1><br />
|
||||||
<img tal:attributes="src
|
<img tal:attributes="src
|
||||||
string:${view/url}/.${view/targetId}/view?version=this" />
|
string:${view/url}/.${view/targetId}/view?version=this" />
|
||||||
|
@ -72,6 +72,7 @@
|
||||||
<div>
|
<div>
|
||||||
<span class="button">
|
<span class="button">
|
||||||
<a i18n:translate=""
|
<a i18n:translate=""
|
||||||
|
target="_blank"
|
||||||
tal:attributes="href
|
tal:attributes="href
|
||||||
string:${view/virtualTargetUrl}/download.html?version=this">
|
string:${view/virtualTargetUrl}/download.html?version=this">
|
||||||
Download
|
Download
|
||||||
|
@ -96,6 +97,7 @@
|
||||||
</a>
|
</a>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
<metal:custom define-slot="custom_info" />
|
||||||
<metal:fields use-macro="view/comment_macros/comments" />
|
<metal:fields use-macro="view/comment_macros/comments" />
|
||||||
</div>
|
</div>
|
||||||
</metal:block>
|
</metal:block>
|
|
@ -1,6 +1,4 @@
|
||||||
"""
|
# package loops.browser.skin
|
||||||
$Id$
|
|
||||||
"""
|
|
||||||
|
|
||||||
from cybertools.browser.liquid import Liquid
|
from cybertools.browser.liquid import Liquid
|
||||||
from cybertools.browser.blue import Blue
|
from cybertools.browser.blue import Blue
|