##// END OF EJS Templates
licensing updates, code cleanups
marcink -
r252:3782a6d6 default
parent child Browse files
Show More
@@ -1,340 +1,340 b''
1 GNU GENERAL PUBLIC LICENSE
1 GNU GENERAL PUBLIC LICENSE
2 Version 2, June 1991
2 Version 2, June 1991
3
3
4 Copyright (C) 1989, 1991 Free Software Foundation, Inc.
4 Copyright (C) 1989, 1991 Free Software Foundation, Inc.
5 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
5 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
6 Everyone is permitted to copy and distribute verbatim copies
6 Everyone is permitted to copy and distribute verbatim copies
7 of this license document, but changing it is not allowed.
7 of this license document, but changing it is not allowed.
8
8
9 Preamble
9 Preamble
10
10
11 The licenses for most software are designed to take away your
11 The licenses for most software are designed to take away your
12 freedom to share and change it. By contrast, the GNU General Public
12 freedom to share and change it. By contrast, the GNU General Public
13 License is intended to guarantee your freedom to share and change free
13 License is intended to guarantee your freedom to share and change free
14 software--to make sure the software is free for all its users. This
14 software--to make sure the software is free for all its users. This
15 General Public License applies to most of the Free Software
15 General Public License applies to most of the Free Software
16 Foundation's software and to any other program whose authors commit to
16 Foundation's software and to any other program whose authors commit to
17 using it. (Some other Free Software Foundation software is covered by
17 using it. (Some other Free Software Foundation software is covered by
18 the GNU Library General Public License instead.) You can apply it to
18 the GNU Library General Public License instead.) You can apply it to
19 your programs, too.
19 your programs, too.
20
20
21 When we speak of free software, we are referring to freedom, not
21 When we speak of free software, we are referring to freedom, not
22 price. Our General Public Licenses are designed to make sure that you
22 price. Our General Public Licenses are designed to make sure that you
23 have the freedom to distribute copies of free software (and charge for
23 have the freedom to distribute copies of free software (and charge for
24 this service if you wish), that you receive source code or can get it
24 this service if you wish), that you receive source code or can get it
25 if you want it, that you can change the software or use pieces of it
25 if you want it, that you can change the software or use pieces of it
26 in new free programs; and that you know you can do these things.
26 in new free programs; and that you know you can do these things.
27
27
28 To protect your rights, we need to make restrictions that forbid
28 To protect your rights, we need to make restrictions that forbid
29 anyone to deny you these rights or to ask you to surrender the rights.
29 anyone to deny you these rights or to ask you to surrender the rights.
30 These restrictions translate to certain responsibilities for you if you
30 These restrictions translate to certain responsibilities for you if you
31 distribute copies of the software, or if you modify it.
31 distribute copies of the software, or if you modify it.
32
32
33 For example, if you distribute copies of such a program, whether
33 For example, if you distribute copies of such a program, whether
34 gratis or for a fee, you must give the recipients all the rights that
34 gratis or for a fee, you must give the recipients all the rights that
35 you have. You must make sure that they, too, receive or can get the
35 you have. You must make sure that they, too, receive or can get the
36 source code. And you must show them these terms so they know their
36 source code. And you must show them these terms so they know their
37 rights.
37 rights.
38
38
39 We protect your rights with two steps: (1) copyright the software, and
39 We protect your rights with two steps: (1) copyright the software, and
40 (2) offer you this license which gives you legal permission to copy,
40 (2) offer you this license which gives you legal permission to copy,
41 distribute and/or modify the software.
41 distribute and/or modify the software.
42
42
43 Also, for each author's protection and ours, we want to make certain
43 Also, for each author's protection and ours, we want to make certain
44 that everyone understands that there is no warranty for this free
44 that everyone understands that there is no warranty for this free
45 software. If the software is modified by someone else and passed on, we
45 software. If the software is modified by someone else and passed on, we
46 want its recipients to know that what they have is not the original, so
46 want its recipients to know that what they have is not the original, so
47 that any problems introduced by others will not reflect on the original
47 that any problems introduced by others will not reflect on the original
48 authors' reputations.
48 authors' reputations.
49
49
50 Finally, any free program is threatened constantly by software
50 Finally, any free program is threatened constantly by software
51 patents. We wish to avoid the danger that redistributors of a free
51 patents. We wish to avoid the danger that redistributors of a free
52 program will individually obtain patent licenses, in effect making the
52 program will individually obtain patent licenses, in effect making the
53 program proprietary. To prevent this, we have made it clear that any
53 program proprietary. To prevent this, we have made it clear that any
54 patent must be licensed for everyone's free use or not licensed at all.
54 patent must be licensed for everyone's free use or not licensed at all.
55
55
56 The precise terms and conditions for copying, distribution and
56 The precise terms and conditions for copying, distribution and
57 modification follow.
57 modification follow.
58
58
59 GNU GENERAL PUBLIC LICENSE
59 GNU GENERAL PUBLIC LICENSE
60 TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
60 TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
61
61
62 0. This License applies to any program or other work which contains
62 0. This License applies to any program or other work which contains
63 a notice placed by the copyright holder saying it may be distributed
63 a notice placed by the copyright holder saying it may be distributed
64 under the terms of this General Public License. The "Program", below,
64 under the terms of this General Public License. The "Program", below,
65 refers to any such program or work, and a "work based on the Program"
65 refers to any such program or work, and a "work based on the Program"
66 means either the Program or any derivative work under copyright law:
66 means either the Program or any derivative work under copyright law:
67 that is to say, a work containing the Program or a portion of it,
67 that is to say, a work containing the Program or a portion of it,
68 either verbatim or with modifications and/or translated into another
68 either verbatim or with modifications and/or translated into another
69 language. (Hereinafter, translation is included without limitation in
69 language. (Hereinafter, translation is included without limitation in
70 the term "modification".) Each licensee is addressed as "you".
70 the term "modification".) Each licensee is addressed as "you".
71
71
72 Activities other than copying, distribution and modification are not
72 Activities other than copying, distribution and modification are not
73 covered by this License; they are outside its scope. The act of
73 covered by this License; they are outside its scope. The act of
74 running the Program is not restricted, and the output from the Program
74 running the Program is not restricted, and the output from the Program
75 is covered only if its contents constitute a work based on the
75 is covered only if its contents constitute a work based on the
76 Program (independent of having been made by running the Program).
76 Program (independent of having been made by running the Program).
77 Whether that is true depends on what the Program does.
77 Whether that is true depends on what the Program does.
78
78
79 1. You may copy and distribute verbatim copies of the Program's
79 1. You may copy and distribute verbatim copies of the Program's
80 source code as you receive it, in any medium, provided that you
80 source code as you receive it, in any medium, provided that you
81 conspicuously and appropriately publish on each copy an appropriate
81 conspicuously and appropriately publish on each copy an appropriate
82 copyright notice and disclaimer of warranty; keep intact all the
82 copyright notice and disclaimer of warranty; keep intact all the
83 notices that refer to this License and to the absence of any warranty;
83 notices that refer to this License and to the absence of any warranty;
84 and give any other recipients of the Program a copy of this License
84 and give any other recipients of the Program a copy of this License
85 along with the Program.
85 along with the Program.
86
86
87 You may charge a fee for the physical act of transferring a copy, and
87 You may charge a fee for the physical act of transferring a copy, and
88 you may at your option offer warranty protection in exchange for a fee.
88 you may at your option offer warranty protection in exchange for a fee.
89
89
90 2. You may modify your copy or copies of the Program or any portion
90 2. You may modify your copy or copies of the Program or any portion
91 of it, thus forming a work based on the Program, and copy and
91 of it, thus forming a work based on the Program, and copy and
92 distribute such modifications or work under the terms of Section 1
92 distribute such modifications or work under the terms of Section 1
93 above, provided that you also meet all of these conditions:
93 above, provided that you also meet all of these conditions:
94
94
95 a) You must cause the modified files to carry prominent notices
95 a) You must cause the modified files to carry prominent notices
96 stating that you changed the files and the date of any change.
96 stating that you changed the files and the date of any change.
97
97
98 b) You must cause any work that you distribute or publish, that in
98 b) You must cause any work that you distribute or publish, that in
99 whole or in part contains or is derived from the Program or any
99 whole or in part contains or is derived from the Program or any
100 part thereof, to be licensed as a whole at no charge to all third
100 part thereof, to be licensed as a whole at no charge to all third
101 parties under the terms of this License.
101 parties under the terms of this License.
102
102
103 c) If the modified program normally reads commands interactively
103 c) If the modified program normally reads commands interactively
104 when run, you must cause it, when started running for such
104 when run, you must cause it, when started running for such
105 interactive use in the most ordinary way, to print or display an
105 interactive use in the most ordinary way, to print or display an
106 announcement including an appropriate copyright notice and a
106 announcement including an appropriate copyright notice and a
107 notice that there is no warranty (or else, saying that you provide
107 notice that there is no warranty (or else, saying that you provide
108 a warranty) and that users may redistribute the program under
108 a warranty) and that users may redistribute the program under
109 these conditions, and telling the user how to view a copy of this
109 these conditions, and telling the user how to view a copy of this
110 License. (Exception: if the Program itself is interactive but
110 License. (Exception: if the Program itself is interactive but
111 does not normally print such an announcement, your work based on
111 does not normally print such an announcement, your work based on
112 the Program is not required to print an announcement.)
112 the Program is not required to print an announcement.)
113
113
114 These requirements apply to the modified work as a whole. If
114 These requirements apply to the modified work as a whole. If
115 identifiable sections of that work are not derived from the Program,
115 identifiable sections of that work are not derived from the Program,
116 and can be reasonably considered independent and separate works in
116 and can be reasonably considered independent and separate works in
117 themselves, then this License, and its terms, do not apply to those
117 themselves, then this License, and its terms, do not apply to those
118 sections when you distribute them as separate works. But when you
118 sections when you distribute them as separate works. But when you
119 distribute the same sections as part of a whole which is a work based
119 distribute the same sections as part of a whole which is a work based
120 on the Program, the distribution of the whole must be on the terms of
120 on the Program, the distribution of the whole must be on the terms of
121 this License, whose permissions for other licensees extend to the
121 this License, whose permissions for other licensees extend to the
122 entire whole, and thus to each and every part regardless of who wrote it.
122 entire whole, and thus to each and every part regardless of who wrote it.
123
123
124 Thus, it is not the intent of this section to claim rights or contest
124 Thus, it is not the intent of this section to claim rights or contest
125 your rights to work written entirely by you; rather, the intent is to
125 your rights to work written entirely by you; rather, the intent is to
126 exercise the right to control the distribution of derivative or
126 exercise the right to control the distribution of derivative or
127 collective works based on the Program.
127 collective works based on the Program.
128
128
129 In addition, mere aggregation of another work not based on the Program
129 In addition, mere aggregation of another work not based on the Program
130 with the Program (or with a work based on the Program) on a volume of
130 with the Program (or with a work based on the Program) on a volume of
131 a storage or distribution medium does not bring the other work under
131 a storage or distribution medium does not bring the other work under
132 the scope of this License.
132 the scope of this License.
133
133
134 3. You may copy and distribute the Program (or a work based on it,
134 3. You may copy and distribute the Program (or a work based on it,
135 under Section 2) in object code or executable form under the terms of
135 under Section 2) in object code or executable form under the terms of
136 Sections 1 and 2 above provided that you also do one of the following:
136 Sections 1 and 2 above provided that you also do one of the following:
137
137
138 a) Accompany it with the complete corresponding machine-readable
138 a) Accompany it with the complete corresponding machine-readable
139 source code, which must be distributed under the terms of Sections
139 source code, which must be distributed under the terms of Sections
140 1 and 2 above on a medium customarily used for software interchange; or,
140 1 and 2 above on a medium customarily used for software interchange; or,
141
141
142 b) Accompany it with a written offer, valid for at least three
142 b) Accompany it with a written offer, valid for at least three
143 years, to give any third party, for a charge no more than your
143 years, to give any third party, for a charge no more than your
144 cost of physically performing source distribution, a complete
144 cost of physically performing source distribution, a complete
145 machine-readable copy of the corresponding source code, to be
145 machine-readable copy of the corresponding source code, to be
146 distributed under the terms of Sections 1 and 2 above on a medium
146 distributed under the terms of Sections 1 and 2 above on a medium
147 customarily used for software interchange; or,
147 customarily used for software interchange; or,
148
148
149 c) Accompany it with the information you received as to the offer
149 c) Accompany it with the information you received as to the offer
150 to distribute corresponding source code. (This alternative is
150 to distribute corresponding source code. (This alternative is
151 allowed only for noncommercial distribution and only if you
151 allowed only for noncommercial distribution and only if you
152 received the program in object code or executable form with such
152 received the program in object code or executable form with such
153 an offer, in accord with Subsection b above.)
153 an offer, in accord with Subsection b above.)
154
154
155 The source code for a work means the preferred form of the work for
155 The source code for a work means the preferred form of the work for
156 making modifications to it. For an executable work, complete source
156 making modifications to it. For an executable work, complete source
157 code means all the source code for all modules it contains, plus any
157 code means all the source code for all modules it contains, plus any
158 associated interface definition files, plus the scripts used to
158 associated interface definition files, plus the scripts used to
159 control compilation and installation of the executable. However, as a
159 control compilation and installation of the executable. However, as a
160 special exception, the source code distributed need not include
160 special exception, the source code distributed need not include
161 anything that is normally distributed (in either source or binary
161 anything that is normally distributed (in either source or binary
162 form) with the major components (compiler, kernel, and so on) of the
162 form) with the major components (compiler, kernel, and so on) of the
163 operating system on which the executable runs, unless that component
163 operating system on which the executable runs, unless that component
164 itself accompanies the executable.
164 itself accompanies the executable.
165
165
166 If distribution of executable or object code is made by offering
166 If distribution of executable or object code is made by offering
167 access to copy from a designated place, then offering equivalent
167 access to copy from a designated place, then offering equivalent
168 access to copy the source code from the same place counts as
168 access to copy the source code from the same place counts as
169 distribution of the source code, even though third parties are not
169 distribution of the source code, even though third parties are not
170 compelled to copy the source along with the object code.
170 compelled to copy the source along with the object code.
171
171
172 4. You may not copy, modify, sublicense, or distribute the Program
172 4. You may not copy, modify, sublicense, or distribute the Program
173 except as expressly provided under this License. Any attempt
173 except as expressly provided under this License. Any attempt
174 otherwise to copy, modify, sublicense or distribute the Program is
174 otherwise to copy, modify, sublicense or distribute the Program is
175 void, and will automatically terminate your rights under this License.
175 void, and will automatically terminate your rights under this License.
176 However, parties who have received copies, or rights, from you under
176 However, parties who have received copies, or rights, from you under
177 this License will not have their licenses terminated so long as such
177 this License will not have their licenses terminated so long as such
178 parties remain in full compliance.
178 parties remain in full compliance.
179
179
180 5. You are not required to accept this License, since you have not
180 5. You are not required to accept this License, since you have not
181 signed it. However, nothing else grants you permission to modify or
181 signed it. However, nothing else grants you permission to modify or
182 distribute the Program or its derivative works. These actions are
182 distribute the Program or its derivative works. These actions are
183 prohibited by law if you do not accept this License. Therefore, by
183 prohibited by law if you do not accept this License. Therefore, by
184 modifying or distributing the Program (or any work based on the
184 modifying or distributing the Program (or any work based on the
185 Program), you indicate your acceptance of this License to do so, and
185 Program), you indicate your acceptance of this License to do so, and
186 all its terms and conditions for copying, distributing or modifying
186 all its terms and conditions for copying, distributing or modifying
187 the Program or works based on it.
187 the Program or works based on it.
188
188
189 6. Each time you redistribute the Program (or any work based on the
189 6. Each time you redistribute the Program (or any work based on the
190 Program), the recipient automatically receives a license from the
190 Program), the recipient automatically receives a license from the
191 original licensor to copy, distribute or modify the Program subject to
191 original licensor to copy, distribute or modify the Program subject to
192 these terms and conditions. You may not impose any further
192 these terms and conditions. You may not impose any further
193 restrictions on the recipients' exercise of the rights granted herein.
193 restrictions on the recipients' exercise of the rights granted herein.
194 You are not responsible for enforcing compliance by third parties to
194 You are not responsible for enforcing compliance by third parties to
195 this License.
195 this License.
196
196
197 7. If, as a consequence of a court judgment or allegation of patent
197 7. If, as a consequence of a court judgment or allegation of patent
198 infringement or for any other reason (not limited to patent issues),
198 infringement or for any other reason (not limited to patent issues),
199 conditions are imposed on you (whether by court order, agreement or
199 conditions are imposed on you (whether by court order, agreement or
200 otherwise) that contradict the conditions of this License, they do not
200 otherwise) that contradict the conditions of this License, they do not
201 excuse you from the conditions of this License. If you cannot
201 excuse you from the conditions of this License. If you cannot
202 distribute so as to satisfy simultaneously your obligations under this
202 distribute so as to satisfy simultaneously your obligations under this
203 License and any other pertinent obligations, then as a consequence you
203 License and any other pertinent obligations, then as a consequence you
204 may not distribute the Program at all. For example, if a patent
204 may not distribute the Program at all. For example, if a patent
205 license would not permit royalty-free redistribution of the Program by
205 license would not permit royalty-free redistribution of the Program by
206 all those who receive copies directly or indirectly through you, then
206 all those who receive copies directly or indirectly through you, then
207 the only way you could satisfy both it and this License would be to
207 the only way you could satisfy both it and this License would be to
208 refrain entirely from distribution of the Program.
208 refrain entirely from distribution of the Program.
209
209
210 If any portion of this section is held invalid or unenforceable under
210 If any portion of this section is held invalid or unenforceable under
211 any particular circumstance, the balance of the section is intended to
211 any particular circumstance, the balance of the section is intended to
212 apply and the section as a whole is intended to apply in other
212 apply and the section as a whole is intended to apply in other
213 circumstances.
213 circumstances.
214
214
215 It is not the purpose of this section to induce you to infringe any
215 It is not the purpose of this section to induce you to infringe any
216 patents or other property right claims or to contest validity of any
216 patents or other property right claims or to contest validity of any
217 such claims; this section has the sole purpose of protecting the
217 such claims; this section has the sole purpose of protecting the
218 integrity of the free software distribution system, which is
218 integrity of the free software distribution system, which is
219 implemented by public license practices. Many people have made
219 implemented by public license practices. Many people have made
220 generous contributions to the wide range of software distributed
220 generous contributions to the wide range of software distributed
221 through that system in reliance on consistent application of that
221 through that system in reliance on consistent application of that
222 system; it is up to the author/donor to decide if he or she is willing
222 system; it is up to the author/donor to decide if he or she is willing
223 to distribute software through any other system and a licensee cannot
223 to distribute software through any other system and a licensee cannot
224 impose that choice.
224 impose that choice.
225
225
226 This section is intended to make thoroughly clear what is believed to
226 This section is intended to make thoroughly clear what is believed to
227 be a consequence of the rest of this License.
227 be a consequence of the rest of this License.
228
228
229 8. If the distribution and/or use of the Program is restricted in
229 8. If the distribution and/or use of the Program is restricted in
230 certain countries either by patents or by copyrighted interfaces, the
230 certain countries either by patents or by copyrighted interfaces, the
231 original copyright holder who places the Program under this License
231 original copyright holder who places the Program under this License
232 may add an explicit geographical distribution limitation excluding
232 may add an explicit geographical distribution limitation excluding
233 those countries, so that distribution is permitted only in or among
233 those countries, so that distribution is permitted only in or among
234 countries not thus excluded. In such case, this License incorporates
234 countries not thus excluded. In such case, this License incorporates
235 the limitation as if written in the body of this License.
235 the limitation as if written in the body of this License.
236
236
237 9. The Free Software Foundation may publish revised and/or new versions
237 9. The Free Software Foundation may publish revised and/or new versions
238 of the General Public License from time to time. Such new versions will
238 of the General Public License from time to time. Such new versions will
239 be similar in spirit to the present version, but may differ in detail to
239 be similar in spirit to the present version, but may differ in detail to
240 address new problems or concerns.
240 address new problems or concerns.
241
241
242 Each version is given a distinguishing version number. If the Program
242 Each version is given a distinguishing version number. If the Program
243 specifies a version number of this License which applies to it and "any
243 specifies a version number of this License which applies to it and "any
244 later version", you have the option of following the terms and conditions
244 later version", you have the option of following the terms and conditions
245 either of that version or of any later version published by the Free
245 either of that version or of any later version published by the Free
246 Software Foundation. If the Program does not specify a version number of
246 Software Foundation. If the Program does not specify a version number of
247 this License, you may choose any version ever published by the Free Software
247 this License, you may choose any version ever published by the Free Software
248 Foundation.
248 Foundation.
249
249
250 10. If you wish to incorporate parts of the Program into other free
250 10. If you wish to incorporate parts of the Program into other free
251 programs whose distribution conditions are different, write to the author
251 programs whose distribution conditions are different, write to the author
252 to ask for permission. For software which is copyrighted by the Free
252 to ask for permission. For software which is copyrighted by the Free
253 Software Foundation, write to the Free Software Foundation; we sometimes
253 Software Foundation, write to the Free Software Foundation; we sometimes
254 make exceptions for this. Our decision will be guided by the two goals
254 make exceptions for this. Our decision will be guided by the two goals
255 of preserving the free status of all derivatives of our free software and
255 of preserving the free status of all derivatives of our free software and
256 of promoting the sharing and reuse of software generally.
256 of promoting the sharing and reuse of software generally.
257
257
258 NO WARRANTY
258 NO WARRANTY
259
259
260 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
260 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
261 FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
261 FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
262 OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
262 OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
263 PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
263 PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
264 OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
264 OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
265 MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
265 MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
266 TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
266 TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
267 PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
267 PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
268 REPAIR OR CORRECTION.
268 REPAIR OR CORRECTION.
269
269
270 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
270 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
271 WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
271 WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
272 REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
272 REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
273 INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
273 INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
274 OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
274 OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
275 TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
275 TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
276 YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
276 YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
277 PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
277 PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
278 POSSIBILITY OF SUCH DAMAGES.
278 POSSIBILITY OF SUCH DAMAGES.
279
279
280 END OF TERMS AND CONDITIONS
280 END OF TERMS AND CONDITIONS
281
281
282 How to Apply These Terms to Your New Programs
282 How to Apply These Terms to Your New Programs
283
283
284 If you develop a new program, and you want it to be of the greatest
284 If you develop a new program, and you want it to be of the greatest
285 possible use to the public, the best way to achieve this is to make it
285 possible use to the public, the best way to achieve this is to make it
286 free software which everyone can redistribute and change under these terms.
286 free software which everyone can redistribute and change under these terms.
287
287
288 To do so, attach the following notices to the program. It is safest
288 To do so, attach the following notices to the program. It is safest
289 to attach them to the start of each source file to most effectively
289 to attach them to the start of each source file to most effectively
290 convey the exclusion of warranty; and each file should have at least
290 convey the exclusion of warranty; and each file should have at least
291 the "copyright" line and a pointer to where the full notice is found.
291 the "copyright" line and a pointer to where the full notice is found.
292
292
293 <one line to give the program's name and a brief idea of what it does.>
293 <one line to give the program's name and a brief idea of what it does.>
294 Copyright (C) <year> <name of author>
294 Copyright (C) <year> <name of author>
295
295
296 This program is free software; you can redistribute it and/or modify
296 This program is free software; you can redistribute it and/or modify
297 it under the terms of the GNU General Public License as published by
297 it under the terms of the GNU General Public License as published by
298 the Free Software Foundation; either version 2 of the License, or
298 the Free Software Foundation; either version 2 of the License, or
299 (at your option) any later version.
299 (at your option) any later version.
300
300
301 This program is distributed in the hope that it will be useful,
301 This program is distributed in the hope that it will be useful,
302 but WITHOUT ANY WARRANTY; without even the implied warranty of
302 but WITHOUT ANY WARRANTY; without even the implied warranty of
303 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
303 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
304 GNU General Public License for more details.
304 GNU General Public License for more details.
305
305
306 You should have received a copy of the GNU General Public License
306 You should have received a copy of the GNU General Public License
307 along with this program; if not, write to the Free Software
307 along with this program; if not, write to the Free Software
308 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
308 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
309
309
310
310
311 Also add information on how to contact you by electronic and paper mail.
311 Also add information on how to contact you by electronic and paper mail.
312
312
313 If the program is interactive, make it output a short notice like this
313 If the program is interactive, make it output a short notice like this
314 when it starts in an interactive mode:
314 when it starts in an interactive mode:
315
315
316 Gnomovision version 69, Copyright (C) year name of author
316 Gnomovision version 69, Copyright (C) year name of author
317 Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
317 Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
318 This is free software, and you are welcome to redistribute it
318 This is free software, and you are welcome to redistribute it
319 under certain conditions; type `show c' for details.
319 under certain conditions; type `show c' for details.
320
320
321 The hypothetical commands `show w' and `show c' should show the appropriate
321 The hypothetical commands `show w' and `show c' should show the appropriate
322 parts of the General Public License. Of course, the commands you use may
322 parts of the General Public License. Of course, the commands you use may
323 be called something other than `show w' and `show c'; they could even be
323 be called something other than `show w' and `show c'; they could even be
324 mouse-clicks or menu items--whatever suits your program.
324 mouse-clicks or menu items--whatever suits your program.
325
325
326 You should also get your employer (if you work as a programmer) or your
326 You should also get your employer (if you work as a programmer) or your
327 school, if any, to sign a "copyright disclaimer" for the program, if
327 school, if any, to sign a "copyright disclaimer" for the program, if
328 necessary. Here is a sample; alter the names:
328 necessary. Here is a sample; alter the names:
329
329
330 Yoyodyne, Inc., hereby disclaims all copyright interest in the program
330 Yoyodyne, Inc., hereby disclaims all copyright interest in the program
331 `Gnomovision' (which makes passes at compilers) written by James Hacker.
331 `Gnomovision' (which makes passes at compilers) written by James Hacker.
332
332
333 <signature of Ty Coon>, 1 April 1989
333 <signature of Ty Coon>, 1 April 1989
334 Ty Coon, President of Vice
334 Ty Coon, President of Vice
335
335
336 This General Public License does not permit incorporating your program into
336 This General Public License does not permit incorporating your program into
337 proprietary programs. If your program is a subroutine library, you may
337 proprietary programs. If your program is a subroutine library, you may
338 consider it more useful to permit linking proprietary applications with the
338 consider it more useful to permit linking proprietary applications with the
339 library. If this is what you want to do, use the GNU Library General
339 library. If this is what you want to do, use the GNU Library General
340 Public License instead of this License.
340 Public License instead of this License.
@@ -1,13 +1,35 b''
1 #!/usr/bin/env python
2 # encoding: utf-8
3 # Hg app, a web based mercurial repository managment based on pylons
4 # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
5
6 # This program is free software; you can redistribute it and/or
7 # modify it under the terms of the GNU General Public License
8 # as published by the Free Software Foundation; version 2
9 # of the License or (at your opinion) any later version of the license.
10 #
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
15 #
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19 # MA 02110-1301, USA.
20
1 """
21 """
22 Created on April 9, 2010
2 Hg app, a web based mercurial repository managment based on pylons
23 Hg app, a web based mercurial repository managment based on pylons
24 @author: marcink
3 """
25 """
4
26
5 VERSION = (0, 7, 6, 'beta')
27 VERSION = (0, 7, 6, 'beta')
6
28
7 __version__ = '.'.join((str(each) for each in VERSION[:4]))
29 __version__ = '.'.join((str(each) for each in VERSION[:4]))
8
30
9 def get_version():
31 def get_version():
10 """
32 """
11 Returns shorter version (digit parts only) as string.
33 Returns shorter version (digit parts only) as string.
12 """
34 """
13 return '.'.join((str(each) for each in VERSION[:3]))
35 return '.'.join((str(each) for each in VERSION[:3]))
@@ -1,70 +1,68 b''
1 """Pylons environment configuration"""
1 """Pylons environment configuration"""
2 from mako.lookup import TemplateLookup
2 from mako.lookup import TemplateLookup
3 from pylons.configuration import PylonsConfig
3 from pylons.configuration import PylonsConfig
4 from pylons.error import handle_mako_error
4 from pylons.error import handle_mako_error
5 from pylons_app.config.routing import make_map
5 from pylons_app.config.routing import make_map
6 from pylons_app.lib.auth import set_available_permissions
6 from pylons_app.lib.auth import set_available_permissions
7 from pylons_app.lib.utils import repo2db_mapper
7 from pylons_app.model import init_model
8 from pylons_app.model import init_model
8 from sqlalchemy import engine_from_config
9 from sqlalchemy import engine_from_config
9 import logging
10 import logging
10 import os
11 import os
11 import pylons_app.lib.app_globals as app_globals
12 import pylons_app.lib.app_globals as app_globals
12 import pylons_app.lib.helpers
13 import pylons_app.lib.helpers
13
14
14
15
16 log = logging.getLogger(__name__)
15 log = logging.getLogger(__name__)
17
16
18 def load_environment(global_conf, app_conf):
17 def load_environment(global_conf, app_conf):
19 """Configure the Pylons environment via the ``pylons.config``
18 """Configure the Pylons environment via the ``pylons.config``
20 object
19 object
21 """
20 """
22 config = PylonsConfig()
21 config = PylonsConfig()
23
22
24 # Pylons paths
23 # Pylons paths
25 root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
24 root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
26 paths = dict(root=root,
25 paths = dict(root=root,
27 controllers=os.path.join(root, 'controllers'),
26 controllers=os.path.join(root, 'controllers'),
28 static_files=os.path.join(root, 'public'),
27 static_files=os.path.join(root, 'public'),
29 templates=[os.path.join(root, 'templates')])
28 templates=[os.path.join(root, 'templates')])
30
29
31 # Initialize config with the basic options
30 # Initialize config with the basic options
32 config.init_app(global_conf, app_conf, package='pylons_app', paths=paths)
31 config.init_app(global_conf, app_conf, package='pylons_app', paths=paths)
33
32
34 config['routes.map'] = make_map(config)
33 config['routes.map'] = make_map(config)
35 config['pylons.app_globals'] = app_globals.Globals(config)
34 config['pylons.app_globals'] = app_globals.Globals(config)
36 config['pylons.h'] = pylons_app.lib.helpers
35 config['pylons.h'] = pylons_app.lib.helpers
37
36
38 # Setup cache object as early as possible
37 # Setup cache object as early as possible
39 import pylons
38 import pylons
40 pylons.cache._push_object(config['pylons.app_globals'].cache)
39 pylons.cache._push_object(config['pylons.app_globals'].cache)
41
40
42
43 # Create the Mako TemplateLookup, with the default auto-escaping
41 # Create the Mako TemplateLookup, with the default auto-escaping
44 config['pylons.app_globals'].mako_lookup = TemplateLookup(
42 config['pylons.app_globals'].mako_lookup = TemplateLookup(
45 directories=paths['templates'],
43 directories=paths['templates'],
46 error_handler=handle_mako_error,
44 error_handler=handle_mako_error,
47 module_directory=os.path.join(app_conf['cache_dir'], 'templates'),
45 module_directory=os.path.join(app_conf['cache_dir'], 'templates'),
48 input_encoding='utf-8', default_filters=['escape'],
46 input_encoding='utf-8', default_filters=['escape'],
49 imports=['from webhelpers.html import escape'])
47 imports=['from webhelpers.html import escape'])
50
48
51 #sets the c attribute access when don't existing attribute ar accessed
49 #sets the c attribute access when don't existing attribute are accessed
52 config['pylons.strict_tmpl_context'] = True
50 config['pylons.strict_tmpl_context'] = True
53
51
54 #MULTIPLE DB configs
52 #MULTIPLE DB configs
55 # Setup the SQLAlchemy database engine
53 # Setup the SQLAlchemy database engine
56 if config['debug']:
54 if config['debug']:
57 #use query time debugging.
55 #use query time debugging.
58 from pylons_app.lib.timerproxy import TimerProxy
56 from pylons_app.lib.timerproxy import TimerProxy
59 sa_engine_db1 = engine_from_config(config, 'sqlalchemy.db1.',
57 sa_engine_db1 = engine_from_config(config, 'sqlalchemy.db1.',
60 proxy=TimerProxy())
58 proxy=TimerProxy())
61 else:
59 else:
62 sa_engine_db1 = engine_from_config(config, 'sqlalchemy.db1.')
60 sa_engine_db1 = engine_from_config(config, 'sqlalchemy.db1.')
63
61
64 init_model(sa_engine_db1)
62 init_model(sa_engine_db1)
65
63 repo2db_mapper()
66 set_available_permissions(config)
64 set_available_permissions(config)
67 # CONFIGURATION OPTIONS HERE (note: all config options will override
65 # CONFIGURATION OPTIONS HERE (note: all config options will override
68 # any Pylons config options)
66 # any Pylons config options)
69
67
70 return config
68 return config
@@ -1,31 +1,54 b''
1 #!/usr/bin/env python
2 # encoding: utf-8
3 # admin controller for pylons
4 # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
5
6 # This program is free software; you can redistribute it and/or
7 # modify it under the terms of the GNU General Public License
8 # as published by the Free Software Foundation; version 2
9 # of the License or (at your opinion) any later version of the license.
10 #
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
15 #
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19 # MA 02110-1301, USA.
20 """
21 Created on April 7, 2010
22 admin controller for pylons
23 @author: marcink
24 """
1 import logging
25 import logging
2 from pylons import request, response, session, tmpl_context as c, url, app_globals as g
26 from pylons import request, response, session, tmpl_context as c
3 from pylons.controllers.util import abort, redirect
4 from pylons_app.lib.base import BaseController, render
27 from pylons_app.lib.base import BaseController, render
5 from pylons_app.model import meta
28 from pylons_app.model import meta
6 from pylons_app.model.db import UserLog
29 from pylons_app.model.db import UserLog
7 from webhelpers.paginate import Page
30 from webhelpers.paginate import Page
8 from pylons_app.lib.auth import LoginRequired
31 from pylons_app.lib.auth import LoginRequired
9
32
10 log = logging.getLogger(__name__)
33 log = logging.getLogger(__name__)
11
34
12 class AdminController(BaseController):
35 class AdminController(BaseController):
13
36
14 @LoginRequired()
37 @LoginRequired()
15 def __before__(self):
38 def __before__(self):
16 user = session['hg_app_user']
39 user = session['hg_app_user']
17 c.admin_user = user.is_admin
40 c.admin_user = user.is_admin
18 c.admin_username = user.username
41 c.admin_username = user.username
19 super(AdminController, self).__before__()
42 super(AdminController, self).__before__()
20
43
21 def index(self):
44 def index(self):
22 sa = meta.Session
45 sa = meta.Session
23
46
24 users_log = sa.query(UserLog).order_by(UserLog.action_date.desc())
47 users_log = sa.query(UserLog).order_by(UserLog.action_date.desc())
25 p = int(request.params.get('page', 1))
48 p = int(request.params.get('page', 1))
26 c.users_log = Page(users_log, page=p, items_per_page=10)
49 c.users_log = Page(users_log, page=p, items_per_page=10)
27 c.log_data = render('admin/admin_log.html')
50 c.log_data = render('admin/admin_log.html')
28 if request.params.get('partial'):
51 if request.params.get('partial'):
29 return c.log_data
52 return c.log_data
30 return render('admin/admin.html')
53 return render('admin/admin.html')
31
54
@@ -1,20 +1,44 b''
1 #!/usr/bin/env python
2 # encoding: utf-8
3 # branches controller for pylons
4 # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
5
6 # This program is free software; you can redistribute it and/or
7 # modify it under the terms of the GNU General Public License
8 # as published by the Free Software Foundation; version 2
9 # of the License or (at your opinion) any later version of the license.
10 #
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
15 #
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19 # MA 02110-1301, USA.
20 """
21 Created on April 21, 2010
22 branches controller for pylons
23 @author: marcink
24 """
1 from pylons import tmpl_context as c, app_globals as g
25 from pylons import tmpl_context as c, app_globals as g
2 from pylons_app.lib.auth import LoginRequired
26 from pylons_app.lib.auth import LoginRequired
3 from pylons_app.lib.base import BaseController, render
27 from pylons_app.lib.base import BaseController, render
4 from pylons_app.model.hg_model import HgModel
28 from pylons_app.model.hg_model import HgModel
5 import logging
29 import logging
6
30
7 log = logging.getLogger(__name__)
31 log = logging.getLogger(__name__)
8
32
9 class BranchesController(BaseController):
33 class BranchesController(BaseController):
10
34
11 @LoginRequired()
35 @LoginRequired()
12 def __before__(self):
36 def __before__(self):
13 super(BranchesController, self).__before__()
37 super(BranchesController, self).__before__()
14
38
15 def index(self):
39 def index(self):
16 hg_model = HgModel()
40 hg_model = HgModel()
17 c.repo_info = hg_model.get_repo(c.repo_name)
41 c.repo_info = hg_model.get_repo(c.repo_name)
18 c.repo_branches = c.repo_info.branches
42 c.repo_branches = c.repo_info.branches
19
43
20 return render('branches/branches.html')
44 return render('branches/branches.html')
@@ -1,74 +1,95 b''
1 from mercurial.graphmod import revisions as graph_rev, colored, CHANGESET
1 #!/usr/bin/env python
2 from mercurial.node import short
2 # encoding: utf-8
3 # changelog controller for pylons
4 # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
5
6 # This program is free software; you can redistribute it and/or
7 # modify it under the terms of the GNU General Public License
8 # as published by the Free Software Foundation; version 2
9 # of the License or (at your opinion) any later version of the license.
10 #
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
15 #
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19 # MA 02110-1301, USA.
20 """
21 Created on April 21, 2010
22 changelog controller for pylons
23 @author: marcink
24 """
3 from pylons import request, session, tmpl_context as c
25 from pylons import request, session, tmpl_context as c
4 from pylons_app.lib.auth import LoginRequired
26 from pylons_app.lib.auth import LoginRequired
5 from pylons_app.lib.base import BaseController, render
27 from pylons_app.lib.base import BaseController, render
6 from pylons_app.lib.filters import age as _age, person
7 from pylons_app.model.hg_model import _full_changelog_cached
28 from pylons_app.model.hg_model import _full_changelog_cached
8 from simplejson import dumps
9 from webhelpers.paginate import Page
29 from webhelpers.paginate import Page
10 import logging
30 import logging
11 log = logging.getLogger(__name__)
31 log = logging.getLogger(__name__)
12
32
13 class ChangelogController(BaseController):
33 class ChangelogController(BaseController):
14
34
15 @LoginRequired()
35 @LoginRequired()
16 def __before__(self):
36 def __before__(self):
17 super(ChangelogController, self).__before__()
37 super(ChangelogController, self).__before__()
18
38
19 def index(self):
39 def index(self):
20 limit = 100
40 limit = 100
21 default = 20
41 default = 20
22 if request.params.get('size'):
42 if request.params.get('size'):
23 try:
43 try:
24 int_size = int(request.params.get('size'))
44 int_size = int(request.params.get('size'))
25 except ValueError:
45 except ValueError:
26 int_size = default
46 int_size = default
27 int_size = int_size if int_size <= limit else limit
47 int_size = int_size if int_size <= limit else limit
28 c.size = int_size
48 c.size = int_size
29 session['changelog_size'] = c.size
49 session['changelog_size'] = c.size
30 session.save()
50 session.save()
31 else:
51 else:
32 c.size = session.get('changelog_size', default)
52 c.size = session.get('changelog_size', default)
33
53
34 changesets = _full_changelog_cached(c.repo_name)
54 changesets = _full_changelog_cached(c.repo_name)
35
55
36 p = int(request.params.get('page', 1))
56 p = int(request.params.get('page', 1))
37 c.pagination = Page(changesets, page=p, item_count=len(changesets),
57 c.pagination = Page(changesets, page=p, item_count=len(changesets),
38 items_per_page=c.size)
58 items_per_page=c.size)
39
59
40 #self._graph(c.repo, c.size,p)
60 #self._graph(c.repo, c.size,p)
41
61
42 return render('changelog/changelog.html')
62 return render('changelog/changelog.html')
43
63
44
64
45 def _graph(self, repo, size, p):
65 def _graph(self, repo, size, p):
46 revcount = size
66 pass
47 if not repo.revisions:return dumps([]), 0
67 # revcount = size
48
68 # if not repo.revisions:return dumps([]), 0
49 max_rev = repo.revisions[-1]
69 #
50 offset = 1 if p == 1 else ((p - 1) * revcount)
70 # max_rev = repo.revisions[-1]
51 rev_start = repo.revisions[(-1 * offset)]
71 # offset = 1 if p == 1 else ((p - 1) * revcount)
52 c.bg_height = 120
72 # rev_start = repo.revisions[(-1 * offset)]
53
73 # c.bg_height = 120
54 revcount = min(max_rev, revcount)
74 #
55 rev_end = max(0, rev_start - revcount)
75 # revcount = min(max_rev, revcount)
56 dag = graph_rev(repo.repo, rev_start, rev_end)
76 # rev_end = max(0, rev_start - revcount)
77 # dag = graph_rev(repo.repo, rev_start, rev_end)
78 #
79 # c.dag = tree = list(colored(dag))
80 # canvasheight = (len(tree) + 1) * c.bg_height - 27
81 # data = []
82 # for (id, type, ctx, vtx, edges) in tree:
83 # if type != CHANGESET:
84 # continue
85 # node = short(ctx.node())
86 # age = _age(ctx.date())
87 # desc = ctx.description()
88 # user = person(ctx.user())
89 # branch = ctx.branch()
90 # branch = branch, repo.repo.branchtags().get(branch) == ctx.node()
91 # data.append((node, vtx, edges, desc, user, age, branch, ctx.tags()))
92 #
93 # c.jsdata = dumps(data)
94 # c.canvasheight = canvasheight
57
95
58 c.dag = tree = list(colored(dag))
59 canvasheight = (len(tree) + 1) * c.bg_height - 27
60 data = []
61 for (id, type, ctx, vtx, edges) in tree:
62 if type != CHANGESET:
63 continue
64 node = short(ctx.node())
65 age = _age(ctx.date())
66 desc = ctx.description()
67 user = person(ctx.user())
68 branch = ctx.branch()
69 branch = branch, repo.repo.branchtags().get(branch) == ctx.node()
70 data.append((node, vtx, edges, desc, user, age, branch, ctx.tags()))
71
72 c.jsdata = dumps(data)
73 c.canvasheight = canvasheight
74
@@ -1,40 +1,64 b''
1 #!/usr/bin/env python
2 # encoding: utf-8
3 # changeset controller for pylons
4 # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
5
6 # This program is free software; you can redistribute it and/or
7 # modify it under the terms of the GNU General Public License
8 # as published by the Free Software Foundation; version 2
9 # of the License or (at your opinion) any later version of the license.
10 #
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
15 #
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19 # MA 02110-1301, USA.
20 """
21 Created on April 25, 2010
22 changeset controller for pylons
23 @author: marcink
24 """
1 from pylons import tmpl_context as c
25 from pylons import tmpl_context as c
2 from pylons_app.lib.auth import LoginRequired
26 from pylons_app.lib.auth import LoginRequired
3 from pylons_app.lib.base import BaseController, render
27 from pylons_app.lib.base import BaseController, render
4 from pylons_app.model.hg_model import HgModel
28 from pylons_app.model.hg_model import HgModel
5 from vcs.utils import diffs as differ
29 from vcs.utils import diffs as differ
6 import logging
30 import logging
7 from vcs.nodes import FileNode
31 from vcs.nodes import FileNode
8
32
9
33
10 log = logging.getLogger(__name__)
34 log = logging.getLogger(__name__)
11
35
12 class ChangesetController(BaseController):
36 class ChangesetController(BaseController):
13
37
14 @LoginRequired()
38 @LoginRequired()
15 def __before__(self):
39 def __before__(self):
16 super(ChangesetController, self).__before__()
40 super(ChangesetController, self).__before__()
17
41
18 def index(self, revision):
42 def index(self, revision):
19 hg_model = HgModel()
43 hg_model = HgModel()
20 c.changeset = hg_model.get_repo(c.repo_name).get_changeset(revision)
44 c.changeset = hg_model.get_repo(c.repo_name).get_changeset(revision)
21 c.changeset_old = c.changeset.parents[0]
45 c.changeset_old = c.changeset.parents[0]
22 c.changes = []
46 c.changes = []
23
47
24
48
25 for node in c.changeset.added:
49 for node in c.changeset.added:
26 filenode_old = FileNode(node.path, '')
50 filenode_old = FileNode(node.path, '')
27 f_udiff = differ.get_udiff(filenode_old, node)
51 f_udiff = differ.get_udiff(filenode_old, node)
28 diff = differ.DiffProcessor(f_udiff).as_html()
52 diff = differ.DiffProcessor(f_udiff).as_html()
29 c.changes.append(('added', node, diff))
53 c.changes.append(('added', node, diff))
30
54
31 for node in c.changeset.changed:
55 for node in c.changeset.changed:
32 filenode_old = c.changeset_old.get_node(node.path)
56 filenode_old = c.changeset_old.get_node(node.path)
33 f_udiff = differ.get_udiff(filenode_old, node)
57 f_udiff = differ.get_udiff(filenode_old, node)
34 diff = differ.DiffProcessor(f_udiff).as_html()
58 diff = differ.DiffProcessor(f_udiff).as_html()
35 c.changes.append(('changed', node, diff))
59 c.changes.append(('changed', node, diff))
36
60
37 for node in c.changeset.removed:
61 for node in c.changeset.removed:
38 c.changes.append(('removed', node, None))
62 c.changes.append(('removed', node, None))
39
63
40 return render('changeset/changeset.html')
64 return render('changeset/changeset.html')
@@ -1,59 +1,81 b''
1 #!/usr/bin/python
1 #!/usr/bin/env python
2 # -*- coding: utf-8 -*-
2 # encoding: utf-8
3 # feed controller for pylons
4 # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
5
6 # This program is free software; you can redistribute it and/or
7 # modify it under the terms of the GNU General Public License
8 # as published by the Free Software Foundation; version 2
9 # of the License or (at your opinion) any later version of the license.
10 #
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
15 #
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19 # MA 02110-1301, USA.
20 """
21 Created on April 23, 2010
22 feed controller for pylons
23 @author: marcink
24 """
3 from pylons import tmpl_context as c, url, response
25 from pylons import tmpl_context as c, url, response
4 from pylons_app.lib.base import BaseController, render
26 from pylons_app.lib.base import BaseController, render
5 from pylons_app.model.hg_model import _full_changelog_cached
27 from pylons_app.model.hg_model import _full_changelog_cached
6 from webhelpers.feedgenerator import Atom1Feed, Rss201rev2Feed
28 from webhelpers.feedgenerator import Atom1Feed, Rss201rev2Feed
7 import logging
29 import logging
8 log = logging.getLogger(__name__)
30 log = logging.getLogger(__name__)
9
31
10 class FeedController(BaseController):
32 class FeedController(BaseController):
11
33
12 #secure it or not ?
34 #secure it or not ?
13 def __before__(self):
35 def __before__(self):
14 super(FeedController, self).__before__()
36 super(FeedController, self).__before__()
15 #common values for feeds
37 #common values for feeds
16 self.description = 'Changes on %s repository'
38 self.description = 'Changes on %s repository'
17 self.title = "%s feed"
39 self.title = "%s feed"
18 self.language = 'en-us'
40 self.language = 'en-us'
19 self.ttl = "5"
41 self.ttl = "5"
20 self.feed_nr = 10
42 self.feed_nr = 10
21
43
22 def atom(self, repo_name):
44 def atom(self, repo_name):
23 """Produce an atom-1.0 feed via feedgenerator module"""
45 """Produce an atom-1.0 feed via feedgenerator module"""
24 feed = Atom1Feed(title=self.title % repo_name,
46 feed = Atom1Feed(title=self.title % repo_name,
25 link=url('summary_home', repo_name=repo_name, qualified=True),
47 link=url('summary_home', repo_name=repo_name, qualified=True),
26 description=self.description % repo_name,
48 description=self.description % repo_name,
27 language=self.language,
49 language=self.language,
28 ttl=self.ttl)
50 ttl=self.ttl)
29
51
30
52
31 for cnt, cs in enumerate(_full_changelog_cached(repo_name)):
53 for cnt, cs in enumerate(_full_changelog_cached(repo_name)):
32 if cnt > self.feed_nr:
54 if cnt > self.feed_nr:
33 break
55 break
34 feed.add_item(title=cs.message,
56 feed.add_item(title=cs.message,
35 link=url('changeset_home', repo_name=repo_name,
57 link=url('changeset_home', repo_name=repo_name,
36 revision=cs.raw_id, qualified=True),
58 revision=cs.raw_id, qualified=True),
37 description=str(cs.date))
59 description=str(cs.date))
38
60
39 response.content_type = feed.mime_type
61 response.content_type = feed.mime_type
40 return feed.writeString('utf-8')
62 return feed.writeString('utf-8')
41
63
42
64
43 def rss(self, repo_name):
65 def rss(self, repo_name):
44 """Produce an rss2 feed via feedgenerator module"""
66 """Produce an rss2 feed via feedgenerator module"""
45 feed = Rss201rev2Feed(title=self.title % repo_name,
67 feed = Rss201rev2Feed(title=self.title % repo_name,
46 link=url('summary_home', repo_name=repo_name, qualified=True),
68 link=url('summary_home', repo_name=repo_name, qualified=True),
47 description=self.description % repo_name,
69 description=self.description % repo_name,
48 language=self.language,
70 language=self.language,
49 ttl=self.ttl)
71 ttl=self.ttl)
50
72
51 for cnt, cs in enumerate(_full_changelog_cached(repo_name)):
73 for cnt, cs in enumerate(_full_changelog_cached(repo_name)):
52 if cnt > self.feed_nr:
74 if cnt > self.feed_nr:
53 break
75 break
54 feed.add_item(title=cs.message,
76 feed.add_item(title=cs.message,
55 link=url('changeset_home', repo_name=repo_name, revision=cs.raw_id, qualified=True),
77 link=url('changeset_home', repo_name=repo_name, revision=cs.raw_id, qualified=True),
56 description=str(cs.date))
78 description=str(cs.date))
57
79
58 response.content_type = feed.mime_type
80 response.content_type = feed.mime_type
59 return feed.writeString('utf-8')
81 return feed.writeString('utf-8')
@@ -1,154 +1,178 b''
1 #!/usr/bin/env python
2 # encoding: utf-8
3 # files controller for pylons
4 # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
5
6 # This program is free software; you can redistribute it and/or
7 # modify it under the terms of the GNU General Public License
8 # as published by the Free Software Foundation; version 2
9 # of the License or (at your opinion) any later version of the license.
10 #
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
15 #
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19 # MA 02110-1301, USA.
20 """
21 Created on April 21, 2010
22 files controller for pylons
23 @author: marcink
24 """
1 from mercurial import archival
25 from mercurial import archival
2 from pylons import request, response, session, tmpl_context as c, url
26 from pylons import request, response, session, tmpl_context as c, url
3 from pylons_app.lib.auth import LoginRequired
27 from pylons_app.lib.auth import LoginRequired
4 from pylons_app.lib.base import BaseController, render
28 from pylons_app.lib.base import BaseController, render
5 from pylons_app.model.hg_model import HgModel
29 from pylons_app.model.hg_model import HgModel
6 from vcs.exceptions import RepositoryError, ChangesetError
30 from vcs.exceptions import RepositoryError, ChangesetError
7 from vcs.utils import diffs as differ
31 from vcs.utils import diffs as differ
8 import logging
32 import logging
9 import tempfile
33 import tempfile
10
34
11
35
12 log = logging.getLogger(__name__)
36 log = logging.getLogger(__name__)
13
37
14 class FilesController(BaseController):
38 class FilesController(BaseController):
15
39
16 @LoginRequired()
40 @LoginRequired()
17 def __before__(self):
41 def __before__(self):
18 super(FilesController, self).__before__()
42 super(FilesController, self).__before__()
19
43
20 def index(self, repo_name, revision, f_path):
44 def index(self, repo_name, revision, f_path):
21 hg_model = HgModel()
45 hg_model = HgModel()
22 c.repo = repo = hg_model.get_repo(c.repo_name)
46 c.repo = repo = hg_model.get_repo(c.repo_name)
23 revision = request.POST.get('at_rev', None) or revision
47 revision = request.POST.get('at_rev', None) or revision
24
48
25 def get_next_rev(cur):
49 def get_next_rev(cur):
26 max_rev = len(c.repo.revisions) - 1
50 max_rev = len(c.repo.revisions) - 1
27 r = cur + 1
51 r = cur + 1
28 if r > max_rev:
52 if r > max_rev:
29 r = max_rev
53 r = max_rev
30 return r
54 return r
31
55
32 def get_prev_rev(cur):
56 def get_prev_rev(cur):
33 r = cur - 1
57 r = cur - 1
34 return r
58 return r
35
59
36 c.f_path = f_path
60 c.f_path = f_path
37
61
38
62
39 try:
63 try:
40 cur_rev = repo.get_changeset(revision).revision
64 cur_rev = repo.get_changeset(revision).revision
41 prev_rev = repo.get_changeset(get_prev_rev(cur_rev)).raw_id
65 prev_rev = repo.get_changeset(get_prev_rev(cur_rev)).raw_id
42 next_rev = repo.get_changeset(get_next_rev(cur_rev)).raw_id
66 next_rev = repo.get_changeset(get_next_rev(cur_rev)).raw_id
43
67
44 c.url_prev = url('files_home', repo_name=c.repo_name,
68 c.url_prev = url('files_home', repo_name=c.repo_name,
45 revision=prev_rev, f_path=f_path)
69 revision=prev_rev, f_path=f_path)
46 c.url_next = url('files_home', repo_name=c.repo_name,
70 c.url_next = url('files_home', repo_name=c.repo_name,
47 revision=next_rev, f_path=f_path)
71 revision=next_rev, f_path=f_path)
48
72
49 c.changeset = repo.get_changeset(revision)
73 c.changeset = repo.get_changeset(revision)
50 try:
74 try:
51 c.file_msg = c.changeset.get_file_message(f_path)
75 c.file_msg = c.changeset.get_file_message(f_path)
52 except:
76 except:
53 c.file_msg = None
77 c.file_msg = None
54
78
55 c.cur_rev = c.changeset.raw_id
79 c.cur_rev = c.changeset.raw_id
56 c.rev_nr = c.changeset.revision
80 c.rev_nr = c.changeset.revision
57 c.files_list = c.changeset.get_node(f_path)
81 c.files_list = c.changeset.get_node(f_path)
58 c.file_history = self._get_history(repo, c.files_list, f_path)
82 c.file_history = self._get_history(repo, c.files_list, f_path)
59
83
60 except (RepositoryError, ChangesetError):
84 except (RepositoryError, ChangesetError):
61 c.files_list = None
85 c.files_list = None
62
86
63 return render('files/files.html')
87 return render('files/files.html')
64
88
65 def rawfile(self, repo_name, revision, f_path):
89 def rawfile(self, repo_name, revision, f_path):
66 hg_model = HgModel()
90 hg_model = HgModel()
67 c.repo = hg_model.get_repo(c.repo_name)
91 c.repo = hg_model.get_repo(c.repo_name)
68 file_node = c.repo.get_changeset(revision).get_node(f_path)
92 file_node = c.repo.get_changeset(revision).get_node(f_path)
69 response.content_type = file_node.mimetype
93 response.content_type = file_node.mimetype
70 response.content_disposition = 'attachment; filename=%s' \
94 response.content_disposition = 'attachment; filename=%s' \
71 % f_path.split('/')[-1]
95 % f_path.split('/')[-1]
72 return file_node.content
96 return file_node.content
73
97
74 def annotate(self, repo_name, revision, f_path):
98 def annotate(self, repo_name, revision, f_path):
75 hg_model = HgModel()
99 hg_model = HgModel()
76 c.repo = hg_model.get_repo(c.repo_name)
100 c.repo = hg_model.get_repo(c.repo_name)
77 cs = c.repo.get_changeset(revision)
101 cs = c.repo.get_changeset(revision)
78 c.file = cs.get_node(f_path)
102 c.file = cs.get_node(f_path)
79 c.file_msg = cs.get_file_message(f_path)
103 c.file_msg = cs.get_file_message(f_path)
80 c.cur_rev = cs.raw_id
104 c.cur_rev = cs.raw_id
81 c.f_path = f_path
105 c.f_path = f_path
82 c.annotate = cs.get_file_annotate(f_path)
106 c.annotate = cs.get_file_annotate(f_path)
83 return render('files/files_annotate.html')
107 return render('files/files_annotate.html')
84
108
85 def archivefile(self, repo_name, revision, fileformat):
109 def archivefile(self, repo_name, revision, fileformat):
86 archive_specs = {
110 archive_specs = {
87 '.tar.bz2': ('application/x-tar', 'tbz2'),
111 '.tar.bz2': ('application/x-tar', 'tbz2'),
88 '.tar.gz': ('application/x-tar', 'tgz'),
112 '.tar.gz': ('application/x-tar', 'tgz'),
89 '.zip': ('application/zip', 'zip'),
113 '.zip': ('application/zip', 'zip'),
90 }
114 }
91 if not archive_specs.has_key(fileformat):
115 if not archive_specs.has_key(fileformat):
92 return 'Unknown archive type %s' % fileformat
116 return 'Unknown archive type %s' % fileformat
93
117
94 def read_in_chunks(file_object, chunk_size=1024 * 40):
118 def read_in_chunks(file_object, chunk_size=1024 * 40):
95 """Lazy function (generator) to read a file piece by piece.
119 """Lazy function (generator) to read a file piece by piece.
96 Default chunk size: 40k."""
120 Default chunk size: 40k."""
97 while True:
121 while True:
98 data = file_object.read(chunk_size)
122 data = file_object.read(chunk_size)
99 if not data:
123 if not data:
100 break
124 break
101 yield data
125 yield data
102
126
103 archive = tempfile.TemporaryFile()
127 archive = tempfile.TemporaryFile()
104 repo = HgModel().get_repo(repo_name).repo
128 repo = HgModel().get_repo(repo_name).repo
105 fname = '%s-%s%s' % (repo_name, revision, fileformat)
129 fname = '%s-%s%s' % (repo_name, revision, fileformat)
106 archival.archive(repo, archive, revision, archive_specs[fileformat][1],
130 archival.archive(repo, archive, revision, archive_specs[fileformat][1],
107 prefix='%s-%s' % (repo_name, revision))
131 prefix='%s-%s' % (repo_name, revision))
108 response.content_type = archive_specs[fileformat][0]
132 response.content_type = archive_specs[fileformat][0]
109 response.content_disposition = 'attachment; filename=%s' % fname
133 response.content_disposition = 'attachment; filename=%s' % fname
110 archive.seek(0)
134 archive.seek(0)
111 return read_in_chunks(archive)
135 return read_in_chunks(archive)
112
136
113 def diff(self, repo_name, f_path):
137 def diff(self, repo_name, f_path):
114 hg_model = HgModel()
138 hg_model = HgModel()
115 diff1 = request.GET.get('diff1')
139 diff1 = request.GET.get('diff1')
116 diff2 = request.GET.get('diff2')
140 diff2 = request.GET.get('diff2')
117 c.action = action = request.GET.get('diff')
141 c.action = action = request.GET.get('diff')
118 c.no_changes = diff1 == diff2
142 c.no_changes = diff1 == diff2
119 c.f_path = f_path
143 c.f_path = f_path
120 c.repo = hg_model.get_repo(c.repo_name)
144 c.repo = hg_model.get_repo(c.repo_name)
121 c.changeset_1 = c.repo.get_changeset(diff1)
145 c.changeset_1 = c.repo.get_changeset(diff1)
122 c.changeset_2 = c.repo.get_changeset(diff2)
146 c.changeset_2 = c.repo.get_changeset(diff2)
123
147
124 c.diff1 = 'r%s:%s' % (c.changeset_1.revision, c.changeset_1._short)
148 c.diff1 = 'r%s:%s' % (c.changeset_1.revision, c.changeset_1._short)
125 c.diff2 = 'r%s:%s' % (c.changeset_2.revision, c.changeset_2._short)
149 c.diff2 = 'r%s:%s' % (c.changeset_2.revision, c.changeset_2._short)
126 f_udiff = differ.get_udiff(c.changeset_1.get_node(f_path),
150 f_udiff = differ.get_udiff(c.changeset_1.get_node(f_path),
127 c.changeset_2.get_node(f_path))
151 c.changeset_2.get_node(f_path))
128
152
129 diff = differ.DiffProcessor(f_udiff)
153 diff = differ.DiffProcessor(f_udiff)
130
154
131 if action == 'download':
155 if action == 'download':
132 diff_name = '%s_vs_%s.diff' % (diff1, diff2)
156 diff_name = '%s_vs_%s.diff' % (diff1, diff2)
133 response.content_type = 'text/plain'
157 response.content_type = 'text/plain'
134 response.content_disposition = 'attachment; filename=%s' \
158 response.content_disposition = 'attachment; filename=%s' \
135 % diff_name
159 % diff_name
136 return diff.raw_diff()
160 return diff.raw_diff()
137
161
138 elif action == 'raw':
162 elif action == 'raw':
139 c.cur_diff = '<pre class="raw">%s</pre>' % diff.raw_diff()
163 c.cur_diff = '<pre class="raw">%s</pre>' % diff.raw_diff()
140 elif action == 'diff':
164 elif action == 'diff':
141 c.cur_diff = diff.as_html()
165 c.cur_diff = diff.as_html()
142
166
143 return render('files/file_diff.html')
167 return render('files/file_diff.html')
144
168
145 def _get_history(self, repo, node, f_path):
169 def _get_history(self, repo, node, f_path):
146 from vcs.nodes import NodeKind
170 from vcs.nodes import NodeKind
147 if not node.kind is NodeKind.FILE:
171 if not node.kind is NodeKind.FILE:
148 return []
172 return []
149 changesets = node.history
173 changesets = node.history
150 hist_l = []
174 hist_l = []
151 for chs in changesets:
175 for chs in changesets:
152 n_desc = 'r%s:%s' % (chs.revision, chs._short)
176 n_desc = 'r%s:%s' % (chs.revision, chs._short)
153 hist_l.append((chs._short, n_desc,))
177 hist_l.append((chs._short, n_desc,))
154 return hist_l
178 return hist_l
@@ -1,66 +1,84 b''
1 #!/usr/bin/env python
2 # encoding: utf-8
3 # graph controller for pylons
4 # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
5
6 # This program is free software; you can redistribute it and/or
7 # modify it under the terms of the GNU General Public License
8 # as published by the Free Software Foundation; version 2
9 # of the License or (at your opinion) any later version of the license.
10 #
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
15 #
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19 # MA 02110-1301, USA.
20 """
21 Created on April 21, 2010
22 graph controller for pylons
23 @author: marcink
24 """
1 from mercurial.graphmod import revisions as graph_rev, colored, CHANGESET
25 from mercurial.graphmod import revisions as graph_rev, colored, CHANGESET
2 from mercurial.node import short
26 from mercurial.node import short
3 from pylons import request, response, session, tmpl_context as c, url, config, \
27 from pylons import request, tmpl_context as c
4 app_globals as g
5 from pylons.controllers.util import abort, redirect
6 from pylons_app.lib.auth import LoginRequired
28 from pylons_app.lib.auth import LoginRequired
7 from pylons_app.lib.base import BaseController, render
29 from pylons_app.lib.base import BaseController, render
8 from pylons_app.lib.filters import age as _age, person
30 from pylons_app.lib.filters import age as _age, person
9 from pylons_app.lib.utils import get_repo_slug
10 from pylons_app.model.hg_model import HgModel
31 from pylons_app.model.hg_model import HgModel
11 from simplejson import dumps
32 from simplejson import dumps
12 from webhelpers.paginate import Page
33 from webhelpers.paginate import Page
13 import logging
34 import logging
14
35
15
16
17
18 log = logging.getLogger(__name__)
36 log = logging.getLogger(__name__)
19
37
20 class GraphController(BaseController):
38 class GraphController(BaseController):
21
39
22 @LoginRequired()
40 @LoginRequired()
23 def __before__(self):
41 def __before__(self):
24 super(GraphController, self).__before__()
42 super(GraphController, self).__before__()
25
43
26 def index(self):
44 def index(self):
27 # Return a rendered template
45 # Return a rendered template
28 hg_model = HgModel()
46 hg_model = HgModel()
29 if request.POST.get('size'):
47 if request.POST.get('size'):
30 c.size = int(request.params.get('size', 20))
48 c.size = int(request.params.get('size', 20))
31 else:
49 else:
32 c.size = int(request.params.get('size', 20))
50 c.size = int(request.params.get('size', 20))
33 c.jsdata, c.canvasheight = self.graph(hg_model.get_repo(c.repo_name), c.size)
51 c.jsdata, c.canvasheight = self.graph(hg_model.get_repo(c.repo_name), c.size)
34
52
35 return render('/graph.html')
53 return render('/graph.html')
36
54
37
55
38 def graph(self, repo, size):
56 def graph(self, repo, size):
39 revcount = size
57 revcount = size
40 p = int(request.params.get('page', 1))
58 p = int(request.params.get('page', 1))
41 c.pagination = Page(repo.revisions, page=p, item_count=len(repo.revisions), items_per_page=revcount)
59 c.pagination = Page(repo.revisions, page=p, item_count=len(repo.revisions), items_per_page=revcount)
42 if not repo.revisions:return dumps([]), 0
60 if not repo.revisions:return dumps([]), 0
43
61
44 max_rev = repo.revisions[-1]
62 max_rev = repo.revisions[-1]
45 offset = 1 if p == 1 else ((p - 1) * revcount)
63 offset = 1 if p == 1 else ((p - 1) * revcount)
46 rev_start = repo.revisions[(-1 * offset)]
64 rev_start = repo.revisions[(-1 * offset)]
47 bg_height = 39
65 bg_height = 39
48
66
49 revcount = min(max_rev, revcount)
67 revcount = min(max_rev, revcount)
50 rev_end = max(0, rev_start - revcount)
68 rev_end = max(0, rev_start - revcount)
51 dag = graph_rev(repo.repo, rev_start, rev_end)
69 dag = graph_rev(repo.repo, rev_start, rev_end)
52 tree = list(colored(dag))
70 tree = list(colored(dag))
53 canvasheight = (len(tree) + 1) * bg_height - 27
71 canvasheight = (len(tree) + 1) * bg_height - 27
54 data = []
72 data = []
55 for (id, type, ctx, vtx, edges) in tree:
73 for (id, type, ctx, vtx, edges) in tree:
56 if type != CHANGESET:
74 if type != CHANGESET:
57 continue
75 continue
58 node = short(ctx.node())
76 node = short(ctx.node())
59 age = _age(ctx.date())
77 age = _age(ctx.date())
60 desc = ctx.description()
78 desc = ctx.description()
61 user = person(ctx.user())
79 user = person(ctx.user())
62 branch = ctx.branch()
80 branch = ctx.branch()
63 branch = branch, repo.repo.branchtags().get(branch) == ctx.node()
81 branch = branch, repo.repo.branchtags().get(branch) == ctx.node()
64 data.append((node, vtx, edges, desc, user, age, branch, ctx.tags()))
82 data.append((node, vtx, edges, desc, user, age, branch, ctx.tags()))
65
83
66 return dumps(data), canvasheight
84 return dumps(data), canvasheight
@@ -1,30 +1,52 b''
1 #!/usr/bin/python
1 #!/usr/bin/env python
2 # -*- coding: utf-8 -*-
2 # encoding: utf-8
3 # hg controller for pylons
4 # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
5
6 # This program is free software; you can redistribute it and/or
7 # modify it under the terms of the GNU General Public License
8 # as published by the Free Software Foundation; version 2
9 # of the License or (at your opinion) any later version of the license.
10 #
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
15 #
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19 # MA 02110-1301, USA.
20 """
21 Created on February 18, 2010
22 hg controller for pylons
23 @author: marcink
24 """
3 from operator import itemgetter
25 from operator import itemgetter
4 from pylons import tmpl_context as c, request, config
26 from pylons import tmpl_context as c, request
5 from pylons_app.lib.auth import LoginRequired
27 from pylons_app.lib.auth import LoginRequired
6 from pylons_app.lib.base import BaseController, render
28 from pylons_app.lib.base import BaseController, render
7 from pylons_app.model.hg_model import HgModel
29 from pylons_app.model.hg_model import HgModel
8 import logging
30 import logging
9 log = logging.getLogger(__name__)
31 log = logging.getLogger(__name__)
10
32
11 class HgController(BaseController):
33 class HgController(BaseController):
12
34
13 @LoginRequired()
35 @LoginRequired()
14 def __before__(self):
36 def __before__(self):
15 super(HgController, self).__before__()
37 super(HgController, self).__before__()
16
38
17 def index(self):
39 def index(self):
18 c.current_sort = request.GET.get('sort', 'name')
40 c.current_sort = request.GET.get('sort', 'name')
19 cs = c.current_sort
41 cs = c.current_sort
20 c.cs_slug = cs.replace('-', '')
42 c.cs_slug = cs.replace('-', '')
21 sortables = ['name', 'description', 'last_change', 'tip', 'contact']
43 sortables = ['name', 'description', 'last_change', 'tip', 'contact']
22 cached_repo_list = HgModel().get_repos()
44 cached_repo_list = HgModel().get_repos()
23 if cs and c.cs_slug in sortables:
45 if cs and c.cs_slug in sortables:
24 sort_key = c.cs_slug + '_sort'
46 sort_key = c.cs_slug + '_sort'
25 if cs.startswith('-'):
47 if cs.startswith('-'):
26 c.repos_list = sorted(cached_repo_list, key=itemgetter(sort_key), reverse=True)
48 c.repos_list = sorted(cached_repo_list, key=itemgetter(sort_key), reverse=True)
27 else:
49 else:
28 c.repos_list = sorted(cached_repo_list, key=itemgetter(sort_key), reverse=False)
50 c.repos_list = sorted(cached_repo_list, key=itemgetter(sort_key), reverse=False)
29
51
30 return render('/index.html')
52 return render('/index.html')
@@ -1,42 +1,66 b''
1 #!/usr/bin/env python
2 # encoding: utf-8
3 # login controller for pylons
4 # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
5
6 # This program is free software; you can redistribute it and/or
7 # modify it under the terms of the GNU General Public License
8 # as published by the Free Software Foundation; version 2
9 # of the License or (at your opinion) any later version of the license.
10 #
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
15 #
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19 # MA 02110-1301, USA.
20 """
21 Created on April 22, 2010
22 login controller for pylons
23 @author: marcink
24 """
1 import logging
25 import logging
2 from formencode import htmlfill
26 from formencode import htmlfill
3 from pylons import request, response, session, tmpl_context as c, url
27 from pylons import request, response, session, tmpl_context as c, url
4 from pylons.controllers.util import abort, redirect
28 from pylons.controllers.util import abort, redirect
5 from pylons_app.lib.base import BaseController, render
29 from pylons_app.lib.base import BaseController, render
6 import formencode
30 import formencode
7 from pylons_app.model.forms import LoginForm
31 from pylons_app.model.forms import LoginForm
8 from pylons_app.lib.auth import AuthUser
32 from pylons_app.lib.auth import AuthUser
9
33
10 log = logging.getLogger(__name__)
34 log = logging.getLogger(__name__)
11
35
12 class LoginController(BaseController):
36 class LoginController(BaseController):
13
37
14 def __before__(self):
38 def __before__(self):
15 super(LoginController, self).__before__()
39 super(LoginController, self).__before__()
16
40
17 def index(self):
41 def index(self):
18 #redirect if already logged in
42 #redirect if already logged in
19 if c.hg_app_user.is_authenticated:
43 if c.hg_app_user.is_authenticated:
20 return redirect(url('hg_home'))
44 return redirect(url('hg_home'))
21
45
22 if request.POST:
46 if request.POST:
23 #import Login Form validator class
47 #import Login Form validator class
24 login_form = LoginForm()
48 login_form = LoginForm()
25 try:
49 try:
26 c.form_result = login_form.to_python(dict(request.POST))
50 c.form_result = login_form.to_python(dict(request.POST))
27 return redirect(url('hg_home'))
51 return redirect(url('hg_home'))
28
52
29 except formencode.Invalid as errors:
53 except formencode.Invalid as errors:
30 c.form_errors = errors.error_dict
54 c.form_errors = errors.error_dict
31 return htmlfill.render(
55 return htmlfill.render(
32 render('/login.html'),
56 render('/login.html'),
33 defaults=errors.value,
57 defaults=errors.value,
34 encoding="UTF-8")
58 encoding="UTF-8")
35
59
36 return render('/login.html')
60 return render('/login.html')
37
61
38 def logout(self):
62 def logout(self):
39 session['hg_app_user'] = AuthUser()
63 session['hg_app_user'] = AuthUser()
40 session.save()
64 session.save()
41 log.info('Logging out and setting user as Empty')
65 log.info('Logging out and setting user as Empty')
42 redirect(url('hg_home'))
66 redirect(url('hg_home'))
@@ -1,53 +1,77 b''
1 #!/usr/bin/env python
2 # encoding: utf-8
3 # permissions controller for pylons
4 # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
5
6 # This program is free software; you can redistribute it and/or
7 # modify it under the terms of the GNU General Public License
8 # as published by the Free Software Foundation; version 2
9 # of the License or (at your opinion) any later version of the license.
10 #
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
15 #
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19 # MA 02110-1301, USA.
20 """
21 Created on April 27, 2010
22 permissions controller for pylons
23 @author: marcink
24 """
1 import logging
25 import logging
2
26
3 from pylons import request, response, session, tmpl_context as c, url
27 from pylons import request, response, session, tmpl_context as c, url
4 from pylons.controllers.util import abort, redirect
28 from pylons.controllers.util import abort, redirect
5
29
6 from pylons_app.lib.base import BaseController, render
30 from pylons_app.lib.base import BaseController, render
7
31
8 log = logging.getLogger(__name__)
32 log = logging.getLogger(__name__)
9
33
10 class PermissionsController(BaseController):
34 class PermissionsController(BaseController):
11 """REST Controller styled on the Atom Publishing Protocol"""
35 """REST Controller styled on the Atom Publishing Protocol"""
12 # To properly map this controller, ensure your config/routing.py
36 # To properly map this controller, ensure your config/routing.py
13 # file has a resource setup:
37 # file has a resource setup:
14 # map.resource('permission', 'permissions')
38 # map.resource('permission', 'permissions')
15
39
16 def index(self, format='html'):
40 def index(self, format='html'):
17 """GET /permissions: All items in the collection"""
41 """GET /permissions: All items in the collection"""
18 # url('permissions')
42 # url('permissions')
19 return render('admin/permissions/permissions.html')
43 return render('admin/permissions/permissions.html')
20
44
21 def create(self):
45 def create(self):
22 """POST /permissions: Create a new item"""
46 """POST /permissions: Create a new item"""
23 # url('permissions')
47 # url('permissions')
24
48
25 def new(self, format='html'):
49 def new(self, format='html'):
26 """GET /permissions/new: Form to create a new item"""
50 """GET /permissions/new: Form to create a new item"""
27 # url('new_permission')
51 # url('new_permission')
28
52
29 def update(self, id):
53 def update(self, id):
30 """PUT /permissions/id: Update an existing item"""
54 """PUT /permissions/id: Update an existing item"""
31 # Forms posted to this method should contain a hidden field:
55 # Forms posted to this method should contain a hidden field:
32 # <input type="hidden" name="_method" value="PUT" />
56 # <input type="hidden" name="_method" value="PUT" />
33 # Or using helpers:
57 # Or using helpers:
34 # h.form(url('permission', id=ID),
58 # h.form(url('permission', id=ID),
35 # method='put')
59 # method='put')
36 # url('permission', id=ID)
60 # url('permission', id=ID)
37
61
38 def delete(self, id):
62 def delete(self, id):
39 """DELETE /permissions/id: Delete an existing item"""
63 """DELETE /permissions/id: Delete an existing item"""
40 # Forms posted to this method should contain a hidden field:
64 # Forms posted to this method should contain a hidden field:
41 # <input type="hidden" name="_method" value="DELETE" />
65 # <input type="hidden" name="_method" value="DELETE" />
42 # Or using helpers:
66 # Or using helpers:
43 # h.form(url('permission', id=ID),
67 # h.form(url('permission', id=ID),
44 # method='delete')
68 # method='delete')
45 # url('permission', id=ID)
69 # url('permission', id=ID)
46
70
47 def show(self, id, format='html'):
71 def show(self, id, format='html'):
48 """GET /permissions/id: Show a specific item"""
72 """GET /permissions/id: Show a specific item"""
49 # url('permission', id=ID)
73 # url('permission', id=ID)
50
74
51 def edit(self, id, format='html'):
75 def edit(self, id, format='html'):
52 """GET /permissions/id/edit: Form to edit an existing item"""
76 """GET /permissions/id/edit: Form to edit an existing item"""
53 # url('edit_permission', id=ID)
77 # url('edit_permission', id=ID)
@@ -1,103 +1,127 b''
1 #!/usr/bin/env python
2 # encoding: utf-8
3 # repos controller for pylons
4 # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
5
6 # This program is free software; you can redistribute it and/or
7 # modify it under the terms of the GNU General Public License
8 # as published by the Free Software Foundation; version 2
9 # of the License or (at your opinion) any later version of the license.
10 #
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
15 #
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19 # MA 02110-1301, USA.
20 """
21 Created on April 7, 2010
22 admin controller for pylons
23 @author: marcink
24 """
25 import logging
1 from pylons import request, response, session, tmpl_context as c, url, \
26 from pylons import request, response, session, tmpl_context as c, url, \
2 app_globals as g
27 app_globals as g
3 from pylons.controllers.util import abort, redirect
28 from pylons.controllers.util import abort, redirect
4 from pylons_app.lib.auth import LoginRequired
29 from pylons_app.lib.auth import LoginRequired
5 from pylons.i18n.translation import _
30 from pylons.i18n.translation import _
6 from pylons_app.lib import helpers as h
31 from pylons_app.lib import helpers as h
7 from pylons_app.lib.base import BaseController, render
32 from pylons_app.lib.base import BaseController, render
8 from pylons_app.lib.filters import clean_repo
33 from pylons_app.lib.filters import clean_repo
9 from pylons_app.lib.utils import check_repo, invalidate_cache
34 from pylons_app.lib.utils import check_repo, invalidate_cache
10 from pylons_app.model.hg_model import HgModel
35 from pylons_app.model.hg_model import HgModel
11 import logging
12 import os
36 import os
13 import shutil
37 import shutil
14 from operator import itemgetter
38 from operator import itemgetter
15 log = logging.getLogger(__name__)
39 log = logging.getLogger(__name__)
16
40
17 class ReposController(BaseController):
41 class ReposController(BaseController):
18 """REST Controller styled on the Atom Publishing Protocol"""
42 """REST Controller styled on the Atom Publishing Protocol"""
19 # To properly map this controller, ensure your config/routing.py
43 # To properly map this controller, ensure your config/routing.py
20 # file has a resource setup:
44 # file has a resource setup:
21 # map.resource('repo', 'repos')
45 # map.resource('repo', 'repos')
22 @LoginRequired()
46 @LoginRequired()
23 def __before__(self):
47 def __before__(self):
24 c.admin_user = session.get('admin_user')
48 c.admin_user = session.get('admin_user')
25 c.admin_username = session.get('admin_username')
49 c.admin_username = session.get('admin_username')
26 super(ReposController, self).__before__()
50 super(ReposController, self).__before__()
27
51
28 def index(self, format='html'):
52 def index(self, format='html'):
29 """GET /repos: All items in the collection"""
53 """GET /repos: All items in the collection"""
30 # url('repos')
54 # url('repos')
31 cached_repo_list = HgModel().get_repos()
55 cached_repo_list = HgModel().get_repos()
32 c.repos_list = sorted(cached_repo_list, key=itemgetter('name_sort'))
56 c.repos_list = sorted(cached_repo_list, key=itemgetter('name_sort'))
33 return render('admin/repos/repos.html')
57 return render('admin/repos/repos.html')
34
58
35 def create(self):
59 def create(self):
36 """POST /repos: Create a new item"""
60 """POST /repos: Create a new item"""
37 # url('repos')
61 # url('repos')
38 name = request.POST.get('name')
62 name = request.POST.get('name')
39
63
40 try:
64 try:
41 self._create_repo(name)
65 self._create_repo(name)
42 #clear our cached list for refresh with new repo
66 #clear our cached list for refresh with new repo
43 invalidate_cache('cached_repo_list')
67 invalidate_cache('cached_repo_list')
44 h.flash(_('created repository %s') % name, category='success')
68 h.flash(_('created repository %s') % name, category='success')
45 except Exception as e:
69 except Exception as e:
46 log.error(e)
70 log.error(e)
47
71
48 return redirect('repos')
72 return redirect('repos')
49
73
50 def _create_repo(self, repo_name):
74 def _create_repo(self, repo_name):
51 repo_path = os.path.join(g.base_path, repo_name)
75 repo_path = os.path.join(g.base_path, repo_name)
52 if check_repo(repo_name, g.base_path):
76 if check_repo(repo_name, g.base_path):
53 log.info('creating repo %s in %s', repo_name, repo_path)
77 log.info('creating repo %s in %s', repo_name, repo_path)
54 from vcs.backends.hg import MercurialRepository
78 from vcs.backends.hg import MercurialRepository
55 MercurialRepository(repo_path, create=True)
79 MercurialRepository(repo_path, create=True)
56
80
57
81
58 def new(self, format='html'):
82 def new(self, format='html'):
59 """GET /repos/new: Form to create a new item"""
83 """GET /repos/new: Form to create a new item"""
60 new_repo = request.GET.get('repo', '')
84 new_repo = request.GET.get('repo', '')
61 c.new_repo = clean_repo(new_repo)
85 c.new_repo = clean_repo(new_repo)
62
86
63 return render('admin/repos/repo_add.html')
87 return render('admin/repos/repo_add.html')
64
88
65 def update(self, id):
89 def update(self, id):
66 """PUT /repos/id: Update an existing item"""
90 """PUT /repos/id: Update an existing item"""
67 # Forms posted to this method should contain a hidden field:
91 # Forms posted to this method should contain a hidden field:
68 # <input type="hidden" name="_method" value="PUT" />
92 # <input type="hidden" name="_method" value="PUT" />
69 # Or using helpers:
93 # Or using helpers:
70 # h.form(url('repo', id=ID),
94 # h.form(url('repo', id=ID),
71 # method='put')
95 # method='put')
72 # url('repo', id=ID)
96 # url('repo', id=ID)
73
97
74 def delete(self, id):
98 def delete(self, id):
75 """DELETE /repos/id: Delete an existing item"""
99 """DELETE /repos/id: Delete an existing item"""
76 # Forms posted to this method should contain a hidden field:
100 # Forms posted to this method should contain a hidden field:
77 # <input type="hidden" name="_method" value="DELETE" />
101 # <input type="hidden" name="_method" value="DELETE" />
78 # Or using helpers:
102 # Or using helpers:
79 # h.form(url('repo', id=ID),
103 # h.form(url('repo', id=ID),
80 # method='delete')
104 # method='delete')
81 # url('repo', id=ID)
105 # url('repo', id=ID)
82 from datetime import datetime
106 from datetime import datetime
83 path = g.paths[0][1].replace('*', '')
107 path = g.paths[0][1].replace('*', '')
84 rm_path = os.path.join(path, id)
108 rm_path = os.path.join(path, id)
85 log.info("Removing %s", rm_path)
109 log.info("Removing %s", rm_path)
86 shutil.move(os.path.join(rm_path, '.hg'), os.path.join(rm_path, 'rm__.hg'))
110 shutil.move(os.path.join(rm_path, '.hg'), os.path.join(rm_path, 'rm__.hg'))
87 shutil.move(rm_path, os.path.join(path, 'rm__%s-%s' % (datetime.today(), id)))
111 shutil.move(rm_path, os.path.join(path, 'rm__%s-%s' % (datetime.today(), id)))
88
112
89 #clear our cached list for refresh with new repo
113 #clear our cached list for refresh with new repo
90 invalidate_cache('cached_repo_list')
114 invalidate_cache('cached_repo_list')
91 h.flash(_('deleted repository %s') % rm_path, category='success')
115 h.flash(_('deleted repository %s') % rm_path, category='success')
92 return redirect(url('repos'))
116 return redirect(url('repos'))
93
117
94
118
95 def show(self, id, format='html'):
119 def show(self, id, format='html'):
96 """GET /repos/id: Show a specific item"""
120 """GET /repos/id: Show a specific item"""
97 # url('repo', id=ID)
121 # url('repo', id=ID)
98
122
99 def edit(self, id, format='html'):
123 def edit(self, id, format='html'):
100 """GET /repos/id/edit: Form to edit an existing item"""
124 """GET /repos/id/edit: Form to edit an existing item"""
101 # url('edit_repo', id=ID)
125 # url('edit_repo', id=ID)
102 c.new_repo = id
126 c.new_repo = id
103 return render('admin/repos/repo_edit.html')
127 return render('admin/repos/repo_edit.html')
@@ -1,25 +1,49 b''
1 #!/usr/bin/env python
2 # encoding: utf-8
3 # shortlog controller for pylons
4 # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
5
6 # This program is free software; you can redistribute it and/or
7 # modify it under the terms of the GNU General Public License
8 # as published by the Free Software Foundation; version 2
9 # of the License or (at your opinion) any later version of the license.
10 #
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
15 #
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19 # MA 02110-1301, USA.
20 """
21 Created on April 18, 2010
22 shortlog controller for pylons
23 @author: marcink
24 """
1 from pylons import tmpl_context as c, request
25 from pylons import tmpl_context as c, request
2 from pylons_app.lib.auth import LoginRequired
26 from pylons_app.lib.auth import LoginRequired
3 from pylons_app.lib.base import BaseController, render
27 from pylons_app.lib.base import BaseController, render
4 from pylons_app.model.hg_model import HgModel
28 from pylons_app.model.hg_model import HgModel
5 from webhelpers.paginate import Page
29 from webhelpers.paginate import Page
6 import logging
30 import logging
7
31
8 log = logging.getLogger(__name__)
32 log = logging.getLogger(__name__)
9
33
10 class ShortlogController(BaseController):
34 class ShortlogController(BaseController):
11
35
12 @LoginRequired()
36 @LoginRequired()
13 def __before__(self):
37 def __before__(self):
14 super(ShortlogController, self).__before__()
38 super(ShortlogController, self).__before__()
15
39
16 def index(self):
40 def index(self):
17 hg_model = HgModel()
41 hg_model = HgModel()
18 p = int(request.params.get('page', 1))
42 p = int(request.params.get('page', 1))
19 repo = hg_model.get_repo(c.repo_name)
43 repo = hg_model.get_repo(c.repo_name)
20 c.repo_changesets = Page(repo, page=p, items_per_page=20)
44 c.repo_changesets = Page(repo, page=p, items_per_page=20)
21 c.shortlog_data = render('shortlog/shortlog_data.html')
45 c.shortlog_data = render('shortlog/shortlog_data.html')
22 if request.params.get('partial'):
46 if request.params.get('partial'):
23 return c.shortlog_data
47 return c.shortlog_data
24 r = render('shortlog/shortlog.html')
48 r = render('shortlog/shortlog.html')
25 return r
49 return r
@@ -1,28 +1,52 b''
1 #!/usr/bin/env python
2 # encoding: utf-8
3 # summary controller for pylons
4 # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
5
6 # This program is free software; you can redistribute it and/or
7 # modify it under the terms of the GNU General Public License
8 # as published by the Free Software Foundation; version 2
9 # of the License or (at your opinion) any later version of the license.
10 #
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
15 #
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19 # MA 02110-1301, USA.
20 """
21 Created on April 18, 2010
22 summary controller for pylons
23 @author: marcink
24 """
1 from pylons import tmpl_context as c, request
25 from pylons import tmpl_context as c, request
2 from pylons_app.lib.auth import LoginRequired
26 from pylons_app.lib.auth import LoginRequired
3 from pylons_app.lib.base import BaseController, render
27 from pylons_app.lib.base import BaseController, render
4 from pylons_app.model.hg_model import HgModel, _full_changelog_cached
28 from pylons_app.model.hg_model import HgModel, _full_changelog_cached
5 import logging
29 import logging
6
30
7 log = logging.getLogger(__name__)
31 log = logging.getLogger(__name__)
8
32
9 class SummaryController(BaseController):
33 class SummaryController(BaseController):
10
34
11 @LoginRequired()
35 @LoginRequired()
12 def __before__(self):
36 def __before__(self):
13 super(SummaryController, self).__before__()
37 super(SummaryController, self).__before__()
14
38
15 def index(self):
39 def index(self):
16 hg_model = HgModel()
40 hg_model = HgModel()
17 c.repo_info = hg_model.get_repo(c.repo_name)
41 c.repo_info = hg_model.get_repo(c.repo_name)
18 c.repo_changesets = _full_changelog_cached(c.repo_name)[:10]
42 c.repo_changesets = _full_changelog_cached(c.repo_name)[:10]
19 e = request.environ
43 e = request.environ
20 uri = u'%(protocol)s://%(user)s@%(host)s/%(repo_name)s' % {
44 uri = u'%(protocol)s://%(user)s@%(host)s/%(repo_name)s' % {
21 'protocol': e.get('wsgi.url_scheme'),
45 'protocol': e.get('wsgi.url_scheme'),
22 'user':str(c.hg_app_user.username),
46 'user':str(c.hg_app_user.username),
23 'host':e.get('HTTP_HOST'),
47 'host':e.get('HTTP_HOST'),
24 'repo_name':c.repo_name, }
48 'repo_name':c.repo_name, }
25 c.clone_repo_url = uri
49 c.clone_repo_url = uri
26 c.repo_tags = c.repo_info.tags[:10]
50 c.repo_tags = c.repo_info.tags[:10]
27 c.repo_branches = c.repo_info.branches[:10]
51 c.repo_branches = c.repo_info.branches[:10]
28 return render('summary/summary.html')
52 return render('summary/summary.html')
@@ -1,20 +1,44 b''
1 #!/usr/bin/env python
2 # encoding: utf-8
3 # tags controller for pylons
4 # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
5
6 # This program is free software; you can redistribute it and/or
7 # modify it under the terms of the GNU General Public License
8 # as published by the Free Software Foundation; version 2
9 # of the License or (at your opinion) any later version of the license.
10 #
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
15 #
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19 # MA 02110-1301, USA.
20 """
21 Created on April 21, 2010
22 tags controller for pylons
23 @author: marcink
24 """
1 from pylons import tmpl_context as c
25 from pylons import tmpl_context as c
2 from pylons_app.lib.auth import LoginRequired
26 from pylons_app.lib.auth import LoginRequired
3 from pylons_app.lib.base import BaseController, render
27 from pylons_app.lib.base import BaseController, render
4 from pylons_app.model.hg_model import HgModel
28 from pylons_app.model.hg_model import HgModel
5 import logging
29 import logging
6
30
7 log = logging.getLogger(__name__)
31 log = logging.getLogger(__name__)
8
32
9 class TagsController(BaseController):
33 class TagsController(BaseController):
10
34
11 @LoginRequired()
35 @LoginRequired()
12 def __before__(self):
36 def __before__(self):
13 super(TagsController, self).__before__()
37 super(TagsController, self).__before__()
14
38
15 def index(self):
39 def index(self):
16 hg_model = HgModel()
40 hg_model = HgModel()
17 c.repo_info = hg_model.get_repo(c.repo_name)
41 c.repo_info = hg_model.get_repo(c.repo_name)
18 c.repo_tags = c.repo_info.tags
42 c.repo_tags = c.repo_info.tags
19
43
20 return render('tags/tags.html')
44 return render('tags/tags.html')
@@ -1,115 +1,139 b''
1 #!/usr/bin/env python
2 # encoding: utf-8
3 # users controller for pylons
4 # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
5
6 # This program is free software; you can redistribute it and/or
7 # modify it under the terms of the GNU General Public License
8 # as published by the Free Software Foundation; version 2
9 # of the License or (at your opinion) any later version of the license.
10 #
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
15 #
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19 # MA 02110-1301, USA.
20 """
21 Created on April 4, 2010
22 users controller for pylons
23 @author: marcink
24 """
25 import logging
1 from formencode import htmlfill
26 from formencode import htmlfill
2 from pylons import request, session, tmpl_context as c, url
27 from pylons import request, session, tmpl_context as c, url
3 from pylons.controllers.util import abort, redirect
28 from pylons.controllers.util import abort, redirect
4 from pylons.i18n.translation import _
29 from pylons.i18n.translation import _
5 from pylons_app.lib import helpers as h
30 from pylons_app.lib import helpers as h
6 from pylons_app.lib.auth import LoginRequired, CheckPermissionAll
31 from pylons_app.lib.auth import LoginRequired, CheckPermissionAll
7 from pylons_app.lib.base import BaseController, render
32 from pylons_app.lib.base import BaseController, render
8 from pylons_app.model.db import User, UserLog
33 from pylons_app.model.db import User, UserLog
9 from pylons_app.model.forms import UserForm
34 from pylons_app.model.forms import UserForm
10 from pylons_app.model.user_model import UserModel
35 from pylons_app.model.user_model import UserModel
11 import formencode
36 import formencode
12 import logging
13
37
14 log = logging.getLogger(__name__)
38 log = logging.getLogger(__name__)
15
39
16 class UsersController(BaseController):
40 class UsersController(BaseController):
17 """REST Controller styled on the Atom Publishing Protocol"""
41 """REST Controller styled on the Atom Publishing Protocol"""
18 # To properly map this controller, ensure your config/routing.py
42 # To properly map this controller, ensure your config/routing.py
19 # file has a resource setup:
43 # file has a resource setup:
20 # map.resource('user', 'users')
44 # map.resource('user', 'users')
21 @LoginRequired()
45 @LoginRequired()
22 def __before__(self):
46 def __before__(self):
23 c.admin_user = session.get('admin_user')
47 c.admin_user = session.get('admin_user')
24 c.admin_username = session.get('admin_username')
48 c.admin_username = session.get('admin_username')
25 super(UsersController, self).__before__()
49 super(UsersController, self).__before__()
26
50
27
51
28 def index(self, format='html'):
52 def index(self, format='html'):
29 """GET /users: All items in the collection"""
53 """GET /users: All items in the collection"""
30 # url('users')
54 # url('users')
31
55
32 c.users_list = self.sa.query(User).all()
56 c.users_list = self.sa.query(User).all()
33 return render('admin/users/users.html')
57 return render('admin/users/users.html')
34
58
35 def create(self):
59 def create(self):
36 """POST /users: Create a new item"""
60 """POST /users: Create a new item"""
37 # url('users')
61 # url('users')
38
62
39 user_model = UserModel()
63 user_model = UserModel()
40 login_form = UserForm()()
64 login_form = UserForm()()
41 try:
65 try:
42 form_result = login_form.to_python(dict(request.POST))
66 form_result = login_form.to_python(dict(request.POST))
43 user_model.create(form_result)
67 user_model.create(form_result)
44 h.flash(_('created user %s') % form_result['username'], category='success')
68 h.flash(_('created user %s') % form_result['username'], category='success')
45 return redirect(url('users'))
69 return redirect(url('users'))
46
70
47 except formencode.Invalid as errors:
71 except formencode.Invalid as errors:
48 c.form_errors = errors.error_dict
72 c.form_errors = errors.error_dict
49 return htmlfill.render(
73 return htmlfill.render(
50 render('admin/users/user_add.html'),
74 render('admin/users/user_add.html'),
51 defaults=errors.value,
75 defaults=errors.value,
52 encoding="UTF-8")
76 encoding="UTF-8")
53
77
54 def new(self, format='html'):
78 def new(self, format='html'):
55 """GET /users/new: Form to create a new item"""
79 """GET /users/new: Form to create a new item"""
56 # url('new_user')
80 # url('new_user')
57 return render('admin/users/user_add.html')
81 return render('admin/users/user_add.html')
58
82
59 def update(self, id):
83 def update(self, id):
60 """PUT /users/id: Update an existing item"""
84 """PUT /users/id: Update an existing item"""
61 # Forms posted to this method should contain a hidden field:
85 # Forms posted to this method should contain a hidden field:
62 # <input type="hidden" name="_method" value="PUT" />
86 # <input type="hidden" name="_method" value="PUT" />
63 # Or using helpers:
87 # Or using helpers:
64 # h.form(url('user', id=ID),
88 # h.form(url('user', id=ID),
65 # method='put')
89 # method='put')
66 # url('user', id=ID)
90 # url('user', id=ID)
67 user_model = UserModel()
91 user_model = UserModel()
68 login_form = UserForm(edit=True)()
92 login_form = UserForm(edit=True)()
69 try:
93 try:
70 form_result = login_form.to_python(dict(request.POST))
94 form_result = login_form.to_python(dict(request.POST))
71 user_model.update(id, form_result)
95 user_model.update(id, form_result)
72 h.flash(_('User updated succesfully'), category='success')
96 h.flash(_('User updated succesfully'), category='success')
73 return redirect(url('users'))
97 return redirect(url('users'))
74
98
75 except formencode.Invalid as errors:
99 except formencode.Invalid as errors:
76 c.user = user_model.get_user(id)
100 c.user = user_model.get_user(id)
77 c.form_errors = errors.error_dict
101 c.form_errors = errors.error_dict
78 return htmlfill.render(
102 return htmlfill.render(
79 render('admin/users/user_edit.html'),
103 render('admin/users/user_edit.html'),
80 defaults=errors.value,
104 defaults=errors.value,
81 encoding="UTF-8")
105 encoding="UTF-8")
82
106
83 def delete(self, id):
107 def delete(self, id):
84 """DELETE /users/id: Delete an existing item"""
108 """DELETE /users/id: Delete an existing item"""
85 # Forms posted to this method should contain a hidden field:
109 # Forms posted to this method should contain a hidden field:
86 # <input type="hidden" name="_method" value="DELETE" />
110 # <input type="hidden" name="_method" value="DELETE" />
87 # Or using helpers:
111 # Or using helpers:
88 # h.form(url('user', id=ID),
112 # h.form(url('user', id=ID),
89 # method='delete')
113 # method='delete')
90 # url('user', id=ID)
114 # url('user', id=ID)
91 try:
115 try:
92 self.sa.delete(self.sa.query(User).get(id))
116 self.sa.delete(self.sa.query(User).get(id))
93 self.sa.commit()
117 self.sa.commit()
94 h.flash(_('sucessfully deleted user'), category='success')
118 h.flash(_('sucessfully deleted user'), category='success')
95 except:
119 except:
96 self.sa.rollback()
120 self.sa.rollback()
97 raise
121 raise
98 return redirect(url('users'))
122 return redirect(url('users'))
99
123
100 def show(self, id, format='html'):
124 def show(self, id, format='html'):
101 """GET /users/id: Show a specific item"""
125 """GET /users/id: Show a specific item"""
102 # url('user', id=ID)
126 # url('user', id=ID)
103
127
104
128
105 def edit(self, id, format='html'):
129 def edit(self, id, format='html'):
106 """GET /users/id/edit: Form to edit an existing item"""
130 """GET /users/id/edit: Form to edit an existing item"""
107 # url('edit_user', id=ID)
131 # url('edit_user', id=ID)
108 c.user = self.sa.query(User).get(id)
132 c.user = self.sa.query(User).get(id)
109 defaults = c.user.__dict__
133 defaults = c.user.__dict__
110 return htmlfill.render(
134 return htmlfill.render(
111 render('admin/users/user_edit.html'),
135 render('admin/users/user_edit.html'),
112 defaults=defaults,
136 defaults=defaults,
113 encoding="UTF-8",
137 encoding="UTF-8",
114 force_defaults=False
138 force_defaults=False
115 )
139 )
@@ -1,153 +1,178 b''
1 #!/usr/bin/env python
2 # encoding: utf-8
3 # authentication and permission libraries
4 # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
5
6 # This program is free software; you can redistribute it and/or
7 # modify it under the terms of the GNU General Public License
8 # as published by the Free Software Foundation; version 2
9 # of the License or (at your opinion) any later version of the license.
10 #
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
15 #
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19 # MA 02110-1301, USA.
20 """
21 Created on April 4, 2010
22
23 @author: marcink
24 """
25
1 from functools import wraps
26 from functools import wraps
2 from pylons import session, url, app_globals as g
27 from pylons import session, url, app_globals as g
3 from pylons.controllers.util import abort, redirect
28 from pylons.controllers.util import abort, redirect
4 from pylons_app.model import meta
29 from pylons_app.model import meta
5 from pylons_app.model.db import User
30 from pylons_app.model.db import User
6 from sqlalchemy.exc import OperationalError
31 from sqlalchemy.exc import OperationalError
7 from sqlalchemy.orm.exc import NoResultFound, MultipleResultsFound
32 from sqlalchemy.orm.exc import NoResultFound, MultipleResultsFound
8 import crypt
33 import crypt
9 import logging
34 import logging
10 log = logging.getLogger(__name__)
35 log = logging.getLogger(__name__)
11
36
12 def get_crypt_password(password):
37 def get_crypt_password(password):
13 """
38 """
14 Cryptographic function used for password hashing
39 Cryptographic function used for password hashing
15 @param password: password to hash
40 @param password: password to hash
16 """
41 """
17 return crypt.crypt(password, '6a')
42 return crypt.crypt(password, '6a')
18
43
19 def authfunc(environ, username, password):
44 def authfunc(environ, username, password):
20 sa = meta.Session
45 sa = meta.Session
21 password_crypt = get_crypt_password(password)
46 password_crypt = get_crypt_password(password)
22 try:
47 try:
23 user = sa.query(User).filter(User.username == username).one()
48 user = sa.query(User).filter(User.username == username).one()
24 except (NoResultFound, MultipleResultsFound, OperationalError) as e:
49 except (NoResultFound, MultipleResultsFound, OperationalError) as e:
25 log.error(e)
50 log.error(e)
26 user = None
51 user = None
27
52
28 if user:
53 if user:
29 if user.active:
54 if user.active:
30 if user.username == username and user.password == password_crypt:
55 if user.username == username and user.password == password_crypt:
31 log.info('user %s authenticated correctly', username)
56 log.info('user %s authenticated correctly', username)
32 return True
57 return True
33 else:
58 else:
34 log.error('user %s is disabled', username)
59 log.error('user %s is disabled', username)
35
60
36 return False
61 return False
37
62
38 class AuthUser(object):
63 class AuthUser(object):
39 """
64 """
40 A simple object that handles a mercurial username for authentication
65 A simple object that handles a mercurial username for authentication
41 """
66 """
42 username = 'None'
67 username = 'None'
43 is_authenticated = False
68 is_authenticated = False
44 is_admin = False
69 is_admin = False
45 permissions = set()
70 permissions = set()
46 group = set()
71 group = set()
47
72
48 def __init__(self):
73 def __init__(self):
49 pass
74 pass
50
75
51
76
52
77
53 def set_available_permissions(config):
78 def set_available_permissions(config):
54 """
79 """
55 This function will propagate pylons globals with all available defined
80 This function will propagate pylons globals with all available defined
56 permission given in db. We don't wannt to check each time from db for new
81 permission given in db. We don't wannt to check each time from db for new
57 permissions since adding a new permission also requires application restart
82 permissions since adding a new permission also requires application restart
58 ie. to decorate new views with the newly created permission
83 ie. to decorate new views with the newly created permission
59 @param config:
84 @param config:
60 """
85 """
61 from pylons_app.model.meta import Session
86 from pylons_app.model.meta import Session
62 from pylons_app.model.db import Permission
87 from pylons_app.model.db import Permission
63 logging.info('getting information about all available permissions')
88 logging.info('getting information about all available permissions')
64 sa = Session()
89 sa = Session()
65 all_perms = sa.query(Permission).all()
90 all_perms = sa.query(Permission).all()
66 config['pylons.app_globals'].available_permissions = [x.permission_name for x in all_perms]
91 config['pylons.app_globals'].available_permissions = [x.permission_name for x in all_perms]
67
92
68
93
69
94
70 #===============================================================================
95 #===============================================================================
71 # DECORATORS
96 # DECORATORS
72 #===============================================================================
97 #===============================================================================
73 class LoginRequired(object):
98 class LoginRequired(object):
74 """
99 """
75 Must be logged in to execute this function else redirect to login page
100 Must be logged in to execute this function else redirect to login page
76 """
101 """
77 def __init__(self):
102 def __init__(self):
78 pass
103 pass
79
104
80 def __call__(self, func):
105 def __call__(self, func):
81
106
82 @wraps(func)
107 @wraps(func)
83 def _wrapper(*fargs, **fkwargs):
108 def _wrapper(*fargs, **fkwargs):
84 user = session.get('hg_app_user', AuthUser())
109 user = session.get('hg_app_user', AuthUser())
85 log.info('Checking login required for user:%s', user.username)
110 log.info('Checking login required for user:%s', user.username)
86 if user.is_authenticated:
111 if user.is_authenticated:
87 log.info('user %s is authenticated', user.username)
112 log.info('user %s is authenticated', user.username)
88 func(*fargs)
113 func(*fargs)
89 else:
114 else:
90 logging.info('user %s not authenticated', user.username)
115 logging.info('user %s not authenticated', user.username)
91 logging.info('redirecting to login page')
116 logging.info('redirecting to login page')
92 return redirect(url('login_home'))
117 return redirect(url('login_home'))
93
118
94 return _wrapper
119 return _wrapper
95
120
96 class PermsDecorator(object):
121 class PermsDecorator(object):
97
122
98 def __init__(self, *perms):
123 def __init__(self, *perms):
99 available_perms = g.available_permissions
124 available_perms = g.available_permissions
100 for perm in perms:
125 for perm in perms:
101 if perm not in available_perms:
126 if perm not in available_perms:
102 raise Exception("'%s' permission in not defined" % perm)
127 raise Exception("'%s' permission in not defined" % perm)
103 self.required_perms = set(perms)
128 self.required_perms = set(perms)
104 self.user_perms = set([])#propagate this list from somewhere.
129 self.user_perms = set([])#propagate this list from somewhere.
105
130
106 def __call__(self, func):
131 def __call__(self, func):
107 @wraps(func)
132 @wraps(func)
108 def _wrapper(*args, **kwargs):
133 def _wrapper(*args, **kwargs):
109 logging.info('checking %s permissions %s for %s',
134 logging.info('checking %s permissions %s for %s',
110 self.__class__.__name__[-3:], self.required_perms, func.__name__)
135 self.__class__.__name__[-3:], self.required_perms, func.__name__)
111
136
112 if self.check_permissions():
137 if self.check_permissions():
113 logging.info('Permission granted for %s', func.__name__)
138 logging.info('Permission granted for %s', func.__name__)
114 return func(*args, **kwargs)
139 return func(*args, **kwargs)
115
140
116 else:
141 else:
117 logging.warning('Permission denied for %s', func.__name__)
142 logging.warning('Permission denied for %s', func.__name__)
118 #redirect with forbidden ret code
143 #redirect with forbidden ret code
119 return redirect(url('access_denied'), 403)
144 return redirect(url('access_denied'), 403)
120 return _wrapper
145 return _wrapper
121
146
122
147
123 def check_permissions(self):
148 def check_permissions(self):
124 """
149 """
125 Dummy function for overiding
150 Dummy function for overiding
126 """
151 """
127 raise Exception('You have to write this function in child class')
152 raise Exception('You have to write this function in child class')
128
153
129 class CheckPermissionAll(PermsDecorator):
154 class CheckPermissionAll(PermsDecorator):
130 """
155 """
131 Checks for access permission for all given predicates. All of them have to
156 Checks for access permission for all given predicates. All of them have to
132 be meet in order to fulfill the request
157 be meet in order to fulfill the request
133 """
158 """
134
159
135 def check_permissions(self):
160 def check_permissions(self):
136 if self.required_perms.issubset(self.user_perms):
161 if self.required_perms.issubset(self.user_perms):
137 return True
162 return True
138 return False
163 return False
139
164
140
165
141 class CheckPermissionAny(PermsDecorator):
166 class CheckPermissionAny(PermsDecorator):
142 """
167 """
143 Checks for access permission for any of given predicates. In order to
168 Checks for access permission for any of given predicates. In order to
144 fulfill the request any of predicates must be meet
169 fulfill the request any of predicates must be meet
145 """
170 """
146
171
147 def check_permissions(self):
172 def check_permissions(self):
148 if self.required_perms.intersection(self.user_perms):
173 if self.required_perms.intersection(self.user_perms):
149 return True
174 return True
150 return False
175 return False
151
176
152
177
153
178
@@ -1,88 +1,114 b''
1 '''BACKUP MANAGER'''
1 #!/usr/bin/env python
2 # encoding: utf-8
3 # mercurial repository backup manager
4 # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
5
6 # This program is free software; you can redistribute it and/or
7 # modify it under the terms of the GNU General Public License
8 # as published by the Free Software Foundation; version 2
9 # of the License or (at your opinion) any later version of the license.
10 #
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
15 #
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19 # MA 02110-1301, USA.
20
21 """
22 Created on Feb 28, 2010
23 Mercurial repositories backup manager
24 @author: marcink
25 """
26
27
2 import logging
28 import logging
3 from mercurial import config
29 from mercurial import config
4 import tarfile
30 import tarfile
5 import os
31 import os
6 import datetime
32 import datetime
7 import sys
33 import sys
8 import subprocess
34 import subprocess
9 logging.basicConfig(level=logging.DEBUG,
35 logging.basicConfig(level=logging.DEBUG,
10 format="%(asctime)s %(levelname)-5.5s %(message)s")
36 format="%(asctime)s %(levelname)-5.5s %(message)s")
11
37
12 class BackupManager(object):
38 class BackupManager(object):
13 def __init__(self, id_rsa_path, repo_conf):
39 def __init__(self, id_rsa_path, repo_conf):
14 self.repos_path = None
40 self.repos_path = None
15 self.backup_file_name = None
41 self.backup_file_name = None
16 self.id_rsa_path = id_rsa_path
42 self.id_rsa_path = id_rsa_path
17 self.check_id_rsa()
43 self.check_id_rsa()
18 cur_dir = os.path.realpath(__file__)
44 cur_dir = os.path.realpath(__file__)
19 dn = os.path.dirname
45 dn = os.path.dirname
20 self.backup_file_path = os.path.join(dn(dn(dn(cur_dir))), 'data')
46 self.backup_file_path = os.path.join(dn(dn(dn(cur_dir))), 'data')
21 cfg = config.config()
47 cfg = config.config()
22 try:
48 try:
23 cfg.read(os.path.join(dn(dn(dn(cur_dir))), repo_conf))
49 cfg.read(os.path.join(dn(dn(dn(cur_dir))), repo_conf))
24 except IOError:
50 except IOError:
25 logging.error('Could not read %s', repo_conf)
51 logging.error('Could not read %s', repo_conf)
26 sys.exit()
52 sys.exit()
27 self.set_repos_path(cfg.items('paths'))
53 self.set_repos_path(cfg.items('paths'))
28 logging.info('starting backup for %s', self.repos_path)
54 logging.info('starting backup for %s', self.repos_path)
29 logging.info('backup target %s', self.backup_file_path)
55 logging.info('backup target %s', self.backup_file_path)
30
56
31 if not os.path.isdir(self.repos_path):
57 if not os.path.isdir(self.repos_path):
32 raise Exception('Not a valid directory in %s' % self.repos_path)
58 raise Exception('Not a valid directory in %s' % self.repos_path)
33
59
34 def check_id_rsa(self):
60 def check_id_rsa(self):
35 if not os.path.isfile(self.id_rsa_path):
61 if not os.path.isfile(self.id_rsa_path):
36 logging.error('Could not load id_rsa key file in %s',
62 logging.error('Could not load id_rsa key file in %s',
37 self.id_rsa_path)
63 self.id_rsa_path)
38 sys.exit()
64 sys.exit()
39
65
40 def set_repos_path(self, paths):
66 def set_repos_path(self, paths):
41 repos_path = paths[0][1].split('/')
67 repos_path = paths[0][1].split('/')
42 if repos_path[-1] in ['*', '**']:
68 if repos_path[-1] in ['*', '**']:
43 repos_path = repos_path[:-1]
69 repos_path = repos_path[:-1]
44 if repos_path[0] != '/':
70 if repos_path[0] != '/':
45 repos_path[0] = '/'
71 repos_path[0] = '/'
46 self.repos_path = os.path.join(*repos_path)
72 self.repos_path = os.path.join(*repos_path)
47
73
48 def backup_repos(self):
74 def backup_repos(self):
49 today = datetime.datetime.now().weekday() + 1
75 today = datetime.datetime.now().weekday() + 1
50 self.backup_file_name = "mercurial_repos.%s.tar.gz" % today
76 self.backup_file_name = "mercurial_repos.%s.tar.gz" % today
51 bckp_file = os.path.join(self.backup_file_path, self.backup_file_name)
77 bckp_file = os.path.join(self.backup_file_path, self.backup_file_name)
52 tar = tarfile.open(bckp_file, "w:gz")
78 tar = tarfile.open(bckp_file, "w:gz")
53
79
54 for dir_name in os.listdir(self.repos_path):
80 for dir_name in os.listdir(self.repos_path):
55 logging.info('backing up %s', dir_name)
81 logging.info('backing up %s', dir_name)
56 tar.add(os.path.join(self.repos_path, dir_name), dir_name)
82 tar.add(os.path.join(self.repos_path, dir_name), dir_name)
57 tar.close()
83 tar.close()
58 logging.info('finished backup of mercurial repositories')
84 logging.info('finished backup of mercurial repositories')
59
85
60
86
61
87
62 def transfer_files(self):
88 def transfer_files(self):
63 params = {
89 params = {
64 'id_rsa_key': self.id_rsa_path,
90 'id_rsa_key': self.id_rsa_path,
65 'backup_file_path':self.backup_file_path,
91 'backup_file_path':self.backup_file_path,
66 'backup_file_name':self.backup_file_name,
92 'backup_file_name':self.backup_file_name,
67 }
93 }
68 cmd = ['scp', '-l', '40000', '-i', '%(id_rsa_key)s' % params,
94 cmd = ['scp', '-l', '40000', '-i', '%(id_rsa_key)s' % params,
69 '%(backup_file_path)s/%(backup_file_name)s' % params,
95 '%(backup_file_path)s/%(backup_file_name)s' % params,
70 'root@192.168.2.102:/backups/mercurial' % params]
96 'root@192.168.2.102:/backups/mercurial' % params]
71
97
72 subprocess.call(cmd)
98 subprocess.call(cmd)
73 logging.info('Transfered file %s to %s', self.backup_file_name, cmd[4])
99 logging.info('Transfered file %s to %s', self.backup_file_name, cmd[4])
74
100
75
101
76 def rm_file(self):
102 def rm_file(self):
77 logging.info('Removing file %s', self.backup_file_name)
103 logging.info('Removing file %s', self.backup_file_name)
78 os.remove(os.path.join(self.backup_file_path, self.backup_file_name))
104 os.remove(os.path.join(self.backup_file_path, self.backup_file_name))
79
105
80
106
81
107
82 if __name__ == "__main__":
108 if __name__ == "__main__":
83 B_MANAGER = BackupManager('/home/pylons/id_rsa', 'repositories.config')
109 B_MANAGER = BackupManager('/home/pylons/id_rsa', 'repositories.config')
84 B_MANAGER.backup_repos()
110 B_MANAGER.backup_repos()
85 B_MANAGER.transfer_files()
111 B_MANAGER.transfer_files()
86 B_MANAGER.rm_file()
112 B_MANAGER.rm_file()
87
113
88
114
@@ -1,97 +1,123 b''
1 #!/usr/bin/env python
2 # encoding: utf-8
3 # database managment for hg app
4 # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
5
6 # This program is free software; you can redistribute it and/or
7 # modify it under the terms of the GNU General Public License
8 # as published by the Free Software Foundation; version 2
9 # of the License or (at your opinion) any later version of the license.
10 #
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
15 #
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19 # MA 02110-1301, USA.
20
21 """
22 Created on April 10, 2010
23 database managment and creation for hg app
24 @author: marcink
25 """
26
1 from os.path import dirname as dn, join as jn
27 from os.path import dirname as dn, join as jn
2 import os
28 import os
3 import sys
29 import sys
4 ROOT = dn(dn(dn(os.path.realpath(__file__))))
30 ROOT = dn(dn(dn(os.path.realpath(__file__))))
5 sys.path.append(ROOT)
31 sys.path.append(ROOT)
6
32
7 from pylons_app.lib.auth import get_crypt_password
33 from pylons_app.lib.auth import get_crypt_password
8 from pylons_app.model import init_model
34 from pylons_app.model import init_model
9 from pylons_app.model.db import User, Permission
35 from pylons_app.model.db import User, Permission
10 from pylons_app.model.meta import Session, Base
36 from pylons_app.model.meta import Session, Base
11 from sqlalchemy.engine import create_engine
37 from sqlalchemy.engine import create_engine
12 import logging
38 import logging
13
39
14 log = logging.getLogger('db manage')
40 log = logging.getLogger('db manage')
15 log.setLevel(logging.DEBUG)
41 log.setLevel(logging.DEBUG)
16 console_handler = logging.StreamHandler()
42 console_handler = logging.StreamHandler()
17 console_handler.setFormatter(logging.Formatter("%(asctime)s.%(msecs)03d"
43 console_handler.setFormatter(logging.Formatter("%(asctime)s.%(msecs)03d"
18 " %(levelname)-5.5s [%(name)s] %(message)s"))
44 " %(levelname)-5.5s [%(name)s] %(message)s"))
19 log.addHandler(console_handler)
45 log.addHandler(console_handler)
20
46
21 class DbManage(object):
47 class DbManage(object):
22 def __init__(self, log_sql):
48 def __init__(self, log_sql):
23 self.dbname = 'hg_app.db'
49 self.dbname = 'hg_app.db'
24 dburi = 'sqlite:////%s' % jn(ROOT, self.dbname)
50 dburi = 'sqlite:////%s' % jn(ROOT, self.dbname)
25 engine = create_engine(dburi, echo=log_sql)
51 engine = create_engine(dburi, echo=log_sql)
26 init_model(engine)
52 init_model(engine)
27 self.sa = Session()
53 self.sa = Session()
28 self.db_exists = False
54 self.db_exists = False
29
55
30 def check_for_db(self, override):
56 def check_for_db(self, override):
31 log.info('checking for exisiting db')
57 log.info('checking for exisiting db')
32 if os.path.isfile(jn(ROOT, self.dbname)):
58 if os.path.isfile(jn(ROOT, self.dbname)):
33 self.db_exists = True
59 self.db_exists = True
34 log.info('database exisist')
60 log.info('database exisist')
35 if not override:
61 if not override:
36 raise Exception('database already exists')
62 raise Exception('database already exists')
37
63
38 def create_tables(self, override=False):
64 def create_tables(self, override=False):
39 """
65 """
40 Create a auth database
66 Create a auth database
41 """
67 """
42 self.check_for_db(override)
68 self.check_for_db(override)
43 if override:
69 if override:
44 log.info("database exisist and it's going to be destroyed")
70 log.info("database exisist and it's going to be destroyed")
45 if self.db_exists:
71 if self.db_exists:
46 os.remove(jn(ROOT, self.dbname))
72 os.remove(jn(ROOT, self.dbname))
47 Base.metadata.create_all(checkfirst=override)
73 Base.metadata.create_all(checkfirst=override)
48 log.info('Created tables for %s', self.dbname)
74 log.info('Created tables for %s', self.dbname)
49
75
50 def admin_prompt(self):
76 def admin_prompt(self):
51 import getpass
77 import getpass
52 username = raw_input('Specify admin username:')
78 username = raw_input('Specify admin username:')
53 password = getpass.getpass('Specify admin password:')
79 password = getpass.getpass('Specify admin password:')
54 self.create_user(username, password, True)
80 self.create_user(username, password, True)
55
81
56 def create_user(self, username, password, admin=False):
82 def create_user(self, username, password, admin=False):
57 log.info('creating administrator user %s', username)
83 log.info('creating administrator user %s', username)
58
84
59 new_user = User()
85 new_user = User()
60 new_user.username = username
86 new_user.username = username
61 new_user.password = get_crypt_password(password)
87 new_user.password = get_crypt_password(password)
62 new_user.admin = admin
88 new_user.admin = admin
63 new_user.active = True
89 new_user.active = True
64
90
65 try:
91 try:
66 self.sa.add(new_user)
92 self.sa.add(new_user)
67 self.sa.commit()
93 self.sa.commit()
68 except:
94 except:
69 self.sa.rollback()
95 self.sa.rollback()
70 raise
96 raise
71
97
72 def create_permissions(self):
98 def create_permissions(self):
73 #module.(access|create|change|delete)_[name]
99 #module.(access|create|change|delete)_[name]
74 perms = [('admin.access_home', 'Access to admin user view'),
100 perms = [('admin.access_home', 'Access to admin user view'),
75
101
76 ]
102 ]
77
103
78 for p in perms:
104 for p in perms:
79 new_perm = Permission()
105 new_perm = Permission()
80 new_perm.permission_name = p[0]
106 new_perm.permission_name = p[0]
81 new_perm.permission_longname = p[1]
107 new_perm.permission_longname = p[1]
82 try:
108 try:
83 self.sa.add(new_perm)
109 self.sa.add(new_perm)
84 self.sa.commit()
110 self.sa.commit()
85 except:
111 except:
86 self.sa.rollback()
112 self.sa.rollback()
87 raise
113 raise
88
114
89
115
90
116
91 if __name__ == '__main__':
117 if __name__ == '__main__':
92 dbmanage = DbManage(log_sql=True)
118 dbmanage = DbManage(log_sql=True)
93 dbmanage.create_tables(override=True)
119 dbmanage.create_tables(override=True)
94 dbmanage.admin_prompt()
120 dbmanage.admin_prompt()
95 dbmanage.create_permissions()
121 dbmanage.create_permissions()
96
122
97
123
@@ -1,23 +1,49 b''
1 #!/usr/bin/env python
2 # encoding: utf-8
3 # simple filters for hg apps html templates
4 # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
5
6 # This program is free software; you can redistribute it and/or
7 # modify it under the terms of the GNU General Public License
8 # as published by the Free Software Foundation; version 2
9 # of the License or (at your opinion) any later version of the license.
10 #
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
15 #
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19 # MA 02110-1301, USA.
20
21 """
22 Created on April 12, 2010
23 simple filters for hg apps html templates
24 @author: marcink
25 """
26
1 from mercurial import util
27 from mercurial import util
2 from mercurial.templatefilters import age as _age, person as _person
28 from mercurial.templatefilters import age as _age, person as _person
3 from string import punctuation
29 from string import punctuation
4
30
5 def clean_repo(repo_name):
31 def clean_repo(repo_name):
6 for x in punctuation:
32 for x in punctuation:
7 if x != '_':
33 if x != '_':
8 repo_name = repo_name.replace(x, '')
34 repo_name = repo_name.replace(x, '')
9 repo_name = repo_name.lower().strip()
35 repo_name = repo_name.lower().strip()
10 return repo_name.replace(' ', '_')
36 return repo_name.replace(' ', '_')
11
37
12 age = lambda x:_age(x)
38 age = lambda x:_age(x)
13 capitalize = lambda x: x.capitalize()
39 capitalize = lambda x: x.capitalize()
14 date = lambda x: util.datestr(x)
40 date = lambda x: util.datestr(x)
15 email = util.email
41 email = util.email
16 person = lambda x: _person(x)
42 person = lambda x: _person(x)
17 hgdate = lambda x: "%d %d" % x
43 hgdate = lambda x: "%d %d" % x
18 isodate = lambda x: util.datestr(x, '%Y-%m-%d %H:%M %1%2')
44 isodate = lambda x: util.datestr(x, '%Y-%m-%d %H:%M %1%2')
19 isodatesec = lambda x: util.datestr(x, '%Y-%m-%d %H:%M:%S %1%2')
45 isodatesec = lambda x: util.datestr(x, '%Y-%m-%d %H:%M:%S %1%2')
20 localdate = lambda x: (x[0], util.makedate()[1])
46 localdate = lambda x: (x[0], util.makedate()[1])
21 rfc822date = lambda x: util.datestr(x, "%a, %d %b %Y %H:%M:%S %1%2")
47 rfc822date = lambda x: util.datestr(x, "%a, %d %b %Y %H:%M:%S %1%2")
22 rfc3339date = lambda x: util.datestr(x, "%Y-%m-%dT%H:%M:%S%1:%2")
48 rfc3339date = lambda x: util.datestr(x, "%Y-%m-%dT%H:%M:%S%1:%2")
23 time_ago = lambda x: util.datestr(_age(x), "%a, %d %b %Y %H:%M:%S %1%2")
49 time_ago = lambda x: util.datestr(_age(x), "%a, %d %b %Y %H:%M:%S %1%2")
@@ -1,21 +1,47 b''
1 #!/usr/bin/env python
2 # encoding: utf-8
3 # middleware to handle https correctly
4 # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
5
6 # This program is free software; you can redistribute it and/or
7 # modify it under the terms of the GNU General Public License
8 # as published by the Free Software Foundation; version 2
9 # of the License or (at your opinion) any later version of the license.
10 #
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
15 #
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19 # MA 02110-1301, USA.
20
21 """
22 Created on May 23, 2010
23
24 @author: marcink
25 """
26
1 class HttpsFixup(object):
27 class HttpsFixup(object):
2 def __init__(self, app):
28 def __init__(self, app):
3 self.application = app
29 self.application = app
4
30
5 def __call__(self, environ, start_response):
31 def __call__(self, environ, start_response):
6 self.__fixup(environ)
32 self.__fixup(environ)
7 return self.application(environ, start_response)
33 return self.application(environ, start_response)
8
34
9
35
10 def __fixup(self, environ):
36 def __fixup(self, environ):
11 """Function to fixup the environ as needed. In order to use this
37 """Function to fixup the environ as needed. In order to use this
12 middleware you should set this header inside your
38 middleware you should set this header inside your
13 proxy ie. nginx, apache etc.
39 proxy ie. nginx, apache etc.
14 """
40 """
15 proto = environ.get('HTTP_X_URL_SCHEME')
41 proto = environ.get('HTTP_X_URL_SCHEME')
16
42
17 if proto == 'https':
43 if proto == 'https':
18 environ['wsgi.url_scheme'] = proto
44 environ['wsgi.url_scheme'] = proto
19 else:
45 else:
20 environ['wsgi.url_scheme'] = 'http'
46 environ['wsgi.url_scheme'] = 'http'
21 return None
47 return None
@@ -1,138 +1,153 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 # encoding: utf-8
2 # encoding: utf-8
3 # middleware to handle mercurial api calls
4 # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
5
6 # This program is free software; you can redistribute it and/or
7 # modify it under the terms of the GNU General Public License
8 # as published by the Free Software Foundation; version 2
9 # of the License or (at your opinion) any later version of the license.
3 #
10 #
4 # Copyright (c) 2010 marcink. All rights reserved.
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
5 #
15 #
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19 # MA 02110-1301, USA.
20
6 """
21 """
7 Created on 2010-04-28
22 Created on 2010-04-28
8
23
9 @author: marcink
24 @author: marcink
10 SimpleHG middleware for handling mercurial protocol request (push/clone etc.)
25 SimpleHG middleware for handling mercurial protocol request (push/clone etc.)
11 It's implemented with basic auth function
26 It's implemented with basic auth function
12 """
27 """
13 from datetime import datetime
28 from datetime import datetime
14 from mercurial.hgweb import hgweb
29 from mercurial.hgweb import hgweb
15 from mercurial.hgweb.request import wsgiapplication
30 from mercurial.hgweb.request import wsgiapplication
16 from paste.auth.basic import AuthBasicAuthenticator
31 from paste.auth.basic import AuthBasicAuthenticator
17 from paste.httpheaders import REMOTE_USER, AUTH_TYPE
32 from paste.httpheaders import REMOTE_USER, AUTH_TYPE
18 from pylons_app.lib.auth import authfunc
33 from pylons_app.lib.auth import authfunc
19 from pylons_app.lib.utils import is_mercurial, make_ui, invalidate_cache
34 from pylons_app.lib.utils import is_mercurial, make_ui, invalidate_cache
20 from pylons_app.model import meta
35 from pylons_app.model import meta
21 from pylons_app.model.db import UserLog, User
36 from pylons_app.model.db import UserLog, User
22 from webob.exc import HTTPNotFound
37 from webob.exc import HTTPNotFound
23 import logging
38 import logging
24 import os
39 import os
25 log = logging.getLogger(__name__)
40 log = logging.getLogger(__name__)
26
41
27 class SimpleHg(object):
42 class SimpleHg(object):
28
43
29 def __init__(self, application, config):
44 def __init__(self, application, config):
30 self.application = application
45 self.application = application
31 self.config = config
46 self.config = config
32 #authenticate this mercurial request using
47 #authenticate this mercurial request using
33 realm = '%s %s' % (config['hg_app_name'], 'mercurial repository')
48 realm = '%s %s' % (config['hg_app_name'], 'mercurial repository')
34 self.authenticate = AuthBasicAuthenticator(realm, authfunc)
49 self.authenticate = AuthBasicAuthenticator(realm, authfunc)
35
50
36 def __call__(self, environ, start_response):
51 def __call__(self, environ, start_response):
37 if not is_mercurial(environ):
52 if not is_mercurial(environ):
38 return self.application(environ, start_response)
53 return self.application(environ, start_response)
39 else:
54 else:
40 #===================================================================
55 #===================================================================
41 # AUTHENTICATE THIS MERCURIAL REQUEST
56 # AUTHENTICATE THIS MERCURIAL REQUEST
42 #===================================================================
57 #===================================================================
43 username = REMOTE_USER(environ)
58 username = REMOTE_USER(environ)
44 if not username:
59 if not username:
45 result = self.authenticate(environ)
60 result = self.authenticate(environ)
46 if isinstance(result, str):
61 if isinstance(result, str):
47 AUTH_TYPE.update(environ, 'basic')
62 AUTH_TYPE.update(environ, 'basic')
48 REMOTE_USER.update(environ, result)
63 REMOTE_USER.update(environ, result)
49 else:
64 else:
50 return result.wsgi_application(environ, start_response)
65 return result.wsgi_application(environ, start_response)
51
66
52 try:
67 try:
53 repo_name = '/'.join(environ['PATH_INFO'].split('/')[1:])
68 repo_name = '/'.join(environ['PATH_INFO'].split('/')[1:])
54 except Exception as e:
69 except Exception as e:
55 log.error(e)
70 log.error(e)
56 return HTTPNotFound()(environ, start_response)
71 return HTTPNotFound()(environ, start_response)
57
72
58 #since we wrap into hgweb, just reset the path
73 #since we wrap into hgweb, just reset the path
59 environ['PATH_INFO'] = '/'
74 environ['PATH_INFO'] = '/'
60 self.baseui = make_ui(self.config['hg_app_repo_conf'])
75 self.baseui = make_ui(self.config['hg_app_repo_conf'])
61 self.basepath = self.baseui.configitems('paths')[0][1]\
76 self.basepath = self.baseui.configitems('paths')[0][1]\
62 .replace('*', '')
77 .replace('*', '')
63 self.repo_path = os.path.join(self.basepath, repo_name)
78 self.repo_path = os.path.join(self.basepath, repo_name)
64 try:
79 try:
65 app = wsgiapplication(self.__make_app)
80 app = wsgiapplication(self.__make_app)
66 except Exception as e:
81 except Exception as e:
67 log.error(e)
82 log.error(e)
68 return HTTPNotFound()(environ, start_response)
83 return HTTPNotFound()(environ, start_response)
69 action = self.__get_action(environ)
84 action = self.__get_action(environ)
70 #invalidate cache on push
85 #invalidate cache on push
71 if action == 'push':
86 if action == 'push':
72 self.__invalidate_cache(repo_name)
87 self.__invalidate_cache(repo_name)
73
88
74 if action:
89 if action:
75 username = self.__get_environ_user(environ)
90 username = self.__get_environ_user(environ)
76 self.__log_user_action(username, action, repo_name)
91 self.__log_user_action(username, action, repo_name)
77
92
78 return app(environ, start_response)
93 return app(environ, start_response)
79
94
80 def __make_app(self):
95 def __make_app(self):
81 hgserve = hgweb(self.repo_path)
96 hgserve = hgweb(self.repo_path)
82 return self.__load_web_settings(hgserve)
97 return self.__load_web_settings(hgserve)
83
98
84 def __get_environ_user(self, environ):
99 def __get_environ_user(self, environ):
85 return environ.get('REMOTE_USER')
100 return environ.get('REMOTE_USER')
86
101
87 def __get_action(self, environ):
102 def __get_action(self, environ):
88 """
103 """
89 Maps mercurial request commands into a pull or push command.
104 Maps mercurial request commands into a pull or push command.
90 @param environ:
105 @param environ:
91 """
106 """
92 mapping = {
107 mapping = {
93 'changegroup': 'pull',
108 'changegroup': 'pull',
94 'changegroupsubset': 'pull',
109 'changegroupsubset': 'pull',
95 'unbundle': 'push',
110 'unbundle': 'push',
96 'stream_out': 'pull',
111 'stream_out': 'pull',
97 }
112 }
98 for qry in environ['QUERY_STRING'].split('&'):
113 for qry in environ['QUERY_STRING'].split('&'):
99 if qry.startswith('cmd'):
114 if qry.startswith('cmd'):
100 cmd = qry.split('=')[-1]
115 cmd = qry.split('=')[-1]
101 if mapping.has_key(cmd):
116 if mapping.has_key(cmd):
102 return mapping[cmd]
117 return mapping[cmd]
103
118
104 def __log_user_action(self, username, action, repo):
119 def __log_user_action(self, username, action, repo):
105 sa = meta.Session
120 sa = meta.Session
106 try:
121 try:
107 user = sa.query(User).filter(User.username == username).one()
122 user = sa.query(User).filter(User.username == username).one()
108 user_log = UserLog()
123 user_log = UserLog()
109 user_log.user_id = user.user_id
124 user_log.user_id = user.user_id
110 user_log.action = action
125 user_log.action = action
111 user_log.repository = repo.replace('/', '')
126 user_log.repository = repo.replace('/', '')
112 user_log.action_date = datetime.now()
127 user_log.action_date = datetime.now()
113 sa.add(user_log)
128 sa.add(user_log)
114 sa.commit()
129 sa.commit()
115 log.info('Adding user %s, action %s on %s',
130 log.info('Adding user %s, action %s on %s',
116 username, action, repo)
131 username, action, repo)
117 except Exception as e:
132 except Exception as e:
118 sa.rollback()
133 sa.rollback()
119 log.error('could not log user action:%s', str(e))
134 log.error('could not log user action:%s', str(e))
120
135
121 def __invalidate_cache(self, repo_name):
136 def __invalidate_cache(self, repo_name):
122 """we know that some change was made to repositories and we should
137 """we know that some change was made to repositories and we should
123 invalidate the cache to see the changes right away but only for
138 invalidate the cache to see the changes right away but only for
124 push requests"""
139 push requests"""
125 invalidate_cache('cached_repo_list')
140 invalidate_cache('cached_repo_list')
126 invalidate_cache('full_changelog', repo_name)
141 invalidate_cache('full_changelog', repo_name)
127
142
128
143
129 def __load_web_settings(self, hgserve):
144 def __load_web_settings(self, hgserve):
130 repoui = make_ui(os.path.join(self.repo_path, '.hg', 'hgrc'), False)
145 repoui = make_ui(os.path.join(self.repo_path, '.hg', 'hgrc'), False)
131 #set the global ui for hgserve
146 #set the global ui for hgserve
132 hgserve.repo.ui = self.baseui
147 hgserve.repo.ui = self.baseui
133
148
134 if repoui:
149 if repoui:
135 #set the repository based config
150 #set the repository based config
136 hgserve.repo.ui = repoui
151 hgserve.repo.ui = repoui
137
152
138 return hgserve
153 return hgserve
@@ -1,134 +1,160 b''
1 #!/usr/bin/env python
2 # encoding: utf-8
3 # Utilities for hg app
4 # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
5
6 # This program is free software; you can redistribute it and/or
7 # modify it under the terms of the GNU General Public License
8 # as published by the Free Software Foundation; version 2
9 # of the License or (at your opinion) any later version of the license.
10 #
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
15 #
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19 # MA 02110-1301, USA.
20
21 """
22 Created on April 18, 2010
23 Utilities for hg app
24 @author: marcink
25 """
26
1 import os
27 import os
2 import logging
28 import logging
3 from mercurial import ui, config, hg
29 from mercurial import ui, config, hg
4 from mercurial.error import RepoError
30 from mercurial.error import RepoError
5 log = logging.getLogger(__name__)
31 log = logging.getLogger(__name__)
6
32
7
33
8 def get_repo_slug(request):
34 def get_repo_slug(request):
9 return request.environ['pylons.routes_dict'].get('repo_name')
35 return request.environ['pylons.routes_dict'].get('repo_name')
10
36
11 def is_mercurial(environ):
37 def is_mercurial(environ):
12 """
38 """
13 Returns True if request's target is mercurial server - header
39 Returns True if request's target is mercurial server - header
14 ``HTTP_ACCEPT`` of such request would start with ``application/mercurial``.
40 ``HTTP_ACCEPT`` of such request would start with ``application/mercurial``.
15 """
41 """
16 http_accept = environ.get('HTTP_ACCEPT')
42 http_accept = environ.get('HTTP_ACCEPT')
17 if http_accept and http_accept.startswith('application/mercurial'):
43 if http_accept and http_accept.startswith('application/mercurial'):
18 return True
44 return True
19 return False
45 return False
20
46
21 def check_repo_dir(paths):
47 def check_repo_dir(paths):
22 repos_path = paths[0][1].split('/')
48 repos_path = paths[0][1].split('/')
23 if repos_path[-1] in ['*', '**']:
49 if repos_path[-1] in ['*', '**']:
24 repos_path = repos_path[:-1]
50 repos_path = repos_path[:-1]
25 if repos_path[0] != '/':
51 if repos_path[0] != '/':
26 repos_path[0] = '/'
52 repos_path[0] = '/'
27 if not os.path.isdir(os.path.join(*repos_path)):
53 if not os.path.isdir(os.path.join(*repos_path)):
28 raise Exception('Not a valid repository in %s' % paths[0][1])
54 raise Exception('Not a valid repository in %s' % paths[0][1])
29
55
30 def check_repo(repo_name, base_path):
56 def check_repo(repo_name, base_path):
31
57
32 repo_path = os.path.join(base_path, repo_name)
58 repo_path = os.path.join(base_path, repo_name)
33
59
34 try:
60 try:
35 r = hg.repository(ui.ui(), repo_path)
61 r = hg.repository(ui.ui(), repo_path)
36 hg.verify(r)
62 hg.verify(r)
37 #here we hnow that repo exists it was verified
63 #here we hnow that repo exists it was verified
38 log.info('%s repo is already created', repo_name)
64 log.info('%s repo is already created', repo_name)
39 return False
65 return False
40 #raise Exception('Repo exists')
66 #raise Exception('Repo exists')
41 except RepoError:
67 except RepoError:
42 log.info('%s repo is free for creation', repo_name)
68 log.info('%s repo is free for creation', repo_name)
43 #it means that there is no valid repo there...
69 #it means that there is no valid repo there...
44 return True
70 return True
45
71
46 def make_ui(path=None, checkpaths=True):
72 def make_ui(path=None, checkpaths=True):
47 """
73 """
48 A funcion that will read python rc files and make an ui from read options
74 A funcion that will read python rc files and make an ui from read options
49
75
50 @param path: path to mercurial config file
76 @param path: path to mercurial config file
51 """
77 """
52 if not path:
78 if not path:
53 log.error('repos config path is empty !')
79 log.error('repos config path is empty !')
54
80
55 if not os.path.isfile(path):
81 if not os.path.isfile(path):
56 log.warning('Unable to read config file %s' % path)
82 log.warning('Unable to read config file %s' % path)
57 return False
83 return False
58 #propagated from mercurial documentation
84 #propagated from mercurial documentation
59 sections = [
85 sections = [
60 'alias',
86 'alias',
61 'auth',
87 'auth',
62 'decode/encode',
88 'decode/encode',
63 'defaults',
89 'defaults',
64 'diff',
90 'diff',
65 'email',
91 'email',
66 'extensions',
92 'extensions',
67 'format',
93 'format',
68 'merge-patterns',
94 'merge-patterns',
69 'merge-tools',
95 'merge-tools',
70 'hooks',
96 'hooks',
71 'http_proxy',
97 'http_proxy',
72 'smtp',
98 'smtp',
73 'patch',
99 'patch',
74 'paths',
100 'paths',
75 'profiling',
101 'profiling',
76 'server',
102 'server',
77 'trusted',
103 'trusted',
78 'ui',
104 'ui',
79 'web',
105 'web',
80 ]
106 ]
81
107
82 baseui = ui.ui()
108 baseui = ui.ui()
83 cfg = config.config()
109 cfg = config.config()
84 cfg.read(path)
110 cfg.read(path)
85 if checkpaths:check_repo_dir(cfg.items('paths'))
111 if checkpaths:check_repo_dir(cfg.items('paths'))
86
112
87 for section in sections:
113 for section in sections:
88 for k, v in cfg.items(section):
114 for k, v in cfg.items(section):
89 baseui.setconfig(section, k, v)
115 baseui.setconfig(section, k, v)
90
116
91 return baseui
117 return baseui
92
118
93 def invalidate_cache(name, *args):
119 def invalidate_cache(name, *args):
94 """Invalidates given name cache"""
120 """Invalidates given name cache"""
95
121
96 from beaker.cache import region_invalidate
122 from beaker.cache import region_invalidate
97 log.info('INVALIDATING CACHE FOR %s', name)
123 log.info('INVALIDATING CACHE FOR %s', name)
98
124
99 """propagate our arguments to make sure invalidation works. First
125 """propagate our arguments to make sure invalidation works. First
100 argument has to be the name of cached func name give to cache decorator
126 argument has to be the name of cached func name give to cache decorator
101 without that the invalidation would not work"""
127 without that the invalidation would not work"""
102 tmp = [name]
128 tmp = [name]
103 tmp.extend(args)
129 tmp.extend(args)
104 args = tuple(tmp)
130 args = tuple(tmp)
105
131
106 if name == 'cached_repo_list':
132 if name == 'cached_repo_list':
107 from pylons_app.model.hg_model import _get_repos_cached
133 from pylons_app.model.hg_model import _get_repos_cached
108 region_invalidate(_get_repos_cached, None, *args)
134 region_invalidate(_get_repos_cached, None, *args)
109
135
110 if name == 'full_changelog':
136 if name == 'full_changelog':
111 from pylons_app.model.hg_model import _full_changelog_cached
137 from pylons_app.model.hg_model import _full_changelog_cached
112 region_invalidate(_full_changelog_cached, None, *args)
138 region_invalidate(_full_changelog_cached, None, *args)
113
139
114 from vcs.backends.base import BaseChangeset
140 from vcs.backends.base import BaseChangeset
115 from vcs.utils.lazy import LazyProperty
141 from vcs.utils.lazy import LazyProperty
116 class EmptyChangeset(BaseChangeset):
142 class EmptyChangeset(BaseChangeset):
117
143
118 revision = -1
144 revision = -1
119
145
120 @LazyProperty
146 @LazyProperty
121 def raw_id(self):
147 def raw_id(self):
122 """
148 """
123 Returns raw string identifing this changeset, useful for web
149 Returns raw string identifing this changeset, useful for web
124 representation.
150 representation.
125 """
151 """
126 return '0' * 12
152 return '0' * 12
127
153
128
154
129 def repo2db_mapper():
155 def repo2db_mapper():
130 """
156 """
131 scann all dirs for .hgdbid
157 scann all dirs for .hgdbid
132 if some dir doesn't have one generate one.
158 if some dir doesn't have one generate one.
133 """
159 """
134 pass No newline at end of file
160 pass
@@ -1,126 +1,141 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 # encoding: utf-8
2 # encoding: utf-8
3 #
3 # Model for hg app
4 # Copyright (c) 2010 marcink. All rights reserved.
4 # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
5
6 # This program is free software; you can redistribute it and/or
7 # modify it under the terms of the GNU General Public License
8 # as published by the Free Software Foundation; version 2
9 # of the License or (at your opinion) any later version of the license.
5 #
10 #
6 '''
11 # This program is distributed in the hope that it will be useful,
7 Created on Apr 9, 2010
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
15 #
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19 # MA 02110-1301, USA.
8
20
21 """
22 Created on April 9, 2010
23 Model for hg app
9 @author: marcink
24 @author: marcink
10 '''
25 """
11
26
12 from beaker.cache import cache_region
27 from beaker.cache import cache_region
13 from mercurial import ui
28 from mercurial import ui
14 from mercurial.hgweb.hgwebdir_mod import findrepos
29 from mercurial.hgweb.hgwebdir_mod import findrepos
15 from pylons import app_globals as g
30 from pylons import app_globals as g
16 from vcs.exceptions import RepositoryError, VCSError
31 from vcs.exceptions import RepositoryError, VCSError
17 import logging
32 import logging
18 import os
33 import os
19 import sys
34 import sys
20 log = logging.getLogger(__name__)
35 log = logging.getLogger(__name__)
21
36
22 try:
37 try:
23 from vcs.backends.hg import MercurialRepository
38 from vcs.backends.hg import MercurialRepository
24 except ImportError:
39 except ImportError:
25 sys.stderr.write('You have to import vcs module')
40 sys.stderr.write('You have to import vcs module')
26 raise Exception('Unable to import vcs')
41 raise Exception('Unable to import vcs')
27
42
28
43
29 @cache_region('long_term', 'cached_repo_list')
44 @cache_region('long_term', 'cached_repo_list')
30 def _get_repos_cached():
45 def _get_repos_cached():
31 """
46 """
32 return cached dict with repos
47 return cached dict with repos
33 """
48 """
34 return HgModel.repo_scan(g.paths[0][0], g.paths[0][1], g.baseui)
49 return HgModel.repo_scan(g.paths[0][0], g.paths[0][1], g.baseui)
35
50
36 @cache_region('long_term', 'full_changelog')
51 @cache_region('long_term', 'full_changelog')
37 def _full_changelog_cached(repo_name):
52 def _full_changelog_cached(repo_name):
38 log.info('getting full changelog for %s', repo_name)
53 log.info('getting full changelog for %s', repo_name)
39 return list(reversed(list(HgModel().get_repo(repo_name))))
54 return list(reversed(list(HgModel().get_repo(repo_name))))
40
55
41 class HgModel(object):
56 class HgModel(object):
42 """
57 """
43 Mercurial Model
58 Mercurial Model
44 """
59 """
45
60
46 def __init__(self):
61 def __init__(self):
47 """
62 """
48 Constructor
63 Constructor
49 """
64 """
50 pass
65 pass
51
66
52 @staticmethod
67 @staticmethod
53 def repo_scan(repos_prefix, repos_path, baseui):
68 def repo_scan(repos_prefix, repos_path, baseui):
54 """
69 """
55 Listing of repositories in given path. This path should not be a
70 Listing of repositories in given path. This path should not be a
56 repository itself. Return a dictionary of repository objects
71 repository itself. Return a dictionary of repository objects
57 :param repos_path: path to directory it could take syntax with
72 :param repos_path: path to directory it could take syntax with
58 * or ** for deep recursive displaying repositories
73 * or ** for deep recursive displaying repositories
59 """
74 """
60 def check_repo_dir(path):
75 def check_repo_dir(path):
61 """
76 """
62 Checks the repository
77 Checks the repository
63 :param path:
78 :param path:
64 """
79 """
65 repos_path = path.split('/')
80 repos_path = path.split('/')
66 if repos_path[-1] in ['*', '**']:
81 if repos_path[-1] in ['*', '**']:
67 repos_path = repos_path[:-1]
82 repos_path = repos_path[:-1]
68 if repos_path[0] != '/':
83 if repos_path[0] != '/':
69 repos_path[0] = '/'
84 repos_path[0] = '/'
70 if not os.path.isdir(os.path.join(*repos_path)):
85 if not os.path.isdir(os.path.join(*repos_path)):
71 raise RepositoryError('Not a valid repository in %s' % path[0][1])
86 raise RepositoryError('Not a valid repository in %s' % path[0][1])
72 if not repos_path.endswith('*'):
87 if not repos_path.endswith('*'):
73 raise VCSError('You need to specify * or ** at the end of path '
88 raise VCSError('You need to specify * or ** at the end of path '
74 'for recursive scanning')
89 'for recursive scanning')
75
90
76 check_repo_dir(repos_path)
91 check_repo_dir(repos_path)
77 log.info('scanning for repositories in %s', repos_path)
92 log.info('scanning for repositories in %s', repos_path)
78 repos = findrepos([(repos_prefix, repos_path)])
93 repos = findrepos([(repos_prefix, repos_path)])
79 if not isinstance(baseui, ui.ui):
94 if not isinstance(baseui, ui.ui):
80 baseui = ui.ui()
95 baseui = ui.ui()
81
96
82 repos_list = {}
97 repos_list = {}
83 for name, path in repos:
98 for name, path in repos:
84 try:
99 try:
85 #name = name.split('/')[-1]
100 #name = name.split('/')[-1]
86 if repos_list.has_key(name):
101 if repos_list.has_key(name):
87 raise RepositoryError('Duplicate repository name %s found in'
102 raise RepositoryError('Duplicate repository name %s found in'
88 ' %s' % (name, path))
103 ' %s' % (name, path))
89 else:
104 else:
90 repos_list[name] = MercurialRepository(path, baseui=baseui)
105 repos_list[name] = MercurialRepository(path, baseui=baseui)
91 repos_list[name].name = name
106 repos_list[name].name = name
92 except OSError:
107 except OSError:
93 continue
108 continue
94 return repos_list
109 return repos_list
95
110
96 def get_repos(self):
111 def get_repos(self):
97 for name, repo in _get_repos_cached().items():
112 for name, repo in _get_repos_cached().items():
98 if repo._get_hidden():
113 if repo._get_hidden():
99 #skip hidden web repository
114 #skip hidden web repository
100 continue
115 continue
101
116
102 last_change = repo.last_change
117 last_change = repo.last_change
103 try:
118 try:
104 tip = repo.get_changeset('tip')
119 tip = repo.get_changeset('tip')
105 except RepositoryError:
120 except RepositoryError:
106 from pylons_app.lib.utils import EmptyChangeset
121 from pylons_app.lib.utils import EmptyChangeset
107 tip = EmptyChangeset()
122 tip = EmptyChangeset()
108
123
109 tmp_d = {}
124 tmp_d = {}
110 tmp_d['name'] = repo.name
125 tmp_d['name'] = repo.name
111 tmp_d['name_sort'] = tmp_d['name'].lower()
126 tmp_d['name_sort'] = tmp_d['name'].lower()
112 tmp_d['description'] = repo.description
127 tmp_d['description'] = repo.description
113 tmp_d['description_sort'] = tmp_d['description']
128 tmp_d['description_sort'] = tmp_d['description']
114 tmp_d['last_change'] = last_change
129 tmp_d['last_change'] = last_change
115 tmp_d['last_change_sort'] = last_change[1] - last_change[0]
130 tmp_d['last_change_sort'] = last_change[1] - last_change[0]
116 tmp_d['tip'] = tip.raw_id
131 tmp_d['tip'] = tip.raw_id
117 tmp_d['tip_sort'] = tip.revision
132 tmp_d['tip_sort'] = tip.revision
118 tmp_d['rev'] = tip.revision
133 tmp_d['rev'] = tip.revision
119 tmp_d['contact'] = repo.contact
134 tmp_d['contact'] = repo.contact
120 tmp_d['contact_sort'] = tmp_d['contact']
135 tmp_d['contact_sort'] = tmp_d['contact']
121 tmp_d['repo_archives'] = list(repo._get_archives())
136 tmp_d['repo_archives'] = list(repo._get_archives())
122
137
123 yield tmp_d
138 yield tmp_d
124
139
125 def get_repo(self, repo_name):
140 def get_repo(self, repo_name):
126 return _get_repos_cached()[repo_name]
141 return _get_repos_cached()[repo_name]
@@ -1,48 +1,64 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 # encoding: utf-8
2 # encoding: utf-8
3 # Model for users
4 # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
5
6 # This program is free software; you can redistribute it and/or
7 # modify it under the terms of the GNU General Public License
8 # as published by the Free Software Foundation; version 2
9 # of the License or (at your opinion) any later version of the license.
3 #
10 #
4 # Copyright (c) 2010 marcink. All rights reserved.
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
5 #
15 #
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19 # MA 02110-1301, USA.
20
21 """
22 Created on April 9, 2010
23 Model for users
24 @author: marcink
25 """
26
6 from pylons_app.model.db import User
27 from pylons_app.model.db import User
7 from pylons_app.model.meta import Session
28 from pylons_app.model.meta import Session
8 '''
9 Created on Apr 9, 2010
10
11 @author: marcink
12 '''
13
29
14 class UserModel(object):
30 class UserModel(object):
15
31
16 def __init__(self):
32 def __init__(self):
17 self.sa = Session()
33 self.sa = Session()
18
34
19 def get_user(self, id):
35 def get_user(self, id):
20 return self.sa.query(User).get(id)
36 return self.sa.query(User).get(id)
21
37
22 def create(self, form_data):
38 def create(self, form_data):
23 try:
39 try:
24 new_user = User()
40 new_user = User()
25 for k, v in form_data.items():
41 for k, v in form_data.items():
26 setattr(new_user, k, v)
42 setattr(new_user, k, v)
27
43
28 self.sa.add(new_user)
44 self.sa.add(new_user)
29 self.sa.commit()
45 self.sa.commit()
30 except:
46 except:
31 self.sa.rollback()
47 self.sa.rollback()
32 raise
48 raise
33
49
34 def update(self, id, form_data):
50 def update(self, id, form_data):
35 try:
51 try:
36 new_user = self.sa.query(User).get(id)
52 new_user = self.sa.query(User).get(id)
37 for k, v in form_data.items():
53 for k, v in form_data.items():
38 if k == 'new_password' and v != '':
54 if k == 'new_password' and v != '':
39
55
40 new_user.password = v
56 new_user.password = v
41 else:
57 else:
42 setattr(new_user, k, v)
58 setattr(new_user, k, v)
43
59
44 self.sa.add(new_user)
60 self.sa.add(new_user)
45 self.sa.commit()
61 self.sa.commit()
46 except:
62 except:
47 self.sa.rollback()
63 self.sa.rollback()
48 raise
64 raise
@@ -1,21 +1,21 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <%inherit file="/base/base.html"/>
2 <%inherit file="/base/base.html"/>
3
3
4 <%def name="title()">
4 <%def name="title()">
5 ${_('Permissions administration')}
5 ${_('Permissions administration')}
6 </%def>
6 </%def>
7 <%def name="breadcrumbs()">
7 <%def name="breadcrumbs()">
8 ${h.link_to(u'Admin',h.url('admin_home'))}
8 ${h.link_to(u'Admin',h.url('admin_home'))}
9 /
9 /
10 ${_('Permissions')}
10 ${_('Permissions')}
11 </%def>
11 </%def>
12 <%def name="page_nav()">
12 <%def name="page_nav()">
13 ${self.menu('admin')}
13 ${self.menu('admin')}
14 ${self.submenu('permissions')}
14 ${self.submenu('permissions')}
15 </%def>
15 </%def>
16 <%def name="main()">
16 <%def name="main()">
17 <div>
17 <div>
18 <h2>${_('Permissions')}</h2>
18 <h2>${_('Permissions')}</h2>
19 todo :)
19
20 </div>
20 </div>
21 </%def>
21 </%def>
General Comments 0
You need to be logged in to leave comments. Login now