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