##// END OF EJS Templates
webhelpers: replaced paginate library with custom lib
dan -
r4091:4e2f3dca default
parent child Browse files
Show More
@@ -1,2379 +1,2364 b''
1 1 # Generated by pip2nix 0.8.0.dev1
2 2 # See https://github.com/johbo/pip2nix
3 3
4 4 { pkgs, fetchurl, fetchgit, fetchhg }:
5 5
6 6 self: super: {
7 7 "alembic" = super.buildPythonPackage {
8 8 name = "alembic-1.3.1";
9 9 doCheck = false;
10 10 propagatedBuildInputs = [
11 11 self."sqlalchemy"
12 12 self."mako"
13 13 self."python-editor"
14 14 self."python-dateutil"
15 15 ];
16 16 src = fetchurl {
17 17 url = "https://files.pythonhosted.org/packages/84/64/493c45119dce700a4b9eeecc436ef9e8835ab67bae6414f040cdc7b58f4b/alembic-1.3.1.tar.gz";
18 18 sha256 = "1cl2chk5jx0rf4hmsd5lljic7iifw17yv3y5xawvp4i14jvpn9s9";
19 19 };
20 20 meta = {
21 21 license = [ pkgs.lib.licenses.mit ];
22 22 };
23 23 };
24 24 "amqp" = super.buildPythonPackage {
25 25 name = "amqp-2.5.2";
26 26 doCheck = false;
27 27 propagatedBuildInputs = [
28 28 self."vine"
29 29 ];
30 30 src = fetchurl {
31 31 url = "https://files.pythonhosted.org/packages/92/1d/433541994a5a69f4ad2fff39746ddbb0bdedb0ea0d85673eb0db68a7edd9/amqp-2.5.2.tar.gz";
32 32 sha256 = "13dhhfxjrqcjybnq4zahg92mydhpg2l76nxcmq7d560687wsxwbp";
33 33 };
34 34 meta = {
35 35 license = [ pkgs.lib.licenses.bsdOriginal ];
36 36 };
37 37 };
38 38 "appenlight-client" = super.buildPythonPackage {
39 39 name = "appenlight-client-0.6.26";
40 40 doCheck = false;
41 41 propagatedBuildInputs = [
42 42 self."webob"
43 43 self."requests"
44 44 self."six"
45 45 ];
46 46 src = fetchurl {
47 47 url = "https://files.pythonhosted.org/packages/2e/56/418fc10379b96e795ee39a15e69a730c222818af04c3821fa354eaa859ec/appenlight_client-0.6.26.tar.gz";
48 48 sha256 = "0s9xw3sb8s3pk73k78nnq4jil3q4mk6bczfa1fmgfx61kdxl2712";
49 49 };
50 50 meta = {
51 51 license = [ pkgs.lib.licenses.bsdOriginal ];
52 52 };
53 53 };
54 54 "asn1crypto" = super.buildPythonPackage {
55 55 name = "asn1crypto-0.24.0";
56 56 doCheck = false;
57 57 src = fetchurl {
58 58 url = "https://files.pythonhosted.org/packages/fc/f1/8db7daa71f414ddabfa056c4ef792e1461ff655c2ae2928a2b675bfed6b4/asn1crypto-0.24.0.tar.gz";
59 59 sha256 = "0jaf8rf9dx1lf23xfv2cdd5h52f1qr3w8k63985bc35g3d220p4x";
60 60 };
61 61 meta = {
62 62 license = [ pkgs.lib.licenses.mit ];
63 63 };
64 64 };
65 65 "atomicwrites" = super.buildPythonPackage {
66 66 name = "atomicwrites-1.3.0";
67 67 doCheck = false;
68 68 src = fetchurl {
69 69 url = "https://files.pythonhosted.org/packages/ec/0f/cd484ac8820fed363b374af30049adc8fd13065720fd4f4c6be8a2309da7/atomicwrites-1.3.0.tar.gz";
70 70 sha256 = "19ngcscdf3jsqmpcxn6zl5b6anmsajb6izp1smcd1n02midl9abm";
71 71 };
72 72 meta = {
73 73 license = [ pkgs.lib.licenses.mit ];
74 74 };
75 75 };
76 76 "attrs" = super.buildPythonPackage {
77 77 name = "attrs-19.1.0";
78 78 doCheck = false;
79 79 src = fetchurl {
80 80 url = "https://files.pythonhosted.org/packages/cc/d9/931a24cc5394f19383fbbe3e1147a0291276afa43a0dc3ed0d6cd9fda813/attrs-19.1.0.tar.gz";
81 81 sha256 = "16g33zr5f449lqc5wgvzpknxryfzrfsxcr6kpgxwn7l5fkv71f7h";
82 82 };
83 83 meta = {
84 84 license = [ pkgs.lib.licenses.mit ];
85 85 };
86 86 };
87 87 "babel" = super.buildPythonPackage {
88 88 name = "babel-1.3";
89 89 doCheck = false;
90 90 propagatedBuildInputs = [
91 91 self."pytz"
92 92 ];
93 93 src = fetchurl {
94 94 url = "https://files.pythonhosted.org/packages/33/27/e3978243a03a76398c384c83f7ca879bc6e8f1511233a621fcada135606e/Babel-1.3.tar.gz";
95 95 sha256 = "0bnin777lc53nxd1hp3apq410jj5wx92n08h7h4izpl4f4sx00lz";
96 96 };
97 97 meta = {
98 98 license = [ pkgs.lib.licenses.bsdOriginal ];
99 99 };
100 100 };
101 101 "backports.shutil-get-terminal-size" = super.buildPythonPackage {
102 102 name = "backports.shutil-get-terminal-size-1.0.0";
103 103 doCheck = false;
104 104 src = fetchurl {
105 105 url = "https://files.pythonhosted.org/packages/ec/9c/368086faa9c016efce5da3e0e13ba392c9db79e3ab740b763fe28620b18b/backports.shutil_get_terminal_size-1.0.0.tar.gz";
106 106 sha256 = "107cmn7g3jnbkp826zlj8rrj19fam301qvaqf0f3905f5217lgki";
107 107 };
108 108 meta = {
109 109 license = [ pkgs.lib.licenses.mit ];
110 110 };
111 111 };
112 112 "beaker" = super.buildPythonPackage {
113 113 name = "beaker-1.9.1";
114 114 doCheck = false;
115 115 propagatedBuildInputs = [
116 116 self."funcsigs"
117 117 ];
118 118 src = fetchurl {
119 119 url = "https://files.pythonhosted.org/packages/ca/14/a626188d0d0c7b55dd7cf1902046c2743bd392a7078bb53073e13280eb1e/Beaker-1.9.1.tar.gz";
120 120 sha256 = "08arsn61r255lhz6hcpn2lsiqpg30clla805ysx06wmbhvb6w9rj";
121 121 };
122 122 meta = {
123 123 license = [ pkgs.lib.licenses.bsdOriginal ];
124 124 };
125 125 };
126 126 "beautifulsoup4" = super.buildPythonPackage {
127 127 name = "beautifulsoup4-4.6.3";
128 128 doCheck = false;
129 129 src = fetchurl {
130 130 url = "https://files.pythonhosted.org/packages/88/df/86bffad6309f74f3ff85ea69344a078fc30003270c8df6894fca7a3c72ff/beautifulsoup4-4.6.3.tar.gz";
131 131 sha256 = "041dhalzjciw6qyzzq7a2k4h1yvyk76xigp35hv5ibnn448ydy4h";
132 132 };
133 133 meta = {
134 134 license = [ pkgs.lib.licenses.mit ];
135 135 };
136 136 };
137 137 "billiard" = super.buildPythonPackage {
138 138 name = "billiard-3.6.1.0";
139 139 doCheck = false;
140 140 src = fetchurl {
141 141 url = "https://files.pythonhosted.org/packages/68/1d/2aea8fbb0b1e1260a8a2e77352de2983d36d7ac01207cf14c2b9c6cc860e/billiard-3.6.1.0.tar.gz";
142 142 sha256 = "09hzy3aqi7visy4vmf4xiish61n0rq5nd3iwjydydps8yrs9r05q";
143 143 };
144 144 meta = {
145 145 license = [ pkgs.lib.licenses.bsdOriginal ];
146 146 };
147 147 };
148 148 "bleach" = super.buildPythonPackage {
149 149 name = "bleach-3.1.0";
150 150 doCheck = false;
151 151 propagatedBuildInputs = [
152 152 self."six"
153 153 self."webencodings"
154 154 ];
155 155 src = fetchurl {
156 156 url = "https://files.pythonhosted.org/packages/78/5a/0df03e8735cd9c75167528299c738702437589b9c71a849489d00ffa82e8/bleach-3.1.0.tar.gz";
157 157 sha256 = "1yhrgrhkln8bd6gn3imj69g1h4xqah9gaz9q26crqr6gmmvpzprz";
158 158 };
159 159 meta = {
160 160 license = [ pkgs.lib.licenses.asl20 ];
161 161 };
162 162 };
163 163 "bumpversion" = super.buildPythonPackage {
164 164 name = "bumpversion-0.5.3";
165 165 doCheck = false;
166 166 src = fetchurl {
167 167 url = "https://files.pythonhosted.org/packages/14/41/8c9da3549f8e00c84f0432c3a8cf8ed6898374714676aab91501d48760db/bumpversion-0.5.3.tar.gz";
168 168 sha256 = "0zn7694yfipxg35ikkfh7kvgl2fissha3dnqad2c5bvsvmrwhi37";
169 169 };
170 170 meta = {
171 171 license = [ pkgs.lib.licenses.mit ];
172 172 };
173 173 };
174 174 "celery" = super.buildPythonPackage {
175 175 name = "celery-4.3.0";
176 176 doCheck = false;
177 177 propagatedBuildInputs = [
178 178 self."pytz"
179 179 self."billiard"
180 180 self."kombu"
181 181 self."vine"
182 182 ];
183 183 src = fetchurl {
184 184 url = "https://files.pythonhosted.org/packages/a2/4b/d020836f751617e907e84753a41c92231cd4b673ff991b8ee9da52361323/celery-4.3.0.tar.gz";
185 185 sha256 = "1y8y0gbgkwimpxqnxq2rm5qz2vy01fvjiybnpm00y5rzd2m34iac";
186 186 };
187 187 meta = {
188 188 license = [ pkgs.lib.licenses.bsdOriginal ];
189 189 };
190 190 };
191 191 "cffi" = super.buildPythonPackage {
192 192 name = "cffi-1.12.3";
193 193 doCheck = false;
194 194 propagatedBuildInputs = [
195 195 self."pycparser"
196 196 ];
197 197 src = fetchurl {
198 198 url = "https://files.pythonhosted.org/packages/93/1a/ab8c62b5838722f29f3daffcc8d4bd61844aa9b5f437341cc890ceee483b/cffi-1.12.3.tar.gz";
199 199 sha256 = "0x075521fxwv0mfp4cqzk7lvmw4n94bjw601qkcv314z5s182704";
200 200 };
201 201 meta = {
202 202 license = [ pkgs.lib.licenses.mit ];
203 203 };
204 204 };
205 205 "chameleon" = super.buildPythonPackage {
206 206 name = "chameleon-2.24";
207 207 doCheck = false;
208 208 src = fetchurl {
209 209 url = "https://files.pythonhosted.org/packages/5a/9e/637379ffa13c5172b5c0e704833ffea6bf51cec7567f93fd6e903d53ed74/Chameleon-2.24.tar.gz";
210 210 sha256 = "0ykqr7syxfa6h9adjfnsv1gdsca2xzm22vmic8859n0f0j09abj5";
211 211 };
212 212 meta = {
213 213 license = [ { fullName = "BSD-like (http://repoze.org/license.html)"; } ];
214 214 };
215 215 };
216 216 "channelstream" = super.buildPythonPackage {
217 217 name = "channelstream-0.5.2";
218 218 doCheck = false;
219 219 propagatedBuildInputs = [
220 220 self."gevent"
221 221 self."ws4py"
222 222 self."pyramid"
223 223 self."pyramid-jinja2"
224 224 self."itsdangerous"
225 225 self."requests"
226 226 self."six"
227 227 ];
228 228 src = fetchurl {
229 229 url = "https://files.pythonhosted.org/packages/2b/31/29a8e085cf5bf97fa88e7b947adabfc581a18a3463adf77fb6dada34a65f/channelstream-0.5.2.tar.gz";
230 230 sha256 = "1qbm4xdl5hfkja683x546bncg3rqq8qv79w1m1a1wd48cqqzb6rm";
231 231 };
232 232 meta = {
233 233 license = [ pkgs.lib.licenses.bsdOriginal ];
234 234 };
235 235 };
236 236 "click" = super.buildPythonPackage {
237 237 name = "click-7.0";
238 238 doCheck = false;
239 239 src = fetchurl {
240 240 url = "https://files.pythonhosted.org/packages/f8/5c/f60e9d8a1e77005f664b76ff8aeaee5bc05d0a91798afd7f53fc998dbc47/Click-7.0.tar.gz";
241 241 sha256 = "1mzjixd4vjbjvzb6vylki9w1556a9qmdh35kzmq6cign46av952v";
242 242 };
243 243 meta = {
244 244 license = [ pkgs.lib.licenses.bsdOriginal ];
245 245 };
246 246 };
247 247 "colander" = super.buildPythonPackage {
248 248 name = "colander-1.7.0";
249 249 doCheck = false;
250 250 propagatedBuildInputs = [
251 251 self."translationstring"
252 252 self."iso8601"
253 253 self."enum34"
254 254 ];
255 255 src = fetchurl {
256 256 url = "https://files.pythonhosted.org/packages/db/e4/74ab06f54211917b41865cafc987ce511e35503de48da9bfe9358a1bdc3e/colander-1.7.0.tar.gz";
257 257 sha256 = "1wl1bqab307lbbcjx81i28s3yl6dlm4rf15fxawkjb6j48x1cn6p";
258 258 };
259 259 meta = {
260 260 license = [ { fullName = "BSD-derived (http://www.repoze.org/LICENSE.txt)"; } ];
261 261 };
262 262 };
263 263 "configobj" = super.buildPythonPackage {
264 264 name = "configobj-5.0.6";
265 265 doCheck = false;
266 266 propagatedBuildInputs = [
267 267 self."six"
268 268 ];
269 269 src = fetchurl {
270 270 url = "https://code.rhodecode.com/upstream/configobj/artifacts/download/0-012de99a-b1e1-4f64-a5c0-07a98a41b324.tar.gz?md5=6a513f51fe04b2c18cf84c1395a7c626";
271 271 sha256 = "0kqfrdfr14mw8yd8qwq14dv2xghpkjmd3yjsy8dfcbvpcc17xnxp";
272 272 };
273 273 meta = {
274 274 license = [ pkgs.lib.licenses.bsdOriginal ];
275 275 };
276 276 };
277 277 "configparser" = super.buildPythonPackage {
278 278 name = "configparser-4.0.2";
279 279 doCheck = false;
280 280 src = fetchurl {
281 281 url = "https://files.pythonhosted.org/packages/16/4f/48975536bd488d3a272549eb795ac4a13a5f7fcdc8995def77fbef3532ee/configparser-4.0.2.tar.gz";
282 282 sha256 = "1priacxym85yjcf68hh38w55nqswaxp71ryjyfdk222kg9l85ln7";
283 283 };
284 284 meta = {
285 285 license = [ pkgs.lib.licenses.mit ];
286 286 };
287 287 };
288 288 "contextlib2" = super.buildPythonPackage {
289 289 name = "contextlib2-0.6.0";
290 290 doCheck = false;
291 291 src = fetchurl {
292 292 url = "https://files.pythonhosted.org/packages/f0/08/ac376929b2c51e2d5fd4b9fa1f72eca0162c990edc526bdd3b16449323ad/contextlib2-0.6.0.tar.gz";
293 293 sha256 = "1lvy0xvh5kkx0cmv2y8rpxjrg909q920k05x7m8srjkpcxrsm5vi";
294 294 };
295 295 meta = {
296 296 license = [ pkgs.lib.licenses.psfl ];
297 297 };
298 298 };
299 299 "cov-core" = super.buildPythonPackage {
300 300 name = "cov-core-1.15.0";
301 301 doCheck = false;
302 302 propagatedBuildInputs = [
303 303 self."coverage"
304 304 ];
305 305 src = fetchurl {
306 306 url = "https://files.pythonhosted.org/packages/4b/87/13e75a47b4ba1be06f29f6d807ca99638bedc6b57fa491cd3de891ca2923/cov-core-1.15.0.tar.gz";
307 307 sha256 = "0k3np9ymh06yv1ib96sb6wfsxjkqhmik8qfsn119vnhga9ywc52a";
308 308 };
309 309 meta = {
310 310 license = [ pkgs.lib.licenses.mit ];
311 311 };
312 312 };
313 313 "coverage" = super.buildPythonPackage {
314 314 name = "coverage-4.5.4";
315 315 doCheck = false;
316 316 src = fetchurl {
317 317 url = "https://files.pythonhosted.org/packages/85/d5/818d0e603685c4a613d56f065a721013e942088047ff1027a632948bdae6/coverage-4.5.4.tar.gz";
318 318 sha256 = "0p0j4di6h8k6ica7jwwj09azdcg4ycxq60i9qsskmsg94cd9yzg0";
319 319 };
320 320 meta = {
321 321 license = [ pkgs.lib.licenses.asl20 ];
322 322 };
323 323 };
324 324 "cryptography" = super.buildPythonPackage {
325 325 name = "cryptography-2.6.1";
326 326 doCheck = false;
327 327 propagatedBuildInputs = [
328 328 self."asn1crypto"
329 329 self."six"
330 330 self."cffi"
331 331 self."enum34"
332 332 self."ipaddress"
333 333 ];
334 334 src = fetchurl {
335 335 url = "https://files.pythonhosted.org/packages/07/ca/bc827c5e55918ad223d59d299fff92f3563476c3b00d0a9157d9c0217449/cryptography-2.6.1.tar.gz";
336 336 sha256 = "19iwz5avym5zl6jrrrkym1rdaa9h61j20ph4cswsqgv8xg5j3j16";
337 337 };
338 338 meta = {
339 339 license = [ pkgs.lib.licenses.bsdOriginal { fullName = "BSD or Apache License, Version 2.0"; } pkgs.lib.licenses.asl20 ];
340 340 };
341 341 };
342 342 "cssselect" = super.buildPythonPackage {
343 343 name = "cssselect-1.0.3";
344 344 doCheck = false;
345 345 src = fetchurl {
346 346 url = "https://files.pythonhosted.org/packages/52/ea/f31e1d2e9eb130fda2a631e22eac369dc644e8807345fbed5113f2d6f92b/cssselect-1.0.3.tar.gz";
347 347 sha256 = "011jqa2jhmydhi0iz4v1w3cr540z5zas8g2bw8brdw4s4b2qnv86";
348 348 };
349 349 meta = {
350 350 license = [ pkgs.lib.licenses.bsdOriginal ];
351 351 };
352 352 };
353 353 "decorator" = super.buildPythonPackage {
354 354 name = "decorator-4.1.2";
355 355 doCheck = false;
356 356 src = fetchurl {
357 357 url = "https://files.pythonhosted.org/packages/bb/e0/f6e41e9091e130bf16d4437dabbac3993908e4d6485ecbc985ef1352db94/decorator-4.1.2.tar.gz";
358 358 sha256 = "1d8npb11kxyi36mrvjdpcjij76l5zfyrz2f820brf0l0rcw4vdkw";
359 359 };
360 360 meta = {
361 361 license = [ pkgs.lib.licenses.bsdOriginal { fullName = "new BSD License"; } ];
362 362 };
363 363 };
364 364 "deform" = super.buildPythonPackage {
365 365 name = "deform-2.0.8";
366 366 doCheck = false;
367 367 propagatedBuildInputs = [
368 368 self."chameleon"
369 369 self."colander"
370 370 self."iso8601"
371 371 self."peppercorn"
372 372 self."translationstring"
373 373 self."zope.deprecation"
374 374 ];
375 375 src = fetchurl {
376 376 url = "https://files.pythonhosted.org/packages/21/d0/45fdf891a82722c02fc2da319cf2d1ae6b5abf9e470ad3762135a895a868/deform-2.0.8.tar.gz";
377 377 sha256 = "0wbjv98sib96649aqaygzxnrkclyy50qij2rha6fn1i4c86bfdl9";
378 378 };
379 379 meta = {
380 380 license = [ { fullName = "Repoze Public License"; } { fullName = "BSD-derived (http://www.repoze.org/LICENSE.txt)"; } ];
381 381 };
382 382 };
383 383 "defusedxml" = super.buildPythonPackage {
384 384 name = "defusedxml-0.6.0";
385 385 doCheck = false;
386 386 src = fetchurl {
387 387 url = "https://files.pythonhosted.org/packages/a4/5f/f8aa58ca0cf01cbcee728abc9d88bfeb74e95e6cb4334cfd5bed5673ea77/defusedxml-0.6.0.tar.gz";
388 388 sha256 = "1xbp8fivl3wlbyg2jrvs4lalaqv1xp9a9f29p75wdx2s2d6h717n";
389 389 };
390 390 meta = {
391 391 license = [ pkgs.lib.licenses.psfl ];
392 392 };
393 393 };
394 394 "dm.xmlsec.binding" = super.buildPythonPackage {
395 395 name = "dm.xmlsec.binding-1.3.7";
396 396 doCheck = false;
397 397 propagatedBuildInputs = [
398 398 self."setuptools"
399 399 self."lxml"
400 400 ];
401 401 src = fetchurl {
402 402 url = "https://files.pythonhosted.org/packages/2c/9e/7651982d50252692991acdae614af821fd6c79bc8dcd598ad71d55be8fc7/dm.xmlsec.binding-1.3.7.tar.gz";
403 403 sha256 = "03jjjscx1pz2nc0dwiw9nia02qbz1c6f0f9zkyr8fmvys2n5jkb3";
404 404 };
405 405 meta = {
406 406 license = [ pkgs.lib.licenses.bsdOriginal ];
407 407 };
408 408 };
409 409 "docutils" = super.buildPythonPackage {
410 410 name = "docutils-0.14";
411 411 doCheck = false;
412 412 src = fetchurl {
413 413 url = "https://files.pythonhosted.org/packages/84/f4/5771e41fdf52aabebbadecc9381d11dea0fa34e4759b4071244fa094804c/docutils-0.14.tar.gz";
414 414 sha256 = "0x22fs3pdmr42kvz6c654756wja305qv6cx1zbhwlagvxgr4xrji";
415 415 };
416 416 meta = {
417 417 license = [ pkgs.lib.licenses.bsdOriginal pkgs.lib.licenses.publicDomain pkgs.lib.licenses.gpl1 { fullName = "public domain, Python, 2-Clause BSD, GPL 3 (see COPYING.txt)"; } pkgs.lib.licenses.psfl ];
418 418 };
419 419 };
420 420 "dogpile.cache" = super.buildPythonPackage {
421 421 name = "dogpile.cache-0.9.0";
422 422 doCheck = false;
423 423 propagatedBuildInputs = [
424 424 self."decorator"
425 425 ];
426 426 src = fetchurl {
427 427 url = "https://files.pythonhosted.org/packages/ac/6a/9ac405686a94b7f009a20a50070a5786b0e1aedc707b88d40d0c4b51a82e/dogpile.cache-0.9.0.tar.gz";
428 428 sha256 = "0sr1fn6b4k5bh0cscd9yi8csqxvj4ngzildav58x5p694mc86j5k";
429 429 };
430 430 meta = {
431 431 license = [ pkgs.lib.licenses.bsdOriginal ];
432 432 };
433 433 };
434 434 "dogpile.core" = super.buildPythonPackage {
435 435 name = "dogpile.core-0.4.1";
436 436 doCheck = false;
437 437 src = fetchurl {
438 438 url = "https://files.pythonhosted.org/packages/0e/77/e72abc04c22aedf874301861e5c1e761231c288b5de369c18be8f4b5c9bb/dogpile.core-0.4.1.tar.gz";
439 439 sha256 = "0xpdvg4kr1isfkrh1rfsh7za4q5a5s6l2kf9wpvndbwf3aqjyrdy";
440 440 };
441 441 meta = {
442 442 license = [ pkgs.lib.licenses.bsdOriginal ];
443 443 };
444 444 };
445 445 "ecdsa" = super.buildPythonPackage {
446 446 name = "ecdsa-0.13.2";
447 447 doCheck = false;
448 448 src = fetchurl {
449 449 url = "https://files.pythonhosted.org/packages/51/76/139bf6e9b7b6684d5891212cdbd9e0739f2bfc03f380a1a6ffa700f392ac/ecdsa-0.13.2.tar.gz";
450 450 sha256 = "116qaq7bh4lcynzi613960jhsnn19v0kmsqwahiwjfj14gx4y0sw";
451 451 };
452 452 meta = {
453 453 license = [ pkgs.lib.licenses.mit ];
454 454 };
455 455 };
456 456 "elasticsearch" = super.buildPythonPackage {
457 457 name = "elasticsearch-6.3.1";
458 458 doCheck = false;
459 459 propagatedBuildInputs = [
460 460 self."urllib3"
461 461 ];
462 462 src = fetchurl {
463 463 url = "https://files.pythonhosted.org/packages/9d/ce/c4664e8380e379a9402ecfbaf158e56396da90d520daba21cfa840e0eb71/elasticsearch-6.3.1.tar.gz";
464 464 sha256 = "12y93v0yn7a4xmf969239g8gb3l4cdkclfpbk1qc8hx5qkymrnma";
465 465 };
466 466 meta = {
467 467 license = [ pkgs.lib.licenses.asl20 ];
468 468 };
469 469 };
470 470 "elasticsearch-dsl" = super.buildPythonPackage {
471 471 name = "elasticsearch-dsl-6.3.1";
472 472 doCheck = false;
473 473 propagatedBuildInputs = [
474 474 self."six"
475 475 self."python-dateutil"
476 476 self."elasticsearch"
477 477 self."ipaddress"
478 478 ];
479 479 src = fetchurl {
480 480 url = "https://files.pythonhosted.org/packages/4c/0d/1549f50c591db6bb4e66cbcc8d34a6e537c3d89aa426b167c244fd46420a/elasticsearch-dsl-6.3.1.tar.gz";
481 481 sha256 = "1gh8a0shqi105k325hgwb9avrpdjh0mc6mxwfg9ba7g6lssb702z";
482 482 };
483 483 meta = {
484 484 license = [ pkgs.lib.licenses.asl20 ];
485 485 };
486 486 };
487 487 "elasticsearch1" = super.buildPythonPackage {
488 488 name = "elasticsearch1-1.10.0";
489 489 doCheck = false;
490 490 propagatedBuildInputs = [
491 491 self."urllib3"
492 492 ];
493 493 src = fetchurl {
494 494 url = "https://files.pythonhosted.org/packages/a6/eb/73e75f9681fa71e3157b8ee878534235d57f24ee64f0e77f8d995fb57076/elasticsearch1-1.10.0.tar.gz";
495 495 sha256 = "0g89444kd5zwql4vbvyrmi2m6l6dcj6ga98j4hqxyyyz6z20aki2";
496 496 };
497 497 meta = {
498 498 license = [ pkgs.lib.licenses.asl20 ];
499 499 };
500 500 };
501 501 "elasticsearch1-dsl" = super.buildPythonPackage {
502 502 name = "elasticsearch1-dsl-0.0.12";
503 503 doCheck = false;
504 504 propagatedBuildInputs = [
505 505 self."six"
506 506 self."python-dateutil"
507 507 self."elasticsearch1"
508 508 ];
509 509 src = fetchurl {
510 510 url = "https://files.pythonhosted.org/packages/eb/9d/785342775cb10eddc9b8d7457d618a423b4f0b89d8b2b2d1bc27190d71db/elasticsearch1-dsl-0.0.12.tar.gz";
511 511 sha256 = "0ig1ly39v93hba0z975wnhbmzwj28w6w1sqlr2g7cn5spp732bhk";
512 512 };
513 513 meta = {
514 514 license = [ pkgs.lib.licenses.asl20 ];
515 515 };
516 516 };
517 517 "elasticsearch2" = super.buildPythonPackage {
518 518 name = "elasticsearch2-2.5.1";
519 519 doCheck = false;
520 520 propagatedBuildInputs = [
521 521 self."urllib3"
522 522 ];
523 523 src = fetchurl {
524 524 url = "https://files.pythonhosted.org/packages/f6/09/f9b24aa6b1120bea371cd57ef6f57c7694cf16660469456a8be6c2bdbe22/elasticsearch2-2.5.1.tar.gz";
525 525 sha256 = "19k2znpjfyp0hrq73cz7pjyj289040xpsxsm0xhh4jfh6y551g7k";
526 526 };
527 527 meta = {
528 528 license = [ pkgs.lib.licenses.asl20 ];
529 529 };
530 530 };
531 531 "entrypoints" = super.buildPythonPackage {
532 532 name = "entrypoints-0.2.2";
533 533 doCheck = false;
534 534 propagatedBuildInputs = [
535 535 self."configparser"
536 536 ];
537 537 src = fetchurl {
538 538 url = "https://code.rhodecode.com/upstream/entrypoints/artifacts/download/0-8e9ee9e4-c4db-409c-b07e-81568fd1832d.tar.gz?md5=3a027b8ff1d257b91fe257de6c43357d";
539 539 sha256 = "0qih72n2myclanplqipqxpgpj9d2yhff1pz5d02zq1cfqyd173w5";
540 540 };
541 541 meta = {
542 542 license = [ pkgs.lib.licenses.mit ];
543 543 };
544 544 };
545 545 "enum34" = super.buildPythonPackage {
546 546 name = "enum34-1.1.6";
547 547 doCheck = false;
548 548 src = fetchurl {
549 549 url = "https://files.pythonhosted.org/packages/bf/3e/31d502c25302814a7c2f1d3959d2a3b3f78e509002ba91aea64993936876/enum34-1.1.6.tar.gz";
550 550 sha256 = "1cgm5ng2gcfrkrm3hc22brl6chdmv67b9zvva9sfs7gn7dwc9n4a";
551 551 };
552 552 meta = {
553 553 license = [ pkgs.lib.licenses.bsdOriginal ];
554 554 };
555 555 };
556 556 "formencode" = super.buildPythonPackage {
557 557 name = "formencode-1.2.4";
558 558 doCheck = false;
559 559 src = fetchurl {
560 560 url = "https://files.pythonhosted.org/packages/8e/59/0174271a6f004512e0201188593e6d319db139d14cb7490e488bbb078015/FormEncode-1.2.4.tar.gz";
561 561 sha256 = "1fgy04sdy4yry5xcjls3x3xy30dqwj58ycnkndim819jx0788w42";
562 562 };
563 563 meta = {
564 564 license = [ pkgs.lib.licenses.psfl ];
565 565 };
566 566 };
567 567 "funcsigs" = super.buildPythonPackage {
568 568 name = "funcsigs-1.0.2";
569 569 doCheck = false;
570 570 src = fetchurl {
571 571 url = "https://files.pythonhosted.org/packages/94/4a/db842e7a0545de1cdb0439bb80e6e42dfe82aaeaadd4072f2263a4fbed23/funcsigs-1.0.2.tar.gz";
572 572 sha256 = "0l4g5818ffyfmfs1a924811azhjj8ax9xd1cffr1mzd3ycn0zfx7";
573 573 };
574 574 meta = {
575 575 license = [ { fullName = "ASL"; } pkgs.lib.licenses.asl20 ];
576 576 };
577 577 };
578 578 "functools32" = super.buildPythonPackage {
579 579 name = "functools32-3.2.3.post2";
580 580 doCheck = false;
581 581 src = fetchurl {
582 582 url = "https://files.pythonhosted.org/packages/c5/60/6ac26ad05857c601308d8fb9e87fa36d0ebf889423f47c3502ef034365db/functools32-3.2.3-2.tar.gz";
583 583 sha256 = "0v8ya0b58x47wp216n1zamimv4iw57cxz3xxhzix52jkw3xks9gn";
584 584 };
585 585 meta = {
586 586 license = [ pkgs.lib.licenses.psfl ];
587 587 };
588 588 };
589 589 "future" = super.buildPythonPackage {
590 590 name = "future-0.14.3";
591 591 doCheck = false;
592 592 src = fetchurl {
593 593 url = "https://files.pythonhosted.org/packages/83/80/8ef3a11a15f8eaafafa0937b20c1b3f73527e69ab6b3fa1cf94a5a96aabb/future-0.14.3.tar.gz";
594 594 sha256 = "1savk7jx7hal032f522c5ajhh8fra6gmnadrj9adv5qxi18pv1b2";
595 595 };
596 596 meta = {
597 597 license = [ { fullName = "OSI Approved"; } pkgs.lib.licenses.mit ];
598 598 };
599 599 };
600 600 "futures" = super.buildPythonPackage {
601 601 name = "futures-3.0.2";
602 602 doCheck = false;
603 603 src = fetchurl {
604 604 url = "https://files.pythonhosted.org/packages/f8/e7/fc0fcbeb9193ba2d4de00b065e7fd5aecd0679e93ce95a07322b2b1434f4/futures-3.0.2.tar.gz";
605 605 sha256 = "0mz2pbgxbc2nbib1szifi07whjbfs4r02pv2z390z7p410awjgyw";
606 606 };
607 607 meta = {
608 608 license = [ pkgs.lib.licenses.bsdOriginal ];
609 609 };
610 610 };
611 611 "gevent" = super.buildPythonPackage {
612 612 name = "gevent-1.4.0";
613 613 doCheck = false;
614 614 propagatedBuildInputs = [
615 615 self."greenlet"
616 616 ];
617 617 src = fetchurl {
618 618 url = "https://files.pythonhosted.org/packages/ed/27/6c49b70808f569b66ec7fac2e78f076e9b204db9cf5768740cff3d5a07ae/gevent-1.4.0.tar.gz";
619 619 sha256 = "1lchr4akw2jkm5v4kz7bdm4wv3knkfhbfn9vkkz4s5yrkcxzmdqy";
620 620 };
621 621 meta = {
622 622 license = [ pkgs.lib.licenses.mit ];
623 623 };
624 624 };
625 625 "gnureadline" = super.buildPythonPackage {
626 626 name = "gnureadline-6.3.8";
627 627 doCheck = false;
628 628 src = fetchurl {
629 629 url = "https://files.pythonhosted.org/packages/50/64/86085c823cd78f9df9d8e33dce0baa71618016f8860460b82cf6610e1eb3/gnureadline-6.3.8.tar.gz";
630 630 sha256 = "0ddhj98x2nv45iz4aadk4b9m0b1kpsn1xhcbypn5cd556knhiqjq";
631 631 };
632 632 meta = {
633 633 license = [ { fullName = "GNU General Public License v3 (GPLv3)"; } pkgs.lib.licenses.gpl1 ];
634 634 };
635 635 };
636 636 "gprof2dot" = super.buildPythonPackage {
637 637 name = "gprof2dot-2017.9.19";
638 638 doCheck = false;
639 639 src = fetchurl {
640 640 url = "https://files.pythonhosted.org/packages/9d/36/f977122502979f3dfb50704979c9ed70e6b620787942b089bf1af15f5aba/gprof2dot-2017.9.19.tar.gz";
641 641 sha256 = "17ih23ld2nzgc3xwgbay911l6lh96jp1zshmskm17n1gg2i7mg6f";
642 642 };
643 643 meta = {
644 644 license = [ { fullName = "GNU Lesser General Public License v3 or later (LGPLv3+)"; } { fullName = "LGPL"; } ];
645 645 };
646 646 };
647 647 "greenlet" = super.buildPythonPackage {
648 648 name = "greenlet-0.4.15";
649 649 doCheck = false;
650 650 src = fetchurl {
651 651 url = "https://files.pythonhosted.org/packages/f8/e8/b30ae23b45f69aa3f024b46064c0ac8e5fcb4f22ace0dca8d6f9c8bbe5e7/greenlet-0.4.15.tar.gz";
652 652 sha256 = "1g4g1wwc472ds89zmqlpyan3fbnzpa8qm48z3z1y6mlk44z485ll";
653 653 };
654 654 meta = {
655 655 license = [ pkgs.lib.licenses.mit ];
656 656 };
657 657 };
658 658 "gunicorn" = super.buildPythonPackage {
659 659 name = "gunicorn-19.9.0";
660 660 doCheck = false;
661 661 src = fetchurl {
662 662 url = "https://files.pythonhosted.org/packages/47/52/68ba8e5e8ba251e54006a49441f7ccabca83b6bef5aedacb4890596c7911/gunicorn-19.9.0.tar.gz";
663 663 sha256 = "1wzlf4xmn6qjirh5w81l6i6kqjnab1n1qqkh7zsj1yb6gh4n49ps";
664 664 };
665 665 meta = {
666 666 license = [ pkgs.lib.licenses.mit ];
667 667 };
668 668 };
669 669 "hupper" = super.buildPythonPackage {
670 670 name = "hupper-1.9.1";
671 671 doCheck = false;
672 672 src = fetchurl {
673 673 url = "https://files.pythonhosted.org/packages/09/3a/4f215659f31eeffe364a984dba486bfa3907bfcc54b7013bdfe825cebb5f/hupper-1.9.1.tar.gz";
674 674 sha256 = "0pyg879fv9mbwlnbzw2a3234qqycqs9l97h5mpkmk0bvxhi2471v";
675 675 };
676 676 meta = {
677 677 license = [ pkgs.lib.licenses.mit ];
678 678 };
679 679 };
680 680 "importlib-metadata" = super.buildPythonPackage {
681 681 name = "importlib-metadata-0.23";
682 682 doCheck = false;
683 683 propagatedBuildInputs = [
684 684 self."zipp"
685 685 self."contextlib2"
686 686 self."configparser"
687 687 self."pathlib2"
688 688 ];
689 689 src = fetchurl {
690 690 url = "https://files.pythonhosted.org/packages/5d/44/636bcd15697791943e2dedda0dbe098d8530a38d113b202817133e0b06c0/importlib_metadata-0.23.tar.gz";
691 691 sha256 = "09mdqdfv5rdrwz80jh9m379gxmvk2vhjfz0fg53hid00icvxf65a";
692 692 };
693 693 meta = {
694 694 license = [ pkgs.lib.licenses.asl20 ];
695 695 };
696 696 };
697 697 "infrae.cache" = super.buildPythonPackage {
698 698 name = "infrae.cache-1.0.1";
699 699 doCheck = false;
700 700 propagatedBuildInputs = [
701 701 self."beaker"
702 702 self."repoze.lru"
703 703 ];
704 704 src = fetchurl {
705 705 url = "https://files.pythonhosted.org/packages/bb/f0/e7d5e984cf6592fd2807dc7bc44a93f9d18e04e6a61f87fdfb2622422d74/infrae.cache-1.0.1.tar.gz";
706 706 sha256 = "1dvqsjn8vw253wz9d1pz17j79mf4bs53dvp2qxck2qdp1am1njw4";
707 707 };
708 708 meta = {
709 709 license = [ pkgs.lib.licenses.zpl21 ];
710 710 };
711 711 };
712 712 "invoke" = super.buildPythonPackage {
713 713 name = "invoke-0.13.0";
714 714 doCheck = false;
715 715 src = fetchurl {
716 716 url = "https://files.pythonhosted.org/packages/47/bf/d07ef52fa1ac645468858bbac7cb95b246a972a045e821493d17d89c81be/invoke-0.13.0.tar.gz";
717 717 sha256 = "0794vhgxfmkh0vzkkg5cfv1w82g3jc3xr18wim29far9qpx9468s";
718 718 };
719 719 meta = {
720 720 license = [ pkgs.lib.licenses.bsdOriginal ];
721 721 };
722 722 };
723 723 "ipaddress" = super.buildPythonPackage {
724 724 name = "ipaddress-1.0.23";
725 725 doCheck = false;
726 726 src = fetchurl {
727 727 url = "https://files.pythonhosted.org/packages/b9/9a/3e9da40ea28b8210dd6504d3fe9fe7e013b62bf45902b458d1cdc3c34ed9/ipaddress-1.0.23.tar.gz";
728 728 sha256 = "1qp743h30s04m3cg3yk3fycad930jv17q7dsslj4mfw0jlvf1y5p";
729 729 };
730 730 meta = {
731 731 license = [ pkgs.lib.licenses.psfl ];
732 732 };
733 733 };
734 734 "ipdb" = super.buildPythonPackage {
735 735 name = "ipdb-0.12";
736 736 doCheck = false;
737 737 propagatedBuildInputs = [
738 738 self."setuptools"
739 739 self."ipython"
740 740 ];
741 741 src = fetchurl {
742 742 url = "https://files.pythonhosted.org/packages/6d/43/c3c2e866a8803e196d6209595020a4a6db1a3c5d07c01455669497ae23d0/ipdb-0.12.tar.gz";
743 743 sha256 = "1khr2n7xfy8hg65kj1bsrjq9g7656pp0ybfa8abpbzpdawji3qnw";
744 744 };
745 745 meta = {
746 746 license = [ pkgs.lib.licenses.bsdOriginal ];
747 747 };
748 748 };
749 749 "ipython" = super.buildPythonPackage {
750 750 name = "ipython-5.1.0";
751 751 doCheck = false;
752 752 propagatedBuildInputs = [
753 753 self."setuptools"
754 754 self."decorator"
755 755 self."pickleshare"
756 756 self."simplegeneric"
757 757 self."traitlets"
758 758 self."prompt-toolkit"
759 759 self."pygments"
760 760 self."pexpect"
761 761 self."backports.shutil-get-terminal-size"
762 762 self."pathlib2"
763 763 self."pexpect"
764 764 ];
765 765 src = fetchurl {
766 766 url = "https://files.pythonhosted.org/packages/89/63/a9292f7cd9d0090a0f995e1167f3f17d5889dcbc9a175261719c513b9848/ipython-5.1.0.tar.gz";
767 767 sha256 = "0qdrf6aj9kvjczd5chj1my8y2iq09am9l8bb2a1334a52d76kx3y";
768 768 };
769 769 meta = {
770 770 license = [ pkgs.lib.licenses.bsdOriginal ];
771 771 };
772 772 };
773 773 "ipython-genutils" = super.buildPythonPackage {
774 774 name = "ipython-genutils-0.2.0";
775 775 doCheck = false;
776 776 src = fetchurl {
777 777 url = "https://files.pythonhosted.org/packages/e8/69/fbeffffc05236398ebfcfb512b6d2511c622871dca1746361006da310399/ipython_genutils-0.2.0.tar.gz";
778 778 sha256 = "1a4bc9y8hnvq6cp08qs4mckgm6i6ajpndp4g496rvvzcfmp12bpb";
779 779 };
780 780 meta = {
781 781 license = [ pkgs.lib.licenses.bsdOriginal ];
782 782 };
783 783 };
784 784 "iso8601" = super.buildPythonPackage {
785 785 name = "iso8601-0.1.12";
786 786 doCheck = false;
787 787 src = fetchurl {
788 788 url = "https://files.pythonhosted.org/packages/45/13/3db24895497345fb44c4248c08b16da34a9eb02643cea2754b21b5ed08b0/iso8601-0.1.12.tar.gz";
789 789 sha256 = "10nyvvnrhw2w3p09v1ica4lgj6f4g9j3kkfx17qmraiq3w7b5i29";
790 790 };
791 791 meta = {
792 792 license = [ pkgs.lib.licenses.mit ];
793 793 };
794 794 };
795 795 "isodate" = super.buildPythonPackage {
796 796 name = "isodate-0.6.0";
797 797 doCheck = false;
798 798 propagatedBuildInputs = [
799 799 self."six"
800 800 ];
801 801 src = fetchurl {
802 802 url = "https://files.pythonhosted.org/packages/b1/80/fb8c13a4cd38eb5021dc3741a9e588e4d1de88d895c1910c6fc8a08b7a70/isodate-0.6.0.tar.gz";
803 803 sha256 = "1n7jkz68kk5pwni540pr5zdh99bf6ywydk1p5pdrqisrawylldif";
804 804 };
805 805 meta = {
806 806 license = [ pkgs.lib.licenses.bsdOriginal ];
807 807 };
808 808 };
809 809 "itsdangerous" = super.buildPythonPackage {
810 810 name = "itsdangerous-0.24";
811 811 doCheck = false;
812 812 src = fetchurl {
813 813 url = "https://files.pythonhosted.org/packages/dc/b4/a60bcdba945c00f6d608d8975131ab3f25b22f2bcfe1dab221165194b2d4/itsdangerous-0.24.tar.gz";
814 814 sha256 = "06856q6x675ly542ig0plbqcyab6ksfzijlyf1hzhgg3sgwgrcyb";
815 815 };
816 816 meta = {
817 817 license = [ pkgs.lib.licenses.bsdOriginal ];
818 818 };
819 819 };
820 820 "jinja2" = super.buildPythonPackage {
821 821 name = "jinja2-2.9.6";
822 822 doCheck = false;
823 823 propagatedBuildInputs = [
824 824 self."markupsafe"
825 825 ];
826 826 src = fetchurl {
827 827 url = "https://files.pythonhosted.org/packages/90/61/f820ff0076a2599dd39406dcb858ecb239438c02ce706c8e91131ab9c7f1/Jinja2-2.9.6.tar.gz";
828 828 sha256 = "1zzrkywhziqffrzks14kzixz7nd4yh2vc0fb04a68vfd2ai03anx";
829 829 };
830 830 meta = {
831 831 license = [ pkgs.lib.licenses.bsdOriginal ];
832 832 };
833 833 };
834 834 "jsonschema" = super.buildPythonPackage {
835 835 name = "jsonschema-2.6.0";
836 836 doCheck = false;
837 837 propagatedBuildInputs = [
838 838 self."functools32"
839 839 ];
840 840 src = fetchurl {
841 841 url = "https://files.pythonhosted.org/packages/58/b9/171dbb07e18c6346090a37f03c7e74410a1a56123f847efed59af260a298/jsonschema-2.6.0.tar.gz";
842 842 sha256 = "00kf3zmpp9ya4sydffpifn0j0mzm342a2vzh82p6r0vh10cg7xbg";
843 843 };
844 844 meta = {
845 845 license = [ pkgs.lib.licenses.mit ];
846 846 };
847 847 };
848 848 "jupyter-client" = super.buildPythonPackage {
849 849 name = "jupyter-client-5.0.0";
850 850 doCheck = false;
851 851 propagatedBuildInputs = [
852 852 self."traitlets"
853 853 self."jupyter-core"
854 854 self."pyzmq"
855 855 self."python-dateutil"
856 856 ];
857 857 src = fetchurl {
858 858 url = "https://files.pythonhosted.org/packages/e5/6f/65412ed462202b90134b7e761b0b7e7f949e07a549c1755475333727b3d0/jupyter_client-5.0.0.tar.gz";
859 859 sha256 = "0nxw4rqk4wsjhc87gjqd7pv89cb9dnimcfnmcmp85bmrvv1gjri7";
860 860 };
861 861 meta = {
862 862 license = [ pkgs.lib.licenses.bsdOriginal ];
863 863 };
864 864 };
865 865 "jupyter-core" = super.buildPythonPackage {
866 866 name = "jupyter-core-4.5.0";
867 867 doCheck = false;
868 868 propagatedBuildInputs = [
869 869 self."traitlets"
870 870 ];
871 871 src = fetchurl {
872 872 url = "https://files.pythonhosted.org/packages/4a/de/ff4ca734656d17ebe0450807b59d728f45277e2e7f4b82bc9aae6cb82961/jupyter_core-4.5.0.tar.gz";
873 873 sha256 = "1xr4pbghwk5hayn5wwnhb7z95380r45p79gf5if5pi1akwg7qvic";
874 874 };
875 875 meta = {
876 876 license = [ pkgs.lib.licenses.bsdOriginal ];
877 877 };
878 878 };
879 879 "kombu" = super.buildPythonPackage {
880 880 name = "kombu-4.6.6";
881 881 doCheck = false;
882 882 propagatedBuildInputs = [
883 883 self."amqp"
884 884 self."importlib-metadata"
885 885 ];
886 886 src = fetchurl {
887 887 url = "https://files.pythonhosted.org/packages/20/e6/bc2d9affba6138a1dc143f77fef253e9e08e238fa7c0688d917c09005e96/kombu-4.6.6.tar.gz";
888 888 sha256 = "11mxpcy8mg1l35bgbhba70v29bydr2hrhdbdlb4lg98m3m5vaq0p";
889 889 };
890 890 meta = {
891 891 license = [ pkgs.lib.licenses.bsdOriginal ];
892 892 };
893 893 };
894 894 "lxml" = super.buildPythonPackage {
895 895 name = "lxml-4.2.5";
896 896 doCheck = false;
897 897 src = fetchurl {
898 898 url = "https://files.pythonhosted.org/packages/4b/20/ddf5eb3bd5c57582d2b4652b4bbcf8da301bdfe5d805cb94e805f4d7464d/lxml-4.2.5.tar.gz";
899 899 sha256 = "0zw0y9hs0nflxhl9cs6ipwwh53szi3w2x06wl0k9cylyqac0cwin";
900 900 };
901 901 meta = {
902 902 license = [ pkgs.lib.licenses.bsdOriginal ];
903 903 };
904 904 };
905 905 "mako" = super.buildPythonPackage {
906 906 name = "mako-1.1.0";
907 907 doCheck = false;
908 908 propagatedBuildInputs = [
909 909 self."markupsafe"
910 910 ];
911 911 src = fetchurl {
912 912 url = "https://files.pythonhosted.org/packages/b0/3c/8dcd6883d009f7cae0f3157fb53e9afb05a0d3d33b3db1268ec2e6f4a56b/Mako-1.1.0.tar.gz";
913 913 sha256 = "0jqa3qfpykyn4fmkn0kh6043sfls7br8i2bsdbccazcvk9cijsd3";
914 914 };
915 915 meta = {
916 916 license = [ pkgs.lib.licenses.mit ];
917 917 };
918 918 };
919 919 "markdown" = super.buildPythonPackage {
920 920 name = "markdown-2.6.11";
921 921 doCheck = false;
922 922 src = fetchurl {
923 923 url = "https://files.pythonhosted.org/packages/b3/73/fc5c850f44af5889192dff783b7b0d8f3fe8d30b65c8e3f78f8f0265fecf/Markdown-2.6.11.tar.gz";
924 924 sha256 = "108g80ryzykh8bj0i7jfp71510wrcixdi771lf2asyghgyf8cmm8";
925 925 };
926 926 meta = {
927 927 license = [ pkgs.lib.licenses.bsdOriginal ];
928 928 };
929 929 };
930 930 "markupsafe" = super.buildPythonPackage {
931 931 name = "markupsafe-1.1.1";
932 932 doCheck = false;
933 933 src = fetchurl {
934 934 url = "https://files.pythonhosted.org/packages/b9/2e/64db92e53b86efccfaea71321f597fa2e1b2bd3853d8ce658568f7a13094/MarkupSafe-1.1.1.tar.gz";
935 935 sha256 = "0sqipg4fk7xbixqd8kq6rlkxj664d157bdwbh93farcphf92x1r9";
936 936 };
937 937 meta = {
938 938 license = [ pkgs.lib.licenses.bsdOriginal pkgs.lib.licenses.bsd3 ];
939 939 };
940 940 };
941 941 "mistune" = super.buildPythonPackage {
942 942 name = "mistune-0.8.4";
943 943 doCheck = false;
944 944 src = fetchurl {
945 945 url = "https://files.pythonhosted.org/packages/2d/a4/509f6e7783ddd35482feda27bc7f72e65b5e7dc910eca4ab2164daf9c577/mistune-0.8.4.tar.gz";
946 946 sha256 = "0vkmsh0x480rni51lhyvigfdf06b9247z868pk3bal1wnnfl58sr";
947 947 };
948 948 meta = {
949 949 license = [ pkgs.lib.licenses.bsdOriginal ];
950 950 };
951 951 };
952 952 "mock" = super.buildPythonPackage {
953 953 name = "mock-3.0.5";
954 954 doCheck = false;
955 955 propagatedBuildInputs = [
956 956 self."six"
957 957 self."funcsigs"
958 958 ];
959 959 src = fetchurl {
960 960 url = "https://files.pythonhosted.org/packages/2e/ab/4fe657d78b270aa6a32f027849513b829b41b0f28d9d8d7f8c3d29ea559a/mock-3.0.5.tar.gz";
961 961 sha256 = "1hrp6j0yrx2xzylfv02qa8kph661m6yq4p0mc8fnimch9j4psrc3";
962 962 };
963 963 meta = {
964 964 license = [ pkgs.lib.licenses.bsdOriginal { fullName = "OSI Approved :: BSD License"; } ];
965 965 };
966 966 };
967 967 "more-itertools" = super.buildPythonPackage {
968 968 name = "more-itertools-5.0.0";
969 969 doCheck = false;
970 970 propagatedBuildInputs = [
971 971 self."six"
972 972 ];
973 973 src = fetchurl {
974 974 url = "https://files.pythonhosted.org/packages/dd/26/30fc0d541d9fdf55faf5ba4b0fd68f81d5bd2447579224820ad525934178/more-itertools-5.0.0.tar.gz";
975 975 sha256 = "1r12cm6mcdwdzz7d47a6g4l437xsvapdlgyhqay3i2nrlv03da9q";
976 976 };
977 977 meta = {
978 978 license = [ pkgs.lib.licenses.mit ];
979 979 };
980 980 };
981 981 "msgpack-python" = super.buildPythonPackage {
982 982 name = "msgpack-python-0.5.6";
983 983 doCheck = false;
984 984 src = fetchurl {
985 985 url = "https://files.pythonhosted.org/packages/8a/20/6eca772d1a5830336f84aca1d8198e5a3f4715cd1c7fc36d3cc7f7185091/msgpack-python-0.5.6.tar.gz";
986 986 sha256 = "16wh8qgybmfh4pjp8vfv78mdlkxfmcasg78lzlnm6nslsfkci31p";
987 987 };
988 988 meta = {
989 989 license = [ pkgs.lib.licenses.asl20 ];
990 990 };
991 991 };
992 992 "mysql-python" = super.buildPythonPackage {
993 993 name = "mysql-python-1.2.5";
994 994 doCheck = false;
995 995 src = fetchurl {
996 996 url = "https://files.pythonhosted.org/packages/a5/e9/51b544da85a36a68debe7a7091f068d802fc515a3a202652828c73453cad/MySQL-python-1.2.5.zip";
997 997 sha256 = "0x0c2jg0bb3pp84njaqiic050qkyd7ymwhfvhipnimg58yv40441";
998 998 };
999 999 meta = {
1000 1000 license = [ pkgs.lib.licenses.gpl1 ];
1001 1001 };
1002 1002 };
1003 1003 "nbconvert" = super.buildPythonPackage {
1004 1004 name = "nbconvert-5.3.1";
1005 1005 doCheck = false;
1006 1006 propagatedBuildInputs = [
1007 1007 self."mistune"
1008 1008 self."jinja2"
1009 1009 self."pygments"
1010 1010 self."traitlets"
1011 1011 self."jupyter-core"
1012 1012 self."nbformat"
1013 1013 self."entrypoints"
1014 1014 self."bleach"
1015 1015 self."pandocfilters"
1016 1016 self."testpath"
1017 1017 ];
1018 1018 src = fetchurl {
1019 1019 url = "https://files.pythonhosted.org/packages/b9/a4/d0a0938ad6f5eeb4dea4e73d255c617ef94b0b2849d51194c9bbdb838412/nbconvert-5.3.1.tar.gz";
1020 1020 sha256 = "1f9dkvpx186xjm4xab0qbph588mncp4vqk3fmxrsnqs43mks9c8j";
1021 1021 };
1022 1022 meta = {
1023 1023 license = [ pkgs.lib.licenses.bsdOriginal ];
1024 1024 };
1025 1025 };
1026 1026 "nbformat" = super.buildPythonPackage {
1027 1027 name = "nbformat-4.4.0";
1028 1028 doCheck = false;
1029 1029 propagatedBuildInputs = [
1030 1030 self."ipython-genutils"
1031 1031 self."traitlets"
1032 1032 self."jsonschema"
1033 1033 self."jupyter-core"
1034 1034 ];
1035 1035 src = fetchurl {
1036 1036 url = "https://files.pythonhosted.org/packages/6e/0e/160754f7ae3e984863f585a3743b0ed1702043a81245907c8fae2d537155/nbformat-4.4.0.tar.gz";
1037 1037 sha256 = "00nlf08h8yc4q73nphfvfhxrcnilaqanb8z0mdy6nxk0vzq4wjgp";
1038 1038 };
1039 1039 meta = {
1040 1040 license = [ pkgs.lib.licenses.bsdOriginal ];
1041 1041 };
1042 1042 };
1043 1043 "packaging" = super.buildPythonPackage {
1044 1044 name = "packaging-19.2";
1045 1045 doCheck = false;
1046 1046 propagatedBuildInputs = [
1047 1047 self."pyparsing"
1048 1048 self."six"
1049 1049 ];
1050 1050 src = fetchurl {
1051 1051 url = "https://files.pythonhosted.org/packages/5a/2f/449ded84226d0e2fda8da9252e5ee7731bdf14cd338f622dfcd9934e0377/packaging-19.2.tar.gz";
1052 1052 sha256 = "0izwlz9h0bw171a1chr311g2y7n657zjaf4mq4rgm8pp9lbj9f98";
1053 1053 };
1054 1054 meta = {
1055 1055 license = [ pkgs.lib.licenses.bsdOriginal { fullName = "BSD or Apache License, Version 2.0"; } pkgs.lib.licenses.asl20 ];
1056 1056 };
1057 1057 };
1058 1058 "pandocfilters" = super.buildPythonPackage {
1059 1059 name = "pandocfilters-1.4.2";
1060 1060 doCheck = false;
1061 1061 src = fetchurl {
1062 1062 url = "https://files.pythonhosted.org/packages/4c/ea/236e2584af67bb6df960832731a6e5325fd4441de001767da328c33368ce/pandocfilters-1.4.2.tar.gz";
1063 1063 sha256 = "1a8d9b7s48gmq9zj0pmbyv2sivn5i7m6mybgpkk4jm5vd7hp1pdk";
1064 1064 };
1065 1065 meta = {
1066 1066 license = [ pkgs.lib.licenses.bsdOriginal ];
1067 1067 };
1068 1068 };
1069 1069 "paste" = super.buildPythonPackage {
1070 1070 name = "paste-3.2.1";
1071 1071 doCheck = false;
1072 1072 propagatedBuildInputs = [
1073 1073 self."six"
1074 1074 ];
1075 1075 src = fetchurl {
1076 1076 url = "https://files.pythonhosted.org/packages/0d/86/7008b5563594e8a63763f05212a3eb84c85f0b2eff834e5697716e56bca9/Paste-3.2.1.tar.gz";
1077 1077 sha256 = "1vjxr8n1p31c9x9rh8g0f34yisa9028cxpvn36q7g1s0m2b9x71x";
1078 1078 };
1079 1079 meta = {
1080 1080 license = [ pkgs.lib.licenses.mit ];
1081 1081 };
1082 1082 };
1083 1083 "pastedeploy" = super.buildPythonPackage {
1084 1084 name = "pastedeploy-2.0.1";
1085 1085 doCheck = false;
1086 1086 src = fetchurl {
1087 1087 url = "https://files.pythonhosted.org/packages/19/a0/5623701df7e2478a68a1b685d1a84518024eef994cde7e4da8449a31616f/PasteDeploy-2.0.1.tar.gz";
1088 1088 sha256 = "02imfbbx1mi2h546f3sr37m47dk9qizaqhzzlhx8bkzxa6fzn8yl";
1089 1089 };
1090 1090 meta = {
1091 1091 license = [ pkgs.lib.licenses.mit ];
1092 1092 };
1093 1093 };
1094 1094 "pastescript" = super.buildPythonPackage {
1095 1095 name = "pastescript-3.2.0";
1096 1096 doCheck = false;
1097 1097 propagatedBuildInputs = [
1098 1098 self."paste"
1099 1099 self."pastedeploy"
1100 1100 self."six"
1101 1101 ];
1102 1102 src = fetchurl {
1103 1103 url = "https://files.pythonhosted.org/packages/ff/47/45c6f5a3cb8f5abf786fea98dbb8d02400a55768a9b623afb7df12346c61/PasteScript-3.2.0.tar.gz";
1104 1104 sha256 = "1b3jq7xh383nvrrlblk05m37345bv97xrhx77wshllba3h7mq3wv";
1105 1105 };
1106 1106 meta = {
1107 1107 license = [ pkgs.lib.licenses.mit ];
1108 1108 };
1109 1109 };
1110 1110 "pathlib2" = super.buildPythonPackage {
1111 1111 name = "pathlib2-2.3.5";
1112 1112 doCheck = false;
1113 1113 propagatedBuildInputs = [
1114 1114 self."six"
1115 1115 self."scandir"
1116 1116 ];
1117 1117 src = fetchurl {
1118 1118 url = "https://files.pythonhosted.org/packages/94/d8/65c86584e7e97ef824a1845c72bbe95d79f5b306364fa778a3c3e401b309/pathlib2-2.3.5.tar.gz";
1119 1119 sha256 = "0s4qa8c082fdkb17izh4mfgwrjd1n5pya18wvrbwqdvvb5xs9nbc";
1120 1120 };
1121 1121 meta = {
1122 1122 license = [ pkgs.lib.licenses.mit ];
1123 1123 };
1124 1124 };
1125 1125 "peppercorn" = super.buildPythonPackage {
1126 1126 name = "peppercorn-0.6";
1127 1127 doCheck = false;
1128 1128 src = fetchurl {
1129 1129 url = "https://files.pythonhosted.org/packages/e4/77/93085de7108cdf1a0b092ff443872a8f9442c736d7ddebdf2f27627935f4/peppercorn-0.6.tar.gz";
1130 1130 sha256 = "1ip4bfwcpwkq9hz2dai14k2cyabvwrnvcvrcmzxmqm04g8fnimwn";
1131 1131 };
1132 1132 meta = {
1133 1133 license = [ { fullName = "BSD-derived (http://www.repoze.org/LICENSE.txt)"; } ];
1134 1134 };
1135 1135 };
1136 1136 "pexpect" = super.buildPythonPackage {
1137 1137 name = "pexpect-4.7.0";
1138 1138 doCheck = false;
1139 1139 propagatedBuildInputs = [
1140 1140 self."ptyprocess"
1141 1141 ];
1142 1142 src = fetchurl {
1143 1143 url = "https://files.pythonhosted.org/packages/1c/b1/362a0d4235496cb42c33d1d8732b5e2c607b0129ad5fdd76f5a583b9fcb3/pexpect-4.7.0.tar.gz";
1144 1144 sha256 = "1sv2rri15zwhds85a4kamwh9pj49qcxv7m4miyr4jfpfwv81yb4y";
1145 1145 };
1146 1146 meta = {
1147 1147 license = [ pkgs.lib.licenses.isc { fullName = "ISC License (ISCL)"; } ];
1148 1148 };
1149 1149 };
1150 1150 "pickleshare" = super.buildPythonPackage {
1151 1151 name = "pickleshare-0.7.5";
1152 1152 doCheck = false;
1153 1153 propagatedBuildInputs = [
1154 1154 self."pathlib2"
1155 1155 ];
1156 1156 src = fetchurl {
1157 1157 url = "https://files.pythonhosted.org/packages/d8/b6/df3c1c9b616e9c0edbc4fbab6ddd09df9535849c64ba51fcb6531c32d4d8/pickleshare-0.7.5.tar.gz";
1158 1158 sha256 = "1jmghg3c53yp1i8cm6pcrm280ayi8621rwyav9fac7awjr3kss47";
1159 1159 };
1160 1160 meta = {
1161 1161 license = [ pkgs.lib.licenses.mit ];
1162 1162 };
1163 1163 };
1164 1164 "plaster" = super.buildPythonPackage {
1165 1165 name = "plaster-1.0";
1166 1166 doCheck = false;
1167 1167 propagatedBuildInputs = [
1168 1168 self."setuptools"
1169 1169 ];
1170 1170 src = fetchurl {
1171 1171 url = "https://files.pythonhosted.org/packages/37/e1/56d04382d718d32751017d32f351214384e529b794084eee20bb52405563/plaster-1.0.tar.gz";
1172 1172 sha256 = "1hy8k0nv2mxq94y5aysk6hjk9ryb4bsd13g83m60hcyzxz3wflc3";
1173 1173 };
1174 1174 meta = {
1175 1175 license = [ pkgs.lib.licenses.mit ];
1176 1176 };
1177 1177 };
1178 1178 "plaster-pastedeploy" = super.buildPythonPackage {
1179 1179 name = "plaster-pastedeploy-0.7";
1180 1180 doCheck = false;
1181 1181 propagatedBuildInputs = [
1182 1182 self."pastedeploy"
1183 1183 self."plaster"
1184 1184 ];
1185 1185 src = fetchurl {
1186 1186 url = "https://files.pythonhosted.org/packages/99/69/2d3bc33091249266a1bd3cf24499e40ab31d54dffb4a7d76fe647950b98c/plaster_pastedeploy-0.7.tar.gz";
1187 1187 sha256 = "1zg7gcsvc1kzay1ry5p699rg2qavfsxqwl17mqxzr0gzw6j9679r";
1188 1188 };
1189 1189 meta = {
1190 1190 license = [ pkgs.lib.licenses.mit ];
1191 1191 };
1192 1192 };
1193 1193 "pluggy" = super.buildPythonPackage {
1194 1194 name = "pluggy-0.13.0";
1195 1195 doCheck = false;
1196 1196 propagatedBuildInputs = [
1197 1197 self."importlib-metadata"
1198 1198 ];
1199 1199 src = fetchurl {
1200 1200 url = "https://files.pythonhosted.org/packages/d7/9d/ae82a5facf2dd89f557a33ad18eb68e5ac7b7a75cf52bf6a208f29077ecf/pluggy-0.13.0.tar.gz";
1201 1201 sha256 = "0d4gsvb4kjqhiqqi4bbsdp7s1xlyl5phibcw1q1mrpd65xia2pzs";
1202 1202 };
1203 1203 meta = {
1204 1204 license = [ pkgs.lib.licenses.mit ];
1205 1205 };
1206 1206 };
1207 1207 "prompt-toolkit" = super.buildPythonPackage {
1208 1208 name = "prompt-toolkit-1.0.18";
1209 1209 doCheck = false;
1210 1210 propagatedBuildInputs = [
1211 1211 self."six"
1212 1212 self."wcwidth"
1213 1213 ];
1214 1214 src = fetchurl {
1215 1215 url = "https://files.pythonhosted.org/packages/c5/64/c170e5b1913b540bf0c8ab7676b21fdd1d25b65ddeb10025c6ca43cccd4c/prompt_toolkit-1.0.18.tar.gz";
1216 1216 sha256 = "09h1153wgr5x2ny7ds0w2m81n3bb9j8hjb8sjfnrg506r01clkyx";
1217 1217 };
1218 1218 meta = {
1219 1219 license = [ pkgs.lib.licenses.bsdOriginal ];
1220 1220 };
1221 1221 };
1222 1222 "psutil" = super.buildPythonPackage {
1223 1223 name = "psutil-5.6.5";
1224 1224 doCheck = false;
1225 1225 src = fetchurl {
1226 1226 url = "https://files.pythonhosted.org/packages/03/9a/95c4b3d0424426e5fd94b5302ff74cea44d5d4f53466e1228ac8e73e14b4/psutil-5.6.5.tar.gz";
1227 1227 sha256 = "0isil5jxwwd8awz54qk28rpgjg43i5l6yl70g40vxwa4r4m56lfh";
1228 1228 };
1229 1229 meta = {
1230 1230 license = [ pkgs.lib.licenses.bsdOriginal ];
1231 1231 };
1232 1232 };
1233 1233 "psycopg2" = super.buildPythonPackage {
1234 1234 name = "psycopg2-2.8.4";
1235 1235 doCheck = false;
1236 1236 src = fetchurl {
1237 1237 url = "https://files.pythonhosted.org/packages/84/d7/6a93c99b5ba4d4d22daa3928b983cec66df4536ca50b22ce5dcac65e4e71/psycopg2-2.8.4.tar.gz";
1238 1238 sha256 = "1djvh98pi4hjd8rxbq8qzc63bg8v78k33yg6pl99wak61b6fb67q";
1239 1239 };
1240 1240 meta = {
1241 1241 license = [ pkgs.lib.licenses.zpl21 { fullName = "GNU Library or Lesser General Public License (LGPL)"; } { fullName = "LGPL with exceptions or ZPL"; } ];
1242 1242 };
1243 1243 };
1244 1244 "ptyprocess" = super.buildPythonPackage {
1245 1245 name = "ptyprocess-0.6.0";
1246 1246 doCheck = false;
1247 1247 src = fetchurl {
1248 1248 url = "https://files.pythonhosted.org/packages/7d/2d/e4b8733cf79b7309d84c9081a4ab558c89d8c89da5961bf4ddb050ca1ce0/ptyprocess-0.6.0.tar.gz";
1249 1249 sha256 = "1h4lcd3w5nrxnsk436ar7fwkiy5rfn5wj2xwy9l0r4mdqnf2jgwj";
1250 1250 };
1251 1251 meta = {
1252 1252 license = [ ];
1253 1253 };
1254 1254 };
1255 1255 "py" = super.buildPythonPackage {
1256 1256 name = "py-1.8.0";
1257 1257 doCheck = false;
1258 1258 src = fetchurl {
1259 1259 url = "https://files.pythonhosted.org/packages/f1/5a/87ca5909f400a2de1561f1648883af74345fe96349f34f737cdfc94eba8c/py-1.8.0.tar.gz";
1260 1260 sha256 = "0lsy1gajva083pzc7csj1cvbmminb7b4l6a0prdzyb3fd829nqyw";
1261 1261 };
1262 1262 meta = {
1263 1263 license = [ pkgs.lib.licenses.mit ];
1264 1264 };
1265 1265 };
1266 1266 "py-bcrypt" = super.buildPythonPackage {
1267 1267 name = "py-bcrypt-0.4";
1268 1268 doCheck = false;
1269 1269 src = fetchurl {
1270 1270 url = "https://files.pythonhosted.org/packages/68/b1/1c3068c5c4d2e35c48b38dcc865301ebfdf45f54507086ac65ced1fd3b3d/py-bcrypt-0.4.tar.gz";
1271 1271 sha256 = "0y6smdggwi5s72v6p1nn53dg6w05hna3d264cq6kas0lap73p8az";
1272 1272 };
1273 1273 meta = {
1274 1274 license = [ pkgs.lib.licenses.bsdOriginal ];
1275 1275 };
1276 1276 };
1277 1277 "py-gfm" = super.buildPythonPackage {
1278 1278 name = "py-gfm-0.1.4";
1279 1279 doCheck = false;
1280 1280 propagatedBuildInputs = [
1281 1281 self."setuptools"
1282 1282 self."markdown"
1283 1283 ];
1284 1284 src = fetchurl {
1285 1285 url = "https://files.pythonhosted.org/packages/06/ee/004a03a1d92bb386dae44f6dd087db541bc5093374f1637d4d4ae5596cc2/py-gfm-0.1.4.tar.gz";
1286 1286 sha256 = "0zip06g2isivx8fzgqd4n9qzsa22c25jas1rsb7m2rnjg72m0rzg";
1287 1287 };
1288 1288 meta = {
1289 1289 license = [ pkgs.lib.licenses.bsdOriginal ];
1290 1290 };
1291 1291 };
1292 1292 "pyasn1" = super.buildPythonPackage {
1293 1293 name = "pyasn1-0.4.7";
1294 1294 doCheck = false;
1295 1295 src = fetchurl {
1296 1296 url = "https://files.pythonhosted.org/packages/ca/f8/2a60a2c88a97558bdd289b6dc9eb75b00bd90ff34155d681ba6dbbcb46b2/pyasn1-0.4.7.tar.gz";
1297 1297 sha256 = "0146ryp4g09ycy8p3l2vigmgfg42n4gb8whgg8cysrhxr9b56jd9";
1298 1298 };
1299 1299 meta = {
1300 1300 license = [ pkgs.lib.licenses.bsdOriginal ];
1301 1301 };
1302 1302 };
1303 1303 "pyasn1-modules" = super.buildPythonPackage {
1304 1304 name = "pyasn1-modules-0.2.6";
1305 1305 doCheck = false;
1306 1306 propagatedBuildInputs = [
1307 1307 self."pyasn1"
1308 1308 ];
1309 1309 src = fetchurl {
1310 1310 url = "https://files.pythonhosted.org/packages/f1/a9/a1ef72a0e43feff643cf0130a08123dea76205e7a0dda37e3efb5f054a31/pyasn1-modules-0.2.6.tar.gz";
1311 1311 sha256 = "08hph9j1r018drnrny29l7dl2q0cin78csswrhwrh8jmq61pmha3";
1312 1312 };
1313 1313 meta = {
1314 1314 license = [ pkgs.lib.licenses.bsdOriginal pkgs.lib.licenses.bsd2 ];
1315 1315 };
1316 1316 };
1317 1317 "pycparser" = super.buildPythonPackage {
1318 1318 name = "pycparser-2.19";
1319 1319 doCheck = false;
1320 1320 src = fetchurl {
1321 1321 url = "https://files.pythonhosted.org/packages/68/9e/49196946aee219aead1290e00d1e7fdeab8567783e83e1b9ab5585e6206a/pycparser-2.19.tar.gz";
1322 1322 sha256 = "1cr5dcj9628lkz1qlwq3fv97c25363qppkmcayqvd05dpy573259";
1323 1323 };
1324 1324 meta = {
1325 1325 license = [ pkgs.lib.licenses.bsdOriginal ];
1326 1326 };
1327 1327 };
1328 1328 "pycrypto" = super.buildPythonPackage {
1329 1329 name = "pycrypto-2.6.1";
1330 1330 doCheck = false;
1331 1331 src = fetchurl {
1332 1332 url = "https://files.pythonhosted.org/packages/60/db/645aa9af249f059cc3a368b118de33889219e0362141e75d4eaf6f80f163/pycrypto-2.6.1.tar.gz";
1333 1333 sha256 = "0g0ayql5b9mkjam8hym6zyg6bv77lbh66rv1fyvgqb17kfc1xkpj";
1334 1334 };
1335 1335 meta = {
1336 1336 license = [ pkgs.lib.licenses.publicDomain ];
1337 1337 };
1338 1338 };
1339 1339 "pycurl" = super.buildPythonPackage {
1340 1340 name = "pycurl-7.43.0.3";
1341 1341 doCheck = false;
1342 1342 src = fetchurl {
1343 1343 url = "https://files.pythonhosted.org/packages/ac/b3/0f3979633b7890bab6098d84c84467030b807a1e2b31f5d30103af5a71ca/pycurl-7.43.0.3.tar.gz";
1344 1344 sha256 = "13nsvqhvnmnvfk75s8iynqsgszyv06cjp4drd3psi7zpbh63623g";
1345 1345 };
1346 1346 meta = {
1347 1347 license = [ pkgs.lib.licenses.mit { fullName = "LGPL/MIT"; } { fullName = "GNU Library or Lesser General Public License (LGPL)"; } ];
1348 1348 };
1349 1349 };
1350 1350 "pygments" = super.buildPythonPackage {
1351 1351 name = "pygments-2.4.2";
1352 1352 doCheck = false;
1353 1353 src = fetchurl {
1354 1354 url = "https://files.pythonhosted.org/packages/7e/ae/26808275fc76bf2832deb10d3a3ed3107bc4de01b85dcccbe525f2cd6d1e/Pygments-2.4.2.tar.gz";
1355 1355 sha256 = "15v2sqm5g12bqa0c7wikfh9ck2nl97ayizy1hpqhmws5gqalq748";
1356 1356 };
1357 1357 meta = {
1358 1358 license = [ pkgs.lib.licenses.bsdOriginal ];
1359 1359 };
1360 1360 };
1361 1361 "pymysql" = super.buildPythonPackage {
1362 1362 name = "pymysql-0.8.1";
1363 1363 doCheck = false;
1364 1364 src = fetchurl {
1365 1365 url = "https://files.pythonhosted.org/packages/44/39/6bcb83cae0095a31b6be4511707fdf2009d3e29903a55a0494d3a9a2fac0/PyMySQL-0.8.1.tar.gz";
1366 1366 sha256 = "0a96crz55bw4h6myh833skrli7b0ck89m3x673y2z2ryy7zrpq9l";
1367 1367 };
1368 1368 meta = {
1369 1369 license = [ pkgs.lib.licenses.mit ];
1370 1370 };
1371 1371 };
1372 1372 "pyotp" = super.buildPythonPackage {
1373 1373 name = "pyotp-2.3.0";
1374 1374 doCheck = false;
1375 1375 src = fetchurl {
1376 1376 url = "https://files.pythonhosted.org/packages/f7/15/395c4945ea6bc37e8811280bb675615cb4c2b2c1cd70bdc43329da91a386/pyotp-2.3.0.tar.gz";
1377 1377 sha256 = "18d13ikra1iq0xyfqfm72zhgwxi2qi9ps6z1a6zmqp4qrn57wlzw";
1378 1378 };
1379 1379 meta = {
1380 1380 license = [ pkgs.lib.licenses.mit ];
1381 1381 };
1382 1382 };
1383 1383 "pyparsing" = super.buildPythonPackage {
1384 1384 name = "pyparsing-2.4.2";
1385 1385 doCheck = false;
1386 1386 src = fetchurl {
1387 1387 url = "https://files.pythonhosted.org/packages/7e/24/eaa8d7003aee23eda270099eeec754d7bf4399f75c6a011ef948304f66a2/pyparsing-2.4.2.tar.gz";
1388 1388 sha256 = "106xmhd2xn5bv6blsbqaa6wciwcq2c9i1pq1riw6s83y76wsg63g";
1389 1389 };
1390 1390 meta = {
1391 1391 license = [ pkgs.lib.licenses.mit ];
1392 1392 };
1393 1393 };
1394 1394 "pyramid" = super.buildPythonPackage {
1395 1395 name = "pyramid-1.10.4";
1396 1396 doCheck = false;
1397 1397 propagatedBuildInputs = [
1398 1398 self."hupper"
1399 1399 self."plaster"
1400 1400 self."plaster-pastedeploy"
1401 1401 self."setuptools"
1402 1402 self."translationstring"
1403 1403 self."venusian"
1404 1404 self."webob"
1405 1405 self."zope.deprecation"
1406 1406 self."zope.interface"
1407 1407 self."repoze.lru"
1408 1408 ];
1409 1409 src = fetchurl {
1410 1410 url = "https://files.pythonhosted.org/packages/c2/43/1ae701c9c6bb3a434358e678a5e72c96e8aa55cf4cb1d2fa2041b5dd38b7/pyramid-1.10.4.tar.gz";
1411 1411 sha256 = "0rkxs1ajycg2zh1c94xlmls56mx5m161sn8112skj0amza6cn36q";
1412 1412 };
1413 1413 meta = {
1414 1414 license = [ { fullName = "Repoze Public License"; } { fullName = "BSD-derived (http://www.repoze.org/LICENSE.txt)"; } ];
1415 1415 };
1416 1416 };
1417 1417 "pyramid-debugtoolbar" = super.buildPythonPackage {
1418 1418 name = "pyramid-debugtoolbar-4.5.1";
1419 1419 doCheck = false;
1420 1420 propagatedBuildInputs = [
1421 1421 self."pyramid"
1422 1422 self."pyramid-mako"
1423 1423 self."repoze.lru"
1424 1424 self."pygments"
1425 1425 self."ipaddress"
1426 1426 ];
1427 1427 src = fetchurl {
1428 1428 url = "https://files.pythonhosted.org/packages/88/21/74e7fa52edc74667e29403bd0cb4f2bb74dc4014711de313868001bf639f/pyramid_debugtoolbar-4.5.1.tar.gz";
1429 1429 sha256 = "0hgf6i1fzvq43m9vjdmb24nnv8fwp7sdzrx9bcwrgpy24n07am9a";
1430 1430 };
1431 1431 meta = {
1432 1432 license = [ { fullName = "Repoze Public License"; } pkgs.lib.licenses.bsdOriginal ];
1433 1433 };
1434 1434 };
1435 1435 "pyramid-jinja2" = super.buildPythonPackage {
1436 1436 name = "pyramid-jinja2-2.7";
1437 1437 doCheck = false;
1438 1438 propagatedBuildInputs = [
1439 1439 self."pyramid"
1440 1440 self."zope.deprecation"
1441 1441 self."jinja2"
1442 1442 self."markupsafe"
1443 1443 ];
1444 1444 src = fetchurl {
1445 1445 url = "https://files.pythonhosted.org/packages/d8/80/d60a7233823de22ce77bd864a8a83736a1fe8b49884b08303a2e68b2c853/pyramid_jinja2-2.7.tar.gz";
1446 1446 sha256 = "1sz5s0pp5jqhf4w22w9527yz8hgdi4mhr6apd6vw1gm5clghh8aw";
1447 1447 };
1448 1448 meta = {
1449 1449 license = [ { fullName = "Repoze Public License"; } { fullName = "BSD-derived (http://www.repoze.org/LICENSE.txt)"; } ];
1450 1450 };
1451 1451 };
1452 1452 "pyramid-mailer" = super.buildPythonPackage {
1453 1453 name = "pyramid-mailer-0.15.1";
1454 1454 doCheck = false;
1455 1455 propagatedBuildInputs = [
1456 1456 self."pyramid"
1457 1457 self."repoze.sendmail"
1458 1458 self."transaction"
1459 1459 ];
1460 1460 src = fetchurl {
1461 1461 url = "https://files.pythonhosted.org/packages/a0/f2/6febf5459dff4d7e653314d575469ad2e11b9d2af2c3606360e1c67202f2/pyramid_mailer-0.15.1.tar.gz";
1462 1462 sha256 = "16vg8jb203jgb7b0hd6wllfqvp542qh2ry1gjai2m6qpv5agy2pc";
1463 1463 };
1464 1464 meta = {
1465 1465 license = [ pkgs.lib.licenses.bsdOriginal ];
1466 1466 };
1467 1467 };
1468 1468 "pyramid-mako" = super.buildPythonPackage {
1469 1469 name = "pyramid-mako-1.1.0";
1470 1470 doCheck = false;
1471 1471 propagatedBuildInputs = [
1472 1472 self."pyramid"
1473 1473 self."mako"
1474 1474 ];
1475 1475 src = fetchurl {
1476 1476 url = "https://files.pythonhosted.org/packages/63/7b/5e2af68f675071a6bad148c1c393928f0ef5fcd94e95cbf53b89d6471a83/pyramid_mako-1.1.0.tar.gz";
1477 1477 sha256 = "1qj0m091mnii86j2q1d82yir22nha361rvhclvg3s70z8iiwhrh0";
1478 1478 };
1479 1479 meta = {
1480 1480 license = [ { fullName = "Repoze Public License"; } { fullName = "BSD-derived (http://www.repoze.org/LICENSE.txt)"; } ];
1481 1481 };
1482 1482 };
1483 1483 "pysqlite" = super.buildPythonPackage {
1484 1484 name = "pysqlite-2.8.3";
1485 1485 doCheck = false;
1486 1486 src = fetchurl {
1487 1487 url = "https://files.pythonhosted.org/packages/42/02/981b6703e3c83c5b25a829c6e77aad059f9481b0bbacb47e6e8ca12bd731/pysqlite-2.8.3.tar.gz";
1488 1488 sha256 = "1424gwq9sil2ffmnizk60q36vydkv8rxs6m7xs987kz8cdc37lqp";
1489 1489 };
1490 1490 meta = {
1491 1491 license = [ { fullName = "zlib/libpng License"; } { fullName = "zlib/libpng license"; } ];
1492 1492 };
1493 1493 };
1494 1494 "pytest" = super.buildPythonPackage {
1495 1495 name = "pytest-4.6.5";
1496 1496 doCheck = false;
1497 1497 propagatedBuildInputs = [
1498 1498 self."py"
1499 1499 self."six"
1500 1500 self."packaging"
1501 1501 self."attrs"
1502 1502 self."atomicwrites"
1503 1503 self."pluggy"
1504 1504 self."importlib-metadata"
1505 1505 self."wcwidth"
1506 1506 self."funcsigs"
1507 1507 self."pathlib2"
1508 1508 self."more-itertools"
1509 1509 ];
1510 1510 src = fetchurl {
1511 1511 url = "https://files.pythonhosted.org/packages/2a/c6/1d1f32f6a5009900521b12e6560fb6b7245b0d4bc3fb771acd63d10e30e1/pytest-4.6.5.tar.gz";
1512 1512 sha256 = "0iykwwfp4h181nd7rsihh2120b0rkawlw7rvbl19sgfspncr3hwg";
1513 1513 };
1514 1514 meta = {
1515 1515 license = [ pkgs.lib.licenses.mit ];
1516 1516 };
1517 1517 };
1518 1518 "pytest-cov" = super.buildPythonPackage {
1519 1519 name = "pytest-cov-2.7.1";
1520 1520 doCheck = false;
1521 1521 propagatedBuildInputs = [
1522 1522 self."pytest"
1523 1523 self."coverage"
1524 1524 ];
1525 1525 src = fetchurl {
1526 1526 url = "https://files.pythonhosted.org/packages/bb/0f/3db7ff86801883b21d5353b258c994b1b8e2abbc804e2273b8d0fd19004b/pytest-cov-2.7.1.tar.gz";
1527 1527 sha256 = "0filvmmyqm715azsl09ql8hy2x7h286n6d8z5x42a1wpvvys83p0";
1528 1528 };
1529 1529 meta = {
1530 1530 license = [ pkgs.lib.licenses.bsdOriginal pkgs.lib.licenses.mit ];
1531 1531 };
1532 1532 };
1533 1533 "pytest-profiling" = super.buildPythonPackage {
1534 1534 name = "pytest-profiling-1.7.0";
1535 1535 doCheck = false;
1536 1536 propagatedBuildInputs = [
1537 1537 self."six"
1538 1538 self."pytest"
1539 1539 self."gprof2dot"
1540 1540 ];
1541 1541 src = fetchurl {
1542 1542 url = "https://files.pythonhosted.org/packages/39/70/22a4b33739f07f1732a63e33bbfbf68e0fa58cfba9d200e76d01921eddbf/pytest-profiling-1.7.0.tar.gz";
1543 1543 sha256 = "0abz9gi26jpcfdzgsvwad91555lpgdc8kbymicmms8k2fqa8z4wk";
1544 1544 };
1545 1545 meta = {
1546 1546 license = [ pkgs.lib.licenses.mit ];
1547 1547 };
1548 1548 };
1549 1549 "pytest-runner" = super.buildPythonPackage {
1550 1550 name = "pytest-runner-5.1";
1551 1551 doCheck = false;
1552 1552 src = fetchurl {
1553 1553 url = "https://files.pythonhosted.org/packages/d9/6d/4b41a74b31720e25abd4799be72d54811da4b4d0233e38b75864dcc1f7ad/pytest-runner-5.1.tar.gz";
1554 1554 sha256 = "0ykfcnpp8c22winj63qzc07l5axwlc9ikl8vn05sc32gv3417815";
1555 1555 };
1556 1556 meta = {
1557 1557 license = [ pkgs.lib.licenses.mit ];
1558 1558 };
1559 1559 };
1560 1560 "pytest-sugar" = super.buildPythonPackage {
1561 1561 name = "pytest-sugar-0.9.2";
1562 1562 doCheck = false;
1563 1563 propagatedBuildInputs = [
1564 1564 self."pytest"
1565 1565 self."termcolor"
1566 1566 self."packaging"
1567 1567 ];
1568 1568 src = fetchurl {
1569 1569 url = "https://files.pythonhosted.org/packages/55/59/f02f78d1c80f7e03e23177f60624c8106d4f23d124c921df103f65692464/pytest-sugar-0.9.2.tar.gz";
1570 1570 sha256 = "1asq7yc4g8bx2sn7yy974mhc9ywvaihasjab4inkirdwn9s7mn7w";
1571 1571 };
1572 1572 meta = {
1573 1573 license = [ pkgs.lib.licenses.bsdOriginal ];
1574 1574 };
1575 1575 };
1576 1576 "pytest-timeout" = super.buildPythonPackage {
1577 1577 name = "pytest-timeout-1.3.3";
1578 1578 doCheck = false;
1579 1579 propagatedBuildInputs = [
1580 1580 self."pytest"
1581 1581 ];
1582 1582 src = fetchurl {
1583 1583 url = "https://files.pythonhosted.org/packages/13/48/7a166eaa29c1dca6cc253e3ba5773ff2e4aa4f567c1ea3905808e95ac5c1/pytest-timeout-1.3.3.tar.gz";
1584 1584 sha256 = "1cczcjhw4xx5sjkhxlhc5c1bkr7x6fcyx12wrnvwfckshdvblc2a";
1585 1585 };
1586 1586 meta = {
1587 1587 license = [ pkgs.lib.licenses.mit { fullName = "DFSG approved"; } ];
1588 1588 };
1589 1589 };
1590 1590 "python-dateutil" = super.buildPythonPackage {
1591 1591 name = "python-dateutil-2.8.1";
1592 1592 doCheck = false;
1593 1593 propagatedBuildInputs = [
1594 1594 self."six"
1595 1595 ];
1596 1596 src = fetchurl {
1597 1597 url = "https://files.pythonhosted.org/packages/be/ed/5bbc91f03fa4c839c4c7360375da77f9659af5f7086b7a7bdda65771c8e0/python-dateutil-2.8.1.tar.gz";
1598 1598 sha256 = "0g42w7k5007iv9dam6gnja2ry8ydwirh99mgdll35s12pyfzxsvk";
1599 1599 };
1600 1600 meta = {
1601 1601 license = [ pkgs.lib.licenses.bsdOriginal pkgs.lib.licenses.asl20 { fullName = "Dual License"; } ];
1602 1602 };
1603 1603 };
1604 1604 "python-editor" = super.buildPythonPackage {
1605 1605 name = "python-editor-1.0.4";
1606 1606 doCheck = false;
1607 1607 src = fetchurl {
1608 1608 url = "https://files.pythonhosted.org/packages/0a/85/78f4a216d28343a67b7397c99825cff336330893f00601443f7c7b2f2234/python-editor-1.0.4.tar.gz";
1609 1609 sha256 = "0yrjh8w72ivqxi4i7xsg5b1vz15x8fg51xra7c3bgfyxqnyadzai";
1610 1610 };
1611 1611 meta = {
1612 1612 license = [ pkgs.lib.licenses.asl20 { fullName = "Apache"; } ];
1613 1613 };
1614 1614 };
1615 1615 "python-ldap" = super.buildPythonPackage {
1616 1616 name = "python-ldap-3.1.0";
1617 1617 doCheck = false;
1618 1618 propagatedBuildInputs = [
1619 1619 self."pyasn1"
1620 1620 self."pyasn1-modules"
1621 1621 ];
1622 1622 src = fetchurl {
1623 1623 url = "https://files.pythonhosted.org/packages/7f/1c/28d721dff2fcd2fef9d55b40df63a00be26ec8a11e8c6fc612ae642f9cfd/python-ldap-3.1.0.tar.gz";
1624 1624 sha256 = "1i97nwfnraylyn0myxlf3vciicrf5h6fymrcff9c00k581wmx5s1";
1625 1625 };
1626 1626 meta = {
1627 1627 license = [ pkgs.lib.licenses.psfl ];
1628 1628 };
1629 1629 };
1630 1630 "python-memcached" = super.buildPythonPackage {
1631 1631 name = "python-memcached-1.59";
1632 1632 doCheck = false;
1633 1633 propagatedBuildInputs = [
1634 1634 self."six"
1635 1635 ];
1636 1636 src = fetchurl {
1637 1637 url = "https://files.pythonhosted.org/packages/90/59/5faf6e3cd8a568dd4f737ddae4f2e54204fd8c51f90bf8df99aca6c22318/python-memcached-1.59.tar.gz";
1638 1638 sha256 = "0kvyapavbirk2x3n1jx4yb9nyigrj1s3x15nm3qhpvhkpqvqdqm2";
1639 1639 };
1640 1640 meta = {
1641 1641 license = [ pkgs.lib.licenses.psfl ];
1642 1642 };
1643 1643 };
1644 1644 "python-pam" = super.buildPythonPackage {
1645 1645 name = "python-pam-1.8.4";
1646 1646 doCheck = false;
1647 1647 src = fetchurl {
1648 1648 url = "https://files.pythonhosted.org/packages/01/16/544d01cae9f28e0292dbd092b6b8b0bf222b528f362ee768a5bed2140111/python-pam-1.8.4.tar.gz";
1649 1649 sha256 = "16whhc0vr7gxsbzvsnq65nq8fs3wwmx755cavm8kkczdkz4djmn8";
1650 1650 };
1651 1651 meta = {
1652 1652 license = [ { fullName = "License :: OSI Approved :: MIT License"; } pkgs.lib.licenses.mit ];
1653 1653 };
1654 1654 };
1655 1655 "python-saml" = super.buildPythonPackage {
1656 1656 name = "python-saml-2.4.2";
1657 1657 doCheck = false;
1658 1658 propagatedBuildInputs = [
1659 1659 self."dm.xmlsec.binding"
1660 1660 self."isodate"
1661 1661 self."defusedxml"
1662 1662 ];
1663 1663 src = fetchurl {
1664 1664 url = "https://files.pythonhosted.org/packages/79/a8/a6611017e0883102fd5e2b73c9d90691b8134e38247c04ee1531d3dc647c/python-saml-2.4.2.tar.gz";
1665 1665 sha256 = "0dls4hwvf13yg7x5yfjrghbywg8g38vn5vr0rsf70hli3ydbfm43";
1666 1666 };
1667 1667 meta = {
1668 1668 license = [ pkgs.lib.licenses.mit ];
1669 1669 };
1670 1670 };
1671 1671 "pytz" = super.buildPythonPackage {
1672 1672 name = "pytz-2019.2";
1673 1673 doCheck = false;
1674 1674 src = fetchurl {
1675 1675 url = "https://files.pythonhosted.org/packages/27/c0/fbd352ca76050952a03db776d241959d5a2ee1abddfeb9e2a53fdb489be4/pytz-2019.2.tar.gz";
1676 1676 sha256 = "0ckb27hhjc8i8gcdvk4d9avld62b7k52yjijc60s2m3y8cpb7h16";
1677 1677 };
1678 1678 meta = {
1679 1679 license = [ pkgs.lib.licenses.mit ];
1680 1680 };
1681 1681 };
1682 1682 "pyzmq" = super.buildPythonPackage {
1683 1683 name = "pyzmq-14.6.0";
1684 1684 doCheck = false;
1685 1685 src = fetchurl {
1686 1686 url = "https://files.pythonhosted.org/packages/8a/3b/5463d5a9d712cd8bbdac335daece0d69f6a6792da4e3dd89956c0db4e4e6/pyzmq-14.6.0.tar.gz";
1687 1687 sha256 = "1frmbjykvhmdg64g7sn20c9fpamrsfxwci1nhhg8q7jgz5pq0ikp";
1688 1688 };
1689 1689 meta = {
1690 1690 license = [ pkgs.lib.licenses.bsdOriginal { fullName = "LGPL+BSD"; } { fullName = "GNU Library or Lesser General Public License (LGPL)"; } ];
1691 1691 };
1692 1692 };
1693 1693 "redis" = super.buildPythonPackage {
1694 1694 name = "redis-3.3.11";
1695 1695 doCheck = false;
1696 1696 src = fetchurl {
1697 1697 url = "https://files.pythonhosted.org/packages/06/ca/00557c74279d2f256d3c42cabf237631355f3a132e4c74c2000e6647ad98/redis-3.3.11.tar.gz";
1698 1698 sha256 = "1hicqbi5xl92hhml82awrr2rxl9jar5fp8nbcycj9qgmsdwc43wd";
1699 1699 };
1700 1700 meta = {
1701 1701 license = [ pkgs.lib.licenses.mit ];
1702 1702 };
1703 1703 };
1704 1704 "repoze.lru" = super.buildPythonPackage {
1705 1705 name = "repoze.lru-0.7";
1706 1706 doCheck = false;
1707 1707 src = fetchurl {
1708 1708 url = "https://files.pythonhosted.org/packages/12/bc/595a77c4b5e204847fdf19268314ef59c85193a9dc9f83630fc459c0fee5/repoze.lru-0.7.tar.gz";
1709 1709 sha256 = "0xzz1aw2smy8hdszrq8yhnklx6w1r1mf55061kalw3iq35gafa84";
1710 1710 };
1711 1711 meta = {
1712 1712 license = [ { fullName = "Repoze Public License"; } { fullName = "BSD-derived (http://www.repoze.org/LICENSE.txt)"; } ];
1713 1713 };
1714 1714 };
1715 1715 "repoze.sendmail" = super.buildPythonPackage {
1716 1716 name = "repoze.sendmail-4.4.1";
1717 1717 doCheck = false;
1718 1718 propagatedBuildInputs = [
1719 1719 self."setuptools"
1720 1720 self."zope.interface"
1721 1721 self."transaction"
1722 1722 ];
1723 1723 src = fetchurl {
1724 1724 url = "https://files.pythonhosted.org/packages/12/4e/8ef1fd5c42765d712427b9c391419a77bd48877886d2cbc5e9f23c8cad9b/repoze.sendmail-4.4.1.tar.gz";
1725 1725 sha256 = "096ln02jr2afk7ab9j2czxqv2ryqq7m86ah572nqplx52iws73ks";
1726 1726 };
1727 1727 meta = {
1728 1728 license = [ pkgs.lib.licenses.zpl21 ];
1729 1729 };
1730 1730 };
1731 1731 "requests" = super.buildPythonPackage {
1732 1732 name = "requests-2.9.1";
1733 1733 doCheck = false;
1734 1734 src = fetchurl {
1735 1735 url = "https://files.pythonhosted.org/packages/f9/6d/07c44fb1ebe04d069459a189e7dab9e4abfe9432adcd4477367c25332748/requests-2.9.1.tar.gz";
1736 1736 sha256 = "0zsqrzlybf25xscgi7ja4s48y2abf9wvjkn47wh984qgs1fq2xy5";
1737 1737 };
1738 1738 meta = {
1739 1739 license = [ pkgs.lib.licenses.asl20 ];
1740 1740 };
1741 1741 };
1742 1742 "rhodecode-enterprise-ce" = super.buildPythonPackage {
1743 1743 name = "rhodecode-enterprise-ce-4.18.0";
1744 1744 buildInputs = [
1745 1745 self."pytest"
1746 1746 self."py"
1747 1747 self."pytest-cov"
1748 1748 self."pytest-sugar"
1749 1749 self."pytest-runner"
1750 1750 self."pytest-profiling"
1751 1751 self."pytest-timeout"
1752 1752 self."gprof2dot"
1753 1753 self."mock"
1754 1754 self."cov-core"
1755 1755 self."coverage"
1756 1756 self."webtest"
1757 1757 self."beautifulsoup4"
1758 1758 self."configobj"
1759 1759 ];
1760 1760 doCheck = true;
1761 1761 propagatedBuildInputs = [
1762 1762 self."amqp"
1763 1763 self."babel"
1764 1764 self."beaker"
1765 1765 self."bleach"
1766 1766 self."celery"
1767 1767 self."channelstream"
1768 1768 self."click"
1769 1769 self."colander"
1770 1770 self."configobj"
1771 1771 self."cssselect"
1772 1772 self."cryptography"
1773 1773 self."decorator"
1774 1774 self."deform"
1775 1775 self."docutils"
1776 1776 self."dogpile.cache"
1777 1777 self."dogpile.core"
1778 1778 self."formencode"
1779 1779 self."future"
1780 1780 self."futures"
1781 1781 self."infrae.cache"
1782 1782 self."iso8601"
1783 1783 self."itsdangerous"
1784 1784 self."kombu"
1785 1785 self."lxml"
1786 1786 self."mako"
1787 1787 self."markdown"
1788 1788 self."markupsafe"
1789 1789 self."msgpack-python"
1790 1790 self."pyotp"
1791 1791 self."packaging"
1792 1792 self."pathlib2"
1793 1793 self."paste"
1794 1794 self."pastedeploy"
1795 1795 self."pastescript"
1796 1796 self."peppercorn"
1797 1797 self."psutil"
1798 1798 self."py-bcrypt"
1799 1799 self."pycurl"
1800 1800 self."pycrypto"
1801 1801 self."pygments"
1802 1802 self."pyparsing"
1803 1803 self."pyramid-debugtoolbar"
1804 1804 self."pyramid-mako"
1805 1805 self."pyramid"
1806 1806 self."pyramid-mailer"
1807 1807 self."python-dateutil"
1808 1808 self."python-ldap"
1809 1809 self."python-memcached"
1810 1810 self."python-pam"
1811 1811 self."python-saml"
1812 1812 self."pytz"
1813 1813 self."tzlocal"
1814 1814 self."pyzmq"
1815 1815 self."py-gfm"
1816 1816 self."redis"
1817 1817 self."repoze.lru"
1818 1818 self."requests"
1819 1819 self."routes"
1820 1820 self."simplejson"
1821 1821 self."six"
1822 1822 self."sqlalchemy"
1823 1823 self."sshpubkeys"
1824 1824 self."subprocess32"
1825 1825 self."supervisor"
1826 1826 self."translationstring"
1827 1827 self."urllib3"
1828 1828 self."urlobject"
1829 1829 self."venusian"
1830 1830 self."weberror"
1831 1831 self."webhelpers2"
1832 self."webhelpers"
1833 1832 self."webob"
1834 1833 self."whoosh"
1835 1834 self."wsgiref"
1836 1835 self."zope.cachedescriptors"
1837 1836 self."zope.deprecation"
1838 1837 self."zope.event"
1839 1838 self."zope.interface"
1840 1839 self."mysql-python"
1841 1840 self."pymysql"
1842 1841 self."pysqlite"
1843 1842 self."psycopg2"
1844 1843 self."nbconvert"
1845 1844 self."nbformat"
1846 1845 self."jupyter-client"
1847 1846 self."jupyter-core"
1848 1847 self."alembic"
1849 1848 self."invoke"
1850 1849 self."bumpversion"
1851 1850 self."gevent"
1852 1851 self."greenlet"
1853 1852 self."gunicorn"
1854 1853 self."waitress"
1855 1854 self."ipdb"
1856 1855 self."ipython"
1857 1856 self."rhodecode-tools"
1858 1857 self."appenlight-client"
1859 1858 self."pytest"
1860 1859 self."py"
1861 1860 self."pytest-cov"
1862 1861 self."pytest-sugar"
1863 1862 self."pytest-runner"
1864 1863 self."pytest-profiling"
1865 1864 self."pytest-timeout"
1866 1865 self."gprof2dot"
1867 1866 self."mock"
1868 1867 self."cov-core"
1869 1868 self."coverage"
1870 1869 self."webtest"
1871 1870 self."beautifulsoup4"
1872 1871 ];
1873 1872 src = ./.;
1874 1873 meta = {
1875 1874 license = [ { fullName = "Affero GNU General Public License v3 or later (AGPLv3+)"; } { fullName = "AGPLv3, and Commercial License"; } ];
1876 1875 };
1877 1876 };
1878 1877 "rhodecode-tools" = super.buildPythonPackage {
1879 1878 name = "rhodecode-tools-1.3.0";
1880 1879 doCheck = false;
1881 1880 propagatedBuildInputs = [
1882 1881 self."click"
1883 1882 self."future"
1884 1883 self."six"
1885 1884 self."mako"
1886 1885 self."markupsafe"
1887 1886 self."requests"
1888 1887 self."urllib3"
1889 1888 self."whoosh"
1890 1889 self."elasticsearch"
1891 1890 self."elasticsearch-dsl"
1892 1891 self."elasticsearch2"
1893 1892 self."elasticsearch1-dsl"
1894 1893 ];
1895 1894 src = fetchurl {
1896 1895 url = "https://code.rhodecode.com/rhodecode-tools-ce/artifacts/download/0-2e546668-61c4-42b2-ae03-7850a2dbb846.tar.gz?sha256=0938220f33264b0514dc63207ce1f77e38b1b1fd93fd4f68d34243ab62ecc853";
1897 1896 sha256 = "0ly8xiianhs2sdl4zzckznqv2f3yyzhpq833vha0ajr66c7j4f09";
1898 1897 };
1899 1898 meta = {
1900 1899 license = [ { fullName = "Apache 2.0 and Proprietary"; } ];
1901 1900 };
1902 1901 };
1903 1902 "routes" = super.buildPythonPackage {
1904 1903 name = "routes-2.4.1";
1905 1904 doCheck = false;
1906 1905 propagatedBuildInputs = [
1907 1906 self."six"
1908 1907 self."repoze.lru"
1909 1908 ];
1910 1909 src = fetchurl {
1911 1910 url = "https://files.pythonhosted.org/packages/33/38/ea827837e68d9c7dde4cff7ec122a93c319f0effc08ce92a17095576603f/Routes-2.4.1.tar.gz";
1912 1911 sha256 = "1zamff3m0kc4vyfniyhxpkkcqv1rrgnmh37ykxv34nna1ws47vi6";
1913 1912 };
1914 1913 meta = {
1915 1914 license = [ pkgs.lib.licenses.mit ];
1916 1915 };
1917 1916 };
1918 1917 "scandir" = super.buildPythonPackage {
1919 1918 name = "scandir-1.10.0";
1920 1919 doCheck = false;
1921 1920 src = fetchurl {
1922 1921 url = "https://files.pythonhosted.org/packages/df/f5/9c052db7bd54d0cbf1bc0bb6554362bba1012d03e5888950a4f5c5dadc4e/scandir-1.10.0.tar.gz";
1923 1922 sha256 = "1bkqwmf056pkchf05ywbnf659wqlp6lljcdb0y88wr9f0vv32ijd";
1924 1923 };
1925 1924 meta = {
1926 1925 license = [ pkgs.lib.licenses.bsdOriginal { fullName = "New BSD License"; } ];
1927 1926 };
1928 1927 };
1929 1928 "setproctitle" = super.buildPythonPackage {
1930 1929 name = "setproctitle-1.1.10";
1931 1930 doCheck = false;
1932 1931 src = fetchurl {
1933 1932 url = "https://files.pythonhosted.org/packages/5a/0d/dc0d2234aacba6cf1a729964383e3452c52096dc695581248b548786f2b3/setproctitle-1.1.10.tar.gz";
1934 1933 sha256 = "163kplw9dcrw0lffq1bvli5yws3rngpnvrxrzdw89pbphjjvg0v2";
1935 1934 };
1936 1935 meta = {
1937 1936 license = [ pkgs.lib.licenses.bsdOriginal ];
1938 1937 };
1939 1938 };
1940 1939 "setuptools" = super.buildPythonPackage {
1941 1940 name = "setuptools-41.6.0";
1942 1941 doCheck = false;
1943 1942 src = fetchurl {
1944 1943 url = "https://files.pythonhosted.org/packages/11/0a/7f13ef5cd932a107cd4c0f3ebc9d831d9b78e1a0e8c98a098ca17b1d7d97/setuptools-41.6.0.zip";
1945 1944 sha256 = "08nplghlnlb4qkhb9hp1lqqqlnh1qingdj8fi6w6rlfwj6rn3yka";
1946 1945 };
1947 1946 meta = {
1948 1947 license = [ pkgs.lib.licenses.mit ];
1949 1948 };
1950 1949 };
1951 1950 "simplegeneric" = super.buildPythonPackage {
1952 1951 name = "simplegeneric-0.8.1";
1953 1952 doCheck = false;
1954 1953 src = fetchurl {
1955 1954 url = "https://files.pythonhosted.org/packages/3d/57/4d9c9e3ae9a255cd4e1106bb57e24056d3d0709fc01b2e3e345898e49d5b/simplegeneric-0.8.1.zip";
1956 1955 sha256 = "0wwi1c6md4vkbcsfsf8dklf3vr4mcdj4mpxkanwgb6jb1432x5yw";
1957 1956 };
1958 1957 meta = {
1959 1958 license = [ pkgs.lib.licenses.zpl21 ];
1960 1959 };
1961 1960 };
1962 1961 "simplejson" = super.buildPythonPackage {
1963 1962 name = "simplejson-3.16.0";
1964 1963 doCheck = false;
1965 1964 src = fetchurl {
1966 1965 url = "https://files.pythonhosted.org/packages/e3/24/c35fb1c1c315fc0fffe61ea00d3f88e85469004713dab488dee4f35b0aff/simplejson-3.16.0.tar.gz";
1967 1966 sha256 = "19cws1syk8jzq2pw43878dv6fjkb0ifvjpx0i9aajix6kc9jkwxi";
1968 1967 };
1969 1968 meta = {
1970 1969 license = [ { fullName = "Academic Free License (AFL)"; } pkgs.lib.licenses.mit ];
1971 1970 };
1972 1971 };
1973 1972 "six" = super.buildPythonPackage {
1974 1973 name = "six-1.11.0";
1975 1974 doCheck = false;
1976 1975 src = fetchurl {
1977 1976 url = "https://files.pythonhosted.org/packages/16/d8/bc6316cf98419719bd59c91742194c111b6f2e85abac88e496adefaf7afe/six-1.11.0.tar.gz";
1978 1977 sha256 = "1scqzwc51c875z23phj48gircqjgnn3af8zy2izjwmnlxrxsgs3h";
1979 1978 };
1980 1979 meta = {
1981 1980 license = [ pkgs.lib.licenses.mit ];
1982 1981 };
1983 1982 };
1984 1983 "sqlalchemy" = super.buildPythonPackage {
1985 1984 name = "sqlalchemy-1.3.11";
1986 1985 doCheck = false;
1987 1986 src = fetchurl {
1988 1987 url = "https://files.pythonhosted.org/packages/34/5c/0e1d7ad0ca52544bb12f9cb8d5cc454af45821c92160ffedd38db0a317f6/SQLAlchemy-1.3.11.tar.gz";
1989 1988 sha256 = "12izpqqgy738ndn7qqn962qxi8qw2xb9vg2i880x12paklg599dg";
1990 1989 };
1991 1990 meta = {
1992 1991 license = [ pkgs.lib.licenses.mit ];
1993 1992 };
1994 1993 };
1995 1994 "sshpubkeys" = super.buildPythonPackage {
1996 1995 name = "sshpubkeys-3.1.0";
1997 1996 doCheck = false;
1998 1997 propagatedBuildInputs = [
1999 1998 self."cryptography"
2000 1999 self."ecdsa"
2001 2000 ];
2002 2001 src = fetchurl {
2003 2002 url = "https://files.pythonhosted.org/packages/00/23/f7508a12007c96861c3da811992f14283d79c819d71a217b3e12d5196649/sshpubkeys-3.1.0.tar.gz";
2004 2003 sha256 = "105g2li04nm1hb15a2y6hm9m9k7fbrkd5l3gy12w3kgcmsf3k25k";
2005 2004 };
2006 2005 meta = {
2007 2006 license = [ pkgs.lib.licenses.bsdOriginal ];
2008 2007 };
2009 2008 };
2010 2009 "subprocess32" = super.buildPythonPackage {
2011 2010 name = "subprocess32-3.5.4";
2012 2011 doCheck = false;
2013 2012 src = fetchurl {
2014 2013 url = "https://files.pythonhosted.org/packages/32/c8/564be4d12629b912ea431f1a50eb8b3b9d00f1a0b1ceff17f266be190007/subprocess32-3.5.4.tar.gz";
2015 2014 sha256 = "17f7mvwx2271s1wrl0qac3wjqqnrqag866zs3qc8v5wp0k43fagb";
2016 2015 };
2017 2016 meta = {
2018 2017 license = [ pkgs.lib.licenses.psfl ];
2019 2018 };
2020 2019 };
2021 2020 "supervisor" = super.buildPythonPackage {
2022 2021 name = "supervisor-4.1.0";
2023 2022 doCheck = false;
2024 2023 src = fetchurl {
2025 2024 url = "https://files.pythonhosted.org/packages/de/87/ee1ad8fa533a4b5f2c7623f4a2b585d3c1947af7bed8e65bc7772274320e/supervisor-4.1.0.tar.gz";
2026 2025 sha256 = "10q36sa1jqljyyyl7cif52akpygl5kmlqq9x91hmx53f8zh6zj1d";
2027 2026 };
2028 2027 meta = {
2029 2028 license = [ { fullName = "BSD-derived (http://www.repoze.org/LICENSE.txt)"; } ];
2030 2029 };
2031 2030 };
2032 2031 "tempita" = super.buildPythonPackage {
2033 2032 name = "tempita-0.5.2";
2034 2033 doCheck = false;
2035 2034 src = fetchurl {
2036 2035 url = "https://files.pythonhosted.org/packages/56/c8/8ed6eee83dbddf7b0fc64dd5d4454bc05e6ccaafff47991f73f2894d9ff4/Tempita-0.5.2.tar.gz";
2037 2036 sha256 = "177wwq45slfyajd8csy477bmdmzipyw0dm7i85k3akb7m85wzkna";
2038 2037 };
2039 2038 meta = {
2040 2039 license = [ pkgs.lib.licenses.mit ];
2041 2040 };
2042 2041 };
2043 2042 "termcolor" = super.buildPythonPackage {
2044 2043 name = "termcolor-1.1.0";
2045 2044 doCheck = false;
2046 2045 src = fetchurl {
2047 2046 url = "https://files.pythonhosted.org/packages/8a/48/a76be51647d0eb9f10e2a4511bf3ffb8cc1e6b14e9e4fab46173aa79f981/termcolor-1.1.0.tar.gz";
2048 2047 sha256 = "0fv1vq14rpqwgazxg4981904lfyp84mnammw7y046491cv76jv8x";
2049 2048 };
2050 2049 meta = {
2051 2050 license = [ pkgs.lib.licenses.mit ];
2052 2051 };
2053 2052 };
2054 2053 "testpath" = super.buildPythonPackage {
2055 2054 name = "testpath-0.4.4";
2056 2055 doCheck = false;
2057 2056 src = fetchurl {
2058 2057 url = "https://files.pythonhosted.org/packages/2c/b3/5d57205e896d8998d77ad12aa42ebce75cd97d8b9a97d00ba078c4c9ffeb/testpath-0.4.4.tar.gz";
2059 2058 sha256 = "0zpcmq22dz79ipvvsfnw1ykpjcaj6xyzy7ws77s5b5ql3hka7q30";
2060 2059 };
2061 2060 meta = {
2062 2061 license = [ ];
2063 2062 };
2064 2063 };
2065 2064 "traitlets" = super.buildPythonPackage {
2066 2065 name = "traitlets-4.3.3";
2067 2066 doCheck = false;
2068 2067 propagatedBuildInputs = [
2069 2068 self."ipython-genutils"
2070 2069 self."six"
2071 2070 self."decorator"
2072 2071 self."enum34"
2073 2072 ];
2074 2073 src = fetchurl {
2075 2074 url = "https://files.pythonhosted.org/packages/75/b0/43deb021bc943f18f07cbe3dac1d681626a48997b7ffa1e7fb14ef922b21/traitlets-4.3.3.tar.gz";
2076 2075 sha256 = "1xsrwgivpkxlbr4dfndfsi098s29yqgswgjc1qqn69yxklvfw8yh";
2077 2076 };
2078 2077 meta = {
2079 2078 license = [ pkgs.lib.licenses.bsdOriginal ];
2080 2079 };
2081 2080 };
2082 2081 "transaction" = super.buildPythonPackage {
2083 2082 name = "transaction-2.4.0";
2084 2083 doCheck = false;
2085 2084 propagatedBuildInputs = [
2086 2085 self."zope.interface"
2087 2086 ];
2088 2087 src = fetchurl {
2089 2088 url = "https://files.pythonhosted.org/packages/9d/7d/0e8af0d059e052b9dcf2bb5a08aad20ae3e238746bdd3f8701a60969b363/transaction-2.4.0.tar.gz";
2090 2089 sha256 = "17wz1y524ca07vr03yddy8dv0gbscs06dbdywmllxv5rc725jq3j";
2091 2090 };
2092 2091 meta = {
2093 2092 license = [ pkgs.lib.licenses.zpl21 ];
2094 2093 };
2095 2094 };
2096 2095 "translationstring" = super.buildPythonPackage {
2097 2096 name = "translationstring-1.3";
2098 2097 doCheck = false;
2099 2098 src = fetchurl {
2100 2099 url = "https://files.pythonhosted.org/packages/5e/eb/bee578cc150b44c653b63f5ebe258b5d0d812ddac12497e5f80fcad5d0b4/translationstring-1.3.tar.gz";
2101 2100 sha256 = "0bdpcnd9pv0131dl08h4zbcwmgc45lyvq3pa224xwan5b3x4rr2f";
2102 2101 };
2103 2102 meta = {
2104 2103 license = [ { fullName = "BSD-like (http://repoze.org/license.html)"; } ];
2105 2104 };
2106 2105 };
2107 2106 "tzlocal" = super.buildPythonPackage {
2108 2107 name = "tzlocal-1.5.1";
2109 2108 doCheck = false;
2110 2109 propagatedBuildInputs = [
2111 2110 self."pytz"
2112 2111 ];
2113 2112 src = fetchurl {
2114 2113 url = "https://files.pythonhosted.org/packages/cb/89/e3687d3ed99bc882793f82634e9824e62499fdfdc4b1ae39e211c5b05017/tzlocal-1.5.1.tar.gz";
2115 2114 sha256 = "0kiciwiqx0bv0fbc913idxibc4ygg4cb7f8rcpd9ij2shi4bigjf";
2116 2115 };
2117 2116 meta = {
2118 2117 license = [ pkgs.lib.licenses.mit ];
2119 2118 };
2120 2119 };
2121 2120 "urllib3" = super.buildPythonPackage {
2122 2121 name = "urllib3-1.24.1";
2123 2122 doCheck = false;
2124 2123 src = fetchurl {
2125 2124 url = "https://files.pythonhosted.org/packages/b1/53/37d82ab391393565f2f831b8eedbffd57db5a718216f82f1a8b4d381a1c1/urllib3-1.24.1.tar.gz";
2126 2125 sha256 = "08lwd9f3hqznyf32vnzwvp87pchx062nkbgyrf67rwlkgj0jk5fy";
2127 2126 };
2128 2127 meta = {
2129 2128 license = [ pkgs.lib.licenses.mit ];
2130 2129 };
2131 2130 };
2132 2131 "urlobject" = super.buildPythonPackage {
2133 2132 name = "urlobject-2.4.3";
2134 2133 doCheck = false;
2135 2134 src = fetchurl {
2136 2135 url = "https://files.pythonhosted.org/packages/e2/b8/1d0a916f4b34c4618846e6da0e4eeaa8fcb4a2f39e006434fe38acb74b34/URLObject-2.4.3.tar.gz";
2137 2136 sha256 = "1ahc8ficzfvr2avln71immfh4ls0zyv6cdaa5xmkdj5rd87f5cj7";
2138 2137 };
2139 2138 meta = {
2140 2139 license = [ pkgs.lib.licenses.publicDomain ];
2141 2140 };
2142 2141 };
2143 2142 "venusian" = super.buildPythonPackage {
2144 2143 name = "venusian-1.2.0";
2145 2144 doCheck = false;
2146 2145 src = fetchurl {
2147 2146 url = "https://files.pythonhosted.org/packages/7e/6f/40a9d43ac77cb51cb62be5b5662d170f43f8037bdc4eab56336c4ca92bb7/venusian-1.2.0.tar.gz";
2148 2147 sha256 = "0ghyx66g8ikx9nx1mnwqvdcqm11i1vlq0hnvwl50s48bp22q5v34";
2149 2148 };
2150 2149 meta = {
2151 2150 license = [ { fullName = "BSD-derived (http://www.repoze.org/LICENSE.txt)"; } ];
2152 2151 };
2153 2152 };
2154 2153 "vine" = super.buildPythonPackage {
2155 2154 name = "vine-1.3.0";
2156 2155 doCheck = false;
2157 2156 src = fetchurl {
2158 2157 url = "https://files.pythonhosted.org/packages/1c/e1/79fb8046e607dd6c2ad05c9b8ebac9d0bd31d086a08f02699e96fc5b3046/vine-1.3.0.tar.gz";
2159 2158 sha256 = "11ydsbhl1vabndc2r979dv61s6j2b0giq6dgvryifvq1m7bycghk";
2160 2159 };
2161 2160 meta = {
2162 2161 license = [ pkgs.lib.licenses.bsdOriginal ];
2163 2162 };
2164 2163 };
2165 2164 "waitress" = super.buildPythonPackage {
2166 2165 name = "waitress-1.3.1";
2167 2166 doCheck = false;
2168 2167 src = fetchurl {
2169 2168 url = "https://files.pythonhosted.org/packages/a6/e6/708da7bba65898e5d759ade8391b1077e49d07be0b0223c39f5be04def56/waitress-1.3.1.tar.gz";
2170 2169 sha256 = "1iysl8ka3l4cdrr0r19fh1cv28q41mwpvgsb81ji7k4shkb0k3i7";
2171 2170 };
2172 2171 meta = {
2173 2172 license = [ pkgs.lib.licenses.zpl21 ];
2174 2173 };
2175 2174 };
2176 2175 "wcwidth" = super.buildPythonPackage {
2177 2176 name = "wcwidth-0.1.7";
2178 2177 doCheck = false;
2179 2178 src = fetchurl {
2180 2179 url = "https://files.pythonhosted.org/packages/55/11/e4a2bb08bb450fdbd42cc709dd40de4ed2c472cf0ccb9e64af22279c5495/wcwidth-0.1.7.tar.gz";
2181 2180 sha256 = "0pn6dflzm609m4r3i8ik5ni9ijjbb5fa3vg1n7hn6vkd49r77wrx";
2182 2181 };
2183 2182 meta = {
2184 2183 license = [ pkgs.lib.licenses.mit ];
2185 2184 };
2186 2185 };
2187 2186 "webencodings" = super.buildPythonPackage {
2188 2187 name = "webencodings-0.5.1";
2189 2188 doCheck = false;
2190 2189 src = fetchurl {
2191 2190 url = "https://files.pythonhosted.org/packages/0b/02/ae6ceac1baeda530866a85075641cec12989bd8d31af6d5ab4a3e8c92f47/webencodings-0.5.1.tar.gz";
2192 2191 sha256 = "08qrgrc4hrximb2gqnl69g01s93rhf2842jfxdjljc1dbwj1qsmk";
2193 2192 };
2194 2193 meta = {
2195 2194 license = [ pkgs.lib.licenses.bsdOriginal ];
2196 2195 };
2197 2196 };
2198 2197 "weberror" = super.buildPythonPackage {
2199 2198 name = "weberror-0.10.3";
2200 2199 doCheck = false;
2201 2200 propagatedBuildInputs = [
2202 2201 self."webob"
2203 2202 self."tempita"
2204 2203 self."pygments"
2205 2204 self."paste"
2206 2205 ];
2207 2206 src = fetchurl {
2208 2207 url = "https://files.pythonhosted.org/packages/35/76/e7e5c2ce7e9c7f31b54c1ff295a495886d1279a002557d74dd8957346a79/WebError-0.10.3.tar.gz";
2209 2208 sha256 = "0frg4kvycqpj5bi8asfqfs6bxsr2cvjvb6b56c4d1ai1z57kbjx6";
2210 2209 };
2211 2210 meta = {
2212 2211 license = [ pkgs.lib.licenses.mit ];
2213 2212 };
2214 2213 };
2215 "webhelpers" = super.buildPythonPackage {
2216 name = "webhelpers-1.3";
2217 doCheck = false;
2218 propagatedBuildInputs = [
2219 self."markupsafe"
2220 ];
2221 src = fetchurl {
2222 url = "https://files.pythonhosted.org/packages/ee/68/4d07672821d514184357f1552f2dad923324f597e722de3b016ca4f7844f/WebHelpers-1.3.tar.gz";
2223 sha256 = "10x5i82qdkrvyw18gsybwggfhfpl869siaab89vnndi9x62g51pa";
2224 };
2225 meta = {
2226 license = [ pkgs.lib.licenses.bsdOriginal ];
2227 };
2228 };
2229 2214 "webhelpers2" = super.buildPythonPackage {
2230 2215 name = "webhelpers2-2.0";
2231 2216 doCheck = false;
2232 2217 propagatedBuildInputs = [
2233 2218 self."markupsafe"
2234 2219 self."six"
2235 2220 ];
2236 2221 src = fetchurl {
2237 2222 url = "https://files.pythonhosted.org/packages/ff/30/56342c6ea522439e3662427c8d7b5e5b390dff4ff2dc92d8afcb8ab68b75/WebHelpers2-2.0.tar.gz";
2238 2223 sha256 = "0aphva1qmxh83n01p53f5fd43m4srzbnfbz5ajvbx9aj2aipwmcs";
2239 2224 };
2240 2225 meta = {
2241 2226 license = [ pkgs.lib.licenses.mit ];
2242 2227 };
2243 2228 };
2244 2229 "webob" = super.buildPythonPackage {
2245 2230 name = "webob-1.8.5";
2246 2231 doCheck = false;
2247 2232 src = fetchurl {
2248 2233 url = "https://files.pythonhosted.org/packages/9d/1a/0c89c070ee2829c934cb6c7082287c822e28236a4fcf90063e6be7c35532/WebOb-1.8.5.tar.gz";
2249 2234 sha256 = "11khpzaxc88q31v25ic330gsf56fwmbdc9b30br8mvp0fmwspah5";
2250 2235 };
2251 2236 meta = {
2252 2237 license = [ pkgs.lib.licenses.mit ];
2253 2238 };
2254 2239 };
2255 2240 "webtest" = super.buildPythonPackage {
2256 2241 name = "webtest-2.0.33";
2257 2242 doCheck = false;
2258 2243 propagatedBuildInputs = [
2259 2244 self."six"
2260 2245 self."webob"
2261 2246 self."waitress"
2262 2247 self."beautifulsoup4"
2263 2248 ];
2264 2249 src = fetchurl {
2265 2250 url = "https://files.pythonhosted.org/packages/a8/b0/ffc9413b637dbe26e291429bb0f6ed731e518d0cd03da28524a8fe2e8a8f/WebTest-2.0.33.tar.gz";
2266 2251 sha256 = "1l3z0cwqslsf4rcrhi2gr8kdfh74wn2dw76376i4g9i38gz8wd21";
2267 2252 };
2268 2253 meta = {
2269 2254 license = [ pkgs.lib.licenses.mit ];
2270 2255 };
2271 2256 };
2272 2257 "whoosh" = super.buildPythonPackage {
2273 2258 name = "whoosh-2.7.4";
2274 2259 doCheck = false;
2275 2260 src = fetchurl {
2276 2261 url = "https://files.pythonhosted.org/packages/25/2b/6beed2107b148edc1321da0d489afc4617b9ed317ef7b72d4993cad9b684/Whoosh-2.7.4.tar.gz";
2277 2262 sha256 = "10qsqdjpbc85fykc1vgcs8xwbgn4l2l52c8d83xf1q59pwyn79bw";
2278 2263 };
2279 2264 meta = {
2280 2265 license = [ pkgs.lib.licenses.bsdOriginal pkgs.lib.licenses.bsd2 ];
2281 2266 };
2282 2267 };
2283 2268 "ws4py" = super.buildPythonPackage {
2284 2269 name = "ws4py-0.5.1";
2285 2270 doCheck = false;
2286 2271 src = fetchurl {
2287 2272 url = "https://files.pythonhosted.org/packages/53/20/4019a739b2eefe9282d3822ef6a225250af964b117356971bd55e274193c/ws4py-0.5.1.tar.gz";
2288 2273 sha256 = "10slbbf2jm4hpr92jx7kh7mhf48sjl01v2w4d8z3f1p0ybbp7l19";
2289 2274 };
2290 2275 meta = {
2291 2276 license = [ pkgs.lib.licenses.bsdOriginal ];
2292 2277 };
2293 2278 };
2294 2279 "wsgiref" = super.buildPythonPackage {
2295 2280 name = "wsgiref-0.1.2";
2296 2281 doCheck = false;
2297 2282 src = fetchurl {
2298 2283 url = "https://files.pythonhosted.org/packages/41/9e/309259ce8dff8c596e8c26df86dbc4e848b9249fd36797fd60be456f03fc/wsgiref-0.1.2.zip";
2299 2284 sha256 = "0y8fyjmpq7vwwm4x732w97qbkw78rjwal5409k04cw4m03411rn7";
2300 2285 };
2301 2286 meta = {
2302 2287 license = [ { fullName = "PSF or ZPL"; } ];
2303 2288 };
2304 2289 };
2305 2290 "zipp" = super.buildPythonPackage {
2306 2291 name = "zipp-0.6.0";
2307 2292 doCheck = false;
2308 2293 propagatedBuildInputs = [
2309 2294 self."more-itertools"
2310 2295 ];
2311 2296 src = fetchurl {
2312 2297 url = "https://files.pythonhosted.org/packages/57/dd/585d728479d97d25aeeb9aa470d36a4ad8d0ba5610f84e14770128ce6ff7/zipp-0.6.0.tar.gz";
2313 2298 sha256 = "13ndkf7vklw978a4gdl1yfvn8hch28429a0iam67sg4nrp5v261p";
2314 2299 };
2315 2300 meta = {
2316 2301 license = [ pkgs.lib.licenses.mit ];
2317 2302 };
2318 2303 };
2319 2304 "zope.cachedescriptors" = super.buildPythonPackage {
2320 2305 name = "zope.cachedescriptors-4.3.1";
2321 2306 doCheck = false;
2322 2307 propagatedBuildInputs = [
2323 2308 self."setuptools"
2324 2309 ];
2325 2310 src = fetchurl {
2326 2311 url = "https://files.pythonhosted.org/packages/2f/89/ebe1890cc6d3291ebc935558fa764d5fffe571018dbbee200e9db78762cb/zope.cachedescriptors-4.3.1.tar.gz";
2327 2312 sha256 = "0jhr3m5p74c6r7k8iv0005b8bfsialih9d7zl5vx38rf5xq1lk8z";
2328 2313 };
2329 2314 meta = {
2330 2315 license = [ pkgs.lib.licenses.zpl21 ];
2331 2316 };
2332 2317 };
2333 2318 "zope.deprecation" = super.buildPythonPackage {
2334 2319 name = "zope.deprecation-4.4.0";
2335 2320 doCheck = false;
2336 2321 propagatedBuildInputs = [
2337 2322 self."setuptools"
2338 2323 ];
2339 2324 src = fetchurl {
2340 2325 url = "https://files.pythonhosted.org/packages/34/da/46e92d32d545dd067b9436279d84c339e8b16de2ca393d7b892bc1e1e9fd/zope.deprecation-4.4.0.tar.gz";
2341 2326 sha256 = "1pz2cv7gv9y1r3m0bdv7ks1alagmrn5msm5spwdzkb2by0w36i8d";
2342 2327 };
2343 2328 meta = {
2344 2329 license = [ pkgs.lib.licenses.zpl21 ];
2345 2330 };
2346 2331 };
2347 2332 "zope.event" = super.buildPythonPackage {
2348 2333 name = "zope.event-4.4";
2349 2334 doCheck = false;
2350 2335 propagatedBuildInputs = [
2351 2336 self."setuptools"
2352 2337 ];
2353 2338 src = fetchurl {
2354 2339 url = "https://files.pythonhosted.org/packages/4c/b2/51c0369adcf5be2334280eed230192ab3b03f81f8efda9ddea6f65cc7b32/zope.event-4.4.tar.gz";
2355 2340 sha256 = "1ksbc726av9xacml6jhcfyn828hlhb9xlddpx6fcvnlvmpmpvhk9";
2356 2341 };
2357 2342 meta = {
2358 2343 license = [ pkgs.lib.licenses.zpl21 ];
2359 2344 };
2360 2345 };
2361 2346 "zope.interface" = super.buildPythonPackage {
2362 2347 name = "zope.interface-4.6.0";
2363 2348 doCheck = false;
2364 2349 propagatedBuildInputs = [
2365 2350 self."setuptools"
2366 2351 ];
2367 2352 src = fetchurl {
2368 2353 url = "https://files.pythonhosted.org/packages/4e/d0/c9d16bd5b38de44a20c6dc5d5ed80a49626fafcb3db9f9efdc2a19026db6/zope.interface-4.6.0.tar.gz";
2369 2354 sha256 = "1rgh2x3rcl9r0v0499kf78xy86rnmanajf4ywmqb943wpk50sg8v";
2370 2355 };
2371 2356 meta = {
2372 2357 license = [ pkgs.lib.licenses.zpl21 ];
2373 2358 };
2374 2359 };
2375 2360
2376 2361 ### Test requirements
2377 2362
2378 2363
2379 2364 }
@@ -1,123 +1,122 b''
1 1 ## dependencies
2 2
3 3 amqp==2.5.2
4 4 babel==1.3
5 5 beaker==1.9.1
6 6 bleach==3.1.0
7 7 celery==4.3.0
8 8 channelstream==0.5.2
9 9 click==7.0
10 10 colander==1.7.0
11 11 # our custom configobj
12 12 https://code.rhodecode.com/upstream/configobj/artifacts/download/0-012de99a-b1e1-4f64-a5c0-07a98a41b324.tar.gz?md5=6a513f51fe04b2c18cf84c1395a7c626#egg=configobj==5.0.6
13 13 cssselect==1.0.3
14 14 cryptography==2.6.1
15 15 decorator==4.1.2
16 16 deform==2.0.8
17 17 docutils==0.14.0
18 18 dogpile.cache==0.9.0
19 19 dogpile.core==0.4.1
20 20 formencode==1.2.4
21 21 future==0.14.3
22 22 futures==3.0.2
23 23 infrae.cache==1.0.1
24 24 iso8601==0.1.12
25 25 itsdangerous==0.24
26 26 kombu==4.6.6
27 27 lxml==4.2.5
28 28 mako==1.1.0
29 29 markdown==2.6.11
30 30 markupsafe==1.1.1
31 31 msgpack-python==0.5.6
32 32 pyotp==2.3.0
33 33 packaging==19.2
34 34 pathlib2==2.3.5
35 35 paste==3.2.1
36 36 pastedeploy==2.0.1
37 37 pastescript==3.2.0
38 38 peppercorn==0.6
39 39 psutil==5.6.5
40 40 py-bcrypt==0.4
41 41 pycurl==7.43.0.3
42 42 pycrypto==2.6.1
43 43 pygments==2.4.2
44 44 pyparsing==2.4.2
45 45 pyramid-debugtoolbar==4.5.1
46 46 pyramid-mako==1.1.0
47 47 pyramid==1.10.4
48 48 pyramid_mailer==0.15.1
49 49 python-dateutil==2.8.1
50 50 python-ldap==3.1.0
51 51 python-memcached==1.59
52 52 python-pam==1.8.4
53 53 python-saml==2.4.2
54 54 pytz==2019.2
55 55 tzlocal==1.5.1
56 56 pyzmq==14.6.0
57 57 py-gfm==0.1.4
58 58 redis==3.3.11
59 59 repoze.lru==0.7
60 60 requests==2.9.1
61 61 routes==2.4.1
62 62 simplejson==3.16.0
63 63 six==1.11.0
64 64 sqlalchemy==1.3.11
65 65 sshpubkeys==3.1.0
66 66 subprocess32==3.5.4
67 67 supervisor==4.1.0
68 68 translationstring==1.3
69 69 urllib3==1.24.1
70 70 urlobject==2.4.3
71 71 venusian==1.2.0
72 72 weberror==0.10.3
73 73 webhelpers2==2.0
74 webhelpers==1.3
75 74 webob==1.8.5
76 75 whoosh==2.7.4
77 76 wsgiref==0.1.2
78 77 zope.cachedescriptors==4.3.1
79 78 zope.deprecation==4.4.0
80 79 zope.event==4.4.0
81 80 zope.interface==4.6.0
82 81
83 82 # DB drivers
84 83 mysql-python==1.2.5
85 84 pymysql==0.8.1
86 85 pysqlite==2.8.3
87 86 psycopg2==2.8.4
88 87
89 88 # IPYTHON RENDERING
90 89 # entrypoints backport, pypi version doesn't support egg installs
91 90 https://code.rhodecode.com/upstream/entrypoints/artifacts/download/0-8e9ee9e4-c4db-409c-b07e-81568fd1832d.tar.gz?md5=3a027b8ff1d257b91fe257de6c43357d#egg=entrypoints==0.2.2.rhodecode-upstream1
92 91 nbconvert==5.3.1
93 92 nbformat==4.4.0
94 93 jupyter-client==5.0.0
95 94 jupyter-core==4.5.0
96 95
97 96 ## cli tools
98 97 alembic==1.3.1
99 98 invoke==0.13.0
100 99 bumpversion==0.5.3
101 100
102 101 ## http servers
103 102 gevent==1.4.0
104 103 greenlet==0.4.15
105 104 gunicorn==19.9.0
106 105 waitress==1.3.1
107 106
108 107 ## debug
109 108 ipdb==0.12.0
110 109 ipython==5.1.0
111 110
112 111 ## rhodecode-tools, special case
113 112 https://code.rhodecode.com/rhodecode-tools-ce/artifacts/download/0-2e546668-61c4-42b2-ae03-7850a2dbb846.tar.gz?sha256=0938220f33264b0514dc63207ce1f77e38b1b1fd93fd4f68d34243ab62ecc853#egg=rhodecode-tools==1.3.0
114 113
115 114
116 115 ## appenlight
117 116 appenlight-client==0.6.26
118 117
119 118 ## test related requirements
120 119 -r requirements_test.txt
121 120
122 121 ## uncomment to add the debug libraries
123 122 #-r requirements_debug.txt
@@ -1,90 +1,93 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2016-2019 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import logging
22 22
23 23 from pyramid.httpexceptions import HTTPNotFound
24 24 from pyramid.view import view_config
25 25
26 26 from rhodecode.apps._base import BaseAppView
27 27 from rhodecode.model.db import joinedload, UserLog
28 28 from rhodecode.lib.user_log_filter import user_log_filter
29 29 from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator
30 30 from rhodecode.lib.utils2 import safe_int
31 from rhodecode.lib.helpers import Page
31 from rhodecode.lib.helpers import SqlPage
32 32
33 33 log = logging.getLogger(__name__)
34 34
35 35
36 36 class AdminAuditLogsView(BaseAppView):
37 37 def load_default_context(self):
38 38 c = self._get_local_tmpl_context()
39 39 return c
40 40
41 41 @LoginRequired()
42 42 @HasPermissionAllDecorator('hg.admin')
43 43 @view_config(
44 44 route_name='admin_audit_logs', request_method='GET',
45 45 renderer='rhodecode:templates/admin/admin_audit_logs.mako')
46 46 def admin_audit_logs(self):
47 47 c = self.load_default_context()
48 48
49 49 users_log = UserLog.query()\
50 50 .options(joinedload(UserLog.user))\
51 51 .options(joinedload(UserLog.repository))
52 52
53 53 # FILTERING
54 54 c.search_term = self.request.GET.get('filter')
55 55 try:
56 56 users_log = user_log_filter(users_log, c.search_term)
57 57 except Exception:
58 58 # we want this to crash for now
59 59 raise
60 60
61 61 users_log = users_log.order_by(UserLog.action_date.desc())
62 62
63 63 p = safe_int(self.request.GET.get('page', 1), 1)
64 64
65 def url_generator(**kw):
65 def url_generator(page_num):
66 query_params = {
67 'page': page_num
68 }
66 69 if c.search_term:
67 kw['filter'] = c.search_term
68 return self.request.current_route_path(_query=kw)
70 query_params['filter'] = c.search_term
71 return self.request.current_route_path(_query=query_params)
69 72
70 c.audit_logs = Page(users_log, page=p, items_per_page=10,
71 url=url_generator)
73 c.audit_logs = SqlPage(users_log, page=p, items_per_page=10,
74 url_maker=url_generator)
72 75 return self._get_template_context(c)
73 76
74 77 @LoginRequired()
75 78 @HasPermissionAllDecorator('hg.admin')
76 79 @view_config(
77 80 route_name='admin_audit_log_entry', request_method='GET',
78 81 renderer='rhodecode:templates/admin/admin_audit_log_entry.mako')
79 82 def admin_audit_log_entry(self):
80 83 c = self.load_default_context()
81 84 audit_log_id = self.request.matchdict['audit_log_id']
82 85
83 86 c.audit_log_entry = UserLog.query()\
84 87 .options(joinedload(UserLog.user))\
85 88 .options(joinedload(UserLog.repository))\
86 89 .filter(UserLog.user_log_id == audit_log_id).scalar()
87 90 if not c.audit_log_entry:
88 91 raise HTTPNotFound()
89 92
90 93 return self._get_template_context(c)
@@ -1,1329 +1,1333 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2016-2019 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import logging
22 22 import datetime
23 23 import formencode
24 24 import formencode.htmlfill
25 25
26 26 from pyramid.httpexceptions import HTTPFound
27 27 from pyramid.view import view_config
28 28 from pyramid.renderers import render
29 29 from pyramid.response import Response
30 30
31 31 from rhodecode import events
32 32 from rhodecode.apps._base import BaseAppView, DataGridAppView, UserAppView
33 33 from rhodecode.apps.ssh_support import SshKeyFileChangeEvent
34 34 from rhodecode.authentication.base import get_authn_registry, RhodeCodeExternalAuthPlugin
35 35 from rhodecode.authentication.plugins import auth_rhodecode
36 36 from rhodecode.events import trigger
37 37 from rhodecode.model.db import true
38 38
39 39 from rhodecode.lib import audit_logger, rc_cache
40 40 from rhodecode.lib.exceptions import (
41 41 UserCreationError, UserOwnsReposException, UserOwnsRepoGroupsException,
42 42 UserOwnsUserGroupsException, DefaultUserException)
43 43 from rhodecode.lib.ext_json import json
44 44 from rhodecode.lib.auth import (
45 45 LoginRequired, HasPermissionAllDecorator, CSRFRequired)
46 46 from rhodecode.lib import helpers as h
47 from rhodecode.lib.helpers import SqlPage
47 48 from rhodecode.lib.utils2 import safe_int, safe_unicode, AttributeDict
48 49 from rhodecode.model.auth_token import AuthTokenModel
49 50 from rhodecode.model.forms import (
50 51 UserForm, UserIndividualPermissionsForm, UserPermissionsForm,
51 52 UserExtraEmailForm, UserExtraIpForm)
52 53 from rhodecode.model.permission import PermissionModel
53 54 from rhodecode.model.repo_group import RepoGroupModel
54 55 from rhodecode.model.ssh_key import SshKeyModel
55 56 from rhodecode.model.user import UserModel
56 57 from rhodecode.model.user_group import UserGroupModel
57 58 from rhodecode.model.db import (
58 59 or_, coalesce,IntegrityError, User, UserGroup, UserIpMap, UserEmailMap,
59 60 UserApiKeys, UserSshKeys, RepoGroup)
60 61 from rhodecode.model.meta import Session
61 62
62 63 log = logging.getLogger(__name__)
63 64
64 65
65 66 class AdminUsersView(BaseAppView, DataGridAppView):
66 67
67 68 def load_default_context(self):
68 69 c = self._get_local_tmpl_context()
69 70 return c
70 71
71 72 @LoginRequired()
72 73 @HasPermissionAllDecorator('hg.admin')
73 74 @view_config(
74 75 route_name='users', request_method='GET',
75 76 renderer='rhodecode:templates/admin/users/users.mako')
76 77 def users_list(self):
77 78 c = self.load_default_context()
78 79 return self._get_template_context(c)
79 80
80 81 @LoginRequired()
81 82 @HasPermissionAllDecorator('hg.admin')
82 83 @view_config(
83 84 # renderer defined below
84 85 route_name='users_data', request_method='GET',
85 86 renderer='json_ext', xhr=True)
86 87 def users_list_data(self):
87 88 self.load_default_context()
88 89 column_map = {
89 90 'first_name': 'name',
90 91 'last_name': 'lastname',
91 92 }
92 93 draw, start, limit = self._extract_chunk(self.request)
93 94 search_q, order_by, order_dir = self._extract_ordering(
94 95 self.request, column_map=column_map)
95 96 _render = self.request.get_partial_renderer(
96 97 'rhodecode:templates/data_table/_dt_elements.mako')
97 98
98 99 def user_actions(user_id, username):
99 100 return _render("user_actions", user_id, username)
100 101
101 102 users_data_total_count = User.query()\
102 103 .filter(User.username != User.DEFAULT_USER) \
103 104 .count()
104 105
105 106 users_data_total_inactive_count = User.query()\
106 107 .filter(User.username != User.DEFAULT_USER) \
107 108 .filter(User.active != true())\
108 109 .count()
109 110
110 111 # json generate
111 112 base_q = User.query().filter(User.username != User.DEFAULT_USER)
112 113 base_inactive_q = base_q.filter(User.active != true())
113 114
114 115 if search_q:
115 116 like_expression = u'%{}%'.format(safe_unicode(search_q))
116 117 base_q = base_q.filter(or_(
117 118 User.username.ilike(like_expression),
118 119 User._email.ilike(like_expression),
119 120 User.name.ilike(like_expression),
120 121 User.lastname.ilike(like_expression),
121 122 ))
122 123 base_inactive_q = base_q.filter(User.active != true())
123 124
124 125 users_data_total_filtered_count = base_q.count()
125 126 users_data_total_filtered_inactive_count = base_inactive_q.count()
126 127
127 128 sort_col = getattr(User, order_by, None)
128 129 if sort_col:
129 130 if order_dir == 'asc':
130 131 # handle null values properly to order by NULL last
131 132 if order_by in ['last_activity']:
132 133 sort_col = coalesce(sort_col, datetime.date.max)
133 134 sort_col = sort_col.asc()
134 135 else:
135 136 # handle null values properly to order by NULL last
136 137 if order_by in ['last_activity']:
137 138 sort_col = coalesce(sort_col, datetime.date.min)
138 139 sort_col = sort_col.desc()
139 140
140 141 base_q = base_q.order_by(sort_col)
141 142 base_q = base_q.offset(start).limit(limit)
142 143
143 144 users_list = base_q.all()
144 145
145 146 users_data = []
146 147 for user in users_list:
147 148 users_data.append({
148 149 "username": h.gravatar_with_user(self.request, user.username),
149 150 "email": user.email,
150 151 "first_name": user.first_name,
151 152 "last_name": user.last_name,
152 153 "last_login": h.format_date(user.last_login),
153 154 "last_activity": h.format_date(user.last_activity),
154 155 "active": h.bool2icon(user.active),
155 156 "active_raw": user.active,
156 157 "admin": h.bool2icon(user.admin),
157 158 "extern_type": user.extern_type,
158 159 "extern_name": user.extern_name,
159 160 "action": user_actions(user.user_id, user.username),
160 161 })
161 162 data = ({
162 163 'draw': draw,
163 164 'data': users_data,
164 165 'recordsTotal': users_data_total_count,
165 166 'recordsFiltered': users_data_total_filtered_count,
166 167 'recordsTotalInactive': users_data_total_inactive_count,
167 168 'recordsFilteredInactive': users_data_total_filtered_inactive_count
168 169 })
169 170
170 171 return data
171 172
172 173 def _set_personal_repo_group_template_vars(self, c_obj):
173 174 DummyUser = AttributeDict({
174 175 'username': '${username}',
175 176 'user_id': '${user_id}',
176 177 })
177 178 c_obj.default_create_repo_group = RepoGroupModel() \
178 179 .get_default_create_personal_repo_group()
179 180 c_obj.personal_repo_group_name = RepoGroupModel() \
180 181 .get_personal_group_name(DummyUser)
181 182
182 183 @LoginRequired()
183 184 @HasPermissionAllDecorator('hg.admin')
184 185 @view_config(
185 186 route_name='users_new', request_method='GET',
186 187 renderer='rhodecode:templates/admin/users/user_add.mako')
187 188 def users_new(self):
188 189 _ = self.request.translate
189 190 c = self.load_default_context()
190 191 c.default_extern_type = auth_rhodecode.RhodeCodeAuthPlugin.uid
191 192 self._set_personal_repo_group_template_vars(c)
192 193 return self._get_template_context(c)
193 194
194 195 @LoginRequired()
195 196 @HasPermissionAllDecorator('hg.admin')
196 197 @CSRFRequired()
197 198 @view_config(
198 199 route_name='users_create', request_method='POST',
199 200 renderer='rhodecode:templates/admin/users/user_add.mako')
200 201 def users_create(self):
201 202 _ = self.request.translate
202 203 c = self.load_default_context()
203 204 c.default_extern_type = auth_rhodecode.RhodeCodeAuthPlugin.uid
204 205 user_model = UserModel()
205 206 user_form = UserForm(self.request.translate)()
206 207 try:
207 208 form_result = user_form.to_python(dict(self.request.POST))
208 209 user = user_model.create(form_result)
209 210 Session().flush()
210 211 creation_data = user.get_api_data()
211 212 username = form_result['username']
212 213
213 214 audit_logger.store_web(
214 215 'user.create', action_data={'data': creation_data},
215 216 user=c.rhodecode_user)
216 217
217 218 user_link = h.link_to(
218 219 h.escape(username),
219 220 h.route_path('user_edit', user_id=user.user_id))
220 221 h.flash(h.literal(_('Created user %(user_link)s')
221 222 % {'user_link': user_link}), category='success')
222 223 Session().commit()
223 224 except formencode.Invalid as errors:
224 225 self._set_personal_repo_group_template_vars(c)
225 226 data = render(
226 227 'rhodecode:templates/admin/users/user_add.mako',
227 228 self._get_template_context(c), self.request)
228 229 html = formencode.htmlfill.render(
229 230 data,
230 231 defaults=errors.value,
231 232 errors=errors.error_dict or {},
232 233 prefix_error=False,
233 234 encoding="UTF-8",
234 235 force_defaults=False
235 236 )
236 237 return Response(html)
237 238 except UserCreationError as e:
238 239 h.flash(e, 'error')
239 240 except Exception:
240 241 log.exception("Exception creation of user")
241 242 h.flash(_('Error occurred during creation of user %s')
242 243 % self.request.POST.get('username'), category='error')
243 244 raise HTTPFound(h.route_path('users'))
244 245
245 246
246 247 class UsersView(UserAppView):
247 248 ALLOW_SCOPED_TOKENS = False
248 249 """
249 250 This view has alternative version inside EE, if modified please take a look
250 251 in there as well.
251 252 """
252 253
253 254 def get_auth_plugins(self):
254 255 valid_plugins = []
255 256 authn_registry = get_authn_registry(self.request.registry)
256 257 for plugin in authn_registry.get_plugins_for_authentication():
257 258 if isinstance(plugin, RhodeCodeExternalAuthPlugin):
258 259 valid_plugins.append(plugin)
259 260 elif plugin.name == 'rhodecode':
260 261 valid_plugins.append(plugin)
261 262
262 263 # extend our choices if user has set a bound plugin which isn't enabled at the
263 264 # moment
264 265 extern_type = self.db_user.extern_type
265 266 if extern_type not in [x.uid for x in valid_plugins]:
266 267 try:
267 268 plugin = authn_registry.get_plugin_by_uid(extern_type)
268 269 if plugin:
269 270 valid_plugins.append(plugin)
270 271
271 272 except Exception:
272 273 log.exception(
273 274 'Could not extend user plugins with `{}`'.format(extern_type))
274 275 return valid_plugins
275 276
276 277 def load_default_context(self):
277 278 req = self.request
278 279
279 280 c = self._get_local_tmpl_context()
280 281 c.allow_scoped_tokens = self.ALLOW_SCOPED_TOKENS
281 282 c.allowed_languages = [
282 283 ('en', 'English (en)'),
283 284 ('de', 'German (de)'),
284 285 ('fr', 'French (fr)'),
285 286 ('it', 'Italian (it)'),
286 287 ('ja', 'Japanese (ja)'),
287 288 ('pl', 'Polish (pl)'),
288 289 ('pt', 'Portuguese (pt)'),
289 290 ('ru', 'Russian (ru)'),
290 291 ('zh', 'Chinese (zh)'),
291 292 ]
292 293
293 294 c.allowed_extern_types = [
294 295 (x.uid, x.get_display_name()) for x in self.get_auth_plugins()
295 296 ]
296 297
297 298 c.available_permissions = req.registry.settings['available_permissions']
298 299 PermissionModel().set_global_permission_choices(
299 300 c, gettext_translator=req.translate)
300 301
301 302 return c
302 303
303 304 @LoginRequired()
304 305 @HasPermissionAllDecorator('hg.admin')
305 306 @CSRFRequired()
306 307 @view_config(
307 308 route_name='user_update', request_method='POST',
308 309 renderer='rhodecode:templates/admin/users/user_edit.mako')
309 310 def user_update(self):
310 311 _ = self.request.translate
311 312 c = self.load_default_context()
312 313
313 314 user_id = self.db_user_id
314 315 c.user = self.db_user
315 316
316 317 c.active = 'profile'
317 318 c.extern_type = c.user.extern_type
318 319 c.extern_name = c.user.extern_name
319 320 c.perm_user = c.user.AuthUser(ip_addr=self.request.remote_addr)
320 321 available_languages = [x[0] for x in c.allowed_languages]
321 322 _form = UserForm(self.request.translate, edit=True,
322 323 available_languages=available_languages,
323 324 old_data={'user_id': user_id,
324 325 'email': c.user.email})()
325 326 form_result = {}
326 327 old_values = c.user.get_api_data()
327 328 try:
328 329 form_result = _form.to_python(dict(self.request.POST))
329 330 skip_attrs = ['extern_name']
330 331 # TODO: plugin should define if username can be updated
331 332 if c.extern_type != "rhodecode":
332 333 # forbid updating username for external accounts
333 334 skip_attrs.append('username')
334 335
335 336 UserModel().update_user(
336 337 user_id, skip_attrs=skip_attrs, **form_result)
337 338
338 339 audit_logger.store_web(
339 340 'user.edit', action_data={'old_data': old_values},
340 341 user=c.rhodecode_user)
341 342
342 343 Session().commit()
343 344 h.flash(_('User updated successfully'), category='success')
344 345 except formencode.Invalid as errors:
345 346 data = render(
346 347 'rhodecode:templates/admin/users/user_edit.mako',
347 348 self._get_template_context(c), self.request)
348 349 html = formencode.htmlfill.render(
349 350 data,
350 351 defaults=errors.value,
351 352 errors=errors.error_dict or {},
352 353 prefix_error=False,
353 354 encoding="UTF-8",
354 355 force_defaults=False
355 356 )
356 357 return Response(html)
357 358 except UserCreationError as e:
358 359 h.flash(e, 'error')
359 360 except Exception:
360 361 log.exception("Exception updating user")
361 362 h.flash(_('Error occurred during update of user %s')
362 363 % form_result.get('username'), category='error')
363 364 raise HTTPFound(h.route_path('user_edit', user_id=user_id))
364 365
365 366 @LoginRequired()
366 367 @HasPermissionAllDecorator('hg.admin')
367 368 @CSRFRequired()
368 369 @view_config(
369 370 route_name='user_delete', request_method='POST',
370 371 renderer='rhodecode:templates/admin/users/user_edit.mako')
371 372 def user_delete(self):
372 373 _ = self.request.translate
373 374 c = self.load_default_context()
374 375 c.user = self.db_user
375 376
376 377 _repos = c.user.repositories
377 378 _repo_groups = c.user.repository_groups
378 379 _user_groups = c.user.user_groups
379 380 _artifacts = c.user.artifacts
380 381
381 382 handle_repos = None
382 383 handle_repo_groups = None
383 384 handle_user_groups = None
384 385 handle_artifacts = None
385 386
386 387 # calls for flash of handle based on handle case detach or delete
387 388 def set_handle_flash_repos():
388 389 handle = handle_repos
389 390 if handle == 'detach':
390 391 h.flash(_('Detached %s repositories') % len(_repos),
391 392 category='success')
392 393 elif handle == 'delete':
393 394 h.flash(_('Deleted %s repositories') % len(_repos),
394 395 category='success')
395 396
396 397 def set_handle_flash_repo_groups():
397 398 handle = handle_repo_groups
398 399 if handle == 'detach':
399 400 h.flash(_('Detached %s repository groups') % len(_repo_groups),
400 401 category='success')
401 402 elif handle == 'delete':
402 403 h.flash(_('Deleted %s repository groups') % len(_repo_groups),
403 404 category='success')
404 405
405 406 def set_handle_flash_user_groups():
406 407 handle = handle_user_groups
407 408 if handle == 'detach':
408 409 h.flash(_('Detached %s user groups') % len(_user_groups),
409 410 category='success')
410 411 elif handle == 'delete':
411 412 h.flash(_('Deleted %s user groups') % len(_user_groups),
412 413 category='success')
413 414
414 415 def set_handle_flash_artifacts():
415 416 handle = handle_artifacts
416 417 if handle == 'detach':
417 418 h.flash(_('Detached %s artifacts') % len(_artifacts),
418 419 category='success')
419 420 elif handle == 'delete':
420 421 h.flash(_('Deleted %s artifacts') % len(_artifacts),
421 422 category='success')
422 423
423 424 if _repos and self.request.POST.get('user_repos'):
424 425 handle_repos = self.request.POST['user_repos']
425 426
426 427 if _repo_groups and self.request.POST.get('user_repo_groups'):
427 428 handle_repo_groups = self.request.POST['user_repo_groups']
428 429
429 430 if _user_groups and self.request.POST.get('user_user_groups'):
430 431 handle_user_groups = self.request.POST['user_user_groups']
431 432
432 433 if _artifacts and self.request.POST.get('user_artifacts'):
433 434 handle_artifacts = self.request.POST['user_artifacts']
434 435
435 436 old_values = c.user.get_api_data()
436 437
437 438 try:
438 439 UserModel().delete(c.user, handle_repos=handle_repos,
439 440 handle_repo_groups=handle_repo_groups,
440 441 handle_user_groups=handle_user_groups,
441 442 handle_artifacts=handle_artifacts)
442 443
443 444 audit_logger.store_web(
444 445 'user.delete', action_data={'old_data': old_values},
445 446 user=c.rhodecode_user)
446 447
447 448 Session().commit()
448 449 set_handle_flash_repos()
449 450 set_handle_flash_repo_groups()
450 451 set_handle_flash_user_groups()
451 452 set_handle_flash_artifacts()
452 453 username = h.escape(old_values['username'])
453 454 h.flash(_('Successfully deleted user `{}`').format(username), category='success')
454 455 except (UserOwnsReposException, UserOwnsRepoGroupsException,
455 456 UserOwnsUserGroupsException, DefaultUserException) as e:
456 457 h.flash(e, category='warning')
457 458 except Exception:
458 459 log.exception("Exception during deletion of user")
459 460 h.flash(_('An error occurred during deletion of user'),
460 461 category='error')
461 462 raise HTTPFound(h.route_path('users'))
462 463
463 464 @LoginRequired()
464 465 @HasPermissionAllDecorator('hg.admin')
465 466 @view_config(
466 467 route_name='user_edit', request_method='GET',
467 468 renderer='rhodecode:templates/admin/users/user_edit.mako')
468 469 def user_edit(self):
469 470 _ = self.request.translate
470 471 c = self.load_default_context()
471 472 c.user = self.db_user
472 473
473 474 c.active = 'profile'
474 475 c.extern_type = c.user.extern_type
475 476 c.extern_name = c.user.extern_name
476 477 c.perm_user = c.user.AuthUser(ip_addr=self.request.remote_addr)
477 478
478 479 defaults = c.user.get_dict()
479 480 defaults.update({'language': c.user.user_data.get('language')})
480 481
481 482 data = render(
482 483 'rhodecode:templates/admin/users/user_edit.mako',
483 484 self._get_template_context(c), self.request)
484 485 html = formencode.htmlfill.render(
485 486 data,
486 487 defaults=defaults,
487 488 encoding="UTF-8",
488 489 force_defaults=False
489 490 )
490 491 return Response(html)
491 492
492 493 @LoginRequired()
493 494 @HasPermissionAllDecorator('hg.admin')
494 495 @view_config(
495 496 route_name='user_edit_advanced', request_method='GET',
496 497 renderer='rhodecode:templates/admin/users/user_edit.mako')
497 498 def user_edit_advanced(self):
498 499 _ = self.request.translate
499 500 c = self.load_default_context()
500 501
501 502 user_id = self.db_user_id
502 503 c.user = self.db_user
503 504
504 505 c.active = 'advanced'
505 506 c.personal_repo_group = RepoGroup.get_user_personal_repo_group(user_id)
506 507 c.personal_repo_group_name = RepoGroupModel()\
507 508 .get_personal_group_name(c.user)
508 509
509 510 c.user_to_review_rules = sorted(
510 511 (x.user for x in c.user.user_review_rules),
511 512 key=lambda u: u.username.lower())
512 513
513 514 c.first_admin = User.get_first_super_admin()
514 515 defaults = c.user.get_dict()
515 516
516 517 # Interim workaround if the user participated on any pull requests as a
517 518 # reviewer.
518 519 has_review = len(c.user.reviewer_pull_requests)
519 520 c.can_delete_user = not has_review
520 521 c.can_delete_user_message = ''
521 522 inactive_link = h.link_to(
522 523 'inactive', h.route_path('user_edit', user_id=user_id, _anchor='active'))
523 524 if has_review == 1:
524 525 c.can_delete_user_message = h.literal(_(
525 526 'The user participates as reviewer in {} pull request and '
526 527 'cannot be deleted. \nYou can set the user to '
527 528 '"{}" instead of deleting it.').format(
528 529 has_review, inactive_link))
529 530 elif has_review:
530 531 c.can_delete_user_message = h.literal(_(
531 532 'The user participates as reviewer in {} pull requests and '
532 533 'cannot be deleted. \nYou can set the user to '
533 534 '"{}" instead of deleting it.').format(
534 535 has_review, inactive_link))
535 536
536 537 data = render(
537 538 'rhodecode:templates/admin/users/user_edit.mako',
538 539 self._get_template_context(c), self.request)
539 540 html = formencode.htmlfill.render(
540 541 data,
541 542 defaults=defaults,
542 543 encoding="UTF-8",
543 544 force_defaults=False
544 545 )
545 546 return Response(html)
546 547
547 548 @LoginRequired()
548 549 @HasPermissionAllDecorator('hg.admin')
549 550 @view_config(
550 551 route_name='user_edit_global_perms', request_method='GET',
551 552 renderer='rhodecode:templates/admin/users/user_edit.mako')
552 553 def user_edit_global_perms(self):
553 554 _ = self.request.translate
554 555 c = self.load_default_context()
555 556 c.user = self.db_user
556 557
557 558 c.active = 'global_perms'
558 559
559 560 c.default_user = User.get_default_user()
560 561 defaults = c.user.get_dict()
561 562 defaults.update(c.default_user.get_default_perms(suffix='_inherited'))
562 563 defaults.update(c.default_user.get_default_perms())
563 564 defaults.update(c.user.get_default_perms())
564 565
565 566 data = render(
566 567 'rhodecode:templates/admin/users/user_edit.mako',
567 568 self._get_template_context(c), self.request)
568 569 html = formencode.htmlfill.render(
569 570 data,
570 571 defaults=defaults,
571 572 encoding="UTF-8",
572 573 force_defaults=False
573 574 )
574 575 return Response(html)
575 576
576 577 @LoginRequired()
577 578 @HasPermissionAllDecorator('hg.admin')
578 579 @CSRFRequired()
579 580 @view_config(
580 581 route_name='user_edit_global_perms_update', request_method='POST',
581 582 renderer='rhodecode:templates/admin/users/user_edit.mako')
582 583 def user_edit_global_perms_update(self):
583 584 _ = self.request.translate
584 585 c = self.load_default_context()
585 586
586 587 user_id = self.db_user_id
587 588 c.user = self.db_user
588 589
589 590 c.active = 'global_perms'
590 591 try:
591 592 # first stage that verifies the checkbox
592 593 _form = UserIndividualPermissionsForm(self.request.translate)
593 594 form_result = _form.to_python(dict(self.request.POST))
594 595 inherit_perms = form_result['inherit_default_permissions']
595 596 c.user.inherit_default_permissions = inherit_perms
596 597 Session().add(c.user)
597 598
598 599 if not inherit_perms:
599 600 # only update the individual ones if we un check the flag
600 601 _form = UserPermissionsForm(
601 602 self.request.translate,
602 603 [x[0] for x in c.repo_create_choices],
603 604 [x[0] for x in c.repo_create_on_write_choices],
604 605 [x[0] for x in c.repo_group_create_choices],
605 606 [x[0] for x in c.user_group_create_choices],
606 607 [x[0] for x in c.fork_choices],
607 608 [x[0] for x in c.inherit_default_permission_choices])()
608 609
609 610 form_result = _form.to_python(dict(self.request.POST))
610 611 form_result.update({'perm_user_id': c.user.user_id})
611 612
612 613 PermissionModel().update_user_permissions(form_result)
613 614
614 615 # TODO(marcink): implement global permissions
615 616 # audit_log.store_web('user.edit.permissions')
616 617
617 618 Session().commit()
618 619
619 620 h.flash(_('User global permissions updated successfully'),
620 621 category='success')
621 622
622 623 except formencode.Invalid as errors:
623 624 data = render(
624 625 'rhodecode:templates/admin/users/user_edit.mako',
625 626 self._get_template_context(c), self.request)
626 627 html = formencode.htmlfill.render(
627 628 data,
628 629 defaults=errors.value,
629 630 errors=errors.error_dict or {},
630 631 prefix_error=False,
631 632 encoding="UTF-8",
632 633 force_defaults=False
633 634 )
634 635 return Response(html)
635 636 except Exception:
636 637 log.exception("Exception during permissions saving")
637 638 h.flash(_('An error occurred during permissions saving'),
638 639 category='error')
639 640
640 641 affected_user_ids = [user_id]
641 642 PermissionModel().trigger_permission_flush(affected_user_ids)
642 643 raise HTTPFound(h.route_path('user_edit_global_perms', user_id=user_id))
643 644
644 645 @LoginRequired()
645 646 @HasPermissionAllDecorator('hg.admin')
646 647 @CSRFRequired()
647 648 @view_config(
648 649 route_name='user_enable_force_password_reset', request_method='POST',
649 650 renderer='rhodecode:templates/admin/users/user_edit.mako')
650 651 def user_enable_force_password_reset(self):
651 652 _ = self.request.translate
652 653 c = self.load_default_context()
653 654
654 655 user_id = self.db_user_id
655 656 c.user = self.db_user
656 657
657 658 try:
658 659 c.user.update_userdata(force_password_change=True)
659 660
660 661 msg = _('Force password change enabled for user')
661 662 audit_logger.store_web('user.edit.password_reset.enabled',
662 663 user=c.rhodecode_user)
663 664
664 665 Session().commit()
665 666 h.flash(msg, category='success')
666 667 except Exception:
667 668 log.exception("Exception during password reset for user")
668 669 h.flash(_('An error occurred during password reset for user'),
669 670 category='error')
670 671
671 672 raise HTTPFound(h.route_path('user_edit_advanced', user_id=user_id))
672 673
673 674 @LoginRequired()
674 675 @HasPermissionAllDecorator('hg.admin')
675 676 @CSRFRequired()
676 677 @view_config(
677 678 route_name='user_disable_force_password_reset', request_method='POST',
678 679 renderer='rhodecode:templates/admin/users/user_edit.mako')
679 680 def user_disable_force_password_reset(self):
680 681 _ = self.request.translate
681 682 c = self.load_default_context()
682 683
683 684 user_id = self.db_user_id
684 685 c.user = self.db_user
685 686
686 687 try:
687 688 c.user.update_userdata(force_password_change=False)
688 689
689 690 msg = _('Force password change disabled for user')
690 691 audit_logger.store_web(
691 692 'user.edit.password_reset.disabled',
692 693 user=c.rhodecode_user)
693 694
694 695 Session().commit()
695 696 h.flash(msg, category='success')
696 697 except Exception:
697 698 log.exception("Exception during password reset for user")
698 699 h.flash(_('An error occurred during password reset for user'),
699 700 category='error')
700 701
701 702 raise HTTPFound(h.route_path('user_edit_advanced', user_id=user_id))
702 703
703 704 @LoginRequired()
704 705 @HasPermissionAllDecorator('hg.admin')
705 706 @CSRFRequired()
706 707 @view_config(
707 708 route_name='user_create_personal_repo_group', request_method='POST',
708 709 renderer='rhodecode:templates/admin/users/user_edit.mako')
709 710 def user_create_personal_repo_group(self):
710 711 """
711 712 Create personal repository group for this user
712 713 """
713 714 from rhodecode.model.repo_group import RepoGroupModel
714 715
715 716 _ = self.request.translate
716 717 c = self.load_default_context()
717 718
718 719 user_id = self.db_user_id
719 720 c.user = self.db_user
720 721
721 722 personal_repo_group = RepoGroup.get_user_personal_repo_group(
722 723 c.user.user_id)
723 724 if personal_repo_group:
724 725 raise HTTPFound(h.route_path('user_edit_advanced', user_id=user_id))
725 726
726 727 personal_repo_group_name = RepoGroupModel().get_personal_group_name(c.user)
727 728 named_personal_group = RepoGroup.get_by_group_name(
728 729 personal_repo_group_name)
729 730 try:
730 731
731 732 if named_personal_group and named_personal_group.user_id == c.user.user_id:
732 733 # migrate the same named group, and mark it as personal
733 734 named_personal_group.personal = True
734 735 Session().add(named_personal_group)
735 736 Session().commit()
736 737 msg = _('Linked repository group `%s` as personal' % (
737 738 personal_repo_group_name,))
738 739 h.flash(msg, category='success')
739 740 elif not named_personal_group:
740 741 RepoGroupModel().create_personal_repo_group(c.user)
741 742
742 743 msg = _('Created repository group `%s`' % (
743 744 personal_repo_group_name,))
744 745 h.flash(msg, category='success')
745 746 else:
746 747 msg = _('Repository group `%s` is already taken' % (
747 748 personal_repo_group_name,))
748 749 h.flash(msg, category='warning')
749 750 except Exception:
750 751 log.exception("Exception during repository group creation")
751 752 msg = _(
752 753 'An error occurred during repository group creation for user')
753 754 h.flash(msg, category='error')
754 755 Session().rollback()
755 756
756 757 raise HTTPFound(h.route_path('user_edit_advanced', user_id=user_id))
757 758
758 759 @LoginRequired()
759 760 @HasPermissionAllDecorator('hg.admin')
760 761 @view_config(
761 762 route_name='edit_user_auth_tokens', request_method='GET',
762 763 renderer='rhodecode:templates/admin/users/user_edit.mako')
763 764 def auth_tokens(self):
764 765 _ = self.request.translate
765 766 c = self.load_default_context()
766 767 c.user = self.db_user
767 768
768 769 c.active = 'auth_tokens'
769 770
770 771 c.lifetime_values = AuthTokenModel.get_lifetime_values(translator=_)
771 772 c.role_values = [
772 773 (x, AuthTokenModel.cls._get_role_name(x))
773 774 for x in AuthTokenModel.cls.ROLES]
774 775 c.role_options = [(c.role_values, _("Role"))]
775 776 c.user_auth_tokens = AuthTokenModel().get_auth_tokens(
776 777 c.user.user_id, show_expired=True)
777 778 c.role_vcs = AuthTokenModel.cls.ROLE_VCS
778 779 return self._get_template_context(c)
779 780
780 781 def maybe_attach_token_scope(self, token):
781 782 # implemented in EE edition
782 783 pass
783 784
784 785 @LoginRequired()
785 786 @HasPermissionAllDecorator('hg.admin')
786 787 @CSRFRequired()
787 788 @view_config(
788 789 route_name='edit_user_auth_tokens_add', request_method='POST')
789 790 def auth_tokens_add(self):
790 791 _ = self.request.translate
791 792 c = self.load_default_context()
792 793
793 794 user_id = self.db_user_id
794 795 c.user = self.db_user
795 796
796 797 user_data = c.user.get_api_data()
797 798 lifetime = safe_int(self.request.POST.get('lifetime'), -1)
798 799 description = self.request.POST.get('description')
799 800 role = self.request.POST.get('role')
800 801
801 802 token = UserModel().add_auth_token(
802 803 user=c.user.user_id,
803 804 lifetime_minutes=lifetime, role=role, description=description,
804 805 scope_callback=self.maybe_attach_token_scope)
805 806 token_data = token.get_api_data()
806 807
807 808 audit_logger.store_web(
808 809 'user.edit.token.add', action_data={
809 810 'data': {'token': token_data, 'user': user_data}},
810 811 user=self._rhodecode_user, )
811 812 Session().commit()
812 813
813 814 h.flash(_("Auth token successfully created"), category='success')
814 815 return HTTPFound(h.route_path('edit_user_auth_tokens', user_id=user_id))
815 816
816 817 @LoginRequired()
817 818 @HasPermissionAllDecorator('hg.admin')
818 819 @CSRFRequired()
819 820 @view_config(
820 821 route_name='edit_user_auth_tokens_delete', request_method='POST')
821 822 def auth_tokens_delete(self):
822 823 _ = self.request.translate
823 824 c = self.load_default_context()
824 825
825 826 user_id = self.db_user_id
826 827 c.user = self.db_user
827 828
828 829 user_data = c.user.get_api_data()
829 830
830 831 del_auth_token = self.request.POST.get('del_auth_token')
831 832
832 833 if del_auth_token:
833 834 token = UserApiKeys.get_or_404(del_auth_token)
834 835 token_data = token.get_api_data()
835 836
836 837 AuthTokenModel().delete(del_auth_token, c.user.user_id)
837 838 audit_logger.store_web(
838 839 'user.edit.token.delete', action_data={
839 840 'data': {'token': token_data, 'user': user_data}},
840 841 user=self._rhodecode_user,)
841 842 Session().commit()
842 843 h.flash(_("Auth token successfully deleted"), category='success')
843 844
844 845 return HTTPFound(h.route_path('edit_user_auth_tokens', user_id=user_id))
845 846
846 847 @LoginRequired()
847 848 @HasPermissionAllDecorator('hg.admin')
848 849 @view_config(
849 850 route_name='edit_user_ssh_keys', request_method='GET',
850 851 renderer='rhodecode:templates/admin/users/user_edit.mako')
851 852 def ssh_keys(self):
852 853 _ = self.request.translate
853 854 c = self.load_default_context()
854 855 c.user = self.db_user
855 856
856 857 c.active = 'ssh_keys'
857 858 c.default_key = self.request.GET.get('default_key')
858 859 c.user_ssh_keys = SshKeyModel().get_ssh_keys(c.user.user_id)
859 860 return self._get_template_context(c)
860 861
861 862 @LoginRequired()
862 863 @HasPermissionAllDecorator('hg.admin')
863 864 @view_config(
864 865 route_name='edit_user_ssh_keys_generate_keypair', request_method='GET',
865 866 renderer='rhodecode:templates/admin/users/user_edit.mako')
866 867 def ssh_keys_generate_keypair(self):
867 868 _ = self.request.translate
868 869 c = self.load_default_context()
869 870
870 871 c.user = self.db_user
871 872
872 873 c.active = 'ssh_keys_generate'
873 874 comment = 'RhodeCode-SSH {}'.format(c.user.email or '')
874 875 c.private, c.public = SshKeyModel().generate_keypair(comment=comment)
875 876
876 877 return self._get_template_context(c)
877 878
878 879 @LoginRequired()
879 880 @HasPermissionAllDecorator('hg.admin')
880 881 @CSRFRequired()
881 882 @view_config(
882 883 route_name='edit_user_ssh_keys_add', request_method='POST')
883 884 def ssh_keys_add(self):
884 885 _ = self.request.translate
885 886 c = self.load_default_context()
886 887
887 888 user_id = self.db_user_id
888 889 c.user = self.db_user
889 890
890 891 user_data = c.user.get_api_data()
891 892 key_data = self.request.POST.get('key_data')
892 893 description = self.request.POST.get('description')
893 894
894 895 fingerprint = 'unknown'
895 896 try:
896 897 if not key_data:
897 898 raise ValueError('Please add a valid public key')
898 899
899 900 key = SshKeyModel().parse_key(key_data.strip())
900 901 fingerprint = key.hash_md5()
901 902
902 903 ssh_key = SshKeyModel().create(
903 904 c.user.user_id, fingerprint, key.keydata, description)
904 905 ssh_key_data = ssh_key.get_api_data()
905 906
906 907 audit_logger.store_web(
907 908 'user.edit.ssh_key.add', action_data={
908 909 'data': {'ssh_key': ssh_key_data, 'user': user_data}},
909 910 user=self._rhodecode_user, )
910 911 Session().commit()
911 912
912 913 # Trigger an event on change of keys.
913 914 trigger(SshKeyFileChangeEvent(), self.request.registry)
914 915
915 916 h.flash(_("Ssh Key successfully created"), category='success')
916 917
917 918 except IntegrityError:
918 919 log.exception("Exception during ssh key saving")
919 920 err = 'Such key with fingerprint `{}` already exists, ' \
920 921 'please use a different one'.format(fingerprint)
921 922 h.flash(_('An error occurred during ssh key saving: {}').format(err),
922 923 category='error')
923 924 except Exception as e:
924 925 log.exception("Exception during ssh key saving")
925 926 h.flash(_('An error occurred during ssh key saving: {}').format(e),
926 927 category='error')
927 928
928 929 return HTTPFound(
929 930 h.route_path('edit_user_ssh_keys', user_id=user_id))
930 931
931 932 @LoginRequired()
932 933 @HasPermissionAllDecorator('hg.admin')
933 934 @CSRFRequired()
934 935 @view_config(
935 936 route_name='edit_user_ssh_keys_delete', request_method='POST')
936 937 def ssh_keys_delete(self):
937 938 _ = self.request.translate
938 939 c = self.load_default_context()
939 940
940 941 user_id = self.db_user_id
941 942 c.user = self.db_user
942 943
943 944 user_data = c.user.get_api_data()
944 945
945 946 del_ssh_key = self.request.POST.get('del_ssh_key')
946 947
947 948 if del_ssh_key:
948 949 ssh_key = UserSshKeys.get_or_404(del_ssh_key)
949 950 ssh_key_data = ssh_key.get_api_data()
950 951
951 952 SshKeyModel().delete(del_ssh_key, c.user.user_id)
952 953 audit_logger.store_web(
953 954 'user.edit.ssh_key.delete', action_data={
954 955 'data': {'ssh_key': ssh_key_data, 'user': user_data}},
955 956 user=self._rhodecode_user,)
956 957 Session().commit()
957 958 # Trigger an event on change of keys.
958 959 trigger(SshKeyFileChangeEvent(), self.request.registry)
959 960 h.flash(_("Ssh key successfully deleted"), category='success')
960 961
961 962 return HTTPFound(h.route_path('edit_user_ssh_keys', user_id=user_id))
962 963
963 964 @LoginRequired()
964 965 @HasPermissionAllDecorator('hg.admin')
965 966 @view_config(
966 967 route_name='edit_user_emails', request_method='GET',
967 968 renderer='rhodecode:templates/admin/users/user_edit.mako')
968 969 def emails(self):
969 970 _ = self.request.translate
970 971 c = self.load_default_context()
971 972 c.user = self.db_user
972 973
973 974 c.active = 'emails'
974 975 c.user_email_map = UserEmailMap.query() \
975 976 .filter(UserEmailMap.user == c.user).all()
976 977
977 978 return self._get_template_context(c)
978 979
979 980 @LoginRequired()
980 981 @HasPermissionAllDecorator('hg.admin')
981 982 @CSRFRequired()
982 983 @view_config(
983 984 route_name='edit_user_emails_add', request_method='POST')
984 985 def emails_add(self):
985 986 _ = self.request.translate
986 987 c = self.load_default_context()
987 988
988 989 user_id = self.db_user_id
989 990 c.user = self.db_user
990 991
991 992 email = self.request.POST.get('new_email')
992 993 user_data = c.user.get_api_data()
993 994 try:
994 995
995 996 form = UserExtraEmailForm(self.request.translate)()
996 997 data = form.to_python({'email': email})
997 998 email = data['email']
998 999
999 1000 UserModel().add_extra_email(c.user.user_id, email)
1000 1001 audit_logger.store_web(
1001 1002 'user.edit.email.add',
1002 1003 action_data={'email': email, 'user': user_data},
1003 1004 user=self._rhodecode_user)
1004 1005 Session().commit()
1005 1006 h.flash(_("Added new email address `%s` for user account") % email,
1006 1007 category='success')
1007 1008 except formencode.Invalid as error:
1008 1009 h.flash(h.escape(error.error_dict['email']), category='error')
1009 1010 except IntegrityError:
1010 1011 log.warning("Email %s already exists", email)
1011 1012 h.flash(_('Email `{}` is already registered for another user.').format(email),
1012 1013 category='error')
1013 1014 except Exception:
1014 1015 log.exception("Exception during email saving")
1015 1016 h.flash(_('An error occurred during email saving'),
1016 1017 category='error')
1017 1018 raise HTTPFound(h.route_path('edit_user_emails', user_id=user_id))
1018 1019
1019 1020 @LoginRequired()
1020 1021 @HasPermissionAllDecorator('hg.admin')
1021 1022 @CSRFRequired()
1022 1023 @view_config(
1023 1024 route_name='edit_user_emails_delete', request_method='POST')
1024 1025 def emails_delete(self):
1025 1026 _ = self.request.translate
1026 1027 c = self.load_default_context()
1027 1028
1028 1029 user_id = self.db_user_id
1029 1030 c.user = self.db_user
1030 1031
1031 1032 email_id = self.request.POST.get('del_email_id')
1032 1033 user_model = UserModel()
1033 1034
1034 1035 email = UserEmailMap.query().get(email_id).email
1035 1036 user_data = c.user.get_api_data()
1036 1037 user_model.delete_extra_email(c.user.user_id, email_id)
1037 1038 audit_logger.store_web(
1038 1039 'user.edit.email.delete',
1039 1040 action_data={'email': email, 'user': user_data},
1040 1041 user=self._rhodecode_user)
1041 1042 Session().commit()
1042 1043 h.flash(_("Removed email address from user account"),
1043 1044 category='success')
1044 1045 raise HTTPFound(h.route_path('edit_user_emails', user_id=user_id))
1045 1046
1046 1047 @LoginRequired()
1047 1048 @HasPermissionAllDecorator('hg.admin')
1048 1049 @view_config(
1049 1050 route_name='edit_user_ips', request_method='GET',
1050 1051 renderer='rhodecode:templates/admin/users/user_edit.mako')
1051 1052 def ips(self):
1052 1053 _ = self.request.translate
1053 1054 c = self.load_default_context()
1054 1055 c.user = self.db_user
1055 1056
1056 1057 c.active = 'ips'
1057 1058 c.user_ip_map = UserIpMap.query() \
1058 1059 .filter(UserIpMap.user == c.user).all()
1059 1060
1060 1061 c.inherit_default_ips = c.user.inherit_default_permissions
1061 1062 c.default_user_ip_map = UserIpMap.query() \
1062 1063 .filter(UserIpMap.user == User.get_default_user()).all()
1063 1064
1064 1065 return self._get_template_context(c)
1065 1066
1066 1067 @LoginRequired()
1067 1068 @HasPermissionAllDecorator('hg.admin')
1068 1069 @CSRFRequired()
1069 1070 @view_config(
1070 1071 route_name='edit_user_ips_add', request_method='POST')
1071 1072 # NOTE(marcink): this view is allowed for default users, as we can
1072 1073 # edit their IP white list
1073 1074 def ips_add(self):
1074 1075 _ = self.request.translate
1075 1076 c = self.load_default_context()
1076 1077
1077 1078 user_id = self.db_user_id
1078 1079 c.user = self.db_user
1079 1080
1080 1081 user_model = UserModel()
1081 1082 desc = self.request.POST.get('description')
1082 1083 try:
1083 1084 ip_list = user_model.parse_ip_range(
1084 1085 self.request.POST.get('new_ip'))
1085 1086 except Exception as e:
1086 1087 ip_list = []
1087 1088 log.exception("Exception during ip saving")
1088 1089 h.flash(_('An error occurred during ip saving:%s' % (e,)),
1089 1090 category='error')
1090 1091 added = []
1091 1092 user_data = c.user.get_api_data()
1092 1093 for ip in ip_list:
1093 1094 try:
1094 1095 form = UserExtraIpForm(self.request.translate)()
1095 1096 data = form.to_python({'ip': ip})
1096 1097 ip = data['ip']
1097 1098
1098 1099 user_model.add_extra_ip(c.user.user_id, ip, desc)
1099 1100 audit_logger.store_web(
1100 1101 'user.edit.ip.add',
1101 1102 action_data={'ip': ip, 'user': user_data},
1102 1103 user=self._rhodecode_user)
1103 1104 Session().commit()
1104 1105 added.append(ip)
1105 1106 except formencode.Invalid as error:
1106 1107 msg = error.error_dict['ip']
1107 1108 h.flash(msg, category='error')
1108 1109 except Exception:
1109 1110 log.exception("Exception during ip saving")
1110 1111 h.flash(_('An error occurred during ip saving'),
1111 1112 category='error')
1112 1113 if added:
1113 1114 h.flash(
1114 1115 _("Added ips %s to user whitelist") % (', '.join(ip_list), ),
1115 1116 category='success')
1116 1117 if 'default_user' in self.request.POST:
1117 1118 # case for editing global IP list we do it for 'DEFAULT' user
1118 1119 raise HTTPFound(h.route_path('admin_permissions_ips'))
1119 1120 raise HTTPFound(h.route_path('edit_user_ips', user_id=user_id))
1120 1121
1121 1122 @LoginRequired()
1122 1123 @HasPermissionAllDecorator('hg.admin')
1123 1124 @CSRFRequired()
1124 1125 @view_config(
1125 1126 route_name='edit_user_ips_delete', request_method='POST')
1126 1127 # NOTE(marcink): this view is allowed for default users, as we can
1127 1128 # edit their IP white list
1128 1129 def ips_delete(self):
1129 1130 _ = self.request.translate
1130 1131 c = self.load_default_context()
1131 1132
1132 1133 user_id = self.db_user_id
1133 1134 c.user = self.db_user
1134 1135
1135 1136 ip_id = self.request.POST.get('del_ip_id')
1136 1137 user_model = UserModel()
1137 1138 user_data = c.user.get_api_data()
1138 1139 ip = UserIpMap.query().get(ip_id).ip_addr
1139 1140 user_model.delete_extra_ip(c.user.user_id, ip_id)
1140 1141 audit_logger.store_web(
1141 1142 'user.edit.ip.delete', action_data={'ip': ip, 'user': user_data},
1142 1143 user=self._rhodecode_user)
1143 1144 Session().commit()
1144 1145 h.flash(_("Removed ip address from user whitelist"), category='success')
1145 1146
1146 1147 if 'default_user' in self.request.POST:
1147 1148 # case for editing global IP list we do it for 'DEFAULT' user
1148 1149 raise HTTPFound(h.route_path('admin_permissions_ips'))
1149 1150 raise HTTPFound(h.route_path('edit_user_ips', user_id=user_id))
1150 1151
1151 1152 @LoginRequired()
1152 1153 @HasPermissionAllDecorator('hg.admin')
1153 1154 @view_config(
1154 1155 route_name='edit_user_groups_management', request_method='GET',
1155 1156 renderer='rhodecode:templates/admin/users/user_edit.mako')
1156 1157 def groups_management(self):
1157 1158 c = self.load_default_context()
1158 1159 c.user = self.db_user
1159 1160 c.data = c.user.group_member
1160 1161
1161 1162 groups = [UserGroupModel.get_user_groups_as_dict(group.users_group)
1162 1163 for group in c.user.group_member]
1163 1164 c.groups = json.dumps(groups)
1164 1165 c.active = 'groups'
1165 1166
1166 1167 return self._get_template_context(c)
1167 1168
1168 1169 @LoginRequired()
1169 1170 @HasPermissionAllDecorator('hg.admin')
1170 1171 @CSRFRequired()
1171 1172 @view_config(
1172 1173 route_name='edit_user_groups_management_updates', request_method='POST')
1173 1174 def groups_management_updates(self):
1174 1175 _ = self.request.translate
1175 1176 c = self.load_default_context()
1176 1177
1177 1178 user_id = self.db_user_id
1178 1179 c.user = self.db_user
1179 1180
1180 1181 user_groups = set(self.request.POST.getall('users_group_id'))
1181 1182 user_groups_objects = []
1182 1183
1183 1184 for ugid in user_groups:
1184 1185 user_groups_objects.append(
1185 1186 UserGroupModel().get_group(safe_int(ugid)))
1186 1187 user_group_model = UserGroupModel()
1187 1188 added_to_groups, removed_from_groups = \
1188 1189 user_group_model.change_groups(c.user, user_groups_objects)
1189 1190
1190 1191 user_data = c.user.get_api_data()
1191 1192 for user_group_id in added_to_groups:
1192 1193 user_group = UserGroup.get(user_group_id)
1193 1194 old_values = user_group.get_api_data()
1194 1195 audit_logger.store_web(
1195 1196 'user_group.edit.member.add',
1196 1197 action_data={'user': user_data, 'old_data': old_values},
1197 1198 user=self._rhodecode_user)
1198 1199
1199 1200 for user_group_id in removed_from_groups:
1200 1201 user_group = UserGroup.get(user_group_id)
1201 1202 old_values = user_group.get_api_data()
1202 1203 audit_logger.store_web(
1203 1204 'user_group.edit.member.delete',
1204 1205 action_data={'user': user_data, 'old_data': old_values},
1205 1206 user=self._rhodecode_user)
1206 1207
1207 1208 Session().commit()
1208 1209 c.active = 'user_groups_management'
1209 1210 h.flash(_("Groups successfully changed"), category='success')
1210 1211
1211 1212 return HTTPFound(h.route_path(
1212 1213 'edit_user_groups_management', user_id=user_id))
1213 1214
1214 1215 @LoginRequired()
1215 1216 @HasPermissionAllDecorator('hg.admin')
1216 1217 @view_config(
1217 1218 route_name='edit_user_audit_logs', request_method='GET',
1218 1219 renderer='rhodecode:templates/admin/users/user_edit.mako')
1219 1220 def user_audit_logs(self):
1220 1221 _ = self.request.translate
1221 1222 c = self.load_default_context()
1222 1223 c.user = self.db_user
1223 1224
1224 1225 c.active = 'audit'
1225 1226
1226 1227 p = safe_int(self.request.GET.get('page', 1), 1)
1227 1228
1228 1229 filter_term = self.request.GET.get('filter')
1229 1230 user_log = UserModel().get_user_log(c.user, filter_term)
1230 1231
1231 def url_generator(**kw):
1232 def url_generator(page_num):
1233 query_params = {
1234 'page': page_num
1235 }
1232 1236 if filter_term:
1233 kw['filter'] = filter_term
1234 return self.request.current_route_path(_query=kw)
1237 query_params['filter'] = filter_term
1238 return self.request.current_route_path(_query=query_params)
1235 1239
1236 c.audit_logs = h.Page(
1237 user_log, page=p, items_per_page=10, url=url_generator)
1240 c.audit_logs = SqlPage(
1241 user_log, page=p, items_per_page=10, url_maker=url_generator)
1238 1242 c.filter_term = filter_term
1239 1243 return self._get_template_context(c)
1240 1244
1241 1245 @LoginRequired()
1242 1246 @HasPermissionAllDecorator('hg.admin')
1243 1247 @view_config(
1244 1248 route_name='edit_user_audit_logs_download', request_method='GET',
1245 1249 renderer='string')
1246 1250 def user_audit_logs_download(self):
1247 1251 _ = self.request.translate
1248 1252 c = self.load_default_context()
1249 1253 c.user = self.db_user
1250 1254
1251 1255 user_log = UserModel().get_user_log(c.user, filter_term=None)
1252 1256
1253 1257 audit_log_data = {}
1254 1258 for entry in user_log:
1255 1259 audit_log_data[entry.user_log_id] = entry.get_dict()
1256 1260
1257 1261 response = Response(json.dumps(audit_log_data, indent=4))
1258 1262 response.content_disposition = str(
1259 1263 'attachment; filename=%s' % 'user_{}_audit_logs.json'.format(c.user.user_id))
1260 1264 response.content_type = 'application/json'
1261 1265
1262 1266 return response
1263 1267
1264 1268 @LoginRequired()
1265 1269 @HasPermissionAllDecorator('hg.admin')
1266 1270 @view_config(
1267 1271 route_name='edit_user_perms_summary', request_method='GET',
1268 1272 renderer='rhodecode:templates/admin/users/user_edit.mako')
1269 1273 def user_perms_summary(self):
1270 1274 _ = self.request.translate
1271 1275 c = self.load_default_context()
1272 1276 c.user = self.db_user
1273 1277
1274 1278 c.active = 'perms_summary'
1275 1279 c.perm_user = c.user.AuthUser(ip_addr=self.request.remote_addr)
1276 1280
1277 1281 return self._get_template_context(c)
1278 1282
1279 1283 @LoginRequired()
1280 1284 @HasPermissionAllDecorator('hg.admin')
1281 1285 @view_config(
1282 1286 route_name='edit_user_perms_summary_json', request_method='GET',
1283 1287 renderer='json_ext')
1284 1288 def user_perms_summary_json(self):
1285 1289 self.load_default_context()
1286 1290 perm_user = self.db_user.AuthUser(ip_addr=self.request.remote_addr)
1287 1291
1288 1292 return perm_user.permissions
1289 1293
1290 1294 @LoginRequired()
1291 1295 @HasPermissionAllDecorator('hg.admin')
1292 1296 @view_config(
1293 1297 route_name='edit_user_caches', request_method='GET',
1294 1298 renderer='rhodecode:templates/admin/users/user_edit.mako')
1295 1299 def user_caches(self):
1296 1300 _ = self.request.translate
1297 1301 c = self.load_default_context()
1298 1302 c.user = self.db_user
1299 1303
1300 1304 c.active = 'caches'
1301 1305 c.perm_user = c.user.AuthUser(ip_addr=self.request.remote_addr)
1302 1306
1303 1307 cache_namespace_uid = 'cache_user_auth.{}'.format(self.db_user.user_id)
1304 1308 c.region = rc_cache.get_or_create_region('cache_perms', cache_namespace_uid)
1305 1309 c.backend = c.region.backend
1306 1310 c.user_keys = sorted(c.region.backend.list_keys(prefix=cache_namespace_uid))
1307 1311
1308 1312 return self._get_template_context(c)
1309 1313
1310 1314 @LoginRequired()
1311 1315 @HasPermissionAllDecorator('hg.admin')
1312 1316 @CSRFRequired()
1313 1317 @view_config(
1314 1318 route_name='edit_user_caches_update', request_method='POST')
1315 1319 def user_caches_update(self):
1316 1320 _ = self.request.translate
1317 1321 c = self.load_default_context()
1318 1322 c.user = self.db_user
1319 1323
1320 1324 c.active = 'caches'
1321 1325 c.perm_user = c.user.AuthUser(ip_addr=self.request.remote_addr)
1322 1326
1323 1327 cache_namespace_uid = 'cache_user_auth.{}'.format(self.db_user.user_id)
1324 1328 del_keys = rc_cache.clear_cache_namespace('cache_perms', cache_namespace_uid)
1325 1329
1326 1330 h.flash(_("Deleted {} cache keys").format(del_keys), category='success')
1327 1331
1328 1332 return HTTPFound(h.route_path(
1329 1333 'edit_user_caches', user_id=c.user.user_id))
@@ -1,387 +1,388 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2019 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21
22 22 import logging
23 23 import itertools
24 24
25 25 from webhelpers.feedgenerator import Atom1Feed, Rss201rev2Feed
26 26
27 27 from pyramid.view import view_config
28 28 from pyramid.httpexceptions import HTTPBadRequest
29 29 from pyramid.response import Response
30 30 from pyramid.renderers import render
31 31
32 32 from rhodecode.apps._base import BaseAppView
33 33 from rhodecode.model.db import (
34 34 or_, joinedload, Repository, UserLog, UserFollowing, User, UserApiKeys)
35 35 from rhodecode.model.meta import Session
36 36 import rhodecode.lib.helpers as h
37 from rhodecode.lib.helpers import Page
37 from rhodecode.lib.helpers import SqlPage
38 38 from rhodecode.lib.user_log_filter import user_log_filter
39 39 from rhodecode.lib.auth import LoginRequired, NotAnonymous, CSRFRequired, HasRepoPermissionAny
40 40 from rhodecode.lib.utils2 import safe_int, AttributeDict, md5_safe
41 41 from rhodecode.model.scm import ScmModel
42 42
43 43 log = logging.getLogger(__name__)
44 44
45 45
46 46 class JournalView(BaseAppView):
47 47
48 48 def load_default_context(self):
49 49 c = self._get_local_tmpl_context(include_app_defaults=True)
50 50
51 51 self._load_defaults(c.rhodecode_name)
52 52
53 53 # TODO(marcink): what is this, why we need a global register ?
54 54 c.search_term = self.request.GET.get('filter') or ''
55 55 return c
56 56
57 57 def _get_config(self, rhodecode_name):
58 58 import rhodecode
59 59 config = rhodecode.CONFIG
60 60
61 61 return {
62 62 'language': 'en-us',
63 63 'feed_ttl': '5', # TTL of feed,
64 64 'feed_items_per_page':
65 65 safe_int(config.get('rss_items_per_page', 20)),
66 66 'rhodecode_name': rhodecode_name
67 67 }
68 68
69 69 def _load_defaults(self, rhodecode_name):
70 70 config = self._get_config(rhodecode_name)
71 71 # common values for feeds
72 72 self.language = config["language"]
73 73 self.ttl = config["feed_ttl"]
74 74 self.feed_items_per_page = config['feed_items_per_page']
75 75 self.rhodecode_name = config['rhodecode_name']
76 76
77 77 def _get_daily_aggregate(self, journal):
78 78 groups = []
79 79 for k, g in itertools.groupby(journal, lambda x: x.action_as_day):
80 80 user_group = []
81 81 # groupby username if it's a present value, else
82 82 # fallback to journal username
83 83 for _, g2 in itertools.groupby(
84 84 list(g), lambda x: x.user.username if x.user else x.username):
85 85 l = list(g2)
86 86 user_group.append((l[0].user, l))
87 87
88 88 groups.append((k, user_group,))
89 89
90 90 return groups
91 91
92 92 def _get_journal_data(self, following_repos, search_term):
93 93 repo_ids = [x.follows_repository.repo_id for x in following_repos
94 94 if x.follows_repository is not None]
95 95 user_ids = [x.follows_user.user_id for x in following_repos
96 96 if x.follows_user is not None]
97 97
98 98 filtering_criterion = None
99 99
100 100 if repo_ids and user_ids:
101 101 filtering_criterion = or_(UserLog.repository_id.in_(repo_ids),
102 102 UserLog.user_id.in_(user_ids))
103 103 if repo_ids and not user_ids:
104 104 filtering_criterion = UserLog.repository_id.in_(repo_ids)
105 105 if not repo_ids and user_ids:
106 106 filtering_criterion = UserLog.user_id.in_(user_ids)
107 107 if filtering_criterion is not None:
108 108 journal = Session().query(UserLog)\
109 109 .options(joinedload(UserLog.user))\
110 110 .options(joinedload(UserLog.repository))
111 111 # filter
112 112 try:
113 113 journal = user_log_filter(journal, search_term)
114 114 except Exception:
115 115 # we want this to crash for now
116 116 raise
117 117 journal = journal.filter(filtering_criterion)\
118 118 .order_by(UserLog.action_date.desc())
119 119 else:
120 120 journal = []
121 121
122 122 return journal
123 123
124 124 def feed_uid(self, entry_id):
125 125 return '{}:{}'.format('journal', md5_safe(entry_id))
126 126
127 127 def _atom_feed(self, repos, search_term, public=True):
128 128 _ = self.request.translate
129 129 journal = self._get_journal_data(repos, search_term)
130 130 if public:
131 131 _link = h.route_url('journal_public_atom')
132 132 _desc = '%s %s %s' % (self.rhodecode_name, _('public journal'),
133 133 'atom feed')
134 134 else:
135 135 _link = h.route_url('journal_atom')
136 136 _desc = '%s %s %s' % (self.rhodecode_name, _('journal'), 'atom feed')
137 137
138 138 feed = Atom1Feed(
139 139 title=_desc, link=_link, description=_desc,
140 140 language=self.language, ttl=self.ttl)
141 141
142 142 for entry in journal[:self.feed_items_per_page]:
143 143 user = entry.user
144 144 if user is None:
145 145 # fix deleted users
146 146 user = AttributeDict({'short_contact': entry.username,
147 147 'email': '',
148 148 'full_contact': ''})
149 149 action, action_extra, ico = h.action_parser(
150 150 self.request, entry, feed=True)
151 151 title = "%s - %s %s" % (user.short_contact, action(),
152 152 entry.repository.repo_name)
153 153 desc = action_extra()
154 154 _url = h.route_url('home')
155 155 if entry.repository is not None:
156 156 _url = h.route_url('repo_commits',
157 157 repo_name=entry.repository.repo_name)
158 158
159 159 feed.add_item(
160 160 unique_id=self.feed_uid(entry.user_log_id),
161 161 title=title,
162 162 pubdate=entry.action_date,
163 163 link=_url,
164 164 author_email=user.email,
165 165 author_name=user.full_contact,
166 166 description=desc)
167 167
168 168 response = Response(feed.writeString('utf-8'))
169 169 response.content_type = feed.mime_type
170 170 return response
171 171
172 172 def _rss_feed(self, repos, search_term, public=True):
173 173 _ = self.request.translate
174 174 journal = self._get_journal_data(repos, search_term)
175 175 if public:
176 176 _link = h.route_url('journal_public_atom')
177 177 _desc = '%s %s %s' % (
178 178 self.rhodecode_name, _('public journal'), 'rss feed')
179 179 else:
180 180 _link = h.route_url('journal_atom')
181 181 _desc = '%s %s %s' % (
182 182 self.rhodecode_name, _('journal'), 'rss feed')
183 183
184 184 feed = Rss201rev2Feed(
185 185 title=_desc, link=_link, description=_desc,
186 186 language=self.language, ttl=self.ttl)
187 187
188 188 for entry in journal[:self.feed_items_per_page]:
189 189 user = entry.user
190 190 if user is None:
191 191 # fix deleted users
192 192 user = AttributeDict({'short_contact': entry.username,
193 193 'email': '',
194 194 'full_contact': ''})
195 195 action, action_extra, ico = h.action_parser(
196 196 self.request, entry, feed=True)
197 197 title = "%s - %s %s" % (user.short_contact, action(),
198 198 entry.repository.repo_name)
199 199 desc = action_extra()
200 200 _url = h.route_url('home')
201 201 if entry.repository is not None:
202 202 _url = h.route_url('repo_commits',
203 203 repo_name=entry.repository.repo_name)
204 204
205 205 feed.add_item(
206 206 unique_id=self.feed_uid(entry.user_log_id),
207 207 title=title,
208 208 pubdate=entry.action_date,
209 209 link=_url,
210 210 author_email=user.email,
211 211 author_name=user.full_contact,
212 212 description=desc)
213 213
214 214 response = Response(feed.writeString('utf-8'))
215 215 response.content_type = feed.mime_type
216 216 return response
217 217
218 218 @LoginRequired()
219 219 @NotAnonymous()
220 220 @view_config(
221 221 route_name='journal', request_method='GET',
222 222 renderer=None)
223 223 def journal(self):
224 224 c = self.load_default_context()
225 225
226 226 p = safe_int(self.request.GET.get('page', 1), 1)
227 227 c.user = User.get(self._rhodecode_user.user_id)
228 228 following = Session().query(UserFollowing)\
229 229 .filter(UserFollowing.user_id == self._rhodecode_user.user_id)\
230 230 .options(joinedload(UserFollowing.follows_repository))\
231 231 .all()
232 232
233 233 journal = self._get_journal_data(following, c.search_term)
234 234
235 def url_generator(**kw):
235 def url_generator(page_num):
236 236 query_params = {
237 'page': page_num,
237 238 'filter': c.search_term
238 239 }
239 query_params.update(kw)
240 240 return self.request.current_route_path(_query=query_params)
241 241
242 c.journal_pager = Page(
243 journal, page=p, items_per_page=20, url=url_generator)
242 c.journal_pager = SqlPage(
243 journal, page=p, items_per_page=20, url_maker=url_generator)
244 244 c.journal_day_aggreagate = self._get_daily_aggregate(c.journal_pager)
245 245
246 246 c.journal_data = render(
247 247 'rhodecode:templates/journal/journal_data.mako',
248 248 self._get_template_context(c), self.request)
249 249
250 250 if self.request.is_xhr:
251 251 return Response(c.journal_data)
252 252
253 253 html = render(
254 254 'rhodecode:templates/journal/journal.mako',
255 255 self._get_template_context(c), self.request)
256 256 return Response(html)
257 257
258 258 @LoginRequired(auth_token_access=[UserApiKeys.ROLE_FEED])
259 259 @NotAnonymous()
260 260 @view_config(
261 261 route_name='journal_atom', request_method='GET',
262 262 renderer=None)
263 263 def journal_atom(self):
264 264 """
265 265 Produce an atom-1.0 feed via feedgenerator module
266 266 """
267 267 c = self.load_default_context()
268 268 following_repos = Session().query(UserFollowing)\
269 269 .filter(UserFollowing.user_id == self._rhodecode_user.user_id)\
270 270 .options(joinedload(UserFollowing.follows_repository))\
271 271 .all()
272 272 return self._atom_feed(following_repos, c.search_term, public=False)
273 273
274 274 @LoginRequired(auth_token_access=[UserApiKeys.ROLE_FEED])
275 275 @NotAnonymous()
276 276 @view_config(
277 277 route_name='journal_rss', request_method='GET',
278 278 renderer=None)
279 279 def journal_rss(self):
280 280 """
281 281 Produce an rss feed via feedgenerator module
282 282 """
283 283 c = self.load_default_context()
284 284 following_repos = Session().query(UserFollowing)\
285 285 .filter(UserFollowing.user_id == self._rhodecode_user.user_id)\
286 286 .options(joinedload(UserFollowing.follows_repository))\
287 287 .all()
288 288 return self._rss_feed(following_repos, c.search_term, public=False)
289 289
290 290 @LoginRequired()
291 291 @NotAnonymous()
292 292 @CSRFRequired()
293 293 @view_config(
294 294 route_name='toggle_following', request_method='POST',
295 295 renderer='json_ext')
296 296 def toggle_following(self):
297 297 user_id = self.request.POST.get('follows_user_id')
298 298 if user_id:
299 299 try:
300 300 ScmModel().toggle_following_user(user_id, self._rhodecode_user.user_id)
301 301 Session().commit()
302 302 return 'ok'
303 303 except Exception:
304 304 raise HTTPBadRequest()
305 305
306 306 repo_id = self.request.POST.get('follows_repo_id')
307 307 repo = Repository.get_or_404(repo_id)
308 308 perm_set = ['repository.read', 'repository.write', 'repository.admin']
309 309 has_perm = HasRepoPermissionAny(*perm_set)(repo.repo_name, 'RepoWatch check')
310 310 if repo and has_perm:
311 311 try:
312 312 ScmModel().toggle_following_repo(repo_id, self._rhodecode_user.user_id)
313 313 Session().commit()
314 314 return 'ok'
315 315 except Exception:
316 316 raise HTTPBadRequest()
317 317
318 318 raise HTTPBadRequest()
319 319
320 320 @LoginRequired()
321 321 @view_config(
322 322 route_name='journal_public', request_method='GET',
323 323 renderer=None)
324 324 def journal_public(self):
325 325 c = self.load_default_context()
326 326 # Return a rendered template
327 327 p = safe_int(self.request.GET.get('page', 1), 1)
328 328
329 329 c.following = Session().query(UserFollowing)\
330 330 .filter(UserFollowing.user_id == self._rhodecode_user.user_id)\
331 331 .options(joinedload(UserFollowing.follows_repository))\
332 332 .all()
333 333
334 334 journal = self._get_journal_data(c.following, c.search_term)
335 335
336 def url_generator(**kw):
337 query_params = {}
338 query_params.update(kw)
336 def url_generator(page_num):
337 query_params = {
338 'page': page_num
339 }
339 340 return self.request.current_route_path(_query=query_params)
340 341
341 c.journal_pager = Page(
342 journal, page=p, items_per_page=20, url=url_generator)
342 c.journal_pager = SqlPage(
343 journal, page=p, items_per_page=20, url_maker=url_generator)
343 344 c.journal_day_aggreagate = self._get_daily_aggregate(c.journal_pager)
344 345
345 346 c.journal_data = render(
346 347 'rhodecode:templates/journal/journal_data.mako',
347 348 self._get_template_context(c), self.request)
348 349
349 350 if self.request.is_xhr:
350 351 return Response(c.journal_data)
351 352
352 353 html = render(
353 354 'rhodecode:templates/journal/public_journal.mako',
354 355 self._get_template_context(c), self.request)
355 356 return Response(html)
356 357
357 358 @LoginRequired(auth_token_access=[UserApiKeys.ROLE_FEED])
358 359 @view_config(
359 360 route_name='journal_public_atom', request_method='GET',
360 361 renderer=None)
361 362 def journal_public_atom(self):
362 363 """
363 364 Produce an atom-1.0 feed via feedgenerator module
364 365 """
365 366 c = self.load_default_context()
366 367 following_repos = Session().query(UserFollowing)\
367 368 .filter(UserFollowing.user_id == self._rhodecode_user.user_id)\
368 369 .options(joinedload(UserFollowing.follows_repository))\
369 370 .all()
370 371
371 372 return self._atom_feed(following_repos, c.search_term)
372 373
373 374 @LoginRequired(auth_token_access=[UserApiKeys.ROLE_FEED])
374 375 @view_config(
375 376 route_name='journal_public_rss', request_method='GET',
376 377 renderer=None)
377 378 def journal_public_rss(self):
378 379 """
379 380 Produce an rss2 feed via feedgenerator module
380 381 """
381 382 c = self.load_default_context()
382 383 following_repos = Session().query(UserFollowing)\
383 384 .filter(UserFollowing.user_id == self._rhodecode_user.user_id)\
384 385 .options(joinedload(UserFollowing.follows_repository))\
385 386 .all()
386 387
387 388 return self._rss_feed(following_repos, c.search_term)
@@ -1,198 +1,201 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2019 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import logging
22 22
23 23 from pyramid.httpexceptions import (
24 24 HTTPFound, HTTPNotFound, HTTPInternalServerError)
25 25 from pyramid.view import view_config
26 26
27 27 from rhodecode.apps._base import BaseAppView
28 28 from rhodecode.lib.auth import LoginRequired, NotAnonymous, CSRFRequired
29 29
30 30 from rhodecode.lib import helpers as h
31 from rhodecode.lib.helpers import Page
31 from rhodecode.lib.helpers import SqlPage
32 32 from rhodecode.lib.utils2 import safe_int
33 33 from rhodecode.model.db import Notification
34 34 from rhodecode.model.notification import NotificationModel
35 35 from rhodecode.model.meta import Session
36 36
37 37
38 38 log = logging.getLogger(__name__)
39 39
40 40
41 41 class MyAccountNotificationsView(BaseAppView):
42 42
43 43 def load_default_context(self):
44 44 c = self._get_local_tmpl_context()
45 45 c.user = c.auth_user.get_instance()
46 46
47 47 return c
48 48
49 49 def _has_permissions(self, notification):
50 50 def is_owner():
51 51 user_id = self._rhodecode_db_user.user_id
52 52 for user_notification in notification.notifications_to_users:
53 53 if user_notification.user.user_id == user_id:
54 54 return True
55 55 return False
56 56 return h.HasPermissionAny('hg.admin')() or is_owner()
57 57
58 58 @LoginRequired()
59 59 @NotAnonymous()
60 60 @view_config(
61 61 route_name='notifications_show_all', request_method='GET',
62 62 renderer='rhodecode:templates/admin/notifications/notifications_show_all.mako')
63 63 def notifications_show_all(self):
64 64 c = self.load_default_context()
65 65
66 66 c.unread_count = NotificationModel().get_unread_cnt_for_user(
67 67 self._rhodecode_db_user.user_id)
68 68
69 69 _current_filter = self.request.GET.getall('type') or ['unread']
70 70
71 71 notifications = NotificationModel().get_for_user(
72 72 self._rhodecode_db_user.user_id,
73 73 filter_=_current_filter)
74 74
75 75 p = safe_int(self.request.GET.get('page', 1), 1)
76 76
77 def url_generator(**kw):
77 def url_generator(page_num):
78 query_params = {
79 'page': page_num
80 }
78 81 _query = self.request.GET.mixed()
79 _query.update(kw)
80 return self.request.current_route_path(_query=_query)
82 query_params.update(_query)
83 return self.request.current_route_path(_query=query_params)
81 84
82 c.notifications = Page(notifications, page=p, items_per_page=10,
83 url=url_generator)
85 c.notifications = SqlPage(notifications, page=p, items_per_page=10,
86 url_maker=url_generator)
84 87
85 88 c.unread_type = 'unread'
86 89 c.all_type = 'all'
87 90 c.pull_request_type = Notification.TYPE_PULL_REQUEST
88 91 c.comment_type = [Notification.TYPE_CHANGESET_COMMENT,
89 92 Notification.TYPE_PULL_REQUEST_COMMENT]
90 93
91 94 c.current_filter = 'unread' # default filter
92 95
93 96 if _current_filter == [c.pull_request_type]:
94 97 c.current_filter = 'pull_request'
95 98 elif _current_filter == c.comment_type:
96 99 c.current_filter = 'comment'
97 100 elif _current_filter == [c.unread_type]:
98 101 c.current_filter = 'unread'
99 102 elif _current_filter == [c.all_type]:
100 103 c.current_filter = 'all'
101 104 return self._get_template_context(c)
102 105
103 106 @LoginRequired()
104 107 @NotAnonymous()
105 108 @CSRFRequired()
106 109 @view_config(
107 110 route_name='notifications_mark_all_read', request_method='POST',
108 111 renderer='rhodecode:templates/admin/notifications/notifications_show_all.mako')
109 112 def notifications_mark_all_read(self):
110 113 NotificationModel().mark_all_read_for_user(
111 114 self._rhodecode_db_user.user_id,
112 115 filter_=self.request.GET.getall('type'))
113 116 Session().commit()
114 117 raise HTTPFound(h.route_path('notifications_show_all'))
115 118
116 119 @LoginRequired()
117 120 @NotAnonymous()
118 121 @view_config(
119 122 route_name='notifications_show', request_method='GET',
120 123 renderer='rhodecode:templates/admin/notifications/notifications_show.mako')
121 124 def notifications_show(self):
122 125 c = self.load_default_context()
123 126 notification_id = self.request.matchdict['notification_id']
124 127 notification = Notification.get_or_404(notification_id)
125 128
126 129 if not self._has_permissions(notification):
127 130 log.debug('User %s does not have permission to access notification',
128 131 self._rhodecode_user)
129 132 raise HTTPNotFound()
130 133
131 134 u_notification = NotificationModel().get_user_notification(
132 135 self._rhodecode_db_user.user_id, notification)
133 136 if not u_notification:
134 137 log.debug('User %s notification does not exist',
135 138 self._rhodecode_user)
136 139 raise HTTPNotFound()
137 140
138 141 # when opening this notification, mark it as read for this use
139 142 if not u_notification.read:
140 143 u_notification.mark_as_read()
141 144 Session().commit()
142 145
143 146 c.notification = notification
144 147
145 148 return self._get_template_context(c)
146 149
147 150 @LoginRequired()
148 151 @NotAnonymous()
149 152 @CSRFRequired()
150 153 @view_config(
151 154 route_name='notifications_update', request_method='POST',
152 155 renderer='json_ext')
153 156 def notification_update(self):
154 157 notification_id = self.request.matchdict['notification_id']
155 158 notification = Notification.get_or_404(notification_id)
156 159
157 160 if not self._has_permissions(notification):
158 161 log.debug('User %s does not have permission to access notification',
159 162 self._rhodecode_user)
160 163 raise HTTPNotFound()
161 164
162 165 try:
163 166 # updates notification read flag
164 167 NotificationModel().mark_read(
165 168 self._rhodecode_user.user_id, notification)
166 169 Session().commit()
167 170 return 'ok'
168 171 except Exception:
169 172 Session().rollback()
170 173 log.exception("Exception updating a notification item")
171 174
172 175 raise HTTPInternalServerError()
173 176
174 177 @LoginRequired()
175 178 @NotAnonymous()
176 179 @CSRFRequired()
177 180 @view_config(
178 181 route_name='notifications_delete', request_method='POST',
179 182 renderer='json_ext')
180 183 def notification_delete(self):
181 184 notification_id = self.request.matchdict['notification_id']
182 185 notification = Notification.get_or_404(notification_id)
183 186 if not self._has_permissions(notification):
184 187 log.debug('User %s does not have permission to access notification',
185 188 self._rhodecode_user)
186 189 raise HTTPNotFound()
187 190
188 191 try:
189 192 # deletes only notification2user
190 193 NotificationModel().delete(
191 194 self._rhodecode_user.user_id, notification)
192 195 Session().commit()
193 196 return 'ok'
194 197 except Exception:
195 198 Session().rollback()
196 199 log.exception("Exception deleting a notification item")
197 200
198 201 raise HTTPInternalServerError()
@@ -1,65 +1,67 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2017-2019 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import logging
22 22 from pyramid.view import view_config
23 23
24 24 from rhodecode.apps._base import RepoAppView
25 from rhodecode.lib.helpers import SqlPage
25 26 from rhodecode.lib import helpers as h
26 27 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
27 28 from rhodecode.lib.utils2 import safe_int
28 29 from rhodecode.model.repo import RepoModel
29 30
30 31 log = logging.getLogger(__name__)
31 32
32 33
33 34 class AuditLogsView(RepoAppView):
34 35 def load_default_context(self):
35 36 c = self._get_local_tmpl_context()
36
37
38 37 return c
39 38
40 39 @LoginRequired()
41 40 @HasRepoPermissionAnyDecorator('repository.admin')
42 41 @view_config(
43 42 route_name='edit_repo_audit_logs', request_method='GET',
44 43 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
45 44 def repo_audit_logs(self):
46 45 _ = self.request.translate
47 46 c = self.load_default_context()
48 47 c.db_repo = self.db_repo
49 48
50 49 c.active = 'audit'
51 50
52 51 p = safe_int(self.request.GET.get('page', 1), 1)
53 52
54 53 filter_term = self.request.GET.get('filter')
55 54 user_log = RepoModel().get_repo_log(c.db_repo, filter_term)
56 55
57 def url_generator(**kw):
56 def url_generator(page_num):
57 query_params = {
58 'page': page_num
59 }
58 60 if filter_term:
59 kw['filter'] = filter_term
60 return self.request.current_route_path(_query=kw)
61 query_params['filter'] = filter_term
62 return self.request.current_route_path(_query=query_params)
61 63
62 c.audit_logs = h.Page(
63 user_log, page=p, items_per_page=10, url=url_generator)
64 c.audit_logs = SqlPage(
65 user_log, page=p, items_per_page=10, url_maker=url_generator)
64 66 c.filter_term = filter_term
65 67 return self._get_template_context(c)
@@ -1,365 +1,371 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2019 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21
22 22 import logging
23 23
24 24 from pyramid.httpexceptions import HTTPNotFound, HTTPFound
25 25 from pyramid.view import view_config
26 26 from pyramid.renderers import render
27 27 from pyramid.response import Response
28 28
29 29 from rhodecode.apps._base import RepoAppView
30 30 import rhodecode.lib.helpers as h
31 31 from rhodecode.lib.auth import (
32 32 LoginRequired, HasRepoPermissionAnyDecorator)
33 33
34 34 from rhodecode.lib.ext_json import json
35 35 from rhodecode.lib.graphmod import _colored, _dagwalker
36 36 from rhodecode.lib.helpers import RepoPage
37 37 from rhodecode.lib.utils2 import safe_int, safe_str, str2bool
38 38 from rhodecode.lib.vcs.exceptions import (
39 39 RepositoryError, CommitDoesNotExistError,
40 40 CommitError, NodeDoesNotExistError, EmptyRepositoryError)
41 41
42 42 log = logging.getLogger(__name__)
43 43
44 44 DEFAULT_CHANGELOG_SIZE = 20
45 45
46 46
47 47 class RepoChangelogView(RepoAppView):
48 48
49 49 def _get_commit_or_redirect(self, commit_id, redirect_after=True):
50 50 """
51 51 This is a safe way to get commit. If an error occurs it redirects to
52 52 tip with proper message
53 53
54 54 :param commit_id: id of commit to fetch
55 55 :param redirect_after: toggle redirection
56 56 """
57 57 _ = self.request.translate
58 58
59 59 try:
60 60 return self.rhodecode_vcs_repo.get_commit(commit_id)
61 61 except EmptyRepositoryError:
62 62 if not redirect_after:
63 63 return None
64 64
65 65 h.flash(h.literal(
66 66 _('There are no commits yet')), category='warning')
67 67 raise HTTPFound(
68 68 h.route_path('repo_summary', repo_name=self.db_repo_name))
69 69
70 70 except (CommitDoesNotExistError, LookupError):
71 71 msg = _('No such commit exists for this repository')
72 72 h.flash(msg, category='error')
73 73 raise HTTPNotFound()
74 74 except RepositoryError as e:
75 75 h.flash(safe_str(h.escape(e)), category='error')
76 76 raise HTTPNotFound()
77 77
78 78 def _graph(self, repo, commits, prev_data=None, next_data=None):
79 79 """
80 80 Generates a DAG graph for repo
81 81
82 82 :param repo: repo instance
83 83 :param commits: list of commits
84 84 """
85 85 if not commits:
86 86 return json.dumps([]), json.dumps([])
87 87
88 88 def serialize(commit, parents=True):
89 89 data = dict(
90 90 raw_id=commit.raw_id,
91 91 idx=commit.idx,
92 92 branch=None,
93 93 )
94 94 if parents:
95 95 data['parents'] = [
96 96 serialize(x, parents=False) for x in commit.parents]
97 97 return data
98 98
99 99 prev_data = prev_data or []
100 100 next_data = next_data or []
101 101
102 102 current = [serialize(x) for x in commits]
103 103 commits = prev_data + current + next_data
104 104
105 105 dag = _dagwalker(repo, commits)
106 106
107 107 data = [[commit_id, vtx, edges, branch]
108 108 for commit_id, vtx, edges, branch in _colored(dag)]
109 109 return json.dumps(data), json.dumps(current)
110 110
111 111 def _check_if_valid_branch(self, branch_name, repo_name, f_path):
112 112 if branch_name not in self.rhodecode_vcs_repo.branches_all:
113 113 h.flash('Branch {} is not found.'.format(h.escape(branch_name)),
114 114 category='warning')
115 115 redirect_url = h.route_path(
116 116 'repo_commits_file', repo_name=repo_name,
117 117 commit_id=branch_name, f_path=f_path or '')
118 118 raise HTTPFound(redirect_url)
119 119
120 120 def _load_changelog_data(
121 121 self, c, collection, page, chunk_size, branch_name=None,
122 122 dynamic=False, f_path=None, commit_id=None):
123 123
124 def url_generator(**kw):
125 query_params = {}
126 query_params.update(kw)
124 def url_generator(page_num):
125 query_params = {
126 'page': page_num
127 }
128
129 if branch_name:
130 query_params.update({
131 'branch': branch_name
132 })
133
127 134 if f_path:
128 135 # changelog for file
129 136 return h.route_path(
130 137 'repo_commits_file',
131 138 repo_name=c.rhodecode_db_repo.repo_name,
132 139 commit_id=commit_id, f_path=f_path,
133 140 _query=query_params)
134 141 else:
135 142 return h.route_path(
136 143 'repo_commits',
137 144 repo_name=c.rhodecode_db_repo.repo_name, _query=query_params)
138 145
139 146 c.total_cs = len(collection)
140 147 c.showing_commits = min(chunk_size, c.total_cs)
141 148 c.pagination = RepoPage(collection, page=page, item_count=c.total_cs,
142 items_per_page=chunk_size, branch=branch_name,
143 url=url_generator)
149 items_per_page=chunk_size, url_maker=url_generator)
144 150
145 151 c.next_page = c.pagination.next_page
146 152 c.prev_page = c.pagination.previous_page
147 153
148 154 if dynamic:
149 155 if self.request.GET.get('chunk') != 'next':
150 156 c.next_page = None
151 157 if self.request.GET.get('chunk') != 'prev':
152 158 c.prev_page = None
153 159
154 160 page_commit_ids = [x.raw_id for x in c.pagination]
155 161 c.comments = c.rhodecode_db_repo.get_comments(page_commit_ids)
156 162 c.statuses = c.rhodecode_db_repo.statuses(page_commit_ids)
157 163
158 164 def load_default_context(self):
159 165 c = self._get_local_tmpl_context(include_app_defaults=True)
160 166
161 167 c.rhodecode_repo = self.rhodecode_vcs_repo
162 168
163 169 return c
164 170
165 171 def _get_preload_attrs(self):
166 172 pre_load = ['author', 'branch', 'date', 'message', 'parents',
167 173 'obsolete', 'phase', 'hidden']
168 174 return pre_load
169 175
170 176 @LoginRequired()
171 177 @HasRepoPermissionAnyDecorator(
172 178 'repository.read', 'repository.write', 'repository.admin')
173 179 @view_config(
174 180 route_name='repo_commits', request_method='GET',
175 181 renderer='rhodecode:templates/commits/changelog.mako')
176 182 @view_config(
177 183 route_name='repo_commits_file', request_method='GET',
178 184 renderer='rhodecode:templates/commits/changelog.mako')
179 185 # old routes for backward compat
180 186 @view_config(
181 187 route_name='repo_changelog', request_method='GET',
182 188 renderer='rhodecode:templates/commits/changelog.mako')
183 189 @view_config(
184 190 route_name='repo_changelog_file', request_method='GET',
185 191 renderer='rhodecode:templates/commits/changelog.mako')
186 192 def repo_changelog(self):
187 193 c = self.load_default_context()
188 194
189 195 commit_id = self.request.matchdict.get('commit_id')
190 196 f_path = self._get_f_path(self.request.matchdict)
191 197 show_hidden = str2bool(self.request.GET.get('evolve'))
192 198
193 199 chunk_size = 20
194 200
195 201 c.branch_name = branch_name = self.request.GET.get('branch') or ''
196 202 c.book_name = book_name = self.request.GET.get('bookmark') or ''
197 203 c.f_path = f_path
198 204 c.commit_id = commit_id
199 205 c.show_hidden = show_hidden
200 206
201 207 hist_limit = safe_int(self.request.GET.get('limit')) or None
202 208
203 209 p = safe_int(self.request.GET.get('page', 1), 1)
204 210
205 211 c.selected_name = branch_name or book_name
206 212 if not commit_id and branch_name:
207 213 self._check_if_valid_branch(branch_name, self.db_repo_name, f_path)
208 214
209 215 c.changelog_for_path = f_path
210 216 pre_load = self._get_preload_attrs()
211 217
212 218 partial_xhr = self.request.environ.get('HTTP_X_PARTIAL_XHR')
213 219 try:
214 220 if f_path:
215 221 log.debug('generating changelog for path %s', f_path)
216 222 # get the history for the file !
217 223 base_commit = self.rhodecode_vcs_repo.get_commit(commit_id)
218 224
219 225 try:
220 226 collection = base_commit.get_path_history(
221 227 f_path, limit=hist_limit, pre_load=pre_load)
222 228 if collection and partial_xhr:
223 229 # for ajax call we remove first one since we're looking
224 230 # at it right now in the context of a file commit
225 231 collection.pop(0)
226 232 except (NodeDoesNotExistError, CommitError):
227 233 # this node is not present at tip!
228 234 try:
229 235 commit = self._get_commit_or_redirect(commit_id)
230 236 collection = commit.get_path_history(f_path)
231 237 except RepositoryError as e:
232 238 h.flash(safe_str(e), category='warning')
233 239 redirect_url = h.route_path(
234 240 'repo_commits', repo_name=self.db_repo_name)
235 241 raise HTTPFound(redirect_url)
236 242 collection = list(reversed(collection))
237 243 else:
238 244 collection = self.rhodecode_vcs_repo.get_commits(
239 245 branch_name=branch_name, show_hidden=show_hidden,
240 246 pre_load=pre_load, translate_tags=False)
241 247
242 248 self._load_changelog_data(
243 249 c, collection, p, chunk_size, c.branch_name,
244 250 f_path=f_path, commit_id=commit_id)
245 251
246 252 except EmptyRepositoryError as e:
247 253 h.flash(safe_str(h.escape(e)), category='warning')
248 254 raise HTTPFound(
249 255 h.route_path('repo_summary', repo_name=self.db_repo_name))
250 256 except HTTPFound:
251 257 raise
252 258 except (RepositoryError, CommitDoesNotExistError, Exception) as e:
253 259 log.exception(safe_str(e))
254 260 h.flash(safe_str(h.escape(e)), category='error')
255 261 raise HTTPFound(
256 262 h.route_path('repo_commits', repo_name=self.db_repo_name))
257 263
258 264 if partial_xhr or self.request.environ.get('HTTP_X_PJAX'):
259 265 # case when loading dynamic file history in file view
260 266 # loading from ajax, we don't want the first result, it's popped
261 267 # in the code above
262 268 html = render(
263 269 'rhodecode:templates/commits/changelog_file_history.mako',
264 270 self._get_template_context(c), self.request)
265 271 return Response(html)
266 272
267 273 commit_ids = []
268 274 if not f_path:
269 275 # only load graph data when not in file history mode
270 276 commit_ids = c.pagination
271 277
272 278 c.graph_data, c.graph_commits = self._graph(
273 279 self.rhodecode_vcs_repo, commit_ids)
274 280
275 281 return self._get_template_context(c)
276 282
277 283 @LoginRequired()
278 284 @HasRepoPermissionAnyDecorator(
279 285 'repository.read', 'repository.write', 'repository.admin')
280 286 @view_config(
281 287 route_name='repo_commits_elements', request_method=('GET', 'POST'),
282 288 renderer='rhodecode:templates/commits/changelog_elements.mako',
283 289 xhr=True)
284 290 @view_config(
285 291 route_name='repo_commits_elements_file', request_method=('GET', 'POST'),
286 292 renderer='rhodecode:templates/commits/changelog_elements.mako',
287 293 xhr=True)
288 294 def repo_commits_elements(self):
289 295 c = self.load_default_context()
290 296 commit_id = self.request.matchdict.get('commit_id')
291 297 f_path = self._get_f_path(self.request.matchdict)
292 298 show_hidden = str2bool(self.request.GET.get('evolve'))
293 299
294 300 chunk_size = 20
295 301 hist_limit = safe_int(self.request.GET.get('limit')) or None
296 302
297 303 def wrap_for_error(err):
298 304 html = '<tr>' \
299 305 '<td colspan="9" class="alert alert-error">ERROR: {}</td>' \
300 306 '</tr>'.format(err)
301 307 return Response(html)
302 308
303 309 c.branch_name = branch_name = self.request.GET.get('branch') or ''
304 310 c.book_name = book_name = self.request.GET.get('bookmark') or ''
305 311 c.f_path = f_path
306 312 c.commit_id = commit_id
307 313 c.show_hidden = show_hidden
308 314
309 315 c.selected_name = branch_name or book_name
310 316 if branch_name and branch_name not in self.rhodecode_vcs_repo.branches_all:
311 317 return wrap_for_error(
312 318 safe_str('Branch: {} is not valid'.format(branch_name)))
313 319
314 320 pre_load = self._get_preload_attrs()
315 321
316 322 if f_path:
317 323 try:
318 324 base_commit = self.rhodecode_vcs_repo.get_commit(commit_id)
319 325 except (RepositoryError, CommitDoesNotExistError, Exception) as e:
320 326 log.exception(safe_str(e))
321 327 raise HTTPFound(
322 328 h.route_path('repo_commits', repo_name=self.db_repo_name))
323 329
324 330 collection = base_commit.get_path_history(
325 331 f_path, limit=hist_limit, pre_load=pre_load)
326 332 collection = list(reversed(collection))
327 333 else:
328 334 collection = self.rhodecode_vcs_repo.get_commits(
329 335 branch_name=branch_name, show_hidden=show_hidden, pre_load=pre_load,
330 336 translate_tags=False)
331 337
332 338 p = safe_int(self.request.GET.get('page', 1), 1)
333 339 try:
334 340 self._load_changelog_data(
335 341 c, collection, p, chunk_size, dynamic=True,
336 342 f_path=f_path, commit_id=commit_id)
337 343 except EmptyRepositoryError as e:
338 344 return wrap_for_error(safe_str(e))
339 345 except (RepositoryError, CommitDoesNotExistError, Exception) as e:
340 346 log.exception('Failed to fetch commits')
341 347 return wrap_for_error(safe_str(e))
342 348
343 349 prev_data = None
344 350 next_data = None
345 351
346 352 try:
347 353 prev_graph = json.loads(self.request.POST.get('graph') or '{}')
348 354 except json.JSONDecodeError:
349 355 prev_graph = {}
350 356
351 357 if self.request.GET.get('chunk') == 'prev':
352 358 next_data = prev_graph
353 359 elif self.request.GET.get('chunk') == 'next':
354 360 prev_data = prev_graph
355 361
356 362 commit_ids = []
357 363 if not f_path:
358 364 # only load graph data when not in file history mode
359 365 commit_ids = c.pagination
360 366
361 367 c.graph_data, c.graph_commits = self._graph(
362 368 self.rhodecode_vcs_repo, commit_ids,
363 369 prev_data=prev_data, next_data=next_data)
364 370
365 371 return self._get_template_context(c)
@@ -1,319 +1,319 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2011-2019 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import logging
22 22 import string
23 23 import time
24 24
25 25 import rhodecode
26 26
27 27 from pyramid.view import view_config
28 28
29 29 from rhodecode.lib.view_utils import get_format_ref_id
30 30 from rhodecode.apps._base import RepoAppView
31 31 from rhodecode.config.conf import (LANGUAGES_EXTENSIONS_MAP)
32 32 from rhodecode.lib import helpers as h, rc_cache
33 33 from rhodecode.lib.utils2 import safe_str, safe_int
34 34 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
35 35 from rhodecode.lib.ext_json import json
36 36 from rhodecode.lib.vcs.backends.base import EmptyCommit
37 37 from rhodecode.lib.vcs.exceptions import (
38 38 CommitError, EmptyRepositoryError, CommitDoesNotExistError)
39 39 from rhodecode.model.db import Statistics, CacheKey, User
40 40 from rhodecode.model.meta import Session
41 41 from rhodecode.model.scm import ScmModel
42 42
43 43 log = logging.getLogger(__name__)
44 44
45 45
46 46 class RepoSummaryView(RepoAppView):
47 47
48 48 def load_default_context(self):
49 49 c = self._get_local_tmpl_context(include_app_defaults=True)
50 50 c.rhodecode_repo = None
51 51 if not c.repository_requirements_missing:
52 52 c.rhodecode_repo = self.rhodecode_vcs_repo
53 53 return c
54 54
55 55 def _load_commits_context(self, c):
56 56 p = safe_int(self.request.GET.get('page'), 1)
57 57 size = safe_int(self.request.GET.get('size'), 10)
58 58
59 def url_generator(**kw):
59 def url_generator(page_num):
60 60 query_params = {
61 'page': page_num,
61 62 'size': size
62 63 }
63 query_params.update(kw)
64 64 return h.route_path(
65 65 'repo_summary_commits',
66 66 repo_name=c.rhodecode_db_repo.repo_name, _query=query_params)
67 67
68 68 pre_load = ['author', 'branch', 'date', 'message']
69 69 try:
70 70 collection = self.rhodecode_vcs_repo.get_commits(
71 71 pre_load=pre_load, translate_tags=False)
72 72 except EmptyRepositoryError:
73 73 collection = self.rhodecode_vcs_repo
74 74
75 75 c.repo_commits = h.RepoPage(
76 collection, page=p, items_per_page=size, url=url_generator)
76 collection, page=p, items_per_page=size, url_maker=url_generator)
77 77 page_ids = [x.raw_id for x in c.repo_commits]
78 78 c.comments = self.db_repo.get_comments(page_ids)
79 79 c.statuses = self.db_repo.statuses(page_ids)
80 80
81 81 def _prepare_and_set_clone_url(self, c):
82 82 username = ''
83 83 if self._rhodecode_user.username != User.DEFAULT_USER:
84 84 username = safe_str(self._rhodecode_user.username)
85 85
86 86 _def_clone_uri = _def_clone_uri_id = c.clone_uri_tmpl
87 87 _def_clone_uri_ssh = c.clone_uri_ssh_tmpl
88 88
89 89 if '{repo}' in _def_clone_uri:
90 90 _def_clone_uri_id = _def_clone_uri.replace('{repo}', '_{repoid}')
91 91 elif '{repoid}' in _def_clone_uri:
92 92 _def_clone_uri_id = _def_clone_uri.replace('_{repoid}', '{repo}')
93 93
94 94 c.clone_repo_url = self.db_repo.clone_url(
95 95 user=username, uri_tmpl=_def_clone_uri)
96 96 c.clone_repo_url_id = self.db_repo.clone_url(
97 97 user=username, uri_tmpl=_def_clone_uri_id)
98 98 c.clone_repo_url_ssh = self.db_repo.clone_url(
99 99 uri_tmpl=_def_clone_uri_ssh, ssh=True)
100 100
101 101 @LoginRequired()
102 102 @HasRepoPermissionAnyDecorator(
103 103 'repository.read', 'repository.write', 'repository.admin')
104 104 @view_config(
105 105 route_name='repo_summary_commits', request_method='GET',
106 106 renderer='rhodecode:templates/summary/summary_commits.mako')
107 107 def summary_commits(self):
108 108 c = self.load_default_context()
109 109 self._prepare_and_set_clone_url(c)
110 110 self._load_commits_context(c)
111 111 return self._get_template_context(c)
112 112
113 113 @LoginRequired()
114 114 @HasRepoPermissionAnyDecorator(
115 115 'repository.read', 'repository.write', 'repository.admin')
116 116 @view_config(
117 117 route_name='repo_summary', request_method='GET',
118 118 renderer='rhodecode:templates/summary/summary.mako')
119 119 @view_config(
120 120 route_name='repo_summary_slash', request_method='GET',
121 121 renderer='rhodecode:templates/summary/summary.mako')
122 122 @view_config(
123 123 route_name='repo_summary_explicit', request_method='GET',
124 124 renderer='rhodecode:templates/summary/summary.mako')
125 125 def summary(self):
126 126 c = self.load_default_context()
127 127
128 128 # Prepare the clone URL
129 129 self._prepare_and_set_clone_url(c)
130 130
131 131 # update every 5 min
132 132 if self.db_repo.last_commit_cache_update_diff > 60 * 5:
133 133 self.db_repo.update_commit_cache()
134 134
135 135 # If enabled, get statistics data
136 136
137 137 c.show_stats = bool(self.db_repo.enable_statistics)
138 138
139 139 stats = Session().query(Statistics) \
140 140 .filter(Statistics.repository == self.db_repo) \
141 141 .scalar()
142 142
143 143 c.stats_percentage = 0
144 144
145 145 if stats and stats.languages:
146 146 c.no_data = False is self.db_repo.enable_statistics
147 147 lang_stats_d = json.loads(stats.languages)
148 148
149 149 # Sort first by decreasing count and second by the file extension,
150 150 # so we have a consistent output.
151 151 lang_stats_items = sorted(lang_stats_d.iteritems(),
152 152 key=lambda k: (-k[1], k[0]))[:10]
153 153 lang_stats = [(x, {"count": y,
154 154 "desc": LANGUAGES_EXTENSIONS_MAP.get(x)})
155 155 for x, y in lang_stats_items]
156 156
157 157 c.trending_languages = json.dumps(lang_stats)
158 158 else:
159 159 c.no_data = True
160 160 c.trending_languages = json.dumps({})
161 161
162 162 scm_model = ScmModel()
163 163 c.enable_downloads = self.db_repo.enable_downloads
164 164 c.repository_followers = scm_model.get_followers(self.db_repo)
165 165 c.repository_forks = scm_model.get_forks(self.db_repo)
166 166
167 167 # first interaction with the VCS instance after here...
168 168 if c.repository_requirements_missing:
169 169 self.request.override_renderer = \
170 170 'rhodecode:templates/summary/missing_requirements.mako'
171 171 return self._get_template_context(c)
172 172
173 173 c.readme_data, c.readme_file = \
174 174 self._get_readme_data(self.db_repo, c.visual.default_renderer)
175 175
176 176 # loads the summary commits template context
177 177 self._load_commits_context(c)
178 178
179 179 return self._get_template_context(c)
180 180
181 181 @LoginRequired()
182 182 @HasRepoPermissionAnyDecorator(
183 183 'repository.read', 'repository.write', 'repository.admin')
184 184 @view_config(
185 185 route_name='repo_stats', request_method='GET',
186 186 renderer='json_ext')
187 187 def repo_stats(self):
188 188 show_stats = bool(self.db_repo.enable_statistics)
189 189 repo_id = self.db_repo.repo_id
190 190
191 191 landing_commit = self.db_repo.get_landing_commit()
192 192 if isinstance(landing_commit, EmptyCommit):
193 193 return {'size': 0, 'code_stats': {}}
194 194
195 195 cache_seconds = safe_int(rhodecode.CONFIG.get('rc_cache.cache_repo.expiration_time'))
196 196 cache_on = cache_seconds > 0
197 197
198 198 log.debug(
199 199 'Computing REPO STATS for repo_id %s commit_id `%s` '
200 200 'with caching: %s[TTL: %ss]' % (
201 201 repo_id, landing_commit, cache_on, cache_seconds or 0))
202 202
203 203 cache_namespace_uid = 'cache_repo.{}'.format(repo_id)
204 204 region = rc_cache.get_or_create_region('cache_repo', cache_namespace_uid)
205 205
206 206 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid,
207 207 condition=cache_on)
208 208 def compute_stats(repo_id, commit_id, _show_stats):
209 209 code_stats = {}
210 210 size = 0
211 211 try:
212 212 commit = self.db_repo.get_commit(commit_id)
213 213
214 214 for node in commit.get_filenodes_generator():
215 215 size += node.size
216 216 if not _show_stats:
217 217 continue
218 218 ext = string.lower(node.extension)
219 219 ext_info = LANGUAGES_EXTENSIONS_MAP.get(ext)
220 220 if ext_info:
221 221 if ext in code_stats:
222 222 code_stats[ext]['count'] += 1
223 223 else:
224 224 code_stats[ext] = {"count": 1, "desc": ext_info}
225 225 except (EmptyRepositoryError, CommitDoesNotExistError):
226 226 pass
227 227 return {'size': h.format_byte_size_binary(size),
228 228 'code_stats': code_stats}
229 229
230 230 stats = compute_stats(self.db_repo.repo_id, landing_commit.raw_id, show_stats)
231 231 return stats
232 232
233 233 @LoginRequired()
234 234 @HasRepoPermissionAnyDecorator(
235 235 'repository.read', 'repository.write', 'repository.admin')
236 236 @view_config(
237 237 route_name='repo_refs_data', request_method='GET',
238 238 renderer='json_ext')
239 239 def repo_refs_data(self):
240 240 _ = self.request.translate
241 241 self.load_default_context()
242 242
243 243 repo = self.rhodecode_vcs_repo
244 244 refs_to_create = [
245 245 (_("Branch"), repo.branches, 'branch'),
246 246 (_("Tag"), repo.tags, 'tag'),
247 247 (_("Bookmark"), repo.bookmarks, 'book'),
248 248 ]
249 249 res = self._create_reference_data(repo, self.db_repo_name, refs_to_create)
250 250 data = {
251 251 'more': False,
252 252 'results': res
253 253 }
254 254 return data
255 255
256 256 @LoginRequired()
257 257 @HasRepoPermissionAnyDecorator(
258 258 'repository.read', 'repository.write', 'repository.admin')
259 259 @view_config(
260 260 route_name='repo_refs_changelog_data', request_method='GET',
261 261 renderer='json_ext')
262 262 def repo_refs_changelog_data(self):
263 263 _ = self.request.translate
264 264 self.load_default_context()
265 265
266 266 repo = self.rhodecode_vcs_repo
267 267
268 268 refs_to_create = [
269 269 (_("Branches"), repo.branches, 'branch'),
270 270 (_("Closed branches"), repo.branches_closed, 'branch_closed'),
271 271 # TODO: enable when vcs can handle bookmarks filters
272 272 # (_("Bookmarks"), repo.bookmarks, "book"),
273 273 ]
274 274 res = self._create_reference_data(
275 275 repo, self.db_repo_name, refs_to_create)
276 276 data = {
277 277 'more': False,
278 278 'results': res
279 279 }
280 280 return data
281 281
282 282 def _create_reference_data(self, repo, full_repo_name, refs_to_create):
283 283 format_ref_id = get_format_ref_id(repo)
284 284
285 285 result = []
286 286 for title, refs, ref_type in refs_to_create:
287 287 if refs:
288 288 result.append({
289 289 'text': title,
290 290 'children': self._create_reference_items(
291 291 repo, full_repo_name, refs, ref_type,
292 292 format_ref_id),
293 293 })
294 294 return result
295 295
296 296 def _create_reference_items(self, repo, full_repo_name, refs, ref_type, format_ref_id):
297 297 result = []
298 298 is_svn = h.is_svn(repo)
299 299 for ref_name, raw_id in refs.iteritems():
300 300 files_url = self._create_files_url(
301 301 repo, full_repo_name, ref_name, raw_id, is_svn)
302 302 result.append({
303 303 'text': ref_name,
304 304 'id': format_ref_id(ref_name, raw_id),
305 305 'raw_id': raw_id,
306 306 'type': ref_type,
307 307 'files_url': files_url,
308 308 'idx': 0,
309 309 })
310 310 return result
311 311
312 312 def _create_files_url(self, repo, full_repo_name, ref_name, raw_id, is_svn):
313 313 use_commit_id = '/' in ref_name or is_svn
314 314 return h.route_path(
315 315 'repo_files',
316 316 repo_name=full_repo_name,
317 317 f_path=ref_name if is_svn else '',
318 318 commit_id=raw_id if use_commit_id else ref_name,
319 319 _query=dict(at=ref_name))
@@ -1,173 +1,180 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2011-2019 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import logging
22 22 import urllib
23 23 from pyramid.view import view_config
24 24 from webhelpers2.html.tools import update_params
25 25
26 26 from rhodecode.apps._base import BaseAppView, RepoAppView, RepoGroupAppView
27 27 from rhodecode.lib.auth import (
28 28 LoginRequired, HasRepoPermissionAnyDecorator, HasRepoGroupPermissionAnyDecorator)
29 29 from rhodecode.lib.helpers import Page
30 30 from rhodecode.lib.utils2 import safe_str
31 31 from rhodecode.lib.index import searcher_from_config
32 32 from rhodecode.model import validation_schema
33 33 from rhodecode.model.validation_schema.schemas import search_schema
34 34
35 35 log = logging.getLogger(__name__)
36 36
37 37
38 38 def perform_search(request, tmpl_context, repo_name=None, repo_group_name=None):
39 39 searcher = searcher_from_config(request.registry.settings)
40 40 formatted_results = []
41 41 execution_time = ''
42 42
43 43 schema = search_schema.SearchParamsSchema()
44 44 search_tags = []
45 45 search_params = {}
46 46 errors = []
47 47
48 48 try:
49 49 search_params = schema.deserialize(
50 50 dict(
51 51 search_query=request.GET.get('q'),
52 52 search_type=request.GET.get('type'),
53 53 search_sort=request.GET.get('sort'),
54 54 search_max_lines=request.GET.get('max_lines'),
55 55 page_limit=request.GET.get('page_limit'),
56 56 requested_page=request.GET.get('page'),
57 57 )
58 58 )
59 59 except validation_schema.Invalid as e:
60 60 errors = e.children
61 61
62 def url_generator(**kw):
62 def url_generator(page_num):
63 63 q = urllib.quote(safe_str(search_query))
64 return update_params(
65 "?q=%s&type=%s&max_lines=%s&sort=%s" % (
66 q, safe_str(search_type), search_max_lines, search_sort), **kw)
64
65 query_params = {
66 'page': page_num,
67 'q': q,
68 'type': safe_str(search_type),
69 'max_lines': search_max_lines,
70 'sort': search_sort
71 }
72
73 return '?' + urllib.urlencode(query_params)
74
67 75
68 76 c = tmpl_context
69 77 search_query = search_params.get('search_query')
70 78 search_type = search_params.get('search_type')
71 79 search_sort = search_params.get('search_sort')
72 80 search_max_lines = search_params.get('search_max_lines')
73 81 if search_params.get('search_query'):
74 82 page_limit = search_params['page_limit']
75 83 requested_page = search_params['requested_page']
76 84
77 85 try:
78 86 search_result = searcher.search(
79 87 search_query, search_type, c.auth_user, repo_name, repo_group_name,
80 88 requested_page=requested_page, page_limit=page_limit, sort=search_sort)
81 89
82 90 formatted_results = Page(
83 91 search_result['results'], page=requested_page,
84 92 item_count=search_result['count'],
85 items_per_page=page_limit, url=url_generator)
93 items_per_page=page_limit, url_maker=url_generator)
86 94 finally:
87 95 searcher.cleanup()
88 96
89 97 search_tags = searcher.extract_search_tags(search_query)
90 98
91 99 if not search_result['error']:
92 100 execution_time = '%s results (%.4f seconds)' % (
93 101 search_result['count'],
94 102 search_result['runtime'])
95 103 elif not errors:
96 104 node = schema['search_query']
97 105 errors = [
98 106 validation_schema.Invalid(node, search_result['error'])]
99 107
100 108 c.perm_user = c.auth_user
101 109 c.repo_name = repo_name
102 110 c.repo_group_name = repo_group_name
103 c.url_generator = url_generator
104 111 c.errors = errors
105 112 c.formatted_results = formatted_results
106 113 c.runtime = execution_time
107 114 c.cur_query = search_query
108 115 c.search_type = search_type
109 116 c.searcher = searcher
110 117 c.search_tags = search_tags
111 118
112 119 direction, sort_field = searcher.get_sort(search_type, search_sort)
113 120 sort_definition = searcher.sort_def(search_type, direction, sort_field)
114 121 c.sort = ''
115 122 c.sort_tag = None
116 123 c.sort_tag_dir = direction
117 124 if sort_definition:
118 125 c.sort = '{}:{}'.format(direction, sort_field)
119 126 c.sort_tag = sort_field
120 127
121 128
122 129 class SearchView(BaseAppView):
123 130 def load_default_context(self):
124 131 c = self._get_local_tmpl_context()
125 132 return c
126 133
127 134 @LoginRequired()
128 135 @view_config(
129 136 route_name='search', request_method='GET',
130 137 renderer='rhodecode:templates/search/search.mako')
131 138 def search(self):
132 139 c = self.load_default_context()
133 140 perform_search(self.request, c)
134 141 return self._get_template_context(c)
135 142
136 143
137 144 class SearchRepoView(RepoAppView):
138 145 def load_default_context(self):
139 146 c = self._get_local_tmpl_context()
140 147 c.active = 'search'
141 148 return c
142 149
143 150 @LoginRequired()
144 151 @HasRepoPermissionAnyDecorator(
145 152 'repository.read', 'repository.write', 'repository.admin')
146 153 @view_config(
147 154 route_name='search_repo', request_method='GET',
148 155 renderer='rhodecode:templates/search/search.mako')
149 156 @view_config(
150 157 route_name='search_repo_alt', request_method='GET',
151 158 renderer='rhodecode:templates/search/search.mako')
152 159 def search_repo(self):
153 160 c = self.load_default_context()
154 161 perform_search(self.request, c, repo_name=self.db_repo_name)
155 162 return self._get_template_context(c)
156 163
157 164
158 165 class SearchRepoGroupView(RepoGroupAppView):
159 166 def load_default_context(self):
160 167 c = self._get_local_tmpl_context()
161 168 c.active = 'search'
162 169 return c
163 170
164 171 @LoginRequired()
165 172 @HasRepoGroupPermissionAnyDecorator(
166 173 'group.read', 'group.write', 'group.admin')
167 174 @view_config(
168 175 route_name='search_repo_group', request_method='GET',
169 176 renderer='rhodecode:templates/search/search.mako')
170 177 def search_repo_group(self):
171 178 c = self.load_default_context()
172 179 perform_search(self.request, c, repo_group_name=self.db_repo_group_name)
173 180 return self._get_template_context(c)
@@ -1,464 +1,469 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2012-2019 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import deform
22 22 import logging
23 23 import peppercorn
24 24
25 25 from pyramid.httpexceptions import HTTPFound, HTTPForbidden, HTTPNotFound
26 26
27 27 from rhodecode.integrations import integration_type_registry
28 28 from rhodecode.apps._base import BaseAppView
29 29 from rhodecode.apps._base.navigation import navigation_list
30 from rhodecode.lib.paginate import PageURL
31 30 from rhodecode.lib.auth import (
32 31 LoginRequired, CSRFRequired, HasPermissionAnyDecorator,
33 32 HasRepoPermissionAnyDecorator, HasRepoGroupPermissionAnyDecorator)
34 33 from rhodecode.lib.utils2 import safe_int
34 from rhodecode.lib.helpers import Page
35 35 from rhodecode.lib import helpers as h
36 36 from rhodecode.model.db import Repository, RepoGroup, Session, Integration
37 37 from rhodecode.model.scm import ScmModel
38 38 from rhodecode.model.integration import IntegrationModel
39 39 from rhodecode.model.validation_schema.schemas.integration_schema import (
40 40 make_integration_schema, IntegrationScopeType)
41 41
42 42 log = logging.getLogger(__name__)
43 43
44 44
45 45 class IntegrationSettingsViewBase(BaseAppView):
46 46 """
47 47 Base Integration settings view used by both repo / global settings
48 48 """
49 49
50 50 def __init__(self, context, request):
51 51 super(IntegrationSettingsViewBase, self).__init__(context, request)
52 52 self._load_view_context()
53 53
54 54 def _load_view_context(self):
55 55 """
56 56 This avoids boilerplate for repo/global+list/edit+views/templates
57 57 by doing all possible contexts at the same time however it should
58 58 be split up into separate functions once more "contexts" exist
59 59 """
60 60
61 61 self.IntegrationType = None
62 62 self.repo = None
63 63 self.repo_group = None
64 64 self.integration = None
65 65 self.integrations = {}
66 66
67 67 request = self.request
68 68
69 69 if 'repo_name' in request.matchdict: # in repo settings context
70 70 repo_name = request.matchdict['repo_name']
71 71 self.repo = Repository.get_by_repo_name(repo_name)
72 72
73 73 if 'repo_group_name' in request.matchdict: # in group settings context
74 74 repo_group_name = request.matchdict['repo_group_name']
75 75 self.repo_group = RepoGroup.get_by_group_name(repo_group_name)
76 76
77 77 if 'integration' in request.matchdict: # integration type context
78 78 integration_type = request.matchdict['integration']
79 79 if integration_type not in integration_type_registry:
80 80 raise HTTPNotFound()
81 81
82 82 self.IntegrationType = integration_type_registry[integration_type]
83 83 if self.IntegrationType.is_dummy:
84 84 raise HTTPNotFound()
85 85
86 86 if 'integration_id' in request.matchdict: # single integration context
87 87 integration_id = request.matchdict['integration_id']
88 88 self.integration = Integration.get(integration_id)
89 89
90 90 # extra perms check just in case
91 91 if not self._has_perms_for_integration(self.integration):
92 92 raise HTTPForbidden()
93 93
94 94 self.settings = self.integration and self.integration.settings or {}
95 95 self.admin_view = not (self.repo or self.repo_group)
96 96
97 97 def _has_perms_for_integration(self, integration):
98 98 perms = self.request.user.permissions
99 99
100 100 if 'hg.admin' in perms['global']:
101 101 return True
102 102
103 103 if integration.repo:
104 104 return perms['repositories'].get(
105 105 integration.repo.repo_name) == 'repository.admin'
106 106
107 107 if integration.repo_group:
108 108 return perms['repositories_groups'].get(
109 109 integration.repo_group.group_name) == 'group.admin'
110 110
111 111 return False
112 112
113 113 def _get_local_tmpl_context(self, include_app_defaults=True):
114 114 _ = self.request.translate
115 115 c = super(IntegrationSettingsViewBase, self)._get_local_tmpl_context(
116 116 include_app_defaults=include_app_defaults)
117 117 c.active = 'integrations'
118 118
119 119 return c
120 120
121 121 def _form_schema(self):
122 122 schema = make_integration_schema(IntegrationType=self.IntegrationType,
123 123 settings=self.settings)
124 124
125 125 # returns a clone, important if mutating the schema later
126 126 return schema.bind(
127 127 permissions=self.request.user.permissions,
128 128 no_scope=not self.admin_view)
129 129
130 130 def _form_defaults(self):
131 131 _ = self.request.translate
132 132 defaults = {}
133 133
134 134 if self.integration:
135 135 defaults['settings'] = self.integration.settings or {}
136 136 defaults['options'] = {
137 137 'name': self.integration.name,
138 138 'enabled': self.integration.enabled,
139 139 'scope': {
140 140 'repo': self.integration.repo,
141 141 'repo_group': self.integration.repo_group,
142 142 'child_repos_only': self.integration.child_repos_only,
143 143 },
144 144 }
145 145 else:
146 146 if self.repo:
147 147 scope = _('{repo_name} repository').format(
148 148 repo_name=self.repo.repo_name)
149 149 elif self.repo_group:
150 150 scope = _('{repo_group_name} repo group').format(
151 151 repo_group_name=self.repo_group.group_name)
152 152 else:
153 153 scope = _('Global')
154 154
155 155 defaults['options'] = {
156 156 'enabled': True,
157 157 'name': _('{name} integration').format(
158 158 name=self.IntegrationType.display_name),
159 159 }
160 160 defaults['options']['scope'] = {
161 161 'repo': self.repo,
162 162 'repo_group': self.repo_group,
163 163 }
164 164
165 165 return defaults
166 166
167 167 def _delete_integration(self, integration):
168 168 _ = self.request.translate
169 169 Session().delete(integration)
170 170 Session().commit()
171 171 h.flash(
172 172 _('Integration {integration_name} deleted successfully.').format(
173 173 integration_name=integration.name),
174 174 category='success')
175 175
176 176 if self.repo:
177 177 redirect_to = self.request.route_path(
178 178 'repo_integrations_home', repo_name=self.repo.repo_name)
179 179 elif self.repo_group:
180 180 redirect_to = self.request.route_path(
181 181 'repo_group_integrations_home',
182 182 repo_group_name=self.repo_group.group_name)
183 183 else:
184 184 redirect_to = self.request.route_path('global_integrations_home')
185 185 raise HTTPFound(redirect_to)
186 186
187 187 def _integration_list(self):
188 188 """ List integrations """
189 189
190 190 c = self.load_default_context()
191 191 if self.repo:
192 192 scope = self.repo
193 193 elif self.repo_group:
194 194 scope = self.repo_group
195 195 else:
196 196 scope = 'all'
197 197
198 198 integrations = []
199 199
200 200 for IntType, integration in IntegrationModel().get_integrations(
201 201 scope=scope, IntegrationType=self.IntegrationType):
202 202
203 203 # extra permissions check *just in case*
204 204 if not self._has_perms_for_integration(integration):
205 205 continue
206 206
207 207 integrations.append((IntType, integration))
208 208
209 209 sort_arg = self.request.GET.get('sort', 'name:asc')
210 210 sort_dir = 'asc'
211 211 if ':' in sort_arg:
212 212 sort_field, sort_dir = sort_arg.split(':')
213 213 else:
214 214 sort_field = sort_arg, 'asc'
215 215
216 216 assert sort_field in ('name', 'integration_type', 'enabled', 'scope')
217 217
218 218 integrations.sort(
219 219 key=lambda x: getattr(x[1], sort_field),
220 220 reverse=(sort_dir == 'desc'))
221 221
222 page_url = PageURL(self.request.path, self.request.GET)
222 def url_generator(page_num):
223 query_params = {
224 'page': page_num
225 }
226 return self.request.current_route_path(_query=query_params)
227
223 228 page = safe_int(self.request.GET.get('page', 1), 1)
224 229
225 integrations = h.Page(
226 integrations, page=page, items_per_page=10, url=page_url)
230 integrations = Page(
231 integrations, page=page, items_per_page=10, url_maker=url_generator)
227 232
228 233 c.rev_sort_dir = sort_dir != 'desc' and 'desc' or 'asc'
229 234
230 235 c.current_IntegrationType = self.IntegrationType
231 236 c.integrations_list = integrations
232 237 c.available_integrations = integration_type_registry
233 238
234 239 return self._get_template_context(c)
235 240
236 241 def _settings_get(self, defaults=None, form=None):
237 242 """
238 243 View that displays the integration settings as a form.
239 244 """
240 245 c = self.load_default_context()
241 246
242 247 defaults = defaults or self._form_defaults()
243 248 schema = self._form_schema()
244 249
245 250 if self.integration:
246 251 buttons = ('submit', 'delete')
247 252 else:
248 253 buttons = ('submit',)
249 254
250 255 form = form or deform.Form(schema, appstruct=defaults, buttons=buttons)
251 256
252 257 c.form = form
253 258 c.current_IntegrationType = self.IntegrationType
254 259 c.integration = self.integration
255 260
256 261 return self._get_template_context(c)
257 262
258 263 def _settings_post(self):
259 264 """
260 265 View that validates and stores the integration settings.
261 266 """
262 267 _ = self.request.translate
263 268
264 269 controls = self.request.POST.items()
265 270 pstruct = peppercorn.parse(controls)
266 271
267 272 if self.integration and pstruct.get('delete'):
268 273 return self._delete_integration(self.integration)
269 274
270 275 schema = self._form_schema()
271 276
272 277 skip_settings_validation = False
273 278 if self.integration and 'enabled' not in pstruct.get('options', {}):
274 279 skip_settings_validation = True
275 280 schema['settings'].validator = None
276 281 for field in schema['settings'].children:
277 282 field.validator = None
278 283 field.missing = ''
279 284
280 285 if self.integration:
281 286 buttons = ('submit', 'delete')
282 287 else:
283 288 buttons = ('submit',)
284 289
285 290 form = deform.Form(schema, buttons=buttons)
286 291
287 292 if not self.admin_view:
288 293 # scope is read only field in these cases, and has to be added
289 294 options = pstruct.setdefault('options', {})
290 295 if 'scope' not in options:
291 296 options['scope'] = IntegrationScopeType().serialize(None, {
292 297 'repo': self.repo,
293 298 'repo_group': self.repo_group,
294 299 })
295 300
296 301 try:
297 302 valid_data = form.validate_pstruct(pstruct)
298 303 except deform.ValidationFailure as e:
299 304 h.flash(
300 305 _('Errors exist when saving integration settings. '
301 306 'Please check the form inputs.'),
302 307 category='error')
303 308 return self._settings_get(form=e)
304 309
305 310 if not self.integration:
306 311 self.integration = Integration()
307 312 self.integration.integration_type = self.IntegrationType.key
308 313 Session().add(self.integration)
309 314
310 315 scope = valid_data['options']['scope']
311 316
312 317 IntegrationModel().update_integration(self.integration,
313 318 name=valid_data['options']['name'],
314 319 enabled=valid_data['options']['enabled'],
315 320 settings=valid_data['settings'],
316 321 repo=scope['repo'],
317 322 repo_group=scope['repo_group'],
318 323 child_repos_only=scope['child_repos_only'],
319 324 )
320 325
321 326 self.integration.settings = valid_data['settings']
322 327 Session().commit()
323 328 # Display success message and redirect.
324 329 h.flash(
325 330 _('Integration {integration_name} updated successfully.').format(
326 331 integration_name=self.IntegrationType.display_name),
327 332 category='success')
328 333
329 334 # if integration scope changes, we must redirect to the right place
330 335 # keeping in mind if the original view was for /repo/ or /_admin/
331 336 admin_view = not (self.repo or self.repo_group)
332 337
333 338 if self.integration.repo and not admin_view:
334 339 redirect_to = self.request.route_path(
335 340 'repo_integrations_edit',
336 341 repo_name=self.integration.repo.repo_name,
337 342 integration=self.integration.integration_type,
338 343 integration_id=self.integration.integration_id)
339 344 elif self.integration.repo_group and not admin_view:
340 345 redirect_to = self.request.route_path(
341 346 'repo_group_integrations_edit',
342 347 repo_group_name=self.integration.repo_group.group_name,
343 348 integration=self.integration.integration_type,
344 349 integration_id=self.integration.integration_id)
345 350 else:
346 351 redirect_to = self.request.route_path(
347 352 'global_integrations_edit',
348 353 integration=self.integration.integration_type,
349 354 integration_id=self.integration.integration_id)
350 355
351 356 return HTTPFound(redirect_to)
352 357
353 358 def _new_integration(self):
354 359 c = self.load_default_context()
355 360 c.available_integrations = integration_type_registry
356 361 return self._get_template_context(c)
357 362
358 363 def load_default_context(self):
359 364 raise NotImplementedError()
360 365
361 366
362 367 class GlobalIntegrationsView(IntegrationSettingsViewBase):
363 368 def load_default_context(self):
364 369 c = self._get_local_tmpl_context()
365 370 c.repo = self.repo
366 371 c.repo_group = self.repo_group
367 372 c.navlist = navigation_list(self.request)
368 373
369 374 return c
370 375
371 376 @LoginRequired()
372 377 @HasPermissionAnyDecorator('hg.admin')
373 378 def integration_list(self):
374 379 return self._integration_list()
375 380
376 381 @LoginRequired()
377 382 @HasPermissionAnyDecorator('hg.admin')
378 383 def settings_get(self):
379 384 return self._settings_get()
380 385
381 386 @LoginRequired()
382 387 @HasPermissionAnyDecorator('hg.admin')
383 388 @CSRFRequired()
384 389 def settings_post(self):
385 390 return self._settings_post()
386 391
387 392 @LoginRequired()
388 393 @HasPermissionAnyDecorator('hg.admin')
389 394 def new_integration(self):
390 395 return self._new_integration()
391 396
392 397
393 398 class RepoIntegrationsView(IntegrationSettingsViewBase):
394 399 def load_default_context(self):
395 400 c = self._get_local_tmpl_context()
396 401
397 402 c.repo = self.repo
398 403 c.repo_group = self.repo_group
399 404
400 405 self.db_repo = self.repo
401 406 c.rhodecode_db_repo = self.repo
402 407 c.repo_name = self.db_repo.repo_name
403 408 c.repository_pull_requests = ScmModel().get_pull_requests(self.repo)
404 409 c.repository_artifacts = ScmModel().get_artifacts(self.repo)
405 410 c.repository_is_user_following = ScmModel().is_following_repo(
406 411 c.repo_name, self._rhodecode_user.user_id)
407 412 c.has_origin_repo_read_perm = False
408 413 if self.db_repo.fork:
409 414 c.has_origin_repo_read_perm = h.HasRepoPermissionAny(
410 415 'repository.write', 'repository.read', 'repository.admin')(
411 416 self.db_repo.fork.repo_name, 'summary fork link')
412 417 return c
413 418
414 419 @LoginRequired()
415 420 @HasRepoPermissionAnyDecorator('repository.admin')
416 421 def integration_list(self):
417 422 return self._integration_list()
418 423
419 424 @LoginRequired()
420 425 @HasRepoPermissionAnyDecorator('repository.admin')
421 426 def settings_get(self):
422 427 return self._settings_get()
423 428
424 429 @LoginRequired()
425 430 @HasRepoPermissionAnyDecorator('repository.admin')
426 431 @CSRFRequired()
427 432 def settings_post(self):
428 433 return self._settings_post()
429 434
430 435 @LoginRequired()
431 436 @HasRepoPermissionAnyDecorator('repository.admin')
432 437 def new_integration(self):
433 438 return self._new_integration()
434 439
435 440
436 441 class RepoGroupIntegrationsView(IntegrationSettingsViewBase):
437 442 def load_default_context(self):
438 443 c = self._get_local_tmpl_context()
439 444 c.repo = self.repo
440 445 c.repo_group = self.repo_group
441 446 c.navlist = navigation_list(self.request)
442 447
443 448 return c
444 449
445 450 @LoginRequired()
446 451 @HasRepoGroupPermissionAnyDecorator('group.admin')
447 452 def integration_list(self):
448 453 return self._integration_list()
449 454
450 455 @LoginRequired()
451 456 @HasRepoGroupPermissionAnyDecorator('group.admin')
452 457 def settings_get(self):
453 458 return self._settings_get()
454 459
455 460 @LoginRequired()
456 461 @HasRepoGroupPermissionAnyDecorator('group.admin')
457 462 @CSRFRequired()
458 463 def settings_post(self):
459 464 return self._settings_post()
460 465
461 466 @LoginRequired()
462 467 @HasRepoGroupPermissionAnyDecorator('group.admin')
463 468 def new_integration(self):
464 469 return self._new_integration()
@@ -1,2030 +1,1942 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2019 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 """
22 22 Helper functions
23 23
24 24 Consists of functions to typically be used within templates, but also
25 25 available to Controllers. This module is available to both as 'h'.
26 26 """
27 27
28 28 import os
29 29 import random
30 30 import hashlib
31 31 import StringIO
32 32 import textwrap
33 33 import urllib
34 34 import math
35 35 import logging
36 36 import re
37 37 import time
38 38 import string
39 39 import hashlib
40 40 from collections import OrderedDict
41 41
42 42 import pygments
43 43 import itertools
44 44 import fnmatch
45 45 import bleach
46 46
47 47 from pyramid import compat
48 48 from datetime import datetime
49 49 from functools import partial
50 50 from pygments.formatters.html import HtmlFormatter
51 51 from pygments.lexers import (
52 52 get_lexer_by_name, get_lexer_for_filename, get_lexer_for_mimetype)
53 53
54 54 from pyramid.threadlocal import get_current_request
55 55
56 56 from webhelpers2.html import literal, HTML, escape
57 57 from webhelpers2.html._autolink import _auto_link_urls
58 58 from webhelpers2.html.tools import (
59 59 button_to, highlight, js_obfuscate, strip_links, strip_tags)
60 60
61 61 from webhelpers2.text import (
62 62 chop_at, collapse, convert_accented_entities,
63 63 convert_misc_entities, lchop, plural, rchop, remove_formatting,
64 64 replace_whitespace, urlify, truncate, wrap_paragraphs)
65 65 from webhelpers2.date import time_ago_in_words
66 66
67 67 from webhelpers2.html.tags import (
68 68 _input, NotGiven, _make_safe_id_component as safeid,
69 69 form as insecure_form,
70 70 auto_discovery_link, checkbox, end_form, file,
71 71 hidden, image, javascript_link, link_to, link_to_if, link_to_unless, ol,
72 72 select as raw_select, stylesheet_link, submit, text, password, textarea,
73 73 ul, radio, Options)
74 74
75 75 from webhelpers2.number import format_byte_size
76 76
77 77 from rhodecode.lib.action_parser import action_parser
78 from rhodecode.lib.paginate import Page
78 from rhodecode.lib.pagination import Page, RepoPage, SqlPage
79 79 from rhodecode.lib.ext_json import json
80 80 from rhodecode.lib.utils import repo_name_slug, get_custom_lexer
81 81 from rhodecode.lib.utils2 import (
82 82 str2bool, safe_unicode, safe_str,
83 83 get_commit_safe, datetime_to_time, time_to_datetime, time_to_utcdatetime,
84 84 AttributeDict, safe_int, md5, md5_safe, get_host_info)
85 85 from rhodecode.lib.markup_renderer import MarkupRenderer, relative_links
86 86 from rhodecode.lib.vcs.exceptions import CommitDoesNotExistError
87 87 from rhodecode.lib.vcs.backends.base import BaseChangeset, EmptyCommit
88 88 from rhodecode.lib.index.search_utils import get_matching_line_offsets
89 89 from rhodecode.config.conf import DATE_FORMAT, DATETIME_FORMAT
90 90 from rhodecode.model.changeset_status import ChangesetStatusModel
91 91 from rhodecode.model.db import Permission, User, Repository
92 92 from rhodecode.model.repo_group import RepoGroupModel
93 93 from rhodecode.model.settings import IssueTrackerSettingsModel
94 94
95 95
96 96 log = logging.getLogger(__name__)
97 97
98 98
99 99 DEFAULT_USER = User.DEFAULT_USER
100 100 DEFAULT_USER_EMAIL = User.DEFAULT_USER_EMAIL
101 101
102 102
103 103 def asset(path, ver=None, **kwargs):
104 104 """
105 105 Helper to generate a static asset file path for rhodecode assets
106 106
107 107 eg. h.asset('images/image.png', ver='3923')
108 108
109 109 :param path: path of asset
110 110 :param ver: optional version query param to append as ?ver=
111 111 """
112 112 request = get_current_request()
113 113 query = {}
114 114 query.update(kwargs)
115 115 if ver:
116 116 query = {'ver': ver}
117 117 return request.static_path(
118 118 'rhodecode:public/{}'.format(path), _query=query)
119 119
120 120
121 121 default_html_escape_table = {
122 122 ord('&'): u'&amp;',
123 123 ord('<'): u'&lt;',
124 124 ord('>'): u'&gt;',
125 125 ord('"'): u'&quot;',
126 126 ord("'"): u'&#39;',
127 127 }
128 128
129 129
130 130 def html_escape(text, html_escape_table=default_html_escape_table):
131 131 """Produce entities within text."""
132 132 return text.translate(html_escape_table)
133 133
134 134
135 135 def chop_at_smart(s, sub, inclusive=False, suffix_if_chopped=None):
136 136 """
137 137 Truncate string ``s`` at the first occurrence of ``sub``.
138 138
139 139 If ``inclusive`` is true, truncate just after ``sub`` rather than at it.
140 140 """
141 141 suffix_if_chopped = suffix_if_chopped or ''
142 142 pos = s.find(sub)
143 143 if pos == -1:
144 144 return s
145 145
146 146 if inclusive:
147 147 pos += len(sub)
148 148
149 149 chopped = s[:pos]
150 150 left = s[pos:].strip()
151 151
152 152 if left and suffix_if_chopped:
153 153 chopped += suffix_if_chopped
154 154
155 155 return chopped
156 156
157 157
158 158 def shorter(text, size=20, prefix=False):
159 159 postfix = '...'
160 160 if len(text) > size:
161 161 if prefix:
162 162 # shorten in front
163 163 return postfix + text[-(size - len(postfix)):]
164 164 else:
165 165 return text[:size - len(postfix)] + postfix
166 166 return text
167 167
168 168
169 169 def reset(name, value=None, id=NotGiven, type="reset", **attrs):
170 170 """
171 171 Reset button
172 172 """
173 173 return _input(type, name, value, id, attrs)
174 174
175 175
176 176 def select(name, selected_values, options, id=NotGiven, **attrs):
177 177
178 178 if isinstance(options, (list, tuple)):
179 179 options_iter = options
180 180 # Handle old value,label lists ... where value also can be value,label lists
181 181 options = Options()
182 182 for opt in options_iter:
183 183 if isinstance(opt, tuple) and len(opt) == 2:
184 184 value, label = opt
185 185 elif isinstance(opt, basestring):
186 186 value = label = opt
187 187 else:
188 188 raise ValueError('invalid select option type %r' % type(opt))
189 189
190 190 if isinstance(value, (list, tuple)):
191 191 option_group = options.add_optgroup(label)
192 192 for opt2 in value:
193 193 if isinstance(opt2, tuple) and len(opt2) == 2:
194 194 group_value, group_label = opt
195 195 elif isinstance(opt2, basestring):
196 196 group_value = group_label = opt2
197 197 else:
198 198 raise ValueError('invalid select option type %r' % type(opt2))
199 199
200 200 option_group.add_option(group_label, group_value)
201 201 else:
202 202 options.add_option(label, value)
203 203
204 204 return raw_select(name, selected_values, options, id=id, **attrs)
205 205
206 206
207 207 def branding(name, length=40):
208 208 return truncate(name, length, indicator="")
209 209
210 210
211 211 def FID(raw_id, path):
212 212 """
213 213 Creates a unique ID for filenode based on it's hash of path and commit
214 214 it's safe to use in urls
215 215
216 216 :param raw_id:
217 217 :param path:
218 218 """
219 219
220 220 return 'c-%s-%s' % (short_id(raw_id), md5_safe(path)[:12])
221 221
222 222
223 223 class _GetError(object):
224 224 """Get error from form_errors, and represent it as span wrapped error
225 225 message
226 226
227 227 :param field_name: field to fetch errors for
228 228 :param form_errors: form errors dict
229 229 """
230 230
231 231 def __call__(self, field_name, form_errors):
232 232 tmpl = """<span class="error_msg">%s</span>"""
233 233 if form_errors and field_name in form_errors:
234 234 return literal(tmpl % form_errors.get(field_name))
235 235
236 236
237 237 get_error = _GetError()
238 238
239 239
240 240 class _ToolTip(object):
241 241
242 242 def __call__(self, tooltip_title, trim_at=50):
243 243 """
244 244 Special function just to wrap our text into nice formatted
245 245 autowrapped text
246 246
247 247 :param tooltip_title:
248 248 """
249 249 tooltip_title = escape(tooltip_title)
250 250 tooltip_title = tooltip_title.replace('<', '&lt;').replace('>', '&gt;')
251 251 return tooltip_title
252 252
253 253
254 254 tooltip = _ToolTip()
255 255
256 256 files_icon = u'<i class="file-breadcrumb-copy tooltip icon-clipboard clipboard-action" data-clipboard-text="{}" title="Copy the full path"></i>'
257 257
258 258
259 259 def files_breadcrumbs(repo_name, commit_id, file_path, at_ref=None, limit_items=False, linkify_last_item=False):
260 260 if isinstance(file_path, str):
261 261 file_path = safe_unicode(file_path)
262 262
263 263 route_qry = {'at': at_ref} if at_ref else None
264 264
265 265 # first segment is a `..` link to repo files
266 266 root_name = literal(u'<i class="icon-home"></i>')
267 267 url_segments = [
268 268 link_to(
269 269 root_name,
270 270 route_path(
271 271 'repo_files',
272 272 repo_name=repo_name,
273 273 commit_id=commit_id,
274 274 f_path='',
275 275 _query=route_qry),
276 276 )]
277 277
278 278 path_segments = file_path.split('/')
279 279 last_cnt = len(path_segments) - 1
280 280 for cnt, segment in enumerate(path_segments):
281 281 if not segment:
282 282 continue
283 283 segment_html = escape(segment)
284 284
285 285 last_item = cnt == last_cnt
286 286
287 287 if last_item and linkify_last_item is False:
288 288 # plain version
289 289 url_segments.append(segment_html)
290 290 else:
291 291 url_segments.append(
292 292 link_to(
293 293 segment_html,
294 294 route_path(
295 295 'repo_files',
296 296 repo_name=repo_name,
297 297 commit_id=commit_id,
298 298 f_path='/'.join(path_segments[:cnt + 1]),
299 299 _query=route_qry),
300 300 ))
301 301
302 302 limited_url_segments = url_segments[:1] + ['...'] + url_segments[-5:]
303 303 if limit_items and len(limited_url_segments) < len(url_segments):
304 304 url_segments = limited_url_segments
305 305
306 306 full_path = file_path
307 307 icon = files_icon.format(escape(full_path))
308 308 if file_path == '':
309 309 return root_name
310 310 else:
311 311 return literal(' / '.join(url_segments) + icon)
312 312
313 313
314 314 def files_url_data(request):
315 315 matchdict = request.matchdict
316 316
317 317 if 'f_path' not in matchdict:
318 318 matchdict['f_path'] = ''
319 319
320 320 if 'commit_id' not in matchdict:
321 321 matchdict['commit_id'] = 'tip'
322 322
323 323 return json.dumps(matchdict)
324 324
325 325
326 326 def code_highlight(code, lexer, formatter, use_hl_filter=False):
327 327 """
328 328 Lex ``code`` with ``lexer`` and format it with the formatter ``formatter``.
329 329
330 330 If ``outfile`` is given and a valid file object (an object
331 331 with a ``write`` method), the result will be written to it, otherwise
332 332 it is returned as a string.
333 333 """
334 334 if use_hl_filter:
335 335 # add HL filter
336 336 from rhodecode.lib.index import search_utils
337 337 lexer.add_filter(search_utils.ElasticSearchHLFilter())
338 338 return pygments.format(pygments.lex(code, lexer), formatter)
339 339
340 340
341 341 class CodeHtmlFormatter(HtmlFormatter):
342 342 """
343 343 My code Html Formatter for source codes
344 344 """
345 345
346 346 def wrap(self, source, outfile):
347 347 return self._wrap_div(self._wrap_pre(self._wrap_code(source)))
348 348
349 349 def _wrap_code(self, source):
350 350 for cnt, it in enumerate(source):
351 351 i, t = it
352 352 t = '<div id="L%s">%s</div>' % (cnt + 1, t)
353 353 yield i, t
354 354
355 355 def _wrap_tablelinenos(self, inner):
356 356 dummyoutfile = StringIO.StringIO()
357 357 lncount = 0
358 358 for t, line in inner:
359 359 if t:
360 360 lncount += 1
361 361 dummyoutfile.write(line)
362 362
363 363 fl = self.linenostart
364 364 mw = len(str(lncount + fl - 1))
365 365 sp = self.linenospecial
366 366 st = self.linenostep
367 367 la = self.lineanchors
368 368 aln = self.anchorlinenos
369 369 nocls = self.noclasses
370 370 if sp:
371 371 lines = []
372 372
373 373 for i in range(fl, fl + lncount):
374 374 if i % st == 0:
375 375 if i % sp == 0:
376 376 if aln:
377 377 lines.append('<a href="#%s%d" class="special">%*d</a>' %
378 378 (la, i, mw, i))
379 379 else:
380 380 lines.append('<span class="special">%*d</span>' % (mw, i))
381 381 else:
382 382 if aln:
383 383 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
384 384 else:
385 385 lines.append('%*d' % (mw, i))
386 386 else:
387 387 lines.append('')
388 388 ls = '\n'.join(lines)
389 389 else:
390 390 lines = []
391 391 for i in range(fl, fl + lncount):
392 392 if i % st == 0:
393 393 if aln:
394 394 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
395 395 else:
396 396 lines.append('%*d' % (mw, i))
397 397 else:
398 398 lines.append('')
399 399 ls = '\n'.join(lines)
400 400
401 401 # in case you wonder about the seemingly redundant <div> here: since the
402 402 # content in the other cell also is wrapped in a div, some browsers in
403 403 # some configurations seem to mess up the formatting...
404 404 if nocls:
405 405 yield 0, ('<table class="%stable">' % self.cssclass +
406 406 '<tr><td><div class="linenodiv" '
407 407 'style="background-color: #f0f0f0; padding-right: 10px">'
408 408 '<pre style="line-height: 125%">' +
409 409 ls + '</pre></div></td><td id="hlcode" class="code">')
410 410 else:
411 411 yield 0, ('<table class="%stable">' % self.cssclass +
412 412 '<tr><td class="linenos"><div class="linenodiv"><pre>' +
413 413 ls + '</pre></div></td><td id="hlcode" class="code">')
414 414 yield 0, dummyoutfile.getvalue()
415 415 yield 0, '</td></tr></table>'
416 416
417 417
418 418 class SearchContentCodeHtmlFormatter(CodeHtmlFormatter):
419 419 def __init__(self, **kw):
420 420 # only show these line numbers if set
421 421 self.only_lines = kw.pop('only_line_numbers', [])
422 422 self.query_terms = kw.pop('query_terms', [])
423 423 self.max_lines = kw.pop('max_lines', 5)
424 424 self.line_context = kw.pop('line_context', 3)
425 425 self.url = kw.pop('url', None)
426 426
427 427 super(CodeHtmlFormatter, self).__init__(**kw)
428 428
429 429 def _wrap_code(self, source):
430 430 for cnt, it in enumerate(source):
431 431 i, t = it
432 432 t = '<pre>%s</pre>' % t
433 433 yield i, t
434 434
435 435 def _wrap_tablelinenos(self, inner):
436 436 yield 0, '<table class="code-highlight %stable">' % self.cssclass
437 437
438 438 last_shown_line_number = 0
439 439 current_line_number = 1
440 440
441 441 for t, line in inner:
442 442 if not t:
443 443 yield t, line
444 444 continue
445 445
446 446 if current_line_number in self.only_lines:
447 447 if last_shown_line_number + 1 != current_line_number:
448 448 yield 0, '<tr>'
449 449 yield 0, '<td class="line">...</td>'
450 450 yield 0, '<td id="hlcode" class="code"></td>'
451 451 yield 0, '</tr>'
452 452
453 453 yield 0, '<tr>'
454 454 if self.url:
455 455 yield 0, '<td class="line"><a href="%s#L%i">%i</a></td>' % (
456 456 self.url, current_line_number, current_line_number)
457 457 else:
458 458 yield 0, '<td class="line"><a href="">%i</a></td>' % (
459 459 current_line_number)
460 460 yield 0, '<td id="hlcode" class="code">' + line + '</td>'
461 461 yield 0, '</tr>'
462 462
463 463 last_shown_line_number = current_line_number
464 464
465 465 current_line_number += 1
466 466
467 467 yield 0, '</table>'
468 468
469 469
470 470 def hsv_to_rgb(h, s, v):
471 471 """ Convert hsv color values to rgb """
472 472
473 473 if s == 0.0:
474 474 return v, v, v
475 475 i = int(h * 6.0) # XXX assume int() truncates!
476 476 f = (h * 6.0) - i
477 477 p = v * (1.0 - s)
478 478 q = v * (1.0 - s * f)
479 479 t = v * (1.0 - s * (1.0 - f))
480 480 i = i % 6
481 481 if i == 0:
482 482 return v, t, p
483 483 if i == 1:
484 484 return q, v, p
485 485 if i == 2:
486 486 return p, v, t
487 487 if i == 3:
488 488 return p, q, v
489 489 if i == 4:
490 490 return t, p, v
491 491 if i == 5:
492 492 return v, p, q
493 493
494 494
495 495 def unique_color_generator(n=10000, saturation=0.10, lightness=0.95):
496 496 """
497 497 Generator for getting n of evenly distributed colors using
498 498 hsv color and golden ratio. It always return same order of colors
499 499
500 500 :param n: number of colors to generate
501 501 :param saturation: saturation of returned colors
502 502 :param lightness: lightness of returned colors
503 503 :returns: RGB tuple
504 504 """
505 505
506 506 golden_ratio = 0.618033988749895
507 507 h = 0.22717784590367374
508 508
509 509 for _ in xrange(n):
510 510 h += golden_ratio
511 511 h %= 1
512 512 HSV_tuple = [h, saturation, lightness]
513 513 RGB_tuple = hsv_to_rgb(*HSV_tuple)
514 514 yield map(lambda x: str(int(x * 256)), RGB_tuple)
515 515
516 516
517 517 def color_hasher(n=10000, saturation=0.10, lightness=0.95):
518 518 """
519 519 Returns a function which when called with an argument returns a unique
520 520 color for that argument, eg.
521 521
522 522 :param n: number of colors to generate
523 523 :param saturation: saturation of returned colors
524 524 :param lightness: lightness of returned colors
525 525 :returns: css RGB string
526 526
527 527 >>> color_hash = color_hasher()
528 528 >>> color_hash('hello')
529 529 'rgb(34, 12, 59)'
530 530 >>> color_hash('hello')
531 531 'rgb(34, 12, 59)'
532 532 >>> color_hash('other')
533 533 'rgb(90, 224, 159)'
534 534 """
535 535
536 536 color_dict = {}
537 537 cgenerator = unique_color_generator(
538 538 saturation=saturation, lightness=lightness)
539 539
540 540 def get_color_string(thing):
541 541 if thing in color_dict:
542 542 col = color_dict[thing]
543 543 else:
544 544 col = color_dict[thing] = cgenerator.next()
545 545 return "rgb(%s)" % (', '.join(col))
546 546
547 547 return get_color_string
548 548
549 549
550 550 def get_lexer_safe(mimetype=None, filepath=None):
551 551 """
552 552 Tries to return a relevant pygments lexer using mimetype/filepath name,
553 553 defaulting to plain text if none could be found
554 554 """
555 555 lexer = None
556 556 try:
557 557 if mimetype:
558 558 lexer = get_lexer_for_mimetype(mimetype)
559 559 if not lexer:
560 560 lexer = get_lexer_for_filename(filepath)
561 561 except pygments.util.ClassNotFound:
562 562 pass
563 563
564 564 if not lexer:
565 565 lexer = get_lexer_by_name('text')
566 566
567 567 return lexer
568 568
569 569
570 570 def get_lexer_for_filenode(filenode):
571 571 lexer = get_custom_lexer(filenode.extension) or filenode.lexer
572 572 return lexer
573 573
574 574
575 575 def pygmentize(filenode, **kwargs):
576 576 """
577 577 pygmentize function using pygments
578 578
579 579 :param filenode:
580 580 """
581 581 lexer = get_lexer_for_filenode(filenode)
582 582 return literal(code_highlight(filenode.content, lexer,
583 583 CodeHtmlFormatter(**kwargs)))
584 584
585 585
586 586 def is_following_repo(repo_name, user_id):
587 587 from rhodecode.model.scm import ScmModel
588 588 return ScmModel().is_following_repo(repo_name, user_id)
589 589
590 590
591 591 class _Message(object):
592 592 """A message returned by ``Flash.pop_messages()``.
593 593
594 594 Converting the message to a string returns the message text. Instances
595 595 also have the following attributes:
596 596
597 597 * ``message``: the message text.
598 598 * ``category``: the category specified when the message was created.
599 599 """
600 600
601 601 def __init__(self, category, message):
602 602 self.category = category
603 603 self.message = message
604 604
605 605 def __str__(self):
606 606 return self.message
607 607
608 608 __unicode__ = __str__
609 609
610 610 def __html__(self):
611 611 return escape(safe_unicode(self.message))
612 612
613 613
614 614 class Flash(object):
615 615 # List of allowed categories. If None, allow any category.
616 616 categories = ["warning", "notice", "error", "success"]
617 617
618 618 # Default category if none is specified.
619 619 default_category = "notice"
620 620
621 621 def __init__(self, session_key="flash", categories=None,
622 622 default_category=None):
623 623 """
624 624 Instantiate a ``Flash`` object.
625 625
626 626 ``session_key`` is the key to save the messages under in the user's
627 627 session.
628 628
629 629 ``categories`` is an optional list which overrides the default list
630 630 of categories.
631 631
632 632 ``default_category`` overrides the default category used for messages
633 633 when none is specified.
634 634 """
635 635 self.session_key = session_key
636 636 if categories is not None:
637 637 self.categories = categories
638 638 if default_category is not None:
639 639 self.default_category = default_category
640 640 if self.categories and self.default_category not in self.categories:
641 641 raise ValueError(
642 642 "unrecognized default category %r" % (self.default_category,))
643 643
644 644 def pop_messages(self, session=None, request=None):
645 645 """
646 646 Return all accumulated messages and delete them from the session.
647 647
648 648 The return value is a list of ``Message`` objects.
649 649 """
650 650 messages = []
651 651
652 652 if not session:
653 653 if not request:
654 654 request = get_current_request()
655 655 session = request.session
656 656
657 657 # Pop the 'old' pylons flash messages. They are tuples of the form
658 658 # (category, message)
659 659 for cat, msg in session.pop(self.session_key, []):
660 660 messages.append(_Message(cat, msg))
661 661
662 662 # Pop the 'new' pyramid flash messages for each category as list
663 663 # of strings.
664 664 for cat in self.categories:
665 665 for msg in session.pop_flash(queue=cat):
666 666 messages.append(_Message(cat, msg))
667 667 # Map messages from the default queue to the 'notice' category.
668 668 for msg in session.pop_flash():
669 669 messages.append(_Message('notice', msg))
670 670
671 671 session.save()
672 672 return messages
673 673
674 674 def json_alerts(self, session=None, request=None):
675 675 payloads = []
676 676 messages = flash.pop_messages(session=session, request=request)
677 677 if messages:
678 678 for message in messages:
679 679 subdata = {}
680 680 if hasattr(message.message, 'rsplit'):
681 681 flash_data = message.message.rsplit('|DELIM|', 1)
682 682 org_message = flash_data[0]
683 683 if len(flash_data) > 1:
684 684 subdata = json.loads(flash_data[1])
685 685 else:
686 686 org_message = message.message
687 687 payloads.append({
688 688 'message': {
689 689 'message': u'{}'.format(org_message),
690 690 'level': message.category,
691 691 'force': True,
692 692 'subdata': subdata
693 693 }
694 694 })
695 695 return json.dumps(payloads)
696 696
697 697 def __call__(self, message, category=None, ignore_duplicate=True,
698 698 session=None, request=None):
699 699
700 700 if not session:
701 701 if not request:
702 702 request = get_current_request()
703 703 session = request.session
704 704
705 705 session.flash(
706 706 message, queue=category, allow_duplicate=not ignore_duplicate)
707 707
708 708
709 709 flash = Flash()
710 710
711 711 #==============================================================================
712 712 # SCM FILTERS available via h.
713 713 #==============================================================================
714 714 from rhodecode.lib.vcs.utils import author_name, author_email
715 715 from rhodecode.lib.utils2 import credentials_filter, age, age_from_seconds
716 716 from rhodecode.model.db import User, ChangesetStatus
717 717
718 718 capitalize = lambda x: x.capitalize()
719 719 email = author_email
720 720 short_id = lambda x: x[:12]
721 721 hide_credentials = lambda x: ''.join(credentials_filter(x))
722 722
723 723
724 724 import pytz
725 725 import tzlocal
726 726 local_timezone = tzlocal.get_localzone()
727 727
728 728
729 729 def age_component(datetime_iso, value=None, time_is_local=False):
730 730 title = value or format_date(datetime_iso)
731 731 tzinfo = '+00:00'
732 732
733 733 # detect if we have a timezone info, otherwise, add it
734 734 if time_is_local and isinstance(datetime_iso, datetime) and not datetime_iso.tzinfo:
735 735 force_timezone = os.environ.get('RC_TIMEZONE', '')
736 736 if force_timezone:
737 737 force_timezone = pytz.timezone(force_timezone)
738 738 timezone = force_timezone or local_timezone
739 739 offset = timezone.localize(datetime_iso).strftime('%z')
740 740 tzinfo = '{}:{}'.format(offset[:-2], offset[-2:])
741 741
742 742 return literal(
743 743 '<time class="timeago tooltip" '
744 744 'title="{1}{2}" datetime="{0}{2}">{1}</time>'.format(
745 745 datetime_iso, title, tzinfo))
746 746
747 747
748 748 def _shorten_commit_id(commit_id, commit_len=None):
749 749 if commit_len is None:
750 750 request = get_current_request()
751 751 commit_len = request.call_context.visual.show_sha_length
752 752 return commit_id[:commit_len]
753 753
754 754
755 755 def show_id(commit, show_idx=None, commit_len=None):
756 756 """
757 757 Configurable function that shows ID
758 758 by default it's r123:fffeeefffeee
759 759
760 760 :param commit: commit instance
761 761 """
762 762 if show_idx is None:
763 763 request = get_current_request()
764 764 show_idx = request.call_context.visual.show_revision_number
765 765
766 766 raw_id = _shorten_commit_id(commit.raw_id, commit_len=commit_len)
767 767 if show_idx:
768 768 return 'r%s:%s' % (commit.idx, raw_id)
769 769 else:
770 770 return '%s' % (raw_id, )
771 771
772 772
773 773 def format_date(date):
774 774 """
775 775 use a standardized formatting for dates used in RhodeCode
776 776
777 777 :param date: date/datetime object
778 778 :return: formatted date
779 779 """
780 780
781 781 if date:
782 782 _fmt = "%a, %d %b %Y %H:%M:%S"
783 783 return safe_unicode(date.strftime(_fmt))
784 784
785 785 return u""
786 786
787 787
788 788 class _RepoChecker(object):
789 789
790 790 def __init__(self, backend_alias):
791 791 self._backend_alias = backend_alias
792 792
793 793 def __call__(self, repository):
794 794 if hasattr(repository, 'alias'):
795 795 _type = repository.alias
796 796 elif hasattr(repository, 'repo_type'):
797 797 _type = repository.repo_type
798 798 else:
799 799 _type = repository
800 800 return _type == self._backend_alias
801 801
802 802
803 803 is_git = _RepoChecker('git')
804 804 is_hg = _RepoChecker('hg')
805 805 is_svn = _RepoChecker('svn')
806 806
807 807
808 808 def get_repo_type_by_name(repo_name):
809 809 repo = Repository.get_by_repo_name(repo_name)
810 810 if repo:
811 811 return repo.repo_type
812 812
813 813
814 814 def is_svn_without_proxy(repository):
815 815 if is_svn(repository):
816 816 from rhodecode.model.settings import VcsSettingsModel
817 817 conf = VcsSettingsModel().get_ui_settings_as_config_obj()
818 818 return not str2bool(conf.get('vcs_svn_proxy', 'http_requests_enabled'))
819 819 return False
820 820
821 821
822 822 def discover_user(author):
823 823 """
824 824 Tries to discover RhodeCode User based on the autho string. Author string
825 825 is typically `FirstName LastName <email@address.com>`
826 826 """
827 827
828 828 # if author is already an instance use it for extraction
829 829 if isinstance(author, User):
830 830 return author
831 831
832 832 # Valid email in the attribute passed, see if they're in the system
833 833 _email = author_email(author)
834 834 if _email != '':
835 835 user = User.get_by_email(_email, case_insensitive=True, cache=True)
836 836 if user is not None:
837 837 return user
838 838
839 839 # Maybe it's a username, we try to extract it and fetch by username ?
840 840 _author = author_name(author)
841 841 user = User.get_by_username(_author, case_insensitive=True, cache=True)
842 842 if user is not None:
843 843 return user
844 844
845 845 return None
846 846
847 847
848 848 def email_or_none(author):
849 849 # extract email from the commit string
850 850 _email = author_email(author)
851 851
852 852 # If we have an email, use it, otherwise
853 853 # see if it contains a username we can get an email from
854 854 if _email != '':
855 855 return _email
856 856 else:
857 857 user = User.get_by_username(
858 858 author_name(author), case_insensitive=True, cache=True)
859 859
860 860 if user is not None:
861 861 return user.email
862 862
863 863 # No valid email, not a valid user in the system, none!
864 864 return None
865 865
866 866
867 867 def link_to_user(author, length=0, **kwargs):
868 868 user = discover_user(author)
869 869 # user can be None, but if we have it already it means we can re-use it
870 870 # in the person() function, so we save 1 intensive-query
871 871 if user:
872 872 author = user
873 873
874 874 display_person = person(author, 'username_or_name_or_email')
875 875 if length:
876 876 display_person = shorter(display_person, length)
877 877
878 878 if user:
879 879 return link_to(
880 880 escape(display_person),
881 881 route_path('user_profile', username=user.username),
882 882 **kwargs)
883 883 else:
884 884 return escape(display_person)
885 885
886 886
887 887 def link_to_group(users_group_name, **kwargs):
888 888 return link_to(
889 889 escape(users_group_name),
890 890 route_path('user_group_profile', user_group_name=users_group_name),
891 891 **kwargs)
892 892
893 893
894 894 def person(author, show_attr="username_and_name"):
895 895 user = discover_user(author)
896 896 if user:
897 897 return getattr(user, show_attr)
898 898 else:
899 899 _author = author_name(author)
900 900 _email = email(author)
901 901 return _author or _email
902 902
903 903
904 904 def author_string(email):
905 905 if email:
906 906 user = User.get_by_email(email, case_insensitive=True, cache=True)
907 907 if user:
908 908 if user.first_name or user.last_name:
909 909 return '%s %s &lt;%s&gt;' % (
910 910 user.first_name, user.last_name, email)
911 911 else:
912 912 return email
913 913 else:
914 914 return email
915 915 else:
916 916 return None
917 917
918 918
919 919 def person_by_id(id_, show_attr="username_and_name"):
920 920 # attr to return from fetched user
921 921 person_getter = lambda usr: getattr(usr, show_attr)
922 922
923 923 #maybe it's an ID ?
924 924 if str(id_).isdigit() or isinstance(id_, int):
925 925 id_ = int(id_)
926 926 user = User.get(id_)
927 927 if user is not None:
928 928 return person_getter(user)
929 929 return id_
930 930
931 931
932 932 def gravatar_with_user(request, author, show_disabled=False, tooltip=False):
933 933 _render = request.get_partial_renderer('rhodecode:templates/base/base.mako')
934 934 return _render('gravatar_with_user', author, show_disabled=show_disabled, tooltip=tooltip)
935 935
936 936
937 937 tags_paterns = OrderedDict((
938 938 ('lang', (re.compile(r'\[(lang|language)\ \=\&gt;\ *([a-zA-Z\-\/\#\+\.]*)\]'),
939 939 '<div class="metatag" tag="lang">\\2</div>')),
940 940
941 941 ('see', (re.compile(r'\[see\ \=\&gt;\ *([a-zA-Z0-9\/\=\?\&amp;\ \:\/\.\-]*)\]'),
942 942 '<div class="metatag" tag="see">see: \\1 </div>')),
943 943
944 944 ('url', (re.compile(r'\[url\ \=\&gt;\ \[([a-zA-Z0-9\ \.\-\_]+)\]\((http://|https://|/)(.*?)\)\]'),
945 945 '<div class="metatag" tag="url"> <a href="\\2\\3">\\1</a> </div>')),
946 946
947 947 ('license', (re.compile(r'\[license\ \=\&gt;\ *([a-zA-Z0-9\/\=\?\&amp;\ \:\/\.\-]*)\]'),
948 948 '<div class="metatag" tag="license"><a href="http:\/\/www.opensource.org/licenses/\\1">\\1</a></div>')),
949 949
950 950 ('ref', (re.compile(r'\[(requires|recommends|conflicts|base)\ \=\&gt;\ *([a-zA-Z0-9\-\/]*)\]'),
951 951 '<div class="metatag" tag="ref \\1">\\1: <a href="/\\2">\\2</a></div>')),
952 952
953 953 ('state', (re.compile(r'\[(stable|featured|stale|dead|dev|deprecated)\]'),
954 954 '<div class="metatag" tag="state \\1">\\1</div>')),
955 955
956 956 # label in grey
957 957 ('label', (re.compile(r'\[([a-z]+)\]'),
958 958 '<div class="metatag" tag="label">\\1</div>')),
959 959
960 960 # generic catch all in grey
961 961 ('generic', (re.compile(r'\[([a-zA-Z0-9\.\-\_]+)\]'),
962 962 '<div class="metatag" tag="generic">\\1</div>')),
963 963 ))
964 964
965 965
966 966 def extract_metatags(value):
967 967 """
968 968 Extract supported meta-tags from given text value
969 969 """
970 970 tags = []
971 971 if not value:
972 972 return tags, ''
973 973
974 974 for key, val in tags_paterns.items():
975 975 pat, replace_html = val
976 976 tags.extend([(key, x.group()) for x in pat.finditer(value)])
977 977 value = pat.sub('', value)
978 978
979 979 return tags, value
980 980
981 981
982 982 def style_metatag(tag_type, value):
983 983 """
984 984 converts tags from value into html equivalent
985 985 """
986 986 if not value:
987 987 return ''
988 988
989 989 html_value = value
990 990 tag_data = tags_paterns.get(tag_type)
991 991 if tag_data:
992 992 pat, replace_html = tag_data
993 993 # convert to plain `unicode` instead of a markup tag to be used in
994 994 # regex expressions. safe_unicode doesn't work here
995 995 html_value = pat.sub(replace_html, unicode(value))
996 996
997 997 return html_value
998 998
999 999
1000 1000 def bool2icon(value, show_at_false=True):
1001 1001 """
1002 1002 Returns boolean value of a given value, represented as html element with
1003 1003 classes that will represent icons
1004 1004
1005 1005 :param value: given value to convert to html node
1006 1006 """
1007 1007
1008 1008 if value: # does bool conversion
1009 1009 return HTML.tag('i', class_="icon-true", title='True')
1010 1010 else: # not true as bool
1011 1011 if show_at_false:
1012 1012 return HTML.tag('i', class_="icon-false", title='False')
1013 1013 return HTML.tag('i')
1014 1014
1015 1015 #==============================================================================
1016 1016 # PERMS
1017 1017 #==============================================================================
1018 1018 from rhodecode.lib.auth import HasPermissionAny, HasPermissionAll, \
1019 1019 HasRepoPermissionAny, HasRepoPermissionAll, HasRepoGroupPermissionAll, \
1020 1020 HasRepoGroupPermissionAny, HasRepoPermissionAnyApi, get_csrf_token, \
1021 1021 csrf_token_key
1022 1022
1023 1023
1024 1024 #==============================================================================
1025 1025 # GRAVATAR URL
1026 1026 #==============================================================================
1027 1027 class InitialsGravatar(object):
1028 1028 def __init__(self, email_address, first_name, last_name, size=30,
1029 1029 background=None, text_color='#fff'):
1030 1030 self.size = size
1031 1031 self.first_name = first_name
1032 1032 self.last_name = last_name
1033 1033 self.email_address = email_address
1034 1034 self.background = background or self.str2color(email_address)
1035 1035 self.text_color = text_color
1036 1036
1037 1037 def get_color_bank(self):
1038 1038 """
1039 1039 returns a predefined list of colors that gravatars can use.
1040 1040 Those are randomized distinct colors that guarantee readability and
1041 1041 uniqueness.
1042 1042
1043 1043 generated with: http://phrogz.net/css/distinct-colors.html
1044 1044 """
1045 1045 return [
1046 1046 '#bf3030', '#a67f53', '#00ff00', '#5989b3', '#392040', '#d90000',
1047 1047 '#402910', '#204020', '#79baf2', '#a700b3', '#bf6060', '#7f5320',
1048 1048 '#008000', '#003059', '#ee00ff', '#ff0000', '#8c4b00', '#007300',
1049 1049 '#005fb3', '#de73e6', '#ff4040', '#ffaa00', '#3df255', '#203140',
1050 1050 '#47004d', '#591616', '#664400', '#59b365', '#0d2133', '#83008c',
1051 1051 '#592d2d', '#bf9f60', '#73e682', '#1d3f73', '#73006b', '#402020',
1052 1052 '#b2862d', '#397341', '#597db3', '#e600d6', '#a60000', '#736039',
1053 1053 '#00b318', '#79aaf2', '#330d30', '#ff8080', '#403010', '#16591f',
1054 1054 '#002459', '#8c4688', '#e50000', '#ffbf40', '#00732e', '#102340',
1055 1055 '#bf60ac', '#8c4646', '#cc8800', '#00a642', '#1d3473', '#b32d98',
1056 1056 '#660e00', '#ffd580', '#80ffb2', '#7391e6', '#733967', '#d97b6c',
1057 1057 '#8c5e00', '#59b389', '#3967e6', '#590047', '#73281d', '#665200',
1058 1058 '#00e67a', '#2d50b3', '#8c2377', '#734139', '#b2982d', '#16593a',
1059 1059 '#001859', '#ff00aa', '#a65e53', '#ffcc00', '#0d3321', '#2d3959',
1060 1060 '#731d56', '#401610', '#4c3d00', '#468c6c', '#002ca6', '#d936a3',
1061 1061 '#d94c36', '#403920', '#36d9a3', '#0d1733', '#592d4a', '#993626',
1062 1062 '#cca300', '#00734d', '#46598c', '#8c005e', '#7f1100', '#8c7000',
1063 1063 '#00a66f', '#7382e6', '#b32d74', '#d9896c', '#ffe680', '#1d7362',
1064 1064 '#364cd9', '#73003d', '#d93a00', '#998a4d', '#59b3a1', '#5965b3',
1065 1065 '#e5007a', '#73341d', '#665f00', '#00b38f', '#0018b3', '#59163a',
1066 1066 '#b2502d', '#bfb960', '#00ffcc', '#23318c', '#a6537f', '#734939',
1067 1067 '#b2a700', '#104036', '#3d3df2', '#402031', '#e56739', '#736f39',
1068 1068 '#79f2ea', '#000059', '#401029', '#4c1400', '#ffee00', '#005953',
1069 1069 '#101040', '#990052', '#402820', '#403d10', '#00ffee', '#0000d9',
1070 1070 '#ff80c4', '#a66953', '#eeff00', '#00ccbe', '#8080ff', '#e673a1',
1071 1071 '#a62c00', '#474d00', '#1a3331', '#46468c', '#733950', '#662900',
1072 1072 '#858c23', '#238c85', '#0f0073', '#b20047', '#d9986c', '#becc00',
1073 1073 '#396f73', '#281d73', '#ff0066', '#ff6600', '#dee673', '#59adb3',
1074 1074 '#6559b3', '#590024', '#b2622d', '#98b32d', '#36ced9', '#332d59',
1075 1075 '#40001a', '#733f1d', '#526600', '#005359', '#242040', '#bf6079',
1076 1076 '#735039', '#cef23d', '#007780', '#5630bf', '#66001b', '#b24700',
1077 1077 '#acbf60', '#1d6273', '#25008c', '#731d34', '#a67453', '#50592d',
1078 1078 '#00ccff', '#6600ff', '#ff0044', '#4c1f00', '#8a994d', '#79daf2',
1079 1079 '#a173e6', '#d93662', '#402310', '#aaff00', '#2d98b3', '#8c40ff',
1080 1080 '#592d39', '#ff8c40', '#354020', '#103640', '#1a0040', '#331a20',
1081 1081 '#331400', '#334d00', '#1d5673', '#583973', '#7f0022', '#4c3626',
1082 1082 '#88cc00', '#36a3d9', '#3d0073', '#d9364c', '#33241a', '#698c23',
1083 1083 '#5995b3', '#300059', '#e57382', '#7f3300', '#366600', '#00aaff',
1084 1084 '#3a1659', '#733941', '#663600', '#74b32d', '#003c59', '#7f53a6',
1085 1085 '#73000f', '#ff8800', '#baf279', '#79caf2', '#291040', '#a6293a',
1086 1086 '#b2742d', '#587339', '#0077b3', '#632699', '#400009', '#d9a66c',
1087 1087 '#294010', '#2d4a59', '#aa00ff', '#4c131b', '#b25f00', '#5ce600',
1088 1088 '#267399', '#a336d9', '#990014', '#664e33', '#86bf60', '#0088ff',
1089 1089 '#7700b3', '#593a16', '#073300', '#1d4b73', '#ac60bf', '#e59539',
1090 1090 '#4f8c46', '#368dd9', '#5c0073'
1091 1091 ]
1092 1092
1093 1093 def rgb_to_hex_color(self, rgb_tuple):
1094 1094 """
1095 1095 Converts an rgb_tuple passed to an hex color.
1096 1096
1097 1097 :param rgb_tuple: tuple with 3 ints represents rgb color space
1098 1098 """
1099 1099 return '#' + ("".join(map(chr, rgb_tuple)).encode('hex'))
1100 1100
1101 1101 def email_to_int_list(self, email_str):
1102 1102 """
1103 1103 Get every byte of the hex digest value of email and turn it to integer.
1104 1104 It's going to be always between 0-255
1105 1105 """
1106 1106 digest = md5_safe(email_str.lower())
1107 1107 return [int(digest[i * 2:i * 2 + 2], 16) for i in range(16)]
1108 1108
1109 1109 def pick_color_bank_index(self, email_str, color_bank):
1110 1110 return self.email_to_int_list(email_str)[0] % len(color_bank)
1111 1111
1112 1112 def str2color(self, email_str):
1113 1113 """
1114 1114 Tries to map in a stable algorithm an email to color
1115 1115
1116 1116 :param email_str:
1117 1117 """
1118 1118 color_bank = self.get_color_bank()
1119 1119 # pick position (module it's length so we always find it in the
1120 1120 # bank even if it's smaller than 256 values
1121 1121 pos = self.pick_color_bank_index(email_str, color_bank)
1122 1122 return color_bank[pos]
1123 1123
1124 1124 def normalize_email(self, email_address):
1125 1125 import unicodedata
1126 1126 # default host used to fill in the fake/missing email
1127 1127 default_host = u'localhost'
1128 1128
1129 1129 if not email_address:
1130 1130 email_address = u'%s@%s' % (User.DEFAULT_USER, default_host)
1131 1131
1132 1132 email_address = safe_unicode(email_address)
1133 1133
1134 1134 if u'@' not in email_address:
1135 1135 email_address = u'%s@%s' % (email_address, default_host)
1136 1136
1137 1137 if email_address.endswith(u'@'):
1138 1138 email_address = u'%s%s' % (email_address, default_host)
1139 1139
1140 1140 email_address = unicodedata.normalize('NFKD', email_address)\
1141 1141 .encode('ascii', 'ignore')
1142 1142 return email_address
1143 1143
1144 1144 def get_initials(self):
1145 1145 """
1146 1146 Returns 2 letter initials calculated based on the input.
1147 1147 The algorithm picks first given email address, and takes first letter
1148 1148 of part before @, and then the first letter of server name. In case
1149 1149 the part before @ is in a format of `somestring.somestring2` it replaces
1150 1150 the server letter with first letter of somestring2
1151 1151
1152 1152 In case function was initialized with both first and lastname, this
1153 1153 overrides the extraction from email by first letter of the first and
1154 1154 last name. We add special logic to that functionality, In case Full name
1155 1155 is compound, like Guido Von Rossum, we use last part of the last name
1156 1156 (Von Rossum) picking `R`.
1157 1157
1158 1158 Function also normalizes the non-ascii characters to they ascii
1159 1159 representation, eg Δ„ => A
1160 1160 """
1161 1161 import unicodedata
1162 1162 # replace non-ascii to ascii
1163 1163 first_name = unicodedata.normalize(
1164 1164 'NFKD', safe_unicode(self.first_name)).encode('ascii', 'ignore')
1165 1165 last_name = unicodedata.normalize(
1166 1166 'NFKD', safe_unicode(self.last_name)).encode('ascii', 'ignore')
1167 1167
1168 1168 # do NFKD encoding, and also make sure email has proper format
1169 1169 email_address = self.normalize_email(self.email_address)
1170 1170
1171 1171 # first push the email initials
1172 1172 prefix, server = email_address.split('@', 1)
1173 1173
1174 1174 # check if prefix is maybe a 'first_name.last_name' syntax
1175 1175 _dot_split = prefix.rsplit('.', 1)
1176 1176 if len(_dot_split) == 2 and _dot_split[1]:
1177 1177 initials = [_dot_split[0][0], _dot_split[1][0]]
1178 1178 else:
1179 1179 initials = [prefix[0], server[0]]
1180 1180
1181 1181 # then try to replace either first_name or last_name
1182 1182 fn_letter = (first_name or " ")[0].strip()
1183 1183 ln_letter = (last_name.split(' ', 1)[-1] or " ")[0].strip()
1184 1184
1185 1185 if fn_letter:
1186 1186 initials[0] = fn_letter
1187 1187
1188 1188 if ln_letter:
1189 1189 initials[1] = ln_letter
1190 1190
1191 1191 return ''.join(initials).upper()
1192 1192
1193 1193 def get_img_data_by_type(self, font_family, img_type):
1194 1194 default_user = """
1195 1195 <svg xmlns="http://www.w3.org/2000/svg"
1196 1196 version="1.1" x="0px" y="0px" width="{size}" height="{size}"
1197 1197 viewBox="-15 -10 439.165 429.164"
1198 1198
1199 1199 xml:space="preserve"
1200 1200 style="background:{background};" >
1201 1201
1202 1202 <path d="M204.583,216.671c50.664,0,91.74-48.075,
1203 1203 91.74-107.378c0-82.237-41.074-107.377-91.74-107.377
1204 1204 c-50.668,0-91.74,25.14-91.74,107.377C112.844,
1205 1205 168.596,153.916,216.671,
1206 1206 204.583,216.671z" fill="{text_color}"/>
1207 1207 <path d="M407.164,374.717L360.88,
1208 1208 270.454c-2.117-4.771-5.836-8.728-10.465-11.138l-71.83-37.392
1209 1209 c-1.584-0.823-3.502-0.663-4.926,0.415c-20.316,
1210 1210 15.366-44.203,23.488-69.076,23.488c-24.877,
1211 1211 0-48.762-8.122-69.078-23.488
1212 1212 c-1.428-1.078-3.346-1.238-4.93-0.415L58.75,
1213 1213 259.316c-4.631,2.41-8.346,6.365-10.465,11.138L2.001,374.717
1214 1214 c-3.191,7.188-2.537,15.412,1.75,22.005c4.285,
1215 1215 6.592,11.537,10.526,19.4,10.526h362.861c7.863,0,15.117-3.936,
1216 1216 19.402-10.527 C409.699,390.129,
1217 1217 410.355,381.902,407.164,374.717z" fill="{text_color}"/>
1218 1218 </svg>""".format(
1219 1219 size=self.size,
1220 1220 background='#979797', # @grey4
1221 1221 text_color=self.text_color,
1222 1222 font_family=font_family)
1223 1223
1224 1224 return {
1225 1225 "default_user": default_user
1226 1226 }[img_type]
1227 1227
1228 1228 def get_img_data(self, svg_type=None):
1229 1229 """
1230 1230 generates the svg metadata for image
1231 1231 """
1232 1232 fonts = [
1233 1233 '-apple-system',
1234 1234 'BlinkMacSystemFont',
1235 1235 'Segoe UI',
1236 1236 'Roboto',
1237 1237 'Oxygen-Sans',
1238 1238 'Ubuntu',
1239 1239 'Cantarell',
1240 1240 'Helvetica Neue',
1241 1241 'sans-serif'
1242 1242 ]
1243 1243 font_family = ','.join(fonts)
1244 1244 if svg_type:
1245 1245 return self.get_img_data_by_type(font_family, svg_type)
1246 1246
1247 1247 initials = self.get_initials()
1248 1248 img_data = """
1249 1249 <svg xmlns="http://www.w3.org/2000/svg" pointer-events="none"
1250 1250 width="{size}" height="{size}"
1251 1251 style="width: 100%; height: 100%; background-color: {background}"
1252 1252 viewBox="0 0 {size} {size}">
1253 1253 <text text-anchor="middle" y="50%" x="50%" dy="0.35em"
1254 1254 pointer-events="auto" fill="{text_color}"
1255 1255 font-family="{font_family}"
1256 1256 style="font-weight: 400; font-size: {f_size}px;">{text}
1257 1257 </text>
1258 1258 </svg>""".format(
1259 1259 size=self.size,
1260 1260 f_size=self.size/2.05, # scale the text inside the box nicely
1261 1261 background=self.background,
1262 1262 text_color=self.text_color,
1263 1263 text=initials.upper(),
1264 1264 font_family=font_family)
1265 1265
1266 1266 return img_data
1267 1267
1268 1268 def generate_svg(self, svg_type=None):
1269 1269 img_data = self.get_img_data(svg_type)
1270 1270 return "data:image/svg+xml;base64,%s" % img_data.encode('base64')
1271 1271
1272 1272
1273 1273 def initials_gravatar(email_address, first_name, last_name, size=30):
1274 1274 svg_type = None
1275 1275 if email_address == User.DEFAULT_USER_EMAIL:
1276 1276 svg_type = 'default_user'
1277 1277 klass = InitialsGravatar(email_address, first_name, last_name, size)
1278 1278 return klass.generate_svg(svg_type=svg_type)
1279 1279
1280 1280
1281 1281 def gravatar_url(email_address, size=30, request=None):
1282 1282 request = get_current_request()
1283 1283 _use_gravatar = request.call_context.visual.use_gravatar
1284 1284 _gravatar_url = request.call_context.visual.gravatar_url
1285 1285
1286 1286 _gravatar_url = _gravatar_url or User.DEFAULT_GRAVATAR_URL
1287 1287
1288 1288 email_address = email_address or User.DEFAULT_USER_EMAIL
1289 1289 if isinstance(email_address, unicode):
1290 1290 # hashlib crashes on unicode items
1291 1291 email_address = safe_str(email_address)
1292 1292
1293 1293 # empty email or default user
1294 1294 if not email_address or email_address == User.DEFAULT_USER_EMAIL:
1295 1295 return initials_gravatar(User.DEFAULT_USER_EMAIL, '', '', size=size)
1296 1296
1297 1297 if _use_gravatar:
1298 1298 # TODO: Disuse pyramid thread locals. Think about another solution to
1299 1299 # get the host and schema here.
1300 1300 request = get_current_request()
1301 1301 tmpl = safe_str(_gravatar_url)
1302 1302 tmpl = tmpl.replace('{email}', email_address)\
1303 1303 .replace('{md5email}', md5_safe(email_address.lower())) \
1304 1304 .replace('{netloc}', request.host)\
1305 1305 .replace('{scheme}', request.scheme)\
1306 1306 .replace('{size}', safe_str(size))
1307 1307 return tmpl
1308 1308 else:
1309 1309 return initials_gravatar(email_address, '', '', size=size)
1310 1310
1311 1311
1312
1313
1314 #==============================================================================
1315 # REPO PAGER, PAGER FOR REPOSITORY
1316 #==============================================================================
1317 class RepoPage(Page):
1318
1319 def __init__(self, collection, page=1, items_per_page=20,
1320 item_count=None, url=None, **kwargs):
1321
1322 """Create a "RepoPage" instance. special pager for paging
1323 repository
1324 """
1325 self._url_generator = url
1326
1327 # Safe the kwargs class-wide so they can be used in the pager() method
1328 self.kwargs = kwargs
1329
1330 # Save a reference to the collection
1331 self.original_collection = collection
1332
1333 self.collection = collection
1334
1335 # The self.page is the number of the current page.
1336 # The first page has the number 1!
1337 try:
1338 self.page = int(page) # make it int() if we get it as a string
1339 except (ValueError, TypeError):
1340 self.page = 1
1341
1342 self.items_per_page = items_per_page
1343
1344 # Unless the user tells us how many items the collections has
1345 # we calculate that ourselves.
1346 if item_count is not None:
1347 self.item_count = item_count
1348 else:
1349 self.item_count = len(self.collection)
1350
1351 # Compute the number of the first and last available page
1352 if self.item_count > 0:
1353 self.first_page = 1
1354 self.page_count = int(math.ceil(float(self.item_count) /
1355 self.items_per_page))
1356 self.last_page = self.first_page + self.page_count - 1
1357
1358 # Make sure that the requested page number is the range of
1359 # valid pages
1360 if self.page > self.last_page:
1361 self.page = self.last_page
1362 elif self.page < self.first_page:
1363 self.page = self.first_page
1364
1365 # Note: the number of items on this page can be less than
1366 # items_per_page if the last page is not full
1367 self.first_item = max(0, (self.item_count) - (self.page *
1368 items_per_page))
1369 self.last_item = ((self.item_count - 1) - items_per_page *
1370 (self.page - 1))
1371
1372 self.items = list(self.collection[self.first_item:self.last_item + 1])
1373
1374 # Links to previous and next page
1375 if self.page > self.first_page:
1376 self.previous_page = self.page - 1
1377 else:
1378 self.previous_page = None
1379
1380 if self.page < self.last_page:
1381 self.next_page = self.page + 1
1382 else:
1383 self.next_page = None
1384
1385 # No items available
1386 else:
1387 self.first_page = None
1388 self.page_count = 0
1389 self.last_page = None
1390 self.first_item = None
1391 self.last_item = None
1392 self.previous_page = None
1393 self.next_page = None
1394 self.items = []
1395
1396 # This is a subclass of the 'list' type. Initialise the list now.
1397 list.__init__(self, reversed(self.items))
1398
1399
1400 1312 def breadcrumb_repo_link(repo):
1401 1313 """
1402 1314 Makes a breadcrumbs path link to repo
1403 1315
1404 1316 ex::
1405 1317 group >> subgroup >> repo
1406 1318
1407 1319 :param repo: a Repository instance
1408 1320 """
1409 1321
1410 1322 path = [
1411 1323 link_to(group.name, route_path('repo_group_home', repo_group_name=group.group_name),
1412 1324 title='last change:{}'.format(format_date(group.last_commit_change)))
1413 1325 for group in repo.groups_with_parents
1414 1326 ] + [
1415 1327 link_to(repo.just_name, route_path('repo_summary', repo_name=repo.repo_name),
1416 1328 title='last change:{}'.format(format_date(repo.last_commit_change)))
1417 1329 ]
1418 1330
1419 1331 return literal(' &raquo; '.join(path))
1420 1332
1421 1333
1422 1334 def breadcrumb_repo_group_link(repo_group):
1423 1335 """
1424 1336 Makes a breadcrumbs path link to repo
1425 1337
1426 1338 ex::
1427 1339 group >> subgroup
1428 1340
1429 1341 :param repo_group: a Repository Group instance
1430 1342 """
1431 1343
1432 1344 path = [
1433 1345 link_to(group.name,
1434 1346 route_path('repo_group_home', repo_group_name=group.group_name),
1435 1347 title='last change:{}'.format(format_date(group.last_commit_change)))
1436 1348 for group in repo_group.parents
1437 1349 ] + [
1438 1350 link_to(repo_group.name,
1439 1351 route_path('repo_group_home', repo_group_name=repo_group.group_name),
1440 1352 title='last change:{}'.format(format_date(repo_group.last_commit_change)))
1441 1353 ]
1442 1354
1443 1355 return literal(' &raquo; '.join(path))
1444 1356
1445 1357
1446 1358 def format_byte_size_binary(file_size):
1447 1359 """
1448 1360 Formats file/folder sizes to standard.
1449 1361 """
1450 1362 if file_size is None:
1451 1363 file_size = 0
1452 1364
1453 1365 formatted_size = format_byte_size(file_size, binary=True)
1454 1366 return formatted_size
1455 1367
1456 1368
1457 1369 def urlify_text(text_, safe=True, **href_attrs):
1458 1370 """
1459 1371 Extract urls from text and make html links out of them
1460 1372 """
1461 1373
1462 1374 url_pat = re.compile(r'''(http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@#.&+]'''
1463 1375 '''|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+)''')
1464 1376
1465 1377 def url_func(match_obj):
1466 1378 url_full = match_obj.groups()[0]
1467 1379 a_options = dict(href_attrs)
1468 1380 a_options['href'] = url_full
1469 1381 a_text = url_full
1470 1382 return HTML.tag("a", a_text, **a_options)
1471 1383
1472 1384 _new_text = url_pat.sub(url_func, text_)
1473 1385
1474 1386 if safe:
1475 1387 return literal(_new_text)
1476 1388 return _new_text
1477 1389
1478 1390
1479 1391 def urlify_commits(text_, repo_name):
1480 1392 """
1481 1393 Extract commit ids from text and make link from them
1482 1394
1483 1395 :param text_:
1484 1396 :param repo_name: repo name to build the URL with
1485 1397 """
1486 1398
1487 1399 url_pat = re.compile(r'(^|\s)([0-9a-fA-F]{12,40})($|\s)')
1488 1400
1489 1401 def url_func(match_obj):
1490 1402 commit_id = match_obj.groups()[1]
1491 1403 pref = match_obj.groups()[0]
1492 1404 suf = match_obj.groups()[2]
1493 1405
1494 1406 tmpl = (
1495 1407 '%(pref)s<a class="tooltip-hovercard %(cls)s" href="%(url)s" data-hovercard-alt="%(hovercard_alt)s" data-hovercard-url="%(hovercard_url)s">'
1496 1408 '%(commit_id)s</a>%(suf)s'
1497 1409 )
1498 1410 return tmpl % {
1499 1411 'pref': pref,
1500 1412 'cls': 'revision-link',
1501 1413 'url': route_url(
1502 1414 'repo_commit', repo_name=repo_name, commit_id=commit_id),
1503 1415 'commit_id': commit_id,
1504 1416 'suf': suf,
1505 1417 'hovercard_alt': 'Commit: {}'.format(commit_id),
1506 1418 'hovercard_url': route_url(
1507 1419 'hovercard_repo_commit', repo_name=repo_name, commit_id=commit_id)
1508 1420 }
1509 1421
1510 1422 new_text = url_pat.sub(url_func, text_)
1511 1423
1512 1424 return new_text
1513 1425
1514 1426
1515 1427 def _process_url_func(match_obj, repo_name, uid, entry,
1516 1428 return_raw_data=False, link_format='html'):
1517 1429 pref = ''
1518 1430 if match_obj.group().startswith(' '):
1519 1431 pref = ' '
1520 1432
1521 1433 issue_id = ''.join(match_obj.groups())
1522 1434
1523 1435 if link_format == 'html':
1524 1436 tmpl = (
1525 1437 '%(pref)s<a class="tooltip %(cls)s" href="%(url)s" title="%(title)s">'
1526 1438 '%(issue-prefix)s%(id-repr)s'
1527 1439 '</a>')
1528 1440 elif link_format == 'html+hovercard':
1529 1441 tmpl = (
1530 1442 '%(pref)s<a class="tooltip-hovercard %(cls)s" href="%(url)s" data-hovercard-url="%(hovercard_url)s">'
1531 1443 '%(issue-prefix)s%(id-repr)s'
1532 1444 '</a>')
1533 1445 elif link_format in ['rst', 'rst+hovercard']:
1534 1446 tmpl = '`%(issue-prefix)s%(id-repr)s <%(url)s>`_'
1535 1447 elif link_format in ['markdown', 'markdown+hovercard']:
1536 1448 tmpl = '[%(pref)s%(issue-prefix)s%(id-repr)s](%(url)s)'
1537 1449 else:
1538 1450 raise ValueError('Bad link_format:{}'.format(link_format))
1539 1451
1540 1452 (repo_name_cleaned,
1541 1453 parent_group_name) = RepoGroupModel()._get_group_name_and_parent(repo_name)
1542 1454
1543 1455 # variables replacement
1544 1456 named_vars = {
1545 1457 'id': issue_id,
1546 1458 'repo': repo_name,
1547 1459 'repo_name': repo_name_cleaned,
1548 1460 'group_name': parent_group_name,
1549 1461 # set dummy keys so we always have them
1550 1462 'hostname': '',
1551 1463 'netloc': '',
1552 1464 'scheme': ''
1553 1465 }
1554 1466
1555 1467 request = get_current_request()
1556 1468 if request:
1557 1469 # exposes, hostname, netloc, scheme
1558 1470 host_data = get_host_info(request)
1559 1471 named_vars.update(host_data)
1560 1472
1561 1473 # named regex variables
1562 1474 named_vars.update(match_obj.groupdict())
1563 1475 _url = string.Template(entry['url']).safe_substitute(**named_vars)
1564 1476 desc = string.Template(entry['desc']).safe_substitute(**named_vars)
1565 1477 hovercard_url = string.Template(entry.get('hovercard_url', '')).safe_substitute(**named_vars)
1566 1478
1567 1479 def quote_cleaner(input_str):
1568 1480 """Remove quotes as it's HTML"""
1569 1481 return input_str.replace('"', '')
1570 1482
1571 1483 data = {
1572 1484 'pref': pref,
1573 1485 'cls': quote_cleaner('issue-tracker-link'),
1574 1486 'url': quote_cleaner(_url),
1575 1487 'id-repr': issue_id,
1576 1488 'issue-prefix': entry['pref'],
1577 1489 'serv': entry['url'],
1578 1490 'title': desc,
1579 1491 'hovercard_url': hovercard_url
1580 1492 }
1581 1493
1582 1494 if return_raw_data:
1583 1495 return {
1584 1496 'id': issue_id,
1585 1497 'url': _url
1586 1498 }
1587 1499 return tmpl % data
1588 1500
1589 1501
1590 1502 def get_active_pattern_entries(repo_name):
1591 1503 repo = None
1592 1504 if repo_name:
1593 1505 # Retrieving repo_name to avoid invalid repo_name to explode on
1594 1506 # IssueTrackerSettingsModel but still passing invalid name further down
1595 1507 repo = Repository.get_by_repo_name(repo_name, cache=True)
1596 1508
1597 1509 settings_model = IssueTrackerSettingsModel(repo=repo)
1598 1510 active_entries = settings_model.get_settings(cache=True)
1599 1511 return active_entries
1600 1512
1601 1513
1602 1514 def process_patterns(text_string, repo_name, link_format='html', active_entries=None):
1603 1515
1604 1516 allowed_formats = ['html', 'rst', 'markdown',
1605 1517 'html+hovercard', 'rst+hovercard', 'markdown+hovercard']
1606 1518 if link_format not in allowed_formats:
1607 1519 raise ValueError('Link format can be only one of:{} got {}'.format(
1608 1520 allowed_formats, link_format))
1609 1521
1610 1522 active_entries = active_entries or get_active_pattern_entries(repo_name)
1611 1523 issues_data = []
1612 1524 new_text = text_string
1613 1525
1614 1526 log.debug('Got %s entries to process', len(active_entries))
1615 1527 for uid, entry in active_entries.items():
1616 1528 log.debug('found issue tracker entry with uid %s', uid)
1617 1529
1618 1530 if not (entry['pat'] and entry['url']):
1619 1531 log.debug('skipping due to missing data')
1620 1532 continue
1621 1533
1622 1534 log.debug('issue tracker entry: uid: `%s` PAT:%s URL:%s PREFIX:%s',
1623 1535 uid, entry['pat'], entry['url'], entry['pref'])
1624 1536
1625 1537 try:
1626 1538 pattern = re.compile(r'%s' % entry['pat'])
1627 1539 except re.error:
1628 1540 log.exception('issue tracker pattern: `%s` failed to compile', entry['pat'])
1629 1541 continue
1630 1542
1631 1543 data_func = partial(
1632 1544 _process_url_func, repo_name=repo_name, entry=entry, uid=uid,
1633 1545 return_raw_data=True)
1634 1546
1635 1547 for match_obj in pattern.finditer(text_string):
1636 1548 issues_data.append(data_func(match_obj))
1637 1549
1638 1550 url_func = partial(
1639 1551 _process_url_func, repo_name=repo_name, entry=entry, uid=uid,
1640 1552 link_format=link_format)
1641 1553
1642 1554 new_text = pattern.sub(url_func, new_text)
1643 1555 log.debug('processed prefix:uid `%s`', uid)
1644 1556
1645 1557 # finally use global replace, eg !123 -> pr-link, those will not catch
1646 1558 # if already similar pattern exists
1647 1559 server_url = '${scheme}://${netloc}'
1648 1560 pr_entry = {
1649 1561 'pref': '!',
1650 1562 'url': server_url + '/_admin/pull-requests/${id}',
1651 1563 'desc': 'Pull Request !${id}',
1652 1564 'hovercard_url': server_url + '/_hovercard/pull_request/${id}'
1653 1565 }
1654 1566 pr_url_func = partial(
1655 1567 _process_url_func, repo_name=repo_name, entry=pr_entry, uid=None,
1656 1568 link_format=link_format+'+hovercard')
1657 1569 new_text = re.compile(r'(?:(?:^!)|(?: !))(\d+)').sub(pr_url_func, new_text)
1658 1570 log.debug('processed !pr pattern')
1659 1571
1660 1572 return new_text, issues_data
1661 1573
1662 1574
1663 1575 def urlify_commit_message(commit_text, repository=None, active_pattern_entries=None):
1664 1576 """
1665 1577 Parses given text message and makes proper links.
1666 1578 issues are linked to given issue-server, and rest is a commit link
1667 1579 """
1668 1580 def escaper(_text):
1669 1581 return _text.replace('<', '&lt;').replace('>', '&gt;')
1670 1582
1671 1583 new_text = escaper(commit_text)
1672 1584
1673 1585 # extract http/https links and make them real urls
1674 1586 new_text = urlify_text(new_text, safe=False)
1675 1587
1676 1588 # urlify commits - extract commit ids and make link out of them, if we have
1677 1589 # the scope of repository present.
1678 1590 if repository:
1679 1591 new_text = urlify_commits(new_text, repository)
1680 1592
1681 1593 # process issue tracker patterns
1682 1594 new_text, issues = process_patterns(new_text, repository or '',
1683 1595 active_entries=active_pattern_entries)
1684 1596
1685 1597 return literal(new_text)
1686 1598
1687 1599
1688 1600 def render_binary(repo_name, file_obj):
1689 1601 """
1690 1602 Choose how to render a binary file
1691 1603 """
1692 1604
1693 1605 filename = file_obj.name
1694 1606
1695 1607 # images
1696 1608 for ext in ['*.png', '*.jpg', '*.ico', '*.gif']:
1697 1609 if fnmatch.fnmatch(filename, pat=ext):
1698 1610 alt = escape(filename)
1699 1611 src = route_path(
1700 1612 'repo_file_raw', repo_name=repo_name,
1701 1613 commit_id=file_obj.commit.raw_id,
1702 1614 f_path=file_obj.path)
1703 1615 return literal(
1704 1616 '<img class="rendered-binary" alt="{}" src="{}">'.format(alt, src))
1705 1617
1706 1618
1707 1619 def renderer_from_filename(filename, exclude=None):
1708 1620 """
1709 1621 choose a renderer based on filename, this works only for text based files
1710 1622 """
1711 1623
1712 1624 # ipython
1713 1625 for ext in ['*.ipynb']:
1714 1626 if fnmatch.fnmatch(filename, pat=ext):
1715 1627 return 'jupyter'
1716 1628
1717 1629 is_markup = MarkupRenderer.renderer_from_filename(filename, exclude=exclude)
1718 1630 if is_markup:
1719 1631 return is_markup
1720 1632 return None
1721 1633
1722 1634
1723 1635 def render(source, renderer='rst', mentions=False, relative_urls=None,
1724 1636 repo_name=None):
1725 1637
1726 1638 def maybe_convert_relative_links(html_source):
1727 1639 if relative_urls:
1728 1640 return relative_links(html_source, relative_urls)
1729 1641 return html_source
1730 1642
1731 1643 if renderer == 'plain':
1732 1644 return literal(
1733 1645 MarkupRenderer.plain(source, leading_newline=False))
1734 1646
1735 1647 elif renderer == 'rst':
1736 1648 if repo_name:
1737 1649 # process patterns on comments if we pass in repo name
1738 1650 source, issues = process_patterns(
1739 1651 source, repo_name, link_format='rst')
1740 1652
1741 1653 return literal(
1742 1654 '<div class="rst-block">%s</div>' %
1743 1655 maybe_convert_relative_links(
1744 1656 MarkupRenderer.rst(source, mentions=mentions)))
1745 1657
1746 1658 elif renderer == 'markdown':
1747 1659 if repo_name:
1748 1660 # process patterns on comments if we pass in repo name
1749 1661 source, issues = process_patterns(
1750 1662 source, repo_name, link_format='markdown')
1751 1663
1752 1664 return literal(
1753 1665 '<div class="markdown-block">%s</div>' %
1754 1666 maybe_convert_relative_links(
1755 1667 MarkupRenderer.markdown(source, flavored=True,
1756 1668 mentions=mentions)))
1757 1669
1758 1670 elif renderer == 'jupyter':
1759 1671 return literal(
1760 1672 '<div class="ipynb">%s</div>' %
1761 1673 maybe_convert_relative_links(
1762 1674 MarkupRenderer.jupyter(source)))
1763 1675
1764 1676 # None means just show the file-source
1765 1677 return None
1766 1678
1767 1679
1768 1680 def commit_status(repo, commit_id):
1769 1681 return ChangesetStatusModel().get_status(repo, commit_id)
1770 1682
1771 1683
1772 1684 def commit_status_lbl(commit_status):
1773 1685 return dict(ChangesetStatus.STATUSES).get(commit_status)
1774 1686
1775 1687
1776 1688 def commit_time(repo_name, commit_id):
1777 1689 repo = Repository.get_by_repo_name(repo_name)
1778 1690 commit = repo.get_commit(commit_id=commit_id)
1779 1691 return commit.date
1780 1692
1781 1693
1782 1694 def get_permission_name(key):
1783 1695 return dict(Permission.PERMS).get(key)
1784 1696
1785 1697
1786 1698 def journal_filter_help(request):
1787 1699 _ = request.translate
1788 1700 from rhodecode.lib.audit_logger import ACTIONS
1789 1701 actions = '\n'.join(textwrap.wrap(', '.join(sorted(ACTIONS.keys())), 80))
1790 1702
1791 1703 return _(
1792 1704 'Example filter terms:\n' +
1793 1705 ' repository:vcs\n' +
1794 1706 ' username:marcin\n' +
1795 1707 ' username:(NOT marcin)\n' +
1796 1708 ' action:*push*\n' +
1797 1709 ' ip:127.0.0.1\n' +
1798 1710 ' date:20120101\n' +
1799 1711 ' date:[20120101100000 TO 20120102]\n' +
1800 1712 '\n' +
1801 1713 'Actions: {actions}\n' +
1802 1714 '\n' +
1803 1715 'Generate wildcards using \'*\' character:\n' +
1804 1716 ' "repository:vcs*" - search everything starting with \'vcs\'\n' +
1805 1717 ' "repository:*vcs*" - search for repository containing \'vcs\'\n' +
1806 1718 '\n' +
1807 1719 'Optional AND / OR operators in queries\n' +
1808 1720 ' "repository:vcs OR repository:test"\n' +
1809 1721 ' "username:test AND repository:test*"\n'
1810 1722 ).format(actions=actions)
1811 1723
1812 1724
1813 1725 def not_mapped_error(repo_name):
1814 1726 from rhodecode.translation import _
1815 1727 flash(_('%s repository is not mapped to db perhaps'
1816 1728 ' it was created or renamed from the filesystem'
1817 1729 ' please run the application again'
1818 1730 ' in order to rescan repositories') % repo_name, category='error')
1819 1731
1820 1732
1821 1733 def ip_range(ip_addr):
1822 1734 from rhodecode.model.db import UserIpMap
1823 1735 s, e = UserIpMap._get_ip_range(ip_addr)
1824 1736 return '%s - %s' % (s, e)
1825 1737
1826 1738
1827 1739 def form(url, method='post', needs_csrf_token=True, **attrs):
1828 1740 """Wrapper around webhelpers.tags.form to prevent CSRF attacks."""
1829 1741 if method.lower() != 'get' and needs_csrf_token:
1830 1742 raise Exception(
1831 1743 'Forms to POST/PUT/DELETE endpoints should have (in general) a ' +
1832 1744 'CSRF token. If the endpoint does not require such token you can ' +
1833 1745 'explicitly set the parameter needs_csrf_token to false.')
1834 1746
1835 1747 return insecure_form(url, method=method, **attrs)
1836 1748
1837 1749
1838 1750 def secure_form(form_url, method="POST", multipart=False, **attrs):
1839 1751 """Start a form tag that points the action to an url. This
1840 1752 form tag will also include the hidden field containing
1841 1753 the auth token.
1842 1754
1843 1755 The url options should be given either as a string, or as a
1844 1756 ``url()`` function. The method for the form defaults to POST.
1845 1757
1846 1758 Options:
1847 1759
1848 1760 ``multipart``
1849 1761 If set to True, the enctype is set to "multipart/form-data".
1850 1762 ``method``
1851 1763 The method to use when submitting the form, usually either
1852 1764 "GET" or "POST". If "PUT", "DELETE", or another verb is used, a
1853 1765 hidden input with name _method is added to simulate the verb
1854 1766 over POST.
1855 1767
1856 1768 """
1857 1769
1858 1770 if 'request' in attrs:
1859 1771 session = attrs['request'].session
1860 1772 del attrs['request']
1861 1773 else:
1862 1774 raise ValueError(
1863 1775 'Calling this form requires request= to be passed as argument')
1864 1776
1865 1777 _form = insecure_form(form_url, method, multipart, **attrs)
1866 1778 token = literal(
1867 1779 '<input type="hidden" name="{}" value="{}">'.format(
1868 1780 csrf_token_key, get_csrf_token(session)))
1869 1781
1870 1782 return literal("%s\n%s" % (_form, token))
1871 1783
1872 1784
1873 1785 def dropdownmenu(name, selected, options, enable_filter=False, **attrs):
1874 1786 select_html = select(name, selected, options, **attrs)
1875 1787
1876 1788 select2 = """
1877 1789 <script>
1878 1790 $(document).ready(function() {
1879 1791 $('#%s').select2({
1880 1792 containerCssClass: 'drop-menu %s',
1881 1793 dropdownCssClass: 'drop-menu-dropdown',
1882 1794 dropdownAutoWidth: true%s
1883 1795 });
1884 1796 });
1885 1797 </script>
1886 1798 """
1887 1799
1888 1800 filter_option = """,
1889 1801 minimumResultsForSearch: -1
1890 1802 """
1891 1803 input_id = attrs.get('id') or name
1892 1804 extra_classes = ' '.join(attrs.pop('extra_classes', []))
1893 1805 filter_enabled = "" if enable_filter else filter_option
1894 1806 select_script = literal(select2 % (input_id, extra_classes, filter_enabled))
1895 1807
1896 1808 return literal(select_html+select_script)
1897 1809
1898 1810
1899 1811 def get_visual_attr(tmpl_context_var, attr_name):
1900 1812 """
1901 1813 A safe way to get a variable from visual variable of template context
1902 1814
1903 1815 :param tmpl_context_var: instance of tmpl_context, usually present as `c`
1904 1816 :param attr_name: name of the attribute we fetch from the c.visual
1905 1817 """
1906 1818 visual = getattr(tmpl_context_var, 'visual', None)
1907 1819 if not visual:
1908 1820 return
1909 1821 else:
1910 1822 return getattr(visual, attr_name, None)
1911 1823
1912 1824
1913 1825 def get_last_path_part(file_node):
1914 1826 if not file_node.path:
1915 1827 return u'/'
1916 1828
1917 1829 path = safe_unicode(file_node.path.split('/')[-1])
1918 1830 return u'../' + path
1919 1831
1920 1832
1921 1833 def route_url(*args, **kwargs):
1922 1834 """
1923 1835 Wrapper around pyramids `route_url` (fully qualified url) function.
1924 1836 """
1925 1837 req = get_current_request()
1926 1838 return req.route_url(*args, **kwargs)
1927 1839
1928 1840
1929 1841 def route_path(*args, **kwargs):
1930 1842 """
1931 1843 Wrapper around pyramids `route_path` function.
1932 1844 """
1933 1845 req = get_current_request()
1934 1846 return req.route_path(*args, **kwargs)
1935 1847
1936 1848
1937 1849 def route_path_or_none(*args, **kwargs):
1938 1850 try:
1939 1851 return route_path(*args, **kwargs)
1940 1852 except KeyError:
1941 1853 return None
1942 1854
1943 1855
1944 1856 def current_route_path(request, **kw):
1945 1857 new_args = request.GET.mixed()
1946 1858 new_args.update(kw)
1947 1859 return request.current_route_path(_query=new_args)
1948 1860
1949 1861
1950 1862 def curl_api_example(method, args):
1951 1863 args_json = json.dumps(OrderedDict([
1952 1864 ('id', 1),
1953 1865 ('auth_token', 'SECRET'),
1954 1866 ('method', method),
1955 1867 ('args', args)
1956 1868 ]))
1957 1869
1958 1870 return "curl {api_url} -X POST -H 'content-type:text/plain' --data-binary '{args_json}'".format(
1959 1871 api_url=route_url('apiv2'),
1960 1872 args_json=args_json
1961 1873 )
1962 1874
1963 1875
1964 1876 def api_call_example(method, args):
1965 1877 """
1966 1878 Generates an API call example via CURL
1967 1879 """
1968 1880 curl_call = curl_api_example(method, args)
1969 1881
1970 1882 return literal(
1971 1883 curl_call +
1972 1884 "<br/><br/>SECRET can be found in <a href=\"{token_url}\">auth-tokens</a> page, "
1973 1885 "and needs to be of `api calls` role."
1974 1886 .format(token_url=route_url('my_account_auth_tokens')))
1975 1887
1976 1888
1977 1889 def notification_description(notification, request):
1978 1890 """
1979 1891 Generate notification human readable description based on notification type
1980 1892 """
1981 1893 from rhodecode.model.notification import NotificationModel
1982 1894 return NotificationModel().make_description(
1983 1895 notification, translate=request.translate)
1984 1896
1985 1897
1986 1898 def go_import_header(request, db_repo=None):
1987 1899 """
1988 1900 Creates a header for go-import functionality in Go Lang
1989 1901 """
1990 1902
1991 1903 if not db_repo:
1992 1904 return
1993 1905 if 'go-get' not in request.GET:
1994 1906 return
1995 1907
1996 1908 clone_url = db_repo.clone_url()
1997 1909 prefix = re.split(r'^https?:\/\/', clone_url)[-1]
1998 1910 # we have a repo and go-get flag,
1999 1911 return literal('<meta name="go-import" content="{} {} {}">'.format(
2000 1912 prefix, db_repo.repo_type, clone_url))
2001 1913
2002 1914
2003 1915 def reviewer_as_json(*args, **kwargs):
2004 1916 from rhodecode.apps.repository.utils import reviewer_as_json as _reviewer_as_json
2005 1917 return _reviewer_as_json(*args, **kwargs)
2006 1918
2007 1919
2008 1920 def get_repo_view_type(request):
2009 1921 route_name = request.matched_route.name
2010 1922 route_to_view_type = {
2011 1923 'repo_changelog': 'commits',
2012 1924 'repo_commits': 'commits',
2013 1925 'repo_files': 'files',
2014 1926 'repo_summary': 'summary',
2015 1927 'repo_commit': 'commit'
2016 1928 }
2017 1929
2018 1930 return route_to_view_type.get(route_name)
2019 1931
2020 1932
2021 1933 def is_active(menu_entry, selected):
2022 1934 """
2023 1935 Returns active class for selecting menus in templates
2024 1936 <li class=${h.is_active('settings', current_active)}></li>
2025 1937 """
2026 1938 if not isinstance(menu_entry, list):
2027 1939 menu_entry = [menu_entry]
2028 1940
2029 1941 if selected in menu_entry:
2030 1942 return "active"
This diff has been collapsed as it changes many lines, (1143 lines changed) Show them Hide them
@@ -1,165 +1,1056 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 # Copyright (c) 2007-2012 Christoph Haas <email@christoph-haas.de>
4 # NOTE: MIT license based code, backported and edited by RhodeCode GmbH
5
6 """
7 paginate: helps split up large collections into individual pages
8 ================================================================
9
10 What is pagination?
11 ---------------------
12
13 This module helps split large lists of items into pages. The user is shown one page at a time and
14 can navigate to other pages. Imagine you are offering a company phonebook and let the user search
15 the entries. The entire search result may contains 23 entries but you want to display no more than
16 10 entries at once. The first page contains entries 1-10, the second 11-20 and the third 21-23.
17 Each "Page" instance represents the items of one of these three pages.
18
19 See the documentation of the "Page" class for more information.
20
21 How do I use it?
22 ------------------
23
24 A page of items is represented by the *Page* object. A *Page* gets initialized with these arguments:
25
26 - The collection of items to pick a range from. Usually just a list.
27 - The page number you want to display. Default is 1: the first page.
28
29 Now we can make up a collection and create a Page instance of it::
30
31 # Create a sample collection of 1000 items
32 >> my_collection = range(1000)
33
34 # Create a Page object for the 3rd page (20 items per page is the default)
35 >> my_page = Page(my_collection, page=3)
36
37 # The page object can be printed as a string to get its details
38 >> str(my_page)
39 Page:
40 Collection type: <type 'range'>
41 Current page: 3
42 First item: 41
43 Last item: 60
44 First page: 1
45 Last page: 50
46 Previous page: 2
47 Next page: 4
48 Items per page: 20
49 Number of items: 1000
50 Number of pages: 50
51
52 # Print a list of items on the current page
53 >> my_page.items
54 [40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59]
55
56 # The *Page* object can be used as an iterator:
57 >> for my_item in my_page: print(my_item)
58 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59
59
60 # The .pager() method returns an HTML fragment with links to surrounding pages.
61 >> my_page.pager(url="http://example.org/foo/page=$page")
62
63 <a href="http://example.org/foo/page=1">1</a>
64 <a href="http://example.org/foo/page=2">2</a>
65 3
66 <a href="http://example.org/foo/page=4">4</a>
67 <a href="http://example.org/foo/page=5">5</a>
68 ..
69 <a href="http://example.org/foo/page=50">50</a>'
70
71 # Without the HTML it would just look like:
72 # 1 2 [3] 4 5 .. 50
73
74 # The pager can be customized:
75 >> my_page.pager('$link_previous ~3~ $link_next (Page $page of $page_count)',
76 url="http://example.org/foo/page=$page")
77
78 <a href="http://example.org/foo/page=2">&lt;</a>
79 <a href="http://example.org/foo/page=1">1</a>
80 <a href="http://example.org/foo/page=2">2</a>
81 3
82 <a href="http://example.org/foo/page=4">4</a>
83 <a href="http://example.org/foo/page=5">5</a>
84 <a href="http://example.org/foo/page=6">6</a>
85 ..
86 <a href="http://example.org/foo/page=50">50</a>
87 <a href="http://example.org/foo/page=4">&gt;</a>
88 (Page 3 of 50)
89
90 # Without the HTML it would just look like:
91 # 1 2 [3] 4 5 6 .. 50 > (Page 3 of 50)
92
93 # The url argument to the pager method can be omitted when an url_maker is
94 # given during instantiation:
95 >> my_page = Page(my_collection, page=3,
96 url_maker=lambda p: "http://example.org/%s" % p)
97 >> page.pager()
98
99 There are some interesting parameters that customize the Page's behavior. See the documentation on
100 ``Page`` and ``Page.pager()``.
101
102
103 Notes
104 -------
105
106 Page numbers and item numbers start at 1. This concept has been used because users expect that the
107 first page has number 1 and the first item on a page also has number 1. So if you want to use the
108 page's items by their index number please note that you have to subtract 1.
109 """
110
111 import re
112 import sys
113 from string import Template
114 from webhelpers2.html import literal
115
116 # are we running at least python 3.x ?
117 PY3 = sys.version_info[0] >= 3
118
119 if PY3:
120 unicode = str
121
122
123 def make_html_tag(tag, text=None, **params):
124 """Create an HTML tag string.
125
126 tag
127 The HTML tag to use (e.g. 'a', 'span' or 'div')
128
129 text
130 The text to enclose between opening and closing tag. If no text is specified then only
131 the opening tag is returned.
132
133 Example::
134 make_html_tag('a', text="Hello", href="/another/page")
135 -> <a href="/another/page">Hello</a>
136
137 To use reserved Python keywords like "class" as a parameter prepend it with
138 an underscore. Instead of "class='green'" use "_class='green'".
139
140 Warning: Quotes and apostrophes are not escaped."""
141 params_string = ""
142
143 # Parameters are passed. Turn the dict into a string like "a=1 b=2 c=3" string.
144 for key, value in sorted(params.items()):
145 # Strip off a leading underscore from the attribute's key to allow attributes like '_class'
146 # to be used as a CSS class specification instead of the reserved Python keyword 'class'.
147 key = key.lstrip("_")
148
149 params_string += u' {0}="{1}"'.format(key, value)
150
151 # Create the tag string
152 tag_string = u"<{0}{1}>".format(tag, params_string)
153
154 # Add text and closing tag if required.
155 if text:
156 tag_string += u"{0}</{1}>".format(text, tag)
157
158 return tag_string
159
160
161 # Since the items on a page are mainly a list we subclass the "list" type
162 class _Page(list):
163 """A list/iterator representing the items on one page of a larger collection.
164
165 An instance of the "Page" class is created from a _collection_ which is any
166 list-like object that allows random access to its elements.
167
168 The instance works as an iterator running from the first item to the last item on the given
169 page. The Page.pager() method creates a link list allowing the user to go to other pages.
170
171 A "Page" does not only carry the items on a certain page. It gives you additional information
172 about the page in these "Page" object attributes:
173
174 item_count
175 Number of items in the collection
176
177 **WARNING:** Unless you pass in an item_count, a count will be
178 performed on the collection every time a Page instance is created.
179
180 page
181 Number of the current page
182
183 items_per_page
184 Maximal number of items displayed on a page
185
186 first_page
187 Number of the first page - usually 1 :)
188
189 last_page
190 Number of the last page
191
192 previous_page
193 Number of the previous page. If this is the first page it returns None.
194
195 next_page
196 Number of the next page. If this is the last page it returns None.
197
198 page_count
199 Number of pages
200
201 items
202 Sequence/iterator of items on the current page
203
204 first_item
205 Index of first item on the current page - starts with 1
206
207 last_item
208 Index of last item on the current page
209 """
210
211 def __init__(
212 self,
213 collection,
214 page=1,
215 items_per_page=20,
216 item_count=None,
217 wrapper_class=None,
218 url_maker=None,
219 bar_size=10,
220 **kwargs
221 ):
222 """Create a "Page" instance.
223
224 Parameters:
225
226 collection
227 Sequence representing the collection of items to page through.
228
229 page
230 The requested page number - starts with 1. Default: 1.
231
232 items_per_page
233 The maximal number of items to be displayed per page.
234 Default: 20.
235
236 item_count (optional)
237 The total number of items in the collection - if known.
238 If this parameter is not given then the paginator will count
239 the number of elements in the collection every time a "Page"
240 is created. Giving this parameter will speed up things. In a busy
241 real-life application you may want to cache the number of items.
242
243 url_maker (optional)
244 Callback to generate the URL of other pages, given its numbers.
245 Must accept one int parameter and return a URI string.
246
247 bar_size
248 maximum size of rendered pages numbers within radius
249
250 """
251 if collection is not None:
252 if wrapper_class is None:
253 # Default case. The collection is already a list-type object.
254 self.collection = collection
255 else:
256 # Special case. A custom wrapper class is used to access elements of the collection.
257 self.collection = wrapper_class(collection)
258 else:
259 self.collection = []
260
261 self.collection_type = type(collection)
262
263 if url_maker is not None:
264 self.url_maker = url_maker
265 else:
266 self.url_maker = self._default_url_maker
267 self.bar_size = bar_size
268 # Assign kwargs to self
269 self.kwargs = kwargs
270
271 # The self.page is the number of the current page.
272 # The first page has the number 1!
273 try:
274 self.page = int(page) # make it int() if we get it as a string
275 except (ValueError, TypeError):
276 self.page = 1
277 # normally page should be always at least 1 but the original maintainer
278 # decided that for empty collection and empty page it can be...0? (based on tests)
279 # preserving behavior for BW compat
280 if self.page < 1:
281 self.page = 1
282
283 self.items_per_page = items_per_page
284
285 # We subclassed "list" so we need to call its init() method
286 # and fill the new list with the items to be displayed on the page.
287 # We use list() so that the items on the current page are retrieved
288 # only once. In an SQL context that could otherwise lead to running the
289 # same SQL query every time items would be accessed.
290 # We do this here, prior to calling len() on the collection so that a
291 # wrapper class can execute a query with the knowledge of what the
292 # slice will be (for efficiency) and, in the same query, ask for the
293 # total number of items and only execute one query.
294 try:
295 first = (self.page - 1) * items_per_page
296 last = first + items_per_page
297 self.items = list(self.collection[first:last])
298 except TypeError:
299 raise TypeError(
300 "Your collection of type {} cannot be handled "
301 "by paginate.".format(type(self.collection))
302 )
303
304 # Unless the user tells us how many items the collections has
305 # we calculate that ourselves.
306 if item_count is not None:
307 self.item_count = item_count
308 else:
309 self.item_count = len(self.collection)
310
311 # Compute the number of the first and last available page
312 if self.item_count > 0:
313 self.first_page = 1
314 self.page_count = ((self.item_count - 1) // self.items_per_page) + 1
315 self.last_page = self.first_page + self.page_count - 1
316
317 # Make sure that the requested page number is the range of valid pages
318 if self.page > self.last_page:
319 self.page = self.last_page
320 elif self.page < self.first_page:
321 self.page = self.first_page
322
323 # Note: the number of items on this page can be less than
324 # items_per_page if the last page is not full
325 self.first_item = (self.page - 1) * items_per_page + 1
326 self.last_item = min(self.first_item + items_per_page - 1, self.item_count)
327
328 # Links to previous and next page
329 if self.page > self.first_page:
330 self.previous_page = self.page - 1
331 else:
332 self.previous_page = None
333
334 if self.page < self.last_page:
335 self.next_page = self.page + 1
336 else:
337 self.next_page = None
338
339 # No items available
340 else:
341 self.first_page = None
342 self.page_count = 0
343 self.last_page = None
344 self.first_item = None
345 self.last_item = None
346 self.previous_page = None
347 self.next_page = None
348 self.items = []
349
350 # This is a subclass of the 'list' type. Initialise the list now.
351 list.__init__(self, self.items)
352
353 def __str__(self):
354 return (
355 "Page:\n"
356 "Collection type: {0.collection_type}\n"
357 "Current page: {0.page}\n"
358 "First item: {0.first_item}\n"
359 "Last item: {0.last_item}\n"
360 "First page: {0.first_page}\n"
361 "Last page: {0.last_page}\n"
362 "Previous page: {0.previous_page}\n"
363 "Next page: {0.next_page}\n"
364 "Items per page: {0.items_per_page}\n"
365 "Total number of items: {0.item_count}\n"
366 "Number of pages: {0.page_count}\n"
367 ).format(self)
368
369 def __repr__(self):
370 return "<paginate.Page: Page {0}/{1}>".format(self.page, self.page_count)
371
372 def pager(
373 self,
374 tmpl_format="~2~",
375 url=None,
376 show_if_single_page=False,
377 separator=" ",
378 symbol_first="&lt;&lt;",
379 symbol_last="&gt;&gt;",
380 symbol_previous="&lt;",
381 symbol_next="&gt;",
382 link_attr=None,
383 curpage_attr=None,
384 dotdot_attr=None,
385 link_tag=None,
386 ):
387 """
388 Return string with links to other pages (e.g. '1 .. 5 6 7 [8] 9 10 11 .. 50').
389
390 tmpl_format:
391 Format string that defines how the pager is rendered. The string
392 can contain the following $-tokens that are substituted by the
393 string.Template module:
394
395 - $first_page: number of first reachable page
396 - $last_page: number of last reachable page
397 - $page: number of currently selected page
398 - $page_count: number of reachable pages
399 - $items_per_page: maximal number of items per page
400 - $first_item: index of first item on the current page
401 - $last_item: index of last item on the current page
402 - $item_count: total number of items
403 - $link_first: link to first page (unless this is first page)
404 - $link_last: link to last page (unless this is last page)
405 - $link_previous: link to previous page (unless this is first page)
406 - $link_next: link to next page (unless this is last page)
407
408 To render a range of pages the token '~3~' can be used. The
409 number sets the radius of pages around the current page.
410 Example for a range with radius 3:
411
412 '1 .. 5 6 7 [8] 9 10 11 .. 50'
413
414 Default: '~2~'
415
416 url
417 The URL that page links will point to. Make sure it contains the string
418 $page which will be replaced by the actual page number.
419 Must be given unless a url_maker is specified to __init__, in which
420 case this parameter is ignored.
421
422 symbol_first
423 String to be displayed as the text for the $link_first link above.
424
425 Default: '&lt;&lt;' (<<)
426
427 symbol_last
428 String to be displayed as the text for the $link_last link above.
429
430 Default: '&gt;&gt;' (>>)
431
432 symbol_previous
433 String to be displayed as the text for the $link_previous link above.
434
435 Default: '&lt;' (<)
436
437 symbol_next
438 String to be displayed as the text for the $link_next link above.
439
440 Default: '&gt;' (>)
441
442 separator:
443 String that is used to separate page links/numbers in the above range of pages.
444
445 Default: ' '
446
447 show_if_single_page:
448 if True the navigator will be shown even if there is only one page.
449
450 Default: False
451
452 link_attr (optional)
453 A dictionary of attributes that get added to A-HREF links pointing to other pages. Can
454 be used to define a CSS style or class to customize the look of links.
455
456 Example: { 'style':'border: 1px solid green' }
457 Example: { 'class':'pager_link' }
458
459 curpage_attr (optional)
460 A dictionary of attributes that get added to the current page number in the pager (which
461 is obviously not a link). If this dictionary is not empty then the elements will be
462 wrapped in a SPAN tag with the given attributes.
463
464 Example: { 'style':'border: 3px solid blue' }
465 Example: { 'class':'pager_curpage' }
466
467 dotdot_attr (optional)
468 A dictionary of attributes that get added to the '..' string in the pager (which is
469 obviously not a link). If this dictionary is not empty then the elements will be wrapped
470 in a SPAN tag with the given attributes.
471
472 Example: { 'style':'color: #808080' }
473 Example: { 'class':'pager_dotdot' }
474
475 link_tag (optional)
476 A callable that accepts single argument `page` (page link information)
477 and generates string with html that represents the link for specific page.
478 Page objects are supplied from `link_map()` so the keys are the same.
479
480
481 """
482 link_attr = link_attr or {}
483 curpage_attr = curpage_attr or {}
484 dotdot_attr = dotdot_attr or {}
485 self.curpage_attr = curpage_attr
486 self.separator = separator
487 self.link_attr = link_attr
488 self.dotdot_attr = dotdot_attr
489 self.url = url
490 self.link_tag = link_tag or self.default_link_tag
491
492 # Don't show navigator if there is no more than one page
493 if self.page_count == 0 or (self.page_count == 1 and not show_if_single_page):
494 return ""
495
496 regex_res = re.search(r"~(\d+)~", tmpl_format)
497 if regex_res:
498 radius = regex_res.group(1)
499 else:
500 radius = 2
501
502 self.radius = int(radius)
503 link_map = self.link_map(
504 tmpl_format=tmpl_format,
505 url=url,
506 show_if_single_page=show_if_single_page,
507 separator=separator,
508 symbol_first=symbol_first,
509 symbol_last=symbol_last,
510 symbol_previous=symbol_previous,
511 symbol_next=symbol_next,
512 link_attr=link_attr,
513 curpage_attr=curpage_attr,
514 dotdot_attr=dotdot_attr,
515 link_tag=link_tag,
516 )
517 links_markup = self._range(link_map, self.radius)
518
519 # Replace ~...~ in token tmpl_format by range of pages
520 result = re.sub(r"~(\d+)~", links_markup, tmpl_format)
521
522 link_first = (
523 self.page > self.first_page and self.link_tag(link_map["first_page"]) or ""
524 )
525 link_last = (
526 self.page < self.last_page and self.link_tag(link_map["last_page"]) or ""
527 )
528 link_previous = (
529 self.previous_page and self.link_tag(link_map["previous_page"]) or ""
530 )
531 link_next = self.next_page and self.link_tag(link_map["next_page"]) or ""
532 # Interpolate '$' variables
533 result = Template(result).safe_substitute(
534 {
535 "first_page": self.first_page,
536 "last_page": self.last_page,
537 "page": self.page,
538 "page_count": self.page_count,
539 "items_per_page": self.items_per_page,
540 "first_item": self.first_item,
541 "last_item": self.last_item,
542 "item_count": self.item_count,
543 "link_first": link_first,
544 "link_last": link_last,
545 "link_previous": link_previous,
546 "link_next": link_next,
547 }
548 )
549
550 return result
551
552 def _get_edges(self, cur_page, max_page, items):
553 cur_page = int(cur_page)
554 edge = (items / 2) + 1
555 if cur_page <= edge:
556 radius = max(items / 2, items - cur_page)
557 elif (max_page - cur_page) < edge:
558 radius = (items - 1) - (max_page - cur_page)
559 else:
560 radius = (items / 2) - 1
561
562 left = max(1, (cur_page - radius))
563 right = min(max_page, cur_page + radius)
564 return left, right
565
566 def link_map(
567 self,
568 tmpl_format="~2~",
569 url=None,
570 show_if_single_page=False,
571 separator=" ",
572 symbol_first="&lt;&lt;",
573 symbol_last="&gt;&gt;",
574 symbol_previous="&lt;",
575 symbol_next="&gt;",
576 link_attr=None,
577 curpage_attr=None,
578 dotdot_attr=None,
579 link_tag=None
580 ):
581 """ Return map with links to other pages if default pager() function is not suitable solution.
582 tmpl_format:
583 Format string that defines how the pager would be normally rendered rendered. Uses same arguments as pager()
584 method, but returns a simple dictionary in form of:
585 {'current_page': {'attrs': {},
586 'href': 'http://example.org/foo/page=1',
587 'value': 1},
588 'first_page': {'attrs': {},
589 'href': 'http://example.org/foo/page=1',
590 'type': 'first_page',
591 'value': 1},
592 'last_page': {'attrs': {},
593 'href': 'http://example.org/foo/page=8',
594 'type': 'last_page',
595 'value': 8},
596 'next_page': {'attrs': {}, 'href': 'HREF', 'type': 'next_page', 'value': 2},
597 'previous_page': None,
598 'range_pages': [{'attrs': {},
599 'href': 'http://example.org/foo/page=1',
600 'type': 'current_page',
601 'value': 1},
602 ....
603 {'attrs': {}, 'href': '', 'type': 'span', 'value': '..'}]}
604
605
606 The string can contain the following $-tokens that are substituted by the
607 string.Template module:
608
609 - $first_page: number of first reachable page
610 - $last_page: number of last reachable page
611 - $page: number of currently selected page
612 - $page_count: number of reachable pages
613 - $items_per_page: maximal number of items per page
614 - $first_item: index of first item on the current page
615 - $last_item: index of last item on the current page
616 - $item_count: total number of items
617 - $link_first: link to first page (unless this is first page)
618 - $link_last: link to last page (unless this is last page)
619 - $link_previous: link to previous page (unless this is first page)
620 - $link_next: link to next page (unless this is last page)
621
622 To render a range of pages the token '~3~' can be used. The
623 number sets the radius of pages around the current page.
624 Example for a range with radius 3:
625
626 '1 .. 5 6 7 [8] 9 10 11 .. 50'
627
628 Default: '~2~'
629
630 url
631 The URL that page links will point to. Make sure it contains the string
632 $page which will be replaced by the actual page number.
633 Must be given unless a url_maker is specified to __init__, in which
634 case this parameter is ignored.
635
636 symbol_first
637 String to be displayed as the text for the $link_first link above.
638
639 Default: '&lt;&lt;' (<<)
640
641 symbol_last
642 String to be displayed as the text for the $link_last link above.
643
644 Default: '&gt;&gt;' (>>)
645
646 symbol_previous
647 String to be displayed as the text for the $link_previous link above.
648
649 Default: '&lt;' (<)
650
651 symbol_next
652 String to be displayed as the text for the $link_next link above.
653
654 Default: '&gt;' (>)
655
656 separator:
657 String that is used to separate page links/numbers in the above range of pages.
658
659 Default: ' '
660
661 show_if_single_page:
662 if True the navigator will be shown even if there is only one page.
663
664 Default: False
665
666 link_attr (optional)
667 A dictionary of attributes that get added to A-HREF links pointing to other pages. Can
668 be used to define a CSS style or class to customize the look of links.
669
670 Example: { 'style':'border: 1px solid green' }
671 Example: { 'class':'pager_link' }
672
673 curpage_attr (optional)
674 A dictionary of attributes that get added to the current page number in the pager (which
675 is obviously not a link). If this dictionary is not empty then the elements will be
676 wrapped in a SPAN tag with the given attributes.
677
678 Example: { 'style':'border: 3px solid blue' }
679 Example: { 'class':'pager_curpage' }
680
681 dotdot_attr (optional)
682 A dictionary of attributes that get added to the '..' string in the pager (which is
683 obviously not a link). If this dictionary is not empty then the elements will be wrapped
684 in a SPAN tag with the given attributes.
685
686 Example: { 'style':'color: #808080' }
687 Example: { 'class':'pager_dotdot' }
688 """
689 link_attr = link_attr or {}
690 curpage_attr = curpage_attr or {}
691 dotdot_attr = dotdot_attr or {}
692 self.curpage_attr = curpage_attr
693 self.separator = separator
694 self.link_attr = link_attr
695 self.dotdot_attr = dotdot_attr
696 self.url = url
697
698 regex_res = re.search(r"~(\d+)~", tmpl_format)
699 if regex_res:
700 radius = regex_res.group(1)
701 else:
702 radius = 2
703
704 self.radius = int(radius)
705
706 # Compute the first and last page number within the radius
707 # e.g. '1 .. 5 6 [7] 8 9 .. 12'
708 # -> leftmost_page = 5
709 # -> rightmost_page = 9
710 leftmost_page, rightmost_page = self._get_edges(
711 self.page, self.last_page, (self.radius * 2) + 1)
712
713 nav_items = {
714 "first_page": None,
715 "last_page": None,
716 "previous_page": None,
717 "next_page": None,
718 "current_page": None,
719 "radius": self.radius,
720 "range_pages": [],
721 }
722
723 if leftmost_page is None or rightmost_page is None:
724 return nav_items
725
726 nav_items["first_page"] = {
727 "type": "first_page",
728 "value": unicode(symbol_first),
729 "attrs": self.link_attr,
730 "number": self.first_page,
731 "href": self.url_maker(self.first_page),
732 }
733
734 # Insert dots if there are pages between the first page
735 # and the currently displayed page range
736 if leftmost_page - self.first_page > 1:
737 # Wrap in a SPAN tag if dotdot_attr is set
738 nav_items["range_pages"].append(
739 {
740 "type": "span",
741 "value": "..",
742 "attrs": self.dotdot_attr,
743 "href": "",
744 "number": None,
745 }
746 )
747
748 for this_page in range(leftmost_page, rightmost_page + 1):
749 # Highlight the current page number and do not use a link
750 if this_page == self.page:
751 # Wrap in a SPAN tag if curpage_attr is set
752 nav_items["range_pages"].append(
753 {
754 "type": "current_page",
755 "value": unicode(this_page),
756 "number": this_page,
757 "attrs": self.curpage_attr,
758 "href": self.url_maker(this_page),
759 }
760 )
761 nav_items["current_page"] = {
762 "value": this_page,
763 "attrs": self.curpage_attr,
764 "type": "current_page",
765 "href": self.url_maker(this_page),
766 }
767 # Otherwise create just a link to that page
768 else:
769 nav_items["range_pages"].append(
770 {
771 "type": "page",
772 "value": unicode(this_page),
773 "number": this_page,
774 "attrs": self.link_attr,
775 "href": self.url_maker(this_page),
776 }
777 )
778
779 # Insert dots if there are pages between the displayed
780 # page numbers and the end of the page range
781 if self.last_page - rightmost_page > 1:
782 # Wrap in a SPAN tag if dotdot_attr is set
783 nav_items["range_pages"].append(
784 {
785 "type": "span",
786 "value": "..",
787 "attrs": self.dotdot_attr,
788 "href": "",
789 "number": None,
790 }
791 )
792
793 # Create a link to the very last page (unless we are on the last
794 # page or there would be no need to insert '..' spacers)
795 nav_items["last_page"] = {
796 "type": "last_page",
797 "value": unicode(symbol_last),
798 "attrs": self.link_attr,
799 "href": self.url_maker(self.last_page),
800 "number": self.last_page,
801 }
802
803 nav_items["previous_page"] = {
804 "type": "previous_page",
805 "value": unicode(symbol_previous),
806 "attrs": self.link_attr,
807 "number": self.previous_page or self.first_page,
808 "href": self.url_maker(self.previous_page or self.first_page),
809 }
810
811 nav_items["next_page"] = {
812 "type": "next_page",
813 "value": unicode(symbol_next),
814 "attrs": self.link_attr,
815 "number": self.next_page or self.last_page,
816 "href": self.url_maker(self.next_page or self.last_page),
817 }
818
819 return nav_items
820
821 def _range(self, link_map, radius):
822 """
823 Return range of linked pages to substitute placeholder in pattern
824 """
825 # Compute the first and last page number within the radius
826 # e.g. '1 .. 5 6 [7] 8 9 .. 12'
827 # -> leftmost_page = 5
828 # -> rightmost_page = 9
829 leftmost_page, rightmost_page = self._get_edges(
830 self.page, self.last_page, (radius * 2) + 1)
831
832 nav_items = []
833 # Create a link to the first page (unless we are on the first page
834 # or there would be no need to insert '..' spacers)
835 if self.first_page and self.page != self.first_page and self.first_page < leftmost_page:
836 page = link_map["first_page"].copy()
837 page["value"] = unicode(page["number"])
838 nav_items.append(self.link_tag(page))
839
840 for item in link_map["range_pages"]:
841 nav_items.append(self.link_tag(item))
842
843 # Create a link to the very last page (unless we are on the last
844 # page or there would be no need to insert '..' spacers)
845 if self.last_page and self.page != self.last_page and rightmost_page < self.last_page:
846 page = link_map["last_page"].copy()
847 page["value"] = unicode(page["number"])
848 nav_items.append(self.link_tag(page))
849
850 return self.separator.join(nav_items)
851
852 def _default_url_maker(self, page_number):
853 if self.url is None:
854 raise Exception(
855 "You need to specify a 'url' parameter containing a '$page' placeholder."
856 )
857
858 if "$page" not in self.url:
859 raise Exception("The 'url' parameter must contain a '$page' placeholder.")
860
861 return self.url.replace("$page", unicode(page_number))
862
863 @staticmethod
864 def default_link_tag(item):
865 """
866 Create an A-HREF tag that points to another page.
867 """
868 text = item["value"]
869 target_url = item["href"]
870
871 if not item["href"] or item["type"] in ("span", "current_page"):
872 if item["attrs"]:
873 text = make_html_tag("span", **item["attrs"]) + text + "</span>"
874 return text
875
876 return make_html_tag("a", text=text, href=target_url, **item["attrs"])
877
878 # Below is RhodeCode custom code
879
3 880 # Copyright (C) 2010-2019 RhodeCode GmbH
4 881 #
5 882 # This program is free software: you can redistribute it and/or modify
6 883 # it under the terms of the GNU Affero General Public License, version 3
7 884 # (only), as published by the Free Software Foundation.
8 885 #
9 886 # This program is distributed in the hope that it will be useful,
10 887 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 888 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 889 # GNU General Public License for more details.
13 890 #
14 891 # You should have received a copy of the GNU Affero General Public License
15 892 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 893 #
17 894 # This program is dual-licensed. If you wish to learn more about the
18 895 # RhodeCode Enterprise Edition, including its added features, Support services,
19 896 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 import re
897
898
899 PAGE_FORMAT = '$link_previous ~3~ $link_next'
900
901
902 class SqlalchemyOrmWrapper(object):
903 """Wrapper class to access elements of a collection."""
21 904
22 from webhelpers.paginate import Page as _Page
23 from webhelpers.paginate import PageURL
24 from webhelpers2.html import literal, HTML
905 def __init__(self, pager, collection):
906 self.pager = pager
907 self.collection = collection
908
909 def __getitem__(self, range):
910 # Return a range of objects of an sqlalchemy.orm.query.Query object
911 return self.collection[range]
912
913 def __len__(self):
914 # Count the number of objects in an sqlalchemy.orm.query.Query object
915 return self.collection.count()
25 916
26 917
27 class Page(_Page):
918 class CustomPager(_Page):
919
920 @staticmethod
921 def disabled_link_tag(item):
922 """
923 Create an A-HREF tag that is disabled
924 """
925 text = item['value']
926 attrs = item['attrs'].copy()
927 attrs['class'] = 'disabled ' + attrs['class']
928
929 return make_html_tag('a', text=text, **attrs)
930
931 def render(self):
932 # Don't show navigator if there is no more than one page
933 if self.page_count == 0:
934 return ""
935
936 self.link_tag = self.default_link_tag
937
938 link_map = self.link_map(
939 tmpl_format=PAGE_FORMAT, url=None,
940 show_if_single_page=False, separator=' ',
941 symbol_first='<<', symbol_last='>>',
942 symbol_previous='<', symbol_next='>',
943 link_attr={'class': 'pager_link'},
944 curpage_attr={'class': 'pager_curpage'},
945 dotdot_attr={'class': 'pager_dotdot'})
946
947 links_markup = self._range(link_map, self.radius)
948
949 link_first = (
950 self.page > self.first_page and self.link_tag(link_map['first_page']) or ''
951 )
952 link_last = (
953 self.page < self.last_page and self.link_tag(link_map['last_page']) or ''
954 )
955
956 link_previous = (
957 self.previous_page and self.link_tag(link_map['previous_page'])
958 or self.disabled_link_tag(link_map['previous_page'])
959 )
960 link_next = (
961 self.next_page and self.link_tag(link_map['next_page'])
962 or self.disabled_link_tag(link_map['next_page'])
963 )
964
965 # Interpolate '$' variables
966 # Replace ~...~ in token tmpl_format by range of pages
967 result = re.sub(r"~(\d+)~", links_markup, PAGE_FORMAT)
968 result = Template(result).safe_substitute(
969 {
970 "links": links_markup,
971 "first_page": self.first_page,
972 "last_page": self.last_page,
973 "page": self.page,
974 "page_count": self.page_count,
975 "items_per_page": self.items_per_page,
976 "first_item": self.first_item,
977 "last_item": self.last_item,
978 "item_count": self.item_count,
979 "link_first": link_first,
980 "link_last": link_last,
981 "link_previous": link_previous,
982 "link_next": link_next,
983 }
984 )
985
986 return literal(result)
987
988
989 class Page(CustomPager):
28 990 """
29 991 Custom pager to match rendering style with paginator
30 992 """
31 993
32 def _get_pos(self, cur_page, max_page, items):
33 edge = (items / 2) + 1
34 if (cur_page <= edge):
35 radius = max(items / 2, items - cur_page)
36 elif (max_page - cur_page) < edge:
37 radius = (items - 1) - (max_page - cur_page)
38 else:
39 radius = items / 2
40
41 left = max(1, (cur_page - (radius)))
42 right = min(max_page, cur_page + (radius))
43 return left, cur_page, right
44
45 def _range(self, regexp_match):
994 def __init__(self, collection, page=1, items_per_page=20, item_count=None,
995 url_maker=None, **kwargs):
996 """
997 Special type of pager. We intercept collection to wrap it in our custom
998 logic instead of using wrapper_class
46 999 """
47 Return range of linked pages (e.g. '1 2 [3] 4 5 6 7 8').
48
49 Arguments:
50 1000
51 regexp_match
52 A "re" (regular expressions) match object containing the
53 radius of linked pages around the current page in
54 regexp_match.group(1) as a string
1001 super(Page, self).__init__(collection=collection, page=page,
1002 items_per_page=items_per_page, item_count=item_count,
1003 wrapper_class=None, url_maker=url_maker, **kwargs)
55 1004
56 This function is supposed to be called as a callable in
57 re.sub.
58
59 """
60 radius = int(regexp_match.group(1))
61 1005
62 # Compute the first and last page number within the radius
63 # e.g. '1 .. 5 6 [7] 8 9 .. 12'
64 # -> leftmost_page = 5
65 # -> rightmost_page = 9
66 leftmost_page, _cur, rightmost_page = self._get_pos(self.page,
67 self.last_page,
68 (radius * 2) + 1)
69 nav_items = []
70
71 # Create a link to the first page (unless we are on the first page
72 # or there would be no need to insert '..' spacers)
73 if self.page != self.first_page and self.first_page < leftmost_page:
74 nav_items.append(self._pagerlink(self.first_page, self.first_page))
1006 class SqlPage(CustomPager):
1007 """
1008 Custom pager to match rendering style with paginator
1009 """
75 1010
76 # Insert dots if there are pages between the first page
77 # and the currently displayed page range
78 if leftmost_page - self.first_page > 1:
79 # Wrap in a SPAN tag if nolink_attr is set
80 text = '..'
81 if self.dotdot_attr:
82 text = HTML.span(c=text, **self.dotdot_attr)
83 nav_items.append(text)
1011 def __init__(self, collection, page=1, items_per_page=20, item_count=None,
1012 url_maker=None, **kwargs):
1013 """
1014 Special type of pager. We intercept collection to wrap it in our custom
1015 logic instead of using wrapper_class
1016 """
1017 collection = SqlalchemyOrmWrapper(self, collection)
84 1018
85 for thispage in xrange(leftmost_page, rightmost_page + 1):
86 # Hilight the current page number and do not use a link
87 if thispage == self.page:
88 text = '%s' % (thispage,)
89 # Wrap in a SPAN tag if nolink_attr is set
90 if self.curpage_attr:
91 text = HTML.span(c=text, **self.curpage_attr)
92 nav_items.append(text)
93 # Otherwise create just a link to that page
94 else:
95 text = '%s' % (thispage,)
96 nav_items.append(self._pagerlink(thispage, text))
1019 super(SqlPage, self).__init__(collection=collection, page=page,
1020 items_per_page=items_per_page, item_count=item_count,
1021 wrapper_class=None, url_maker=url_maker, **kwargs)
1022
97 1023
98 # Insert dots if there are pages between the displayed
99 # page numbers and the end of the page range
100 if self.last_page - rightmost_page > 1:
101 text = '..'
102 # Wrap in a SPAN tag if nolink_attr is set
103 if self.dotdot_attr:
104 text = HTML.span(c=text, **self.dotdot_attr)
105 nav_items.append(text)
1024 class RepoCommitsWrapper(object):
1025 """Wrapper class to access elements of a collection."""
106 1026
107 # Create a link to the very last page (unless we are on the last
108 # page or there would be no need to insert '..' spacers)
109 if self.page != self.last_page and rightmost_page < self.last_page:
110 nav_items.append(self._pagerlink(self.last_page, self.last_page))
1027 def __init__(self, pager, collection):
1028 self.pager = pager
1029 self.collection = collection
111 1030
112 ## prerender links
113 #_page_link = url.current()
114 #nav_items.append(literal('<link rel="prerender" href="%s?page=%s">' % (_page_link, str(int(self.page)+1))))
115 #nav_items.append(literal('<link rel="prefetch" href="%s?page=%s">' % (_page_link, str(int(self.page)+1))))
116 return self.separator.join(nav_items)
1031 def __getitem__(self, range):
1032 cur_page = self.pager.page
1033 items_per_page = self.pager.items_per_page
1034 first_item = max(0, (len(self.collection) - (cur_page * items_per_page)))
1035 last_item = ((len(self.collection) - 1) - items_per_page * (cur_page - 1))
1036 return reversed(list(self.collection[first_item:last_item + 1]))
117 1037
118 def pager(self, format='~2~', page_param='page', partial_param='partial',
119 show_if_single_page=False, separator=' ', onclick=None,
120 symbol_first='<<', symbol_last='>>',
121 symbol_previous='<', symbol_next='>',
122 link_attr={'class': 'pager_link', 'rel': 'prerender'},
123 curpage_attr={'class': 'pager_curpage'},
124 dotdot_attr={'class': 'pager_dotdot'}, **kwargs):
1038 def __len__(self):
1039 return len(self.collection)
125 1040
126 self.curpage_attr = curpage_attr
127 self.separator = separator
128 self.pager_kwargs = kwargs
129 self.page_param = page_param
130 self.partial_param = partial_param
131 self.onclick = onclick
132 self.link_attr = link_attr
133 self.dotdot_attr = dotdot_attr
134 1041
135 # Don't show navigator if there is no more than one page
136 if self.page_count == 0 or (self.page_count == 1 and not show_if_single_page):
137 return ''
138
139 from string import Template
140 # Replace ~...~ in token format by range of pages
141 result = re.sub(r'~(\d+)~', self._range, format)
1042 class RepoPage(CustomPager):
1043 """
1044 Create a "RepoPage" instance. special pager for paging repository
1045 """
142 1046
143 # Interpolate '%' variables
144 result = Template(result).safe_substitute({
145 'first_page': self.first_page,
146 'last_page': self.last_page,
147 'page': self.page,
148 'page_count': self.page_count,
149 'items_per_page': self.items_per_page,
150 'first_item': self.first_item,
151 'last_item': self.last_item,
152 'item_count': self.item_count,
153 'link_first': self.page > self.first_page and \
154 self._pagerlink(self.first_page, symbol_first) or '',
155 'link_last': self.page < self.last_page and \
156 self._pagerlink(self.last_page, symbol_last) or '',
157 'link_previous': self.previous_page and \
158 self._pagerlink(self.previous_page, symbol_previous) \
159 or HTML.span(symbol_previous, class_="pg-previous disabled"),
160 'link_next': self.next_page and \
161 self._pagerlink(self.next_page, symbol_next) \
162 or HTML.span(symbol_next, class_="pg-next disabled")
163 })
164
165 return literal(result)
1047 def __init__(self, collection, page=1, items_per_page=20, item_count=None,
1048 url_maker=None, **kwargs):
1049 """
1050 Special type of pager. We intercept collection to wrap it in our custom
1051 logic instead of using wrapper_class
1052 """
1053 collection = RepoCommitsWrapper(self, collection)
1054 super(RepoPage, self).__init__(collection=collection, page=page,
1055 items_per_page=items_per_page, item_count=item_count,
1056 wrapper_class=None, url_maker=url_maker, **kwargs)
@@ -1,816 +1,822 b''
1 1 // navigation.less
2 2 // For use in RhodeCode applications;
3 3 // see style guide documentation for guidelines.
4 4
5 5 // TOP MAIN DARK NAVIGATION
6 6
7 7 .header .main_nav.horizontal-list {
8 8 float: right;
9 9 color: @grey4;
10 10 > li {
11 11 a {
12 12 color: @grey4;
13 13 }
14 14 }
15 15 }
16 16
17 17 // HEADER NAVIGATION
18 18
19 19 .horizontal-list {
20 20 display: block;
21 21 margin: 0;
22 22 padding: 0;
23 23 -webkit-padding-start: 0;
24 24 text-align: left;
25 25 font-size: @navigation-fontsize;
26 26 color: @grey6;
27 27 z-index:10;
28 28
29 29 li {
30 30 line-height: 1em;
31 31 list-style-type: none;
32 32 margin: 0 20px 0 0;
33 33
34 34 a {
35 35 padding: 0 .5em;
36 36
37 37 &.menu_link_notifications {
38 38 .pill(7px,@rcblue);
39 39 display: inline;
40 40 margin: 0 7px 0 .7em;
41 41 font-size: @basefontsize;
42 42 color: white;
43 43
44 44 &.empty {
45 45 background-color: @grey4;
46 46 }
47 47
48 48 &:hover {
49 49 background-color: @rcdarkblue;
50 50 }
51 51 }
52 52 }
53 53 .pill_container {
54 54 margin: 1.25em 0px 0px 0px;
55 55 float: right;
56 56 }
57 57
58 58 &#quick_login_li {
59 59 &:hover {
60 60 color: @grey5;
61 61 }
62 62
63 63 a.menu_link_notifications {
64 64 color: white;
65 65 }
66 66
67 67 .user {
68 68 padding-bottom: 10px;
69 69 }
70 70 }
71 71
72 72 &:before { content: none; }
73 73
74 74 &:last-child {
75 75 .menulabel {
76 76 padding-right: 0;
77 77 border-right: none;
78 78
79 79 .show_more {
80 80 padding-right: 0;
81 81 }
82 82 }
83 83
84 84 &> a {
85 85 border-bottom: none;
86 86 }
87 87 }
88 88
89 89 &.open {
90 90
91 91 a {
92 92 color: white;
93 93 }
94 94 }
95 95
96 96 &:focus {
97 97 outline: none;
98 98 }
99 99
100 100 ul li {
101 101 display: block;
102 102
103 103 &:last-child> a {
104 104 border-bottom: none;
105 105 }
106 106
107 107 ul li:last-child a {
108 108 /* we don't expect more then 3 levels of submenu and the third
109 109 level can have different html structure */
110 110 border-bottom: none;
111 111 }
112 112 }
113 113 }
114 114
115 115 > li {
116 116 float: left;
117 117 display: block;
118 118 padding: 0;
119 119
120 120 > a,
121 121 &.has_select2 a {
122 122 display: block;
123 123 padding: 10px 0;
124 124 }
125 125
126 126 .menulabel {
127 127 line-height: 1em;
128 128 // for this specifically we do not use a variable
129 129 }
130 130
131 131 .menulink-counter {
132 132 border: 1px solid @grey2;
133 133 border-radius: @border-radius;
134 134 background: @grey7;
135 135 display: inline-block;
136 136 padding: 0px 4px;
137 137 text-align: center;
138 138 font-size: 12px;
139 139 }
140 140
141 141 .pr_notifications {
142 142 padding-left: .5em;
143 143 }
144 144
145 145 .pr_notifications + .menulabel {
146 146 display:inline;
147 147 padding-left: 0;
148 148 }
149 149
150 150 &:hover,
151 151 &.open,
152 152 &.active {
153 153 a {
154 154 color: @rcblue;
155 155 }
156 156 }
157 157 }
158 158
159 159 pre {
160 160 margin: 0;
161 161 padding: 0;
162 162 }
163 163
164 164 .select2-container,
165 165 .menulink.childs {
166 166 position: relative;
167 167 }
168 168
169 169 .menulink {
170 170 &.disabled {
171 171 color: @grey3;
172 172 cursor: default;
173 173 opacity: 0.5;
174 174 }
175 175 }
176 176
177 177 #quick_login {
178 178
179 179 li a {
180 180 padding: .5em 0;
181 181 border-bottom: none;
182 182 color: @grey2;
183 183
184 184 &:hover { color: @rcblue; }
185 185 }
186 186 }
187 187
188 188 #quick_login_link {
189 189 display: inline-block;
190 190
191 191 .gravatar {
192 192 border: 1px solid @grey5;
193 193 }
194 194
195 195 .gravatar-login {
196 196 height: 20px;
197 197 width: 20px;
198 198 margin: -8px 0;
199 199 padding: 0;
200 200 }
201 201
202 202 &:hover .user {
203 203 color: @grey6;
204 204 }
205 205 }
206 206 }
207 207 .header .horizontal-list {
208 208
209 209 li {
210 210
211 211 &#quick_login_li {
212 212 padding-left: .5em;
213 213 margin-right: 0px;
214 214
215 215 &:hover #quick_login_link {
216 216 color: inherit;
217 217 }
218 218
219 219 .menu_link_user {
220 220 padding: 0 2px;
221 221 }
222 222 }
223 223 list-style-type: none;
224 224 }
225 225
226 226 > li {
227 227
228 228 a {
229 229 padding: 18px 0 12px 0;
230 230 color: @nav-grey;
231 231
232 232 &.menu_link_notifications {
233 233 padding: 1px 8px;
234 234 }
235 235 }
236 236
237 237 &:hover,
238 238 &.open,
239 239 &.active {
240 240 .pill_container a {
241 241 // don't select text for the pill container, it has it' own
242 242 // hover behaviour
243 243 color: @nav-grey;
244 244 }
245 245 }
246 246
247 247 &:hover,
248 248 &.open,
249 249 &.active {
250 250 a {
251 251 color: @grey6;
252 252 }
253 253 }
254 254
255 255 .select2-dropdown-open a {
256 256 color: @grey6;
257 257 }
258 258
259 259 .repo-switcher {
260 260 padding-left: 0;
261 261
262 262 .menulabel {
263 263 padding-left: 0;
264 264 }
265 265 }
266 266 }
267 267
268 268 li ul li {
269 269 background-color:@grey2;
270 270
271 271 a {
272 272 padding: .5em 0;
273 273 border-bottom: @border-thickness solid @border-default-color;
274 274 color: @grey6;
275 275 }
276 276
277 277 &:last-child a, &.last a{
278 278 border-bottom: none;
279 279 }
280 280
281 281 &:hover {
282 282 background-color: @grey3;
283 283 }
284 284 }
285 285
286 286 .submenu {
287 287 margin-top: 5px;
288 288 }
289 289 }
290 290
291 291 // SUBMENUS
292 292 .navigation .submenu {
293 293 display: none;
294 294 }
295 295
296 296 .navigation li.open {
297 297 .submenu {
298 298 display: block;
299 299 }
300 300 }
301 301
302 302 .navigation li:last-child .submenu {
303 303 right: auto;
304 304 left: 0;
305 305 border: 1px solid @grey5;
306 306 background: @white;
307 307 box-shadow: @dropdown-shadow;
308 308 }
309 309
310 310 .submenu {
311 311 position: absolute;
312 312 top: 100%;
313 313 left: 0;
314 314 min-width: 180px;
315 315 margin: 2px 0 0;
316 316 padding: 0;
317 317 text-align: left;
318 318 font-family: @text-light;
319 319 border-radius: @border-radius;
320 320 z-index: 20;
321 321
322 322 li {
323 323 display: block;
324 324 margin: 0;
325 325 padding: 0 .5em;
326 326 line-height: 1em;
327 327 color: @grey3;
328 328 background-color: @white;
329 329 list-style-type: none;
330 330
331 331 a {
332 332 display: block;
333 333 width: 100%;
334 334 padding: .5em 0;
335 335 border-right: none;
336 336 border-bottom: @border-thickness solid white;
337 337 color: @grey3;
338 338 }
339 339
340 340 ul {
341 341 display: none;
342 342 position: absolute;
343 343 top: 0;
344 344 right: 100%;
345 345 padding: 0;
346 346 z-index: 30;
347 347 }
348 348 &:hover {
349 349 background-color: @grey7;
350 350 -webkit-transition: background .3s;
351 351 -moz-transition: background .3s;
352 352 -o-transition: background .3s;
353 353 transition: background .3s;
354 354
355 355 ul {
356 356 display: block;
357 357 }
358 358 }
359 359 }
360 360
361 361 }
362 362
363 363
364 364
365 365
366 366 // repo dropdown
367 367 .quick_repo_menu {
368 368 width: 15px;
369 369 text-align: center;
370 370 position: relative;
371 371 cursor: pointer;
372 372
373 373 div {
374 374 overflow: visible !important;
375 375 }
376 376
377 377 &.sorting {
378 378 cursor: auto;
379 379 }
380 380
381 381 &:hover {
382 382 .menu_items_container {
383 383 position: absolute;
384 384 display: block;
385 385 }
386 386 .menu_items {
387 387 display: block;
388 388 }
389 389 }
390 390
391 391 i {
392 392 margin: 0;
393 393 color: @grey4;
394 394 }
395 395
396 396 .menu_items_container {
397 397 position: absolute;
398 398 top: 0;
399 399 left: 100%;
400 400 margin: 0;
401 401 padding: 0;
402 402 list-style: none;
403 403 background-color: @grey6;
404 404 z-index: 999;
405 405 text-align: left;
406 406
407 407 a {
408 408 color: @grey2;
409 409 }
410 410
411 411 ul.menu_items {
412 412 margin: 0;
413 413 padding: 0;
414 414 }
415 415
416 416 li {
417 417 margin: 0;
418 418 padding: 0;
419 419 line-height: 1em;
420 420 list-style-type: none;
421 421
422 422 a {
423 423 display: block;
424 424 height: 16px;
425 425 padding: 8px; //must add up to td height (28px)
426 426 width: 120px; // set width
427 427
428 428 &:hover {
429 429 background-color: @grey5;
430 430 -webkit-transition: background .3s;
431 431 -moz-transition: background .3s;
432 432 -o-transition: background .3s;
433 433 transition: background .3s;
434 434 }
435 435 }
436 436 }
437 437 }
438 438 }
439 439
440 440
441 441 // new objects main action
442 442 .action-menu {
443 443 left: auto;
444 444 right: 0;
445 445 padding: 12px;
446 446 z-index: 999;
447 447 overflow: hidden;
448 448 background-color: #fff;
449 449 border: 1px solid @grey5;
450 450 color: @grey2;
451 451 box-shadow: @dropdown-shadow;
452 452
453 453 .submenu-title {
454 454 font-weight: bold;
455 455 }
456 456
457 457 .submenu-title:not(:first-of-type) {
458 458 padding-top: 10px;
459 459 }
460 460
461 461 &.submenu {
462 462 min-width: 200px;
463 463
464 464 ol {
465 465 padding:0;
466 466 }
467 467
468 468 li {
469 469 display: block;
470 470 margin: 0;
471 471 padding: .2em .5em;
472 472 line-height: 1em;
473 473
474 474 background-color: #fff;
475 475 list-style-type: none;
476 476
477 477 a {
478 478 padding: 4px;
479 479 color: @grey4 !important;
480 480 border-bottom: none;
481 481 }
482 482 }
483 483 li:not(.submenu-title) a:hover{
484 484 color: @grey2 !important;
485 485 }
486 486 }
487 487 }
488 488
489 489
490 490 // Header Repository Switcher
491 491 // Select2 Dropdown
492 492 #select2-drop.select2-drop.repo-switcher-dropdown {
493 493 width: auto !important;
494 494 margin-top: 5px;
495 495 padding: 1em 0;
496 496 text-align: left;
497 497 .border-radius-bottom(@border-radius);
498 498 border-color: transparent;
499 499 color: @grey6;
500 500 background-color: @grey2;
501 501
502 502 input {
503 503 min-width: 90%;
504 504 }
505 505
506 506 ul.select2-result-sub {
507 507
508 508 li {
509 509 line-height: 1em;
510 510
511 511 &:hover,
512 512 &.select2-highlighted {
513 513 background-color: @grey3;
514 514 }
515 515 }
516 516
517 517 &:before { content: none; }
518 518 }
519 519
520 520 ul.select2-results {
521 521 min-width: 200px;
522 522 margin: 0;
523 523 padding: 0;
524 524 list-style-type: none;
525 525 overflow-x: visible;
526 526 overflow-y: scroll;
527 527
528 528 li {
529 529 padding: 0 8px;
530 530 line-height: 1em;
531 531 color: @grey6;
532 532
533 533 &>.select2-result-label {
534 534 padding: 8px 0;
535 535 border-bottom: @border-thickness solid @grey3;
536 536 white-space: nowrap;
537 537 color: @grey5;
538 538 cursor: pointer;
539 539 }
540 540
541 541 &.select2-result-with-children {
542 542 margin: 0;
543 543 padding: 0;
544 544 }
545 545
546 546 &.select2-result-unselectable > .select2-result-label {
547 547 margin: 0 8px;
548 548 }
549 549
550 550 }
551 551 }
552 552
553 553 ul.select2-result-sub {
554 554 margin: 0;
555 555 padding: 0;
556 556
557 557 li {
558 558 display: block;
559 559 margin: 0;
560 560 border-right: none;
561 561 line-height: 1em;
562 562 font-family: @text-light;
563 563 color: @grey2;
564 564 list-style-type: none;
565 565
566 566 &:hover {
567 567 background-color: @grey3;
568 568 }
569 569 }
570 570 }
571 571 }
572 572
573 573
574 574 #context-bar {
575 575 display: block;
576 576 margin: 0 auto 20px 0;
577 577 padding: 0 @header-padding;
578 578 background-color: @grey7;
579 579 border-bottom: 1px solid @grey5;
580 580
581 581 .clear {
582 582 clear: both;
583 583 }
584 584 }
585 585
586 586 ul#context-pages {
587 587 li {
588 588 list-style-type: none;
589 589
590 590 a {
591 591 color: @grey2;
592 592
593 593 &:hover {
594 594 color: @grey1;
595 595 }
596 596 }
597 597
598 598 &.active {
599 599 // special case, non-variable color
600 600 border-bottom: 2px solid @rcblue;
601 601
602 602 a {
603 603 color: @rcblue;
604 604 }
605 605 }
606 606 }
607 607 }
608 608
609 609 // PAGINATION
610 610
611 611 .pagination {
612 612 border: @border-thickness solid @grey5;
613 613 color: @grey2;
614 614 box-shadow: @button-shadow;
615 615
616 616 .current {
617 617 color: @grey4;
618 618 }
619 619 }
620 620
621 621 .dataTables_processing {
622 622 text-align: center;
623 623 font-size: 1.1em;
624 624 position: relative;
625 625 top: 95px;
626 626 }
627 627
628 .dataTables_paginate, .pagination-wh {
629 text-align: left;
628 .dataTables_paginate,
629 .pagination-wh {
630 text-align: center;
630 631 display: inline-block;
631 632 border-left: 1px solid @grey5;
632 633 float: none;
633 634 overflow: hidden;
634 635 box-shadow: @button-shadow;
635 636
636 637 .paginate_button, .pager_curpage,
637 638 .pager_link, .pg-previous, .pg-next, .pager_dotdot {
638 639 display: inline-block;
639 640 padding: @menupadding/4 @menupadding;
640 641 border: 1px solid @grey5;
641 border-left: 0;
642 margin-left: -1px;
642 643 color: @grey2;
643 644 cursor: pointer;
644 645 float: left;
646 font-weight: 600;
647 white-space: nowrap;
648 vertical-align: middle;
649 user-select: none;
650 min-width: 15px;
645 651
646 652 &:hover {
647 653 color: @rcdarkblue;
648 654 }
649 655 }
650 656
651 657 .paginate_button.disabled,
652 658 .disabled {
653 659 color: @grey3;
654 660 cursor: default;
655 661 opacity: 0.5;
656 662 }
657 663
658 664 .paginate_button.current, .pager_curpage {
659 665 background: @rcblue;
660 666 border-color: @rcblue;
661 667 color: @white;
662 668 }
663 669
664 670 .ellipsis {
665 671 display: inline-block;
666 672 text-align: left;
667 673 padding: @menupadding/4 @menupadding;
668 674 border: 1px solid @grey5;
669 675 border-left: 0;
670 676 float: left;
671 677 }
672 678 }
673 679
674 680 // SIDEBAR
675 681
676 682 .sidebar {
677 683 .block-left;
678 684 clear: left;
679 685 max-width: @sidebar-width;
680 686 margin-right: @sidebarpadding;
681 687 padding-right: @sidebarpadding;
682 688 font-family: @text-regular;
683 689 color: @grey1;
684 690
685 691 .nav-pills {
686 692 margin: 0;
687 693 }
688 694
689 695 .nav {
690 696 list-style: none;
691 697 padding: 0;
692 698
693 699 li {
694 700 padding-bottom: @menupadding;
695 701 line-height: 1em;
696 702 color: @grey4;
697 703 list-style-type: none;
698 704
699 705 &.active a {
700 706 color: @grey2;
701 707 }
702 708
703 709 a {
704 710 color: @grey4;
705 711 }
706 712 }
707 713
708 714 }
709 715 }
710 716
711 717 .main_filter_help_box {
712 718 padding: 7px 7px;
713 719 display: inline-block;
714 720 vertical-align: top;
715 721 background: inherit;
716 722 position: absolute;
717 723 right: 0;
718 724 top: 9px;
719 725 }
720 726
721 727 .main_filter_input_box {
722 728 display: inline-block;
723 729
724 730 .searchItems {
725 731 display:flex;
726 732 background: @black;
727 733 padding: 0px;
728 734 border-radius: 3px;
729 735 border: 1px solid @black;
730 736
731 737 a {
732 738 border: none !important;
733 739 }
734 740 }
735 741
736 742 .searchTag {
737 743 line-height: 28px;
738 744 padding: 0 5px;
739 745
740 746 .tag {
741 747 color: @grey5;
742 748 border-color: @grey2;
743 749 background: @grey1;
744 750 }
745 751 }
746 752
747 753 .searchTagFilter {
748 754 background-color: @black !important;
749 755 margin-right: 0;
750 756 }
751 757
752 758 .searchTagHelp {
753 759 background-color: @grey1 !important;
754 760 margin: 0;
755 761 }
756 762 .searchTagHelp:hover {
757 763 background-color: @grey1 !important;
758 764 }
759 765 .searchTagInput {
760 766 background-color: @grey1 !important;
761 767 margin-right: 0;
762 768 }
763 769 }
764 770
765 771 .main_filter_box {
766 772 margin: 9px 0 0 0;
767 773 }
768 774
769 775 #main_filter_help {
770 776 background: @grey1;
771 777 border: 1px solid black;
772 778 position: absolute;
773 779 white-space: pre;
774 780 z-index: 9999;
775 781 color: @nav-grey;
776 782 padding: 0 10px;
777 783 }
778 784
779 785 input {
780 786
781 787 &.main_filter_input {
782 788 padding: 5px 10px;
783 789 min-width: 340px;
784 790 color: @grey7;
785 791 background: @black;
786 792 min-height: 18px;
787 793 border: 0;
788 794
789 795 &:active {
790 796 color: @grey2 !important;
791 797 background: white !important;
792 798 }
793 799 &:focus {
794 800 color: @grey2 !important;
795 801 background: white !important;
796 802 }
797 803 }
798 804 }
799 805
800 806
801 807
802 808 .main_filter_input::placeholder {
803 809 color: @nav-grey;
804 810 opacity: 1;
805 811 }
806 812
807 813 .notice-box {
808 814 display:block !important;
809 815 padding: 9px 0 !important;
810 816 }
811 817
812 818 .menulabel-notice {
813 819 border: 1px solid @color5;
814 820 padding:7px 10px;
815 821 color: @color5;
816 822 }
@@ -1,69 +1,69 b''
1 1 <%namespace name="base" file="/base/base.mako"/>
2 2
3 3 %if c.audit_logs:
4 4 <table class="rctable admin_log">
5 5 <tr>
6 6 <th>${_('Uid')}</th>
7 7 <th>${_('Username')}</th>
8 8 <th>${_('Action')}</th>
9 9 <th>${_('Action Data')}</th>
10 10 <th>${_('Repository')}</th>
11 11 <th>${_('Date')}</th>
12 12 <th>${_('IP')}</th>
13 13 </tr>
14 14
15 15 %for cnt,l in enumerate(c.audit_logs):
16 16 <tr class="parity${cnt%2}">
17 17 <td class="td-col">
18 18 <a href="${h.route_path('admin_audit_log_entry', audit_log_id=l.entry_id)}">${l.entry_id}</a>
19 19 </td>
20 20 <td class="td-user">
21 21 %if l.user is not None:
22 22 ${base.gravatar_with_user(l.user.email)}
23 23 %else:
24 24 ${l.username}
25 25 %endif
26 26 </td>
27 27 <td class="td-journalaction">
28 28 % if l.version == l.VERSION_1:
29 29 ${h.action_parser(request, l)[0]()}
30 30 % else:
31 31 ${h.literal(l.action)}
32 32 % endif
33 33
34 34 <div class="journal_action_params">
35 35 % if l.version == l.VERSION_1:
36 36 ${h.literal(h.action_parser(request, l)[1]())}
37 37 % endif
38 38 </div>
39 39 </td>
40 40 <td>
41 41 % if l.version == l.VERSION_2:
42 42 <a href="#" onclick="$('#entry-'+${l.user_log_id}).toggle();return false">${_('toggle')}</a>
43 43 <div id="entry-${l.user_log_id}" style="display: none">
44 44 <pre>${h.json.dumps(l.action_data, indent=4, sort_keys=True)}</pre>
45 45 </div>
46 46 % else:
47 47 <pre title="${_('data not available for v1 entries type')}">-</pre>
48 48 % endif
49 49 </td>
50 50 <td class="td-componentname">
51 51 %if l.repository is not None:
52 52 ${h.link_to(l.repository.repo_name, h.route_path('repo_summary',repo_name=l.repository.repo_name))}
53 53 %else:
54 54 ${l.repository_name}
55 55 %endif
56 56 </td>
57 57
58 58 <td class="td-time">${h.format_date(l.action_date)}</td>
59 59 <td class="td-ip">${l.user_ip}</td>
60 60 </tr>
61 61 %endfor
62 62 </table>
63 63
64 64 <div class="pagination-wh pagination-left">
65 ${c.audit_logs.pager('$link_previous ~2~ $link_next')}
65 ${c.audit_logs.render()}
66 66 </div>
67 67 %else:
68 68 ${_('No actions yet')}
69 69 %endif No newline at end of file
@@ -1,220 +1,220 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <%inherit file="base.mako"/>
3 3
4 4 <%def name="breadcrumbs_links()">
5 5 %if c.repo:
6 6 ${h.link_to('Settings',h.route_path('edit_repo', repo_name=c.repo.repo_name))}
7 7 %elif c.repo_group:
8 8 ${h.link_to(_('Admin'),h.route_path('admin_home'))}
9 9 &raquo;
10 10 ${h.link_to(_('Repository Groups'),h.route_path('repo_groups'))}
11 11 &raquo;
12 12 ${h.link_to(c.repo_group.group_name,h.route_path('edit_repo_group', repo_group_name=c.repo_group.group_name))}
13 13 %else:
14 14 ${h.link_to(_('Admin'),h.route_path('admin_home'))}
15 15 &raquo;
16 16 ${h.link_to(_('Settings'),h.route_path('admin_settings'))}
17 17 %endif
18 18 %if c.current_IntegrationType:
19 19 &raquo;
20 20 %if c.repo:
21 21 ${h.link_to(_('Integrations'),
22 22 request.route_path(route_name='repo_integrations_home',
23 23 repo_name=c.repo.repo_name))}
24 24 %elif c.repo_group:
25 25 ${h.link_to(_('Integrations'),
26 26 request.route_path(route_name='repo_group_integrations_home',
27 27 repo_group_name=c.repo_group.group_name))}
28 28 %else:
29 29 ${h.link_to(_('Integrations'),
30 30 request.route_path(route_name='global_integrations_home'))}
31 31 %endif
32 32 &raquo;
33 33 ${c.current_IntegrationType.display_name}
34 34 %else:
35 35 &raquo;
36 36 ${_('Integrations')}
37 37 %endif
38 38 </%def>
39 39
40 40 <div class="panel panel-default">
41 41 <div class="panel-heading">
42 42 <h3 class="panel-title">
43 43 %if c.repo:
44 44 ${_('Current Integrations for Repository: {repo_name}').format(repo_name=c.repo.repo_name)}
45 45 %elif c.repo_group:
46 46 ${_('Repository Group Integrations: {}').format(c.repo_group.group_name)}</h3>
47 47 %else:
48 48 ${_('Current Integrations')}
49 49 %endif
50 50 </h3>
51 51 </div>
52 52 <div class="panel-body">
53 53
54 54 <%
55 55 integration_type = c.current_IntegrationType and c.current_IntegrationType.display_name or ''
56 56
57 57 if c.repo:
58 58 create_url = h.route_path('repo_integrations_new', repo_name=c.repo.repo_name)
59 59 elif c.repo_group:
60 60 create_url = h.route_path('repo_group_integrations_new', repo_group_name=c.repo_group.group_name)
61 61 else:
62 62 create_url = h.route_path('global_integrations_new')
63 63 %>
64 64 <p class="pull-right">
65 65 <a href="${create_url}" class="btn btn-small btn-success">${_(u'Create new integration')}</a>
66 66 </p>
67 67
68 68 <table class="rctable integrations">
69 69 <thead>
70 70 <tr>
71 71 <th><a href="?sort=enabled:${c.rev_sort_dir}">${_('Enabled')}</a></th>
72 72 <th><a href="?sort=name:${c.rev_sort_dir}">${_('Name')}</a></th>
73 73 <th colspan="2"><a href="?sort=integration_type:${c.rev_sort_dir}">${_('Type')}</a></th>
74 74 <th><a href="?sort=scope:${c.rev_sort_dir}">${_('Scope')}</a></th>
75 75 <th>${_('Actions')}</th>
76 76 <th></th>
77 77 </tr>
78 78 </thead>
79 79 <tbody>
80 80 %if not c.integrations_list:
81 81 <tr>
82 82 <td colspan="7">
83 83
84 84 %if c.repo:
85 85 ${_('No {type} integrations for repo {repo} exist yet.').format(type=integration_type, repo=c.repo.repo_name)}
86 86 %elif c.repo_group:
87 87 ${_('No {type} integrations for repogroup {repogroup} exist yet.').format(type=integration_type, repogroup=c.repo_group.group_name)}
88 88 %else:
89 89 ${_('No {type} integrations exist yet.').format(type=integration_type)}
90 90 %endif
91 91
92 92 %if c.current_IntegrationType:
93 93 <%
94 94 if c.repo:
95 95 create_url = h.route_path('repo_integrations_create', repo_name=c.repo.repo_name, integration=c.current_IntegrationType.key)
96 96 elif c.repo_group:
97 97 create_url = h.route_path('repo_group_integrations_create', repo_group_name=c.repo_group.group_name, integration=c.current_IntegrationType.key)
98 98 else:
99 99 create_url = h.route_path('global_integrations_create', integration=c.current_IntegrationType.key)
100 100 %>
101 101 %endif
102 102
103 103 <a href="${create_url}">${_(u'Create one')}</a>
104 104 </td>
105 105 </tr>
106 106 %endif
107 107 %for IntegrationType, integration in c.integrations_list:
108 108 <tr id="integration_${integration.integration_id}">
109 109 <td class="td-enabled">
110 110 <div class="pull-left">
111 111 ${h.bool2icon(integration.enabled)}
112 112 </div>
113 113 </td>
114 114 <td class="td-description">
115 115 ${integration.name}
116 116 </td>
117 117 <td class="td-icon">
118 118 %if integration.integration_type in c.available_integrations:
119 119 <div class="integration-icon">
120 120 ${c.available_integrations[integration.integration_type].icon()|n}
121 121 </div>
122 122 %else:
123 123 ?
124 124 %endif
125 125 </td>
126 126 <td class="td-type">
127 127 ${integration.integration_type}
128 128 </td>
129 129 <td class="td-scope">
130 130 %if integration.repo:
131 131 <a href="${h.route_path('repo_summary', repo_name=integration.repo.repo_name)}">
132 132 ${_('repo')}:${integration.repo.repo_name}
133 133 </a>
134 134 %elif integration.repo_group:
135 135 <a href="${h.route_path('repo_group_home', repo_group_name=integration.repo_group.group_name)}">
136 136 ${_('repogroup')}:${integration.repo_group.group_name}
137 137 %if integration.child_repos_only:
138 138 ${_('child repos only')}
139 139 %else:
140 140 ${_('cascade to all')}
141 141 %endif
142 142 </a>
143 143 %else:
144 144 %if integration.child_repos_only:
145 145 ${_('top level repos only')}
146 146 %else:
147 147 ${_('global')}
148 148 %endif
149 149 </td>
150 150 %endif
151 151 <td class="td-action">
152 152 %if not IntegrationType:
153 153 ${_('unknown integration')}
154 154 %else:
155 155 <%
156 156 if c.repo:
157 157 edit_url = request.route_path('repo_integrations_edit',
158 158 repo_name=c.repo.repo_name,
159 159 integration=integration.integration_type,
160 160 integration_id=integration.integration_id)
161 161 elif c.repo_group:
162 162 edit_url = request.route_path('repo_group_integrations_edit',
163 163 repo_group_name=c.repo_group.group_name,
164 164 integration=integration.integration_type,
165 165 integration_id=integration.integration_id)
166 166 else:
167 167 edit_url = request.route_path('global_integrations_edit',
168 168 integration=integration.integration_type,
169 169 integration_id=integration.integration_id)
170 170 %>
171 171 <div class="grid_edit">
172 172 <a href="${edit_url}">${_('Edit')}</a>
173 173 </div>
174 174 <div class="grid_delete">
175 175 <a href="${edit_url}"
176 176 class="btn btn-link btn-danger delete_integration_entry"
177 177 data-desc="${integration.name}"
178 178 data-uid="${integration.integration_id}">
179 179 ${_('Delete')}
180 180 </a>
181 181 </div>
182 182 %endif
183 183 </td>
184 184 </tr>
185 185 %endfor
186 186 <tr id="last-row"></tr>
187 187 </tbody>
188 188 </table>
189 189 <div class="integrations-paginator">
190 190 <div class="pagination-wh pagination-left">
191 ${c.integrations_list.pager('$link_previous ~2~ $link_next')}
191 ${c.integrations_list.render()}
192 192 </div>
193 193 </div>
194 194 </div>
195 195 </div>
196 196 <script type="text/javascript">
197 197 var delete_integration = function(entry) {
198 198 if (confirm("Confirm to remove this integration: "+$(entry).data('desc'))) {
199 199 var request = $.ajax({
200 200 type: "POST",
201 201 url: $(entry).attr('href'),
202 202 data: {
203 203 'delete': 'delete',
204 204 'csrf_token': CSRF_TOKEN
205 205 },
206 206 success: function(){
207 207 location.reload();
208 208 },
209 209 error: function(data, textStatus, errorThrown){
210 210 alert("Error while deleting entry.\nError code {0} ({1}). URL: {2}".format(data.status,data.statusText,$(entry)[0].url));
211 211 }
212 212 });
213 213 };
214 214 };
215 215
216 216 $('.delete_integration_entry').on('click', function(e){
217 217 e.preventDefault();
218 218 delete_integration(this);
219 219 });
220 220 </script> No newline at end of file
@@ -1,46 +1,46 b''
1 1 <%namespace name="base" file="/base/base.mako"/>
2 2
3 3 <div class="panel panel-default">
4 4 <div class="panel-heading">
5 5 <h3 class="panel-title">${_('My notifications')}</h3>
6 6 </div>
7 7
8 8 <div class="panel-body">
9 9 %if c.notifications:
10 10
11 11 <div class="notification-list notification-table">
12 12 %for notification in c.notifications:
13 13 <div id="notification_${notification.notification.notification_id}" class="container ${'unread' if not notification.read else '' }">
14 14 <div class="notification-header">
15 15 <div class="desc ${'unread' if not notification.read else '' }">
16 16 ${base.gravatar(notification.notification.created_by_user.email, 16)}
17 17 <a href="${h.route_path('notifications_show', notification_id=notification.notification.notification_id)}">
18 18 ${h.notification_description(notification.notification, request)}
19 19 </a>
20 20 </div>
21 21 <div class="delete-notifications">
22 22 <span onclick="deleteNotification(${notification.notification.notification_id})" class="delete-notification tooltip" title="${_('Delete')}"><i class="icon-delete"></i></span>
23 23 </div>
24 24 <div class="read-notifications">
25 25 %if not notification.read:
26 26 <span onclick="readNotification(${notification.notification.notification_id})" class="read-notification tooltip" title="${_('Mark as read')}"><i class="icon-ok"></i></span>
27 27 %endif
28 28 </div>
29 29 </div>
30 30 <div class="notification-subject"></div>
31 31 </div>
32 32 %endfor
33 33 </div>
34 34
35 35 <div class="notification-paginator">
36 36 <div class="pagination-wh pagination-left">
37 ${c.notifications.pager('$link_previous ~2~ $link_next')}
37 ${c.notifications.render()}
38 38 </div>
39 39 </div>
40 40
41 41 %else:
42 42 <div class="table">${_('No notifications here yet')}</div>
43 43 %endif
44 44
45 45 </div>
46 46 </div>
@@ -1,326 +1,326 b''
1 1 ## -*- coding: utf-8 -*-
2 2
3 3 <%inherit file="/base/base.mako"/>
4 4
5 5 <%def name="title()">
6 6 ${_('%s Changelog') % c.repo_name}
7 7 %if c.changelog_for_path:
8 8 /${c.changelog_for_path}
9 9 %endif
10 10 %if c.rhodecode_name:
11 11 &middot; ${h.branding(c.rhodecode_name)}
12 12 %endif
13 13 </%def>
14 14
15 15 <%def name="breadcrumbs_links()">
16 16 %if c.changelog_for_path:
17 17 /${c.changelog_for_path}
18 18 %endif
19 19 </%def>
20 20
21 21 <%def name="menu_bar_nav()">
22 22 ${self.menu_items(active='repositories')}
23 23 </%def>
24 24
25 25 <%def name="menu_bar_subnav()">
26 26 ${self.repo_menu(active='commits')}
27 27 </%def>
28 28
29 29 <%def name="main()">
30 30
31 31 <div class="box">
32 32
33 33 <div class="title">
34 34 <div id="filter_changelog">
35 35 ${h.hidden('branch_filter')}
36 36 %if c.selected_name:
37 37 <div class="btn btn-default" id="clear_filter" >
38 38 ${_('Clear filter')}
39 39 </div>
40 40 %endif
41 41 </div>
42 42 <div class="pull-left obsolete-toggle">
43 43 % if h.is_hg(c.rhodecode_repo):
44 44 % if c.show_hidden:
45 45 <a class="action-link" href="${h.current_route_path(request, evolve=0)}">${_('Hide obsolete/hidden')}</a>
46 46 % else:
47 47 <a class="action-link" href="${h.current_route_path(request, evolve=1)}">${_('Show obsolete/hidden')}</a>
48 48 % endif
49 49 % else:
50 50 <span class="action-link disabled">${_('Show hidden')}</span>
51 51 % endif
52 52 </div>
53 53 <ul class="links">
54 54 <li>
55 55
56 56 %if c.rhodecode_db_repo.fork:
57 57 <span>
58 58 <a id="compare_fork_button"
59 59 title="${h.tooltip(_('Compare fork with %s' % c.rhodecode_db_repo.fork.repo_name))}"
60 60 class="btn btn-small"
61 61 href="${h.route_path('repo_compare',
62 62 repo_name=c.rhodecode_db_repo.fork.repo_name,
63 63 source_ref_type=c.rhodecode_db_repo.landing_rev[0],
64 64 source_ref=c.rhodecode_db_repo.landing_rev[1],
65 65 target_ref_type='branch' if request.GET.get('branch') else c.rhodecode_db_repo.landing_rev[0],
66 66 target_ref=request.GET.get('branch') or c.rhodecode_db_repo.landing_rev[1],
67 67 _query=dict(merge=1, target_repo=c.repo_name))}"
68 68 >
69 69 ${_('Compare fork with Parent (%s)' % c.rhodecode_db_repo.fork.repo_name)}
70 70 </a>
71 71 </span>
72 72 %endif
73 73
74 74 ## pr open link
75 75 %if h.is_hg(c.rhodecode_repo) or h.is_git(c.rhodecode_repo):
76 76 <span>
77 77 <a id="open_new_pull_request" class="btn btn-small btn-success" href="${h.route_path('pullrequest_new',repo_name=c.repo_name)}">
78 78 ${_('Open new pull request')}
79 79 </a>
80 80 </span>
81 81 %endif
82 82
83 83 </li>
84 84 </ul>
85 85 </div>
86 86
87 87 % if c.pagination:
88 88 <script type="text/javascript" src="${h.asset('js/src/plugins/jquery.commits-graph.js')}"></script>
89 89
90 90 <div class="graph-header">
91 91 ${self.breadcrumbs('breadcrumbs_light')}
92 92 </div>
93 93
94 94 <div id="graph">
95 95 <div class="graph-col-wrapper">
96 96 <div id="graph_nodes">
97 97 <div id="graph_canvas"></div>
98 98 </div>
99 99 <div id="graph_content" class="graph_full_width">
100 100
101 101 <div class="table">
102 102 <table id="changesets" class="rctable">
103 103 <tr>
104 104 ## checkbox
105 105 <th colspan="4">
106 106 ## clear selection
107 107 <div title="${_('Clear selection')}" class="btn btn-sm" id="rev_range_clear" style="display:none">
108 108 <i class="icon-cancel-circled2"></i>
109 109 </div>
110 110 <div class="btn btn-sm disabled" disabled="disabled" id="rev_range_more" style="display:none;">${_('Select second commit')}</div>
111 111 <a href="#" class="btn btn-success btn-sm" id="rev_range_container" style="display:none;"></a>
112 112 </th>
113 113
114 114 ## commit message expand arrow
115 115 <th></th>
116 116 <th>${_('Commit Message')}</th>
117 117
118 118 <th>${_('Age')}</th>
119 119 <th>${_('Author')}</th>
120 120
121 121 <th>${_('Refs')}</th>
122 122 ## comments
123 123 <th></th>
124 124 </tr>
125 125
126 126 <tbody class="commits-range">
127 127 <%include file='changelog_elements.mako'/>
128 128 </tbody>
129 129 </table>
130 130 </div>
131 131 </div>
132 132 <div class="pagination-wh pagination-left">
133 ${c.pagination.pager('$link_previous ~2~ $link_next')}
133 ${c.pagination.render()}
134 134 </div>
135 135 <div id="commit-counter" data-total=${c.total_cs} class="pull-right">
136 136 ${_ungettext('showing %d out of %d commit', 'showing %d out of %d commits', c.showing_commits) % (c.showing_commits, c.total_cs)}
137 137 </div>
138 138 </div>
139 139
140 140 <script type="text/javascript">
141 141 var cache = {};
142 142 $(function(){
143 143
144 144 // Create links to commit ranges when range checkboxes are selected
145 145 var $commitCheckboxes = $('.commit-range');
146 146 // cache elements
147 147 var $commitRangeMore = $('#rev_range_more');
148 148 var $commitRangeContainer = $('#rev_range_container');
149 149 var $commitRangeClear = $('#rev_range_clear');
150 150
151 151 var checkboxRangeSelector = function(e){
152 152 var selectedCheckboxes = [];
153 153 for (pos in $commitCheckboxes){
154 154 if($commitCheckboxes[pos].checked){
155 155 selectedCheckboxes.push($commitCheckboxes[pos]);
156 156 }
157 157 }
158 158 var open_new_pull_request = $('#open_new_pull_request');
159 159
160 160 if (open_new_pull_request) {
161 161 var selected_changes = selectedCheckboxes.length;
162 162 open_new_pull_request.hide();
163 163 if (selected_changes == 1) {
164 164 open_new_pull_request.html(_gettext('Open new pull request for selected commit'));
165 165 } else {
166 166 open_new_pull_request.html(_gettext('Open new pull request'));
167 167 }
168 168 open_new_pull_request.show();
169 169 }
170 170
171 171 if (selectedCheckboxes.length > 0) {
172 172 $('#compare_fork_button').hide();
173 173 var commitStart = $(selectedCheckboxes[selectedCheckboxes.length-1]).data();
174 174
175 175 var revStart = commitStart.commitId;
176 176
177 177 var commitEnd = $(selectedCheckboxes[0]).data();
178 178 var revEnd = commitEnd.commitId;
179 179
180 180 var lbl_start = '{0}'.format(commitStart.commitIdx, commitStart.shortId);
181 181 var lbl_end = '{0}'.format(commitEnd.commitIdx, commitEnd.shortId);
182 182
183 183 var url = pyroutes.url('repo_commit', {'repo_name': '${c.repo_name}', 'commit_id': revStart+'...'+revEnd});
184 184 var link = _gettext('Show commit range {0} ... {1}').format(lbl_start, lbl_end);
185 185
186 186 if (selectedCheckboxes.length > 1) {
187 187 $commitRangeClear.show();
188 188 $commitRangeMore.hide();
189 189
190 190 $commitRangeContainer
191 191 .attr('href',url)
192 192 .html(link)
193 193 .show();
194 194
195 195
196 196 } else {
197 197 $commitRangeContainer.hide();
198 198 $commitRangeClear.show();
199 199 $commitRangeMore.show();
200 200 }
201 201
202 202 // pull-request link
203 203 if (selectedCheckboxes.length == 1){
204 204 var _url = pyroutes.url('pullrequest_new', {'repo_name': '${c.repo_name}', 'commit': revEnd});
205 205 open_new_pull_request.attr('href', _url);
206 206 } else {
207 207 var _url = pyroutes.url('pullrequest_new', {'repo_name': '${c.repo_name}'});
208 208 open_new_pull_request.attr('href', _url);
209 209 }
210 210
211 211 } else {
212 212 $commitRangeContainer.hide();
213 213 $commitRangeClear.hide();
214 214 $commitRangeMore.hide();
215 215
216 216 %if c.branch_name:
217 217 var _url = pyroutes.url('pullrequest_new', {'repo_name': '${c.repo_name}', 'branch':'${c.branch_name}'});
218 218 open_new_pull_request.attr('href', _url);
219 219 %else:
220 220 var _url = pyroutes.url('pullrequest_new', {'repo_name': '${c.repo_name}'});
221 221 open_new_pull_request.attr('href', _url);
222 222 %endif
223 223 $('#compare_fork_button').show();
224 224 }
225 225 };
226 226
227 227 $commitCheckboxes.on('click', checkboxRangeSelector);
228 228
229 229 $commitRangeClear.on('click',function(e) {
230 230 $commitCheckboxes.attr('checked', false);
231 231 checkboxRangeSelector();
232 232 e.preventDefault();
233 233 });
234 234
235 235 // make sure the buttons are consistent when navigate back and forth
236 236 checkboxRangeSelector();
237 237
238 238 var msgs = $('.message');
239 239 // get first element height
240 240 var el = $('#graph_content .container')[0];
241 241 var row_h = el.clientHeight;
242 242 for (var i=0; i < msgs.length; i++) {
243 243 var m = msgs[i];
244 244
245 245 var h = m.clientHeight;
246 246 var pad = $(m).css('padding');
247 247 if (h > row_h) {
248 248 var offset = row_h - (h+12);
249 249 $(m.nextElementSibling).css('display','block');
250 250 $(m.nextElementSibling).css('margin-top',offset+'px');
251 251 }
252 252 }
253 253
254 254 $("#clear_filter").on("click", function() {
255 255 var filter = {'repo_name': '${c.repo_name}'};
256 256 window.location = pyroutes.url('repo_commits', filter);
257 257 });
258 258
259 259 $("#branch_filter").select2({
260 260 'dropdownAutoWidth': true,
261 261 'width': 'resolve',
262 262 'placeholder': "${c.selected_name or _('Branch filter')}",
263 263 containerCssClass: "drop-menu",
264 264 dropdownCssClass: "drop-menu-dropdown",
265 265 query: function(query){
266 266 var key = 'cache';
267 267 var cached = cache[key] ;
268 268 if(cached) {
269 269 var data = {results: []};
270 270 //filter results
271 271 $.each(cached.results, function(){
272 272 var section = this.text;
273 273 var children = [];
274 274 $.each(this.children, function(){
275 275 if(query.term.length == 0 || this.text.toUpperCase().indexOf(query.term.toUpperCase()) >= 0 ){
276 276 children.push({'id': this.id, 'text': this.text, 'type': this.type})
277 277 }
278 278 });
279 279 data.results.push({'text': section, 'children': children});
280 280 query.callback({results: data.results});
281 281 });
282 282 }else{
283 283 $.ajax({
284 284 url: pyroutes.url('repo_refs_changelog_data', {'repo_name': '${c.repo_name}'}),
285 285 data: {},
286 286 dataType: 'json',
287 287 type: 'GET',
288 288 success: function(data) {
289 289 cache[key] = data;
290 290 query.callback({results: data.results});
291 291 }
292 292 })
293 293 }
294 294 }
295 295 });
296 296 $('#branch_filter').on('change', function(e){
297 297 var data = $('#branch_filter').select2('data');
298 298 //type: branch_closed
299 299 var selected = data.text;
300 300 var filter = {'repo_name': '${c.repo_name}'};
301 301 if(data.type == 'branch' || data.type == 'branch_closed'){
302 302 filter["branch"] = selected;
303 303 if (data.type == 'branch_closed') {
304 304 filter["evolve"] = '1';
305 305 }
306 306 }
307 307 else if (data.type == 'book'){
308 308 filter["bookmark"] = selected;
309 309 }
310 310 window.location = pyroutes.url('repo_commits', filter);
311 311 });
312 312
313 313 commitsController = new CommitsController();
314 314 % if not c.changelog_for_path:
315 315 commitsController.reloadGraph();
316 316 % endif
317 317
318 318 });
319 319
320 320 </script>
321 321 </div>
322 322 % else:
323 323 ${_('There are no changes yet')}
324 324 % endif
325 325 </div>
326 326 </%def>
@@ -1,58 +1,52 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <%inherit file="/base/base.mako"/>
3 3 <%def name="title()">
4 4 ${_('Journal')}
5 5 %if c.rhodecode_name:
6 6 &middot; ${h.branding(c.rhodecode_name)}
7 7 %endif
8 8 </%def>
9 9
10 10 <%def name="breadcrumbs_links()">
11 11 ${h.form(None, id_="filter_form", method="get")}
12 12 <input class="q_filter_box ${'' if c.search_term else 'initial'}" id="j_filter" size="15" type="text" name="filter" value="${c.search_term}" placeholder="${_('quick filter...')}"/>
13 13 <input type='submit' value="${_('filter')}" class="btn" />
14 14 ${_('Journal')} - ${_ungettext('%s entry', '%s entries', c.journal_pager.item_count) % (c.journal_pager.item_count)}
15 15 ${h.end_form()}
16 16 <p class="filterexample" style="position: inherit" onclick="$('#search-help').toggle()">${_('Example Queries')}</p>
17 17 <pre id="search-help" style="display: none">${h.tooltip(h.journal_filter_help(request))}</pre>
18 18 </%def>
19 19
20 20 <%def name="menu_bar_nav()">
21 21 ${self.menu_items(active='journal')}
22 22 </%def>
23 23
24 24 <%def name="head_extra()">
25 25 <link href="${h.route_path('journal_atom', _query=dict(auth_token=c.rhodecode_user.feed_token))}" rel="alternate" title="${_('ATOM journal feed')}" type="application/atom+xml" />
26 26 <link href="${h.route_path('journal_rss', _query=dict(auth_token=c.rhodecode_user.feed_token))}" rel="alternate" title="${_('RSS journal feed')}" type="application/rss+xml" />
27 27 </%def>
28 28
29 29 <%def name="main()">
30 30
31 31 <div class="box">
32 32 <!-- box / title -->
33 33 <div class="title journal">
34 34 ${self.breadcrumbs()}
35 35 <ul class="links icon-only-links block-right">
36 36 <li>
37 37 <span><a id="refresh" href="${h.route_path('journal')}"><i class="icon-refresh"></i></a></span>
38 38 </li>
39 39 <li>
40 <span><a href="${h.route_path('journal_atom', _query=dict(auth_token=c.rhodecode_user.feed_token))}"><i class="icon-rss-sign"></i></a></span>
40 <span>
41 <a href="${h.route_path('journal_atom', _query=dict(auth_token=c.rhodecode_user.feed_token))}" title="RSS Feed" class="btn btn-sm"><i class="icon-rss-sign"></i>RSS</a>
42 </span>
41 43 </li>
42 44 </ul>
43 45 </div>
44 46 <div id="journal">${c.journal_data|n}</div>
45 47 </div>
46 48
47 49 <script type="text/javascript">
48
49 $('#j_filter').autoGrowInput();
50 $(document).on('pjax:success',function(){
51 show_more_event();
52 });
53 $(document).pjax(
54 '#refresh', '#journal',
55 {url: "${request.current_route_path(_query=dict(filter=c.search_term))}", push: false});
56
50 $('#j_filter').autoGrowInput();
57 51 </script>
58 52 </%def>
@@ -1,54 +1,47 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <%namespace name="base" file="/base/base.mako"/>
3 3
4 4 %if c.journal_day_aggreagate:
5 5 %for day,items in c.journal_day_aggreagate:
6 6 <div class="journal_day">${day}</div>
7 7 % for user,entries in items:
8 8 <div class="journal_container">
9 9 ${base.gravatar(user.email if user else '', 30)}
10 10 %if user:
11 11 <div class="journal_user user">${h.link_to_user(user.username)}</div>
12 12 %else:
13 13 <div class="journal_user user deleted">${entries[0].username}</div>
14 14 %endif
15 15 <div class="journal_action_container">
16 16 % for entry in entries:
17 17 <div class="journal_icon"> ${h.action_parser(request, entry)[2]()}</div>
18 18 <div class="journal_action">${h.action_parser(request, entry)[0]()}</div>
19 19 <div class="journal_repo">
20 20 <span class="journal_repo_name">
21 21 %if entry.repository is not None:
22 22 ${h.link_to(entry.repository.repo_name,
23 23 h.route_path('repo_summary',repo_name=entry.repository.repo_name))}
24 24 %else:
25 25 ${entry.repository_name}
26 26 %endif
27 27 </span>
28 28 </div>
29 29 <div class="journal_action_params">${h.literal(h.action_parser(request, entry)[1]())}</div>
30 30 <div class="date">
31 31 ${h.age_component(entry.action_date, time_is_local=True)}
32 32 </div>
33 33 %endfor
34 34 </div>
35 35 </div>
36 36 %endfor
37 37 %endfor
38 38
39 <div class="pagination-wh pagination-left" >
40 ${c.journal_pager.pager('$link_previous ~2~ $link_next')}
39 <div class="pagination-wh pagination-left">
40 ${c.journal_pager.render()}
41 41 </div>
42 <script type="text/javascript">
43 $(document).pjax('#journal .pager_link','#journal');
44 $(document).on('pjax:success',function(){
45 show_more_event();
46 timeagoActivate();
47 tooltipActivate();
48 });
49 </script>
42
50 43 %else:
51 44 <div>
52 45 ${_('No entries yet')}
53 46 </div>
54 47 %endif
@@ -1,241 +1,241 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <%inherit file="/base/base.mako"/>
3 3
4 4 <%def name="title()">
5 5 %if c.repo_name:
6 6 ${_('Search inside repository {repo_name}').format(repo_name=c.repo_name)}
7 7 %elif c.repo_group_name:
8 8 ${_('Search inside repository group {repo_group_name}').format(repo_group_name=c.repo_group_name)}
9 9 %else:
10 10 ${_('Search inside all accessible repositories')}
11 11 %endif
12 12 %if c.rhodecode_name:
13 13 &middot; ${h.branding(c.rhodecode_name)}
14 14 %endif
15 15 </%def>
16 16
17 17 <%def name="breadcrumbs_links()">
18 18 %if c.repo_name:
19 19 ${_('Search inside repository {repo_name}').format(repo_name=c.repo_name)}
20 20 %elif c.repo_group_name:
21 21 ${_('Search inside repository group {repo_group_name}').format(repo_group_name=c.repo_group_name)}
22 22 %else:
23 23 ${_('Search inside all accessible repositories')}
24 24 %endif
25 25
26 26 </%def>
27 27
28 28 <%def name="menu_bar_nav()">
29 29 %if c.repo_name:
30 30 ${self.menu_items(active='search')}
31 31 %elif c.repo_group_name:
32 32 ${self.menu_items(active='search')}
33 33 %else:
34 34 ${self.menu_items(active='search')}
35 35 %endif
36 36 </%def>
37 37
38 38 <%def name="menu_bar_subnav()">
39 39 %if c.repo_name:
40 40 <% active_entry = {'content':'files', 'path':'files', 'commit':'commits'}.get(c.search_type, 'summary')%>
41 41 ${self.repo_menu(active=active_entry)}
42 42 %elif c.repo_group_name:
43 43 ${self.repo_group_menu(active='home')}
44 44 %endif
45 45 </%def>
46 46
47 47 <%def name="repo_icon(db_repo)">
48 48 %if h.is_hg(db_repo):
49 49 <i class="icon-hg"></i>
50 50 %endif
51 51 %if h.is_git(db_repo):
52 52 <i class="icon-git"></i>
53 53 %endif
54 54 %if h.is_svn(db_repo):
55 55 <i class="icon-svn"></i>
56 56 %endif
57 57 </%def>
58 58
59 59 <%def name="repo_group_icon()">
60 60 <i class="icon-repo-group"></i>
61 61 </%def>
62 62
63 63
64 64 <%def name="field_sort(field_name)">
65 65
66 66 <%
67 67 if c.sort.startswith('asc:'):
68 return c.url_generator(sort='desc:{}'.format(field_name))
68 return h.current_route_path(request, sort='desc:{}'.format(field_name))
69 69 elif c.sort.startswith('desc:'):
70 return c.url_generator(sort='asc:{}'.format(field_name))
70 return h.current_route_path(request, sort='asc:{}'.format(field_name))
71 71
72 return c.url_generator(sort='asc:{}'.format(field_name))
72 return h.current_route_path(request, sort='asc:{}'.format(field_name))
73 73 %>
74 74 </%def>
75 75
76 76
77 77 <%def name="main()">
78 78 <div class="box">
79 79 %if c.repo_name:
80 80 <!-- box / title -->
81 81 ${h.form(h.route_path('search_repo',repo_name=c.repo_name),method='get')}
82 82 %elif c.repo_group_name:
83 83 <!-- box / title -->
84 84 ${h.form(h.route_path('search_repo_group',repo_group_name=c.repo_group_name),method='get')}
85 85 %else:
86 86 <!-- box / title -->
87 87 <div class="title">
88 88 ${self.breadcrumbs()}
89 89 <ul class="links">&nbsp;</ul>
90 90 </div>
91 91 <!-- end box / title -->
92 92 ${h.form(h.route_path('search'), method='get')}
93 93 %endif
94 94 <div class="form search-form">
95 95 <div class="fields">
96 96
97 97 ${h.text('q', c.cur_query, placeholder="Enter query...")}
98 98
99 99 ${h.select('type',c.search_type,[('content',_('Files')), ('path',_('File path')),('commit',_('Commits'))],id='id_search_type')}
100 100 ${h.hidden('max_lines', '10')}
101 101
102 102 <input type="submit" value="${_('Search')}" class="btn"/>
103 103 <br/>
104 104
105 105 <div class="search-tags">
106 106 <span class="tag tag8">
107 107 %if c.repo_name:
108 108 <a href="${h.route_path('search', _query={'q': c.cur_query, 'type': request.GET.get('type', 'content')})}">${_('Global Search')}</a>
109 109 %elif c.repo_group_name:
110 110 <a href="${h.route_path('search', _query={'q': c.cur_query, 'type': request.GET.get('type', 'content')})}">${_('Global Search')}</a>
111 111 % else:
112 112 ${_('Global Search')}
113 113 %endif
114 114 </span>
115 115
116 116 %if c.repo_name:
117 117 Β»
118 118 <span class="tag tag8">
119 119 ${repo_icon(c.rhodecode_db_repo)}
120 120 ${c.repo_name}
121 121 </span>
122 122
123 123 %elif c.repo_group_name:
124 124 Β»
125 125 <span class="tag tag8">
126 126 ${repo_group_icon()}
127 127 ${c.repo_group_name}
128 128 </span>
129 129 %endif
130 130
131 131 % if c.sort_tag:
132 132 <span class="tag tag8">
133 133 % if c.sort_tag_dir == 'asc':
134 134 <i class="icon-angle-down"></i>
135 135 % elif c.sort_tag_dir == 'desc':
136 136 <i class="icon-angle-up"></i>
137 137 % endif
138 138 ${_('sort')}:${c.sort_tag}
139 139 </span>
140 140 % endif
141 141
142 142 % for search_tag in c.search_tags:
143 143 <br/><span class="tag disabled" style="margin-top: 3px">${search_tag}</span>
144 144 % endfor
145 145
146 146 </div>
147 147
148 148 <div class="search-feedback-items">
149 149 % for error in c.errors:
150 150 <span class="error-message">
151 151 % for k,v in error.asdict().items():
152 152 ${k} - ${v}
153 153 % endfor
154 154 </span>
155 155 % endfor
156 156 <div class="field">
157 157 <p class="filterexample" style="position: inherit" onclick="$('#search-help').toggle()">${_('Query Language examples')}</p>
158 158 <pre id="search-help" style="display: none">\
159 159
160 160 % if c.searcher.name == 'whoosh':
161 161 Example filter terms for `Whoosh` search:
162 162 query lang: <a href="${c.searcher.query_lang_doc}">Whoosh Query Language</a>
163 163 Whoosh has limited query capabilities. For advanced search use ElasticSearch 6 from RhodeCode EE edition.
164 164
165 165 Generate wildcards using '*' character:
166 166 "repo_name:vcs*" - search everything starting with 'vcs'
167 167 "repo_name:*vcs*" - search for repository containing 'vcs'
168 168
169 169 Optional AND / OR operators in queries
170 170 "repo_name:vcs OR repo_name:test"
171 171 "owner:test AND repo_name:test*" AND extension:py
172 172
173 173 Move advanced search is available via ElasticSearch6 backend in EE edition.
174 174 % elif c.searcher.name == 'elasticsearch' and c.searcher.es_version == '2':
175 175 Example filter terms for `ElasticSearch-${c.searcher.es_version}`search:
176 176 ElasticSearch-2 has limited query capabilities. For advanced search use ElasticSearch 6 from RhodeCode EE edition.
177 177
178 178 search type: content (File Content)
179 179 indexed fields: content
180 180
181 181 # search for `fix` string in all files
182 182 fix
183 183
184 184 search type: commit (Commit message)
185 185 indexed fields: message
186 186
187 187 search type: path (File name)
188 188 indexed fields: path
189 189
190 190 % else:
191 191 Example filter terms for `ElasticSearch-${c.searcher.es_version}`search:
192 192 query lang: <a href="${c.searcher.query_lang_doc}">ES 6 Query Language</a>
193 193 The reserved characters needed espace by `\`: + - = && || > < ! ( ) { } [ ] ^ " ~ * ? : \ /
194 194 % for handler in c.searcher.get_handlers().values():
195 195
196 196 search type: ${handler.search_type_label}
197 197 *indexed fields*: ${', '.join( [('\n ' if x[0]%4==0 else '')+x[1] for x in enumerate(handler.es_6_field_names)])}
198 198 % for entry in handler.es_6_example_queries:
199 199 ${entry.rstrip()}
200 200 % endfor
201 201 % endfor
202 202
203 203 % endif
204 204 </pre>
205 205 </div>
206 206
207 207 <div class="field">${c.runtime}</div>
208 208 </div>
209 209 </div>
210 210 </div>
211 211
212 212 ${h.end_form()}
213 213 <div class="search">
214 214 % if c.search_type == 'content':
215 215 <%include file='search_content.mako'/>
216 216 % elif c.search_type == 'path':
217 217 <%include file='search_path.mako'/>
218 218 % elif c.search_type == 'commit':
219 219 <%include file='search_commit.mako'/>
220 220 % elif c.search_type == 'repository':
221 221 <%include file='search_repository.mako'/>
222 222 % endif
223 223 </div>
224 224 </div>
225 225 <script>
226 226 $(document).ready(function(){
227 227 $("#id_search_type").select2({
228 228 'containerCssClass': "drop-menu",
229 229 'dropdownCssClass': "drop-menu-dropdown",
230 230 'dropdownAutoWidth': true,
231 231 'minimumResultsForSearch': -1
232 232 });
233 233
234 234 $('#q').autoGrowInput({maxWidth: 920});
235 235
236 236 setTimeout(function() {
237 237 $('#q').keyup()
238 238 }, 1);
239 239 })
240 240 </script>
241 241 </%def>
@@ -1,98 +1,98 b''
1 1 <%namespace name="base" file="/base/base.mako"/>
2 2 <%namespace name="search" file="/search/search.mako"/>
3 3
4 4 % if c.formatted_results:
5 5
6 6 <table class="rctable search-results">
7 7 <tr>
8 8 <th>${_('Repository')}</th>
9 9 <th>${_('Commit')}</th>
10 10 <th></th>
11 11 <th>
12 12 <a href="${search.field_sort('message')}">${_('Commit message')}</a>
13 13 </th>
14 14 <th>
15 15 <a href="${search.field_sort('date')}">${_('Commit date')}</a>
16 16 </th>
17 17 <th>
18 18 <a href="${search.field_sort('author_email')}">${_('Author')}</a>
19 19 </th>
20 20 </tr>
21 21 %for entry in c.formatted_results:
22 22 ## search results are additionally filtered, and this check is just a safe gate
23 23 % if c.rhodecode_user.is_admin or h.HasRepoPermissionAny('repository.write','repository.read','repository.admin')(entry['repository'], 'search results commit check'):
24 24 <tr class="body">
25 25 <td class="td-componentname">
26 26 <% repo_type = entry.get('repo_type') or h.get_repo_type_by_name(entry.get('repository')) %>
27 27 ${search.repo_icon(repo_type)}
28 28 ${h.link_to(entry['repository'], h.route_path('repo_summary',repo_name=entry['repository']))}
29 29 </td>
30 30 <td class="td-hash">
31 31 ${h.link_to(h._shorten_commit_id(entry['commit_id']),
32 32 h.route_path('repo_commit',repo_name=entry['repository'],commit_id=entry['commit_id']))}
33 33 </td>
34 34 <td class="td-message expand_commit search open" data-commit-id="${h.md5_safe(entry['repository'])+entry['commit_id']}" id="t-${h.md5_safe(entry['repository'])+entry['commit_id']}" title="${_('Expand commit message')}">
35 35 <div>
36 36 <i class="icon-expand-linked"></i>&nbsp;
37 37 </div>
38 38 </td>
39 39 <td data-commit-id="${h.md5_safe(entry['repository'])+entry['commit_id']}" id="c-${h.md5_safe(entry['repository'])+entry['commit_id']}" class="message td-description open">
40 40 %if entry.get('message_hl'):
41 41 ${h.literal(entry['message_hl'])}
42 42 %else:
43 43 ${h.urlify_commit_message(entry['message'], entry['repository'])}
44 44 %endif
45 45 </td>
46 46 <td class="td-time">
47 47 ${h.age_component(h.time_to_utcdatetime(entry['date']))}
48 48 </td>
49 49
50 50 <td class="td-user author">
51 51 <%
52 52 ## es6 stores this as object
53 53 author = entry['author']
54 54 if isinstance(author, dict):
55 55 author = author['email']
56 56 %>
57 57 ${base.gravatar_with_user(author, tooltip=True)}
58 58 </td>
59 59 </tr>
60 60 % endif
61 61 %endfor
62 62 </table>
63 63
64 64 %if c.cur_query:
65 65 <div class="pagination-wh pagination-left">
66 ${c.formatted_results.pager('$link_previous ~2~ $link_next')}
66 ${c.formatted_results.render()}
67 67 </div>
68 68 %endif
69 69
70 70 <script>
71 71 $('.expand_commit').on('click',function(e){
72 72 var target_expand = $(this);
73 73 var cid = target_expand.data('commit-id');
74 74
75 75 if (target_expand.hasClass('open')){
76 76 $('#c-'+cid).css({'height': '1.5em', 'white-space': 'nowrap', 'text-overflow': 'ellipsis', 'overflow':'hidden'});
77 77 $('#t-'+cid).css({'height': 'auto', 'line-height': '.9em', 'text-overflow': 'ellipsis', 'overflow':'hidden'});
78 78 target_expand.removeClass('open');
79 79 }
80 80 else {
81 81 $('#c-'+cid).css({'height': 'auto', 'white-space': 'normal', 'text-overflow': 'initial', 'overflow':'visible'});
82 82 $('#t-'+cid).css({'height': 'auto', 'max-height': 'none', 'text-overflow': 'initial', 'overflow':'visible'});
83 83 target_expand.addClass('open');
84 84 }
85 85 });
86 86
87 87 $(".message.td-description").mark(
88 88 "${c.searcher.query_to_mark(c.cur_query, 'message')}",
89 89 {
90 90 "className": 'match',
91 91 "accuracy": "complementary",
92 92 "ignorePunctuation": ":._(){}[]!'+=".split("")
93 93 }
94 94 );
95 95
96 96 </script>
97 97
98 98 % endif
@@ -1,165 +1,165 b''
1 1 <%namespace name="search" file="/search/search.mako"/>
2 2
3 3 <%def name="highlight_text_file(has_matched_content, file_content, lexer, html_formatter, matching_lines, shown_matching_lines, url, use_hl_filter)">
4 4 % if has_matched_content:
5 5 ${h.code_highlight(file_content, lexer, html_formatter, use_hl_filter=use_hl_filter)|n}
6 6 % else:
7 7 ${_('No content matched')} <br/>
8 8 % endif
9 9
10 10 %if len(matching_lines) > shown_matching_lines:
11 11 <a href="${url}">
12 12 ${len(matching_lines) - shown_matching_lines} ${_('more matches in this file')}
13 13 </a>
14 14 %endif
15 15 </%def>
16 16
17 17 <div class="search-results">
18 18 <% query_mark = c.searcher.query_to_mark(c.cur_query, 'content') %>
19 19
20 20 %for entry in c.formatted_results:
21 21
22 22 <%
23 23 file_content = entry['content_highlight'] or entry['content']
24 24 mimetype = entry.get('mimetype')
25 25 filepath = entry.get('path')
26 26 max_lines = h.safe_int(request.GET.get('max_lines', '10'))
27 27 line_context = h.safe_int(request.GET.get('line_contenxt', '3'))
28 28
29 29 match_file_url=h.route_path('repo_files',repo_name=entry['repository'], commit_id=entry.get('commit_id', 'tip'),f_path=entry['f_path'], _query={"mark": query_mark})
30 30 terms = c.cur_query
31 31
32 32 if c.searcher.is_es_6:
33 33 # use empty terms so we default to markers usage
34 34 total_lines, matching_lines = h.get_matching_line_offsets(file_content, terms=None)
35 35 else:
36 36 total_lines, matching_lines = h.get_matching_line_offsets(file_content, terms)
37 37
38 38 shown_matching_lines = 0
39 39 lines_of_interest = set()
40 40 for line_number in matching_lines:
41 41 if len(lines_of_interest) < max_lines:
42 42 lines_of_interest |= set(range(
43 43 max(line_number - line_context, 0),
44 44 min(line_number + line_context, total_lines + 1)))
45 45 shown_matching_lines += 1
46 46 lexer = h.get_lexer_safe(mimetype=mimetype, filepath=filepath)
47 47
48 48 html_formatter = h.SearchContentCodeHtmlFormatter(
49 49 linenos=True,
50 50 cssclass="code-highlight",
51 51 url=match_file_url,
52 52 query_terms=terms,
53 53 only_line_numbers=lines_of_interest
54 54 )
55 55
56 56 has_matched_content = len(lines_of_interest) >= 1
57 57
58 58 %>
59 59 ## search results are additionally filtered, and this check is just a safe gate
60 60 % if c.rhodecode_user.is_admin or h.HasRepoPermissionAny('repository.write','repository.read','repository.admin')(entry['repository'], 'search results content check'):
61 61 <div class="codeblock">
62 62 <h1>
63 63 <% repo_type = entry.get('repo_type') or h.get_repo_type_by_name(entry.get('repository')) %>
64 64 ${search.repo_icon(repo_type)}
65 65 ${h.link_to(entry['repository'], h.route_path('repo_summary', repo_name=entry['repository']))}
66 66 </h1>
67 67
68 68 <div class="codeblock-header">
69 69
70 70 <div class="file-filename">
71 71 <i class="icon-file"></i> ${entry['f_path'].split('/')[-1]}
72 72 </div>
73 73
74 74 <div class="file-stats">
75 75 <div class="stats-info">
76 76 <span class="stats-first-item">
77 77 ${entry.get('lines', 0.)} ${_ungettext('line', 'lines', entry.get('lines', 0.))}
78 78 (${len(matching_lines)} ${_ungettext('matched', 'matched', len(matching_lines))})
79 79 </span>
80 80 <span>
81 81 % if entry.get('size'):
82 82 | ${h.format_byte_size_binary(entry['size'])}
83 83 % endif
84 84 </span>
85 85 <span>
86 86 % if entry.get('mimetype'):
87 87 | ${entry.get('mimetype', "unknown mimetype")}
88 88 % endif
89 89 </span>
90 90 </div>
91 91 </div>
92 92 </div>
93 93
94 94 <div class="path clear-fix">
95 95 <div class="pull-left">
96 96 ${h.files_breadcrumbs(entry['repository'],entry.get('commit_id', 'tip'),entry['f_path'], linkify_last_item=True)}
97 97 </div>
98 98
99 99 <div class="pull-right stats">
100 100 ## <a id="file_history_overview_full" href="${h.route_path('repo_commits_file',repo_name=entry.get('repository',''),commit_id=entry.get('commit_id', 'tip'),f_path=entry.get('f_path',''))}">
101 101 ## ${_('Show Full History')}
102 102 ## </a>
103 103 ## | ${h.link_to(_('Annotation'), h.route_path('repo_files:annotated', repo_name=entry.get('repository',''),commit_id=entry.get('commit_id', 'tip'),f_path=entry.get('f_path','')))}
104 104 ## | ${h.link_to(_('Raw'), h.route_path('repo_file_raw', repo_name=entry.get('repository',''),commit_id=entry.get('commit_id', 'tip'),f_path=entry.get('f_path','')))}
105 105 <div class="search-tags">
106 106
107 107 <% repo_group = entry.get('repository_group')%>
108 108 ## hiden if in repo group view
109 109 % if repo_group and not c.repo_group_name:
110 110 <span class="tag tag8">
111 111 ${search.repo_group_icon()}
112 112 <a href="${h.route_path('search_repo_group', repo_group_name=repo_group, _query={'q': c.cur_query})}">${_('Narrow to this repository group')}</a>
113 113 </span>
114 114 % endif
115 115 ## hiden if in repo view
116 116 % if not c.repo_name:
117 117 <span class="tag tag8">
118 118 ${search.repo_icon(repo_type)}
119 119 <a href="${h.route_path('search_repo', repo_name=entry.get('repo_name'), _query={'q': c.cur_query})}">${_('Narrow to this repository')}</a>
120 120 </span>
121 121 % endif
122 122 </div>
123 123
124 124 </div>
125 125 <div class="clear-fix"></div>
126 126 </div>
127 127
128 128
129 129 <div class="code-body search-code-body clear-fix">
130 130 ${highlight_text_file(
131 131 has_matched_content=has_matched_content,
132 132 file_content=file_content,
133 133 lexer=lexer,
134 134 html_formatter=html_formatter,
135 135 matching_lines=matching_lines,
136 136 shown_matching_lines=shown_matching_lines,
137 137 url=match_file_url,
138 138 use_hl_filter=c.searcher.is_es_6
139 139 )}
140 140 </div>
141 141
142 142 </div>
143 143 % endif
144 144 %endfor
145 145 </div>
146 146 %if c.cur_query and c.formatted_results:
147 147 <div class="pagination-wh pagination-left" >
148 ${c.formatted_results.pager('$link_previous ~2~ $link_next')}
148 ${c.formatted_results.render()}
149 149 </div>
150 150 %endif
151 151
152 152 %if c.cur_query:
153 153 <script type="text/javascript">
154 154 $(function(){
155 155 $(".search-code-body").mark(
156 156 "${query_mark}",
157 157 {
158 158 "className": 'match',
159 159 "accuracy": "complementary",
160 160 "ignorePunctuation": ":._(){}[]!'+=".split("")
161 161 }
162 162 );
163 163 })
164 164 </script>
165 165 %endif
@@ -1,53 +1,53 b''
1 1 <%namespace name="search" file="/search/search.mako"/>
2 2
3 3 % if c.formatted_results:
4 4
5 5 <table class="rctable search-results">
6 6 <tr>
7 7 <th>${_('Repository')}</th>
8 8 <th>
9 9 <a href="${search.field_sort('file')}">${_('File')}</a>
10 10 </th>
11 11 <th>
12 12 <a href="${search.field_sort('size')}">${_('Size')}</a>
13 13 </th>
14 14 <th>
15 15 <a href="${search.field_sort('lines')}">${_('Lines')}</a>
16 16 </th>
17 17 </tr>
18 18 %for entry in c.formatted_results:
19 19 ## search results are additionally filtered, and this check is just a safe gate
20 20 % if c.rhodecode_user.is_admin or h.HasRepoPermissionAny('repository.write','repository.read','repository.admin')(entry['repository'], 'search results path check'):
21 21 <tr class="body">
22 22 <td class="td-componentname">
23 23 <% repo_type = entry.get('repo_type') or h.get_repo_type_by_name(entry.get('repository')) %>
24 24 ${search.repo_icon(repo_type)}
25 25 ${h.link_to(entry['repository'], h.route_path('repo_summary',repo_name=entry['repository']))}
26 26 </td>
27 27 <td class="td-componentname">
28 28 <i class="icon-file"></i>
29 29 ${h.link_to(h.literal(entry['f_path']),
30 30 h.route_path('repo_files',repo_name=entry['repository'],commit_id='tip',f_path=entry['f_path']))}
31 31 </td>
32 32 <td>
33 33 %if entry.get('size'):
34 34 ${h.format_byte_size_binary(entry['size'])}
35 35 %endif
36 36 </td>
37 37 <td>
38 38 %if entry.get('lines'):
39 39 ${entry.get('lines', 0.)}
40 40 %endif
41 41 </td>
42 42 </tr>
43 43 % endif
44 44 %endfor
45 45 </table>
46 46
47 47 %if c.cur_query:
48 48 <div class="pagination-wh pagination-left">
49 ${c.formatted_results.pager('$link_previous ~2~ $link_next')}
49 ${c.formatted_results.render()}
50 50 </div>
51 51 %endif
52 52
53 53 % endif
@@ -1,167 +1,167 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <%namespace name="base" file="/base/base.mako"/>
3 3 %if c.repo_commits:
4 4 <table class="rctable repo_summary table_disp">
5 5 <tr>
6 6
7 7 <th class="status"></th>
8 8 <th>${_('Commit')}</th>
9 9 <th>${_('Commit message')}</th>
10 10 <th>${_('Age')}</th>
11 11 <th>${_('Author')}</th>
12 12 <th colspan="2">${_('Refs')}</th>
13 13 </tr>
14 14
15 15 ## to speed up lookups cache some functions before the loop
16 16 <%
17 17 active_patterns = h.get_active_pattern_entries(c.repo_name)
18 18 urlify_commit_message = h.partial(h.urlify_commit_message, active_pattern_entries=active_patterns)
19 19 %>
20 20 %for cnt,cs in enumerate(c.repo_commits):
21 21 <tr class="parity${cnt%2}">
22 22
23 23 <td class="td-status">
24 24 %if c.statuses.get(cs.raw_id):
25 25 <div class="changeset-status-ico shortlog">
26 26 %if c.statuses.get(cs.raw_id)[2]:
27 27 <a class="tooltip" title="${_('Commit status: %s\nClick to open associated pull request #%s') % (c.statuses.get(cs.raw_id)[0], c.statuses.get(cs.raw_id)[2])}" href="${h.route_path('pullrequest_show',repo_name=c.statuses.get(cs.raw_id)[3],pull_request_id=c.statuses.get(cs.raw_id)[2])}">
28 28 <i class="icon-circle review-status-${c.statuses.get(cs.raw_id)[0]}"></i>
29 29 </a>
30 30 %else:
31 31 <a class="tooltip" title="${_('Commit status: {}').format(h.commit_status_lbl(c.statuses.get(cs.raw_id)[0]))}" href="${h.route_path('repo_commit',repo_name=c.repo_name,commit_id=cs.raw_id,_anchor='comment-%s' % c.comments[cs.raw_id][0].comment_id)}">
32 32 <i class="icon-circle review-status-${c.statuses.get(cs.raw_id)[0]}"></i>
33 33 </a>
34 34 %endif
35 35 </div>
36 36 %else:
37 37 <i class="icon-circle review-status-not_reviewed" title="${_('Commit status: Not Reviewed')}"></i>
38 38 %endif
39 39 </td>
40 40 <td class="td-hash">
41 41 <code>
42 42 <a href="${h.route_path('repo_commit', repo_name=c.repo_name, commit_id=cs.raw_id)}">${h.show_id(cs)}</a>
43 43 <i class="tooltip icon-clipboard clipboard-action" data-clipboard-text="${cs.raw_id}" title="${_('Copy the full commit id')}"></i>
44 44 </code>
45 45 </td>
46 46
47 47 <td class="td-description mid">
48 48 <div class="log-container truncate-wrap">
49 49 <div class="message truncate" id="c-${cs.raw_id}">${urlify_commit_message(cs.message, c.repo_name)}</div>
50 50 </div>
51 51 </td>
52 52
53 53 <td class="td-time">
54 54 ${h.age_component(cs.date)}
55 55 </td>
56 56 <td class="td-user author">
57 57 ${base.gravatar_with_user(cs.author, tooltip=True)}
58 58 </td>
59 59
60 60 <td class="td-tags">
61 61 <div class="autoexpand">
62 62 %if h.is_hg(c.rhodecode_repo):
63 63 %for book in cs.bookmarks:
64 64 <span class="booktag tag" title="${h.tooltip(_('Bookmark %s') % book)}">
65 65 <a href="${h.route_path('repo_files:default_path',repo_name=c.repo_name,commit_id=cs.raw_id, _query=dict(at=book))}"><i class="icon-bookmark"></i>${h.shorter(book)}</a>
66 66 </span>
67 67 %endfor
68 68 %endif
69 69 ## tags
70 70 %for tag in cs.tags:
71 71 <span class="tagtag tag" title="${h.tooltip(_('Tag %s') % tag)}">
72 72 <a href="${h.route_path('repo_files:default_path',repo_name=c.repo_name,commit_id=cs.raw_id, _query=dict(at=tag))}"><i class="icon-tag"></i>${h.shorter(tag)}</a>
73 73 </span>
74 74 %endfor
75 75
76 76 ## branch
77 77 %if cs.branch:
78 78 <span class="branchtag tag" title="${h.tooltip(_('Branch %s') % cs.branch)}">
79 79 <a href="${h.route_path('repo_commits',repo_name=c.repo_name,_query=dict(branch=cs.branch))}"><i class="icon-code-fork"></i>${h.shorter(cs.branch)}</a>
80 80 </span>
81 81 %endif
82 82 </div>
83 83 </td>
84 84 <td class="td-comments">
85 85 <% cs_comments = c.comments.get(cs.raw_id,[]) %>
86 86 % if cs_comments:
87 87 <a title="${_('Commit has comments')}" href="${h.route_path('repo_commit',repo_name=c.repo_name,commit_id=cs.raw_id,_anchor='comment-%s' % cs_comments[0].comment_id)}">
88 88 <i class="icon-comment"></i> ${len(cs_comments)}
89 89 </a>
90 90 % else:
91 91 <i class="icon-comment"></i> ${len(cs_comments)}
92 92 % endif
93 93 </td>
94 94 </tr>
95 95 %endfor
96 96
97 97 </table>
98 98
99 99 <script type="text/javascript">
100 100 $(document).pjax('#shortlog_data .pager_link','#shortlog_data', {timeout: 5000, scrollTo: false, push: false});
101 101 $(document).on('pjax:success', function(){ timeagoActivate(); tooltipActivate();});
102 102 $(document).on('pjax:timeout', function(event) {
103 103 // Prevent default timeout redirection behavior
104 104 event.preventDefault()
105 105 })
106 106
107 107 </script>
108 108
109 109 <div class="pagination-wh pagination-left">
110 ${c.repo_commits.pager('$link_previous ~2~ $link_next')}
110 ${c.repo_commits.render()}
111 111 </div>
112 112 %else:
113 113
114 114 %if h.HasRepoPermissionAny('repository.write','repository.admin')(c.repo_name):
115 115 <div class="quick_start">
116 116 <div class="fieldset">
117 117 <p><b>${_('Add or upload files directly via RhodeCode:')}</b></p>
118 118 <div class="pull-left">
119 119 <a href="${h.route_path('repo_files_add_file',repo_name=c.repo_name,commit_id=0, f_path='')}" class="btn btn-default">${_('Add New File')}</a>
120 120 </div>
121 121 <div class="pull-left">
122 122 <a href="${h.route_path('repo_files_upload_file',repo_name=c.repo_name,commit_id=0, f_path='')}" class="btn btn-default">${_('Upload New File')}</a>
123 123 </div>
124 124 %endif
125 125 </div>
126 126
127 127 <div class="fieldset">
128 128 <p><b>${_('Push new repo:')}</b></p>
129 129 <pre>
130 130 %if h.is_git(c.rhodecode_repo):
131 131 git clone ${c.clone_repo_url}
132 132 git add README # add first file
133 133 git commit -m "Initial commit" # commit with message
134 134 git remote add origin ${c.clone_repo_url}
135 135 git push -u origin master # push changes back to default master branch
136 136 %elif h.is_hg(c.rhodecode_repo):
137 137 hg clone ${c.clone_repo_url}
138 138 hg add README # add first file
139 139 hg commit -m "Initial commit" # commit with message
140 140 hg push ${c.clone_repo_url}
141 141 %elif h.is_svn(c.rhodecode_repo):
142 142 svn co ${c.clone_repo_url}
143 143 svn add README # add first file
144 144 svn commit -m "Initial commit"
145 145 svn commit # send changes back to the server
146 146 %endif
147 147 </pre>
148 148 </div>
149 149
150 150 <div class="fieldset">
151 151 <p><b>${_('Existing repository?')}</b></p>
152 152 <pre>
153 153 %if h.is_git(c.rhodecode_repo):
154 154 git remote add origin ${c.clone_repo_url}
155 155 git push -u origin master
156 156 %elif h.is_hg(c.rhodecode_repo):
157 157 hg push ${c.clone_repo_url}
158 158 %elif h.is_svn(c.rhodecode_repo):
159 159 svn co ${c.clone_repo_url}
160 160 %endif
161 161 </pre>
162 162
163 163 </div>
164 164
165 165
166 166 </div>
167 167 %endif
General Comments 0
You need to be logged in to leave comments. Login now