##// 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 # Generated by pip2nix 0.8.0.dev1
1 # Generated by pip2nix 0.8.0.dev1
2 # See https://github.com/johbo/pip2nix
2 # See https://github.com/johbo/pip2nix
3
3
4 { pkgs, fetchurl, fetchgit, fetchhg }:
4 { pkgs, fetchurl, fetchgit, fetchhg }:
5
5
6 self: super: {
6 self: super: {
7 "alembic" = super.buildPythonPackage {
7 "alembic" = super.buildPythonPackage {
8 name = "alembic-1.3.1";
8 name = "alembic-1.3.1";
9 doCheck = false;
9 doCheck = false;
10 propagatedBuildInputs = [
10 propagatedBuildInputs = [
11 self."sqlalchemy"
11 self."sqlalchemy"
12 self."mako"
12 self."mako"
13 self."python-editor"
13 self."python-editor"
14 self."python-dateutil"
14 self."python-dateutil"
15 ];
15 ];
16 src = fetchurl {
16 src = fetchurl {
17 url = "https://files.pythonhosted.org/packages/84/64/493c45119dce700a4b9eeecc436ef9e8835ab67bae6414f040cdc7b58f4b/alembic-1.3.1.tar.gz";
17 url = "https://files.pythonhosted.org/packages/84/64/493c45119dce700a4b9eeecc436ef9e8835ab67bae6414f040cdc7b58f4b/alembic-1.3.1.tar.gz";
18 sha256 = "1cl2chk5jx0rf4hmsd5lljic7iifw17yv3y5xawvp4i14jvpn9s9";
18 sha256 = "1cl2chk5jx0rf4hmsd5lljic7iifw17yv3y5xawvp4i14jvpn9s9";
19 };
19 };
20 meta = {
20 meta = {
21 license = [ pkgs.lib.licenses.mit ];
21 license = [ pkgs.lib.licenses.mit ];
22 };
22 };
23 };
23 };
24 "amqp" = super.buildPythonPackage {
24 "amqp" = super.buildPythonPackage {
25 name = "amqp-2.5.2";
25 name = "amqp-2.5.2";
26 doCheck = false;
26 doCheck = false;
27 propagatedBuildInputs = [
27 propagatedBuildInputs = [
28 self."vine"
28 self."vine"
29 ];
29 ];
30 src = fetchurl {
30 src = fetchurl {
31 url = "https://files.pythonhosted.org/packages/92/1d/433541994a5a69f4ad2fff39746ddbb0bdedb0ea0d85673eb0db68a7edd9/amqp-2.5.2.tar.gz";
31 url = "https://files.pythonhosted.org/packages/92/1d/433541994a5a69f4ad2fff39746ddbb0bdedb0ea0d85673eb0db68a7edd9/amqp-2.5.2.tar.gz";
32 sha256 = "13dhhfxjrqcjybnq4zahg92mydhpg2l76nxcmq7d560687wsxwbp";
32 sha256 = "13dhhfxjrqcjybnq4zahg92mydhpg2l76nxcmq7d560687wsxwbp";
33 };
33 };
34 meta = {
34 meta = {
35 license = [ pkgs.lib.licenses.bsdOriginal ];
35 license = [ pkgs.lib.licenses.bsdOriginal ];
36 };
36 };
37 };
37 };
38 "appenlight-client" = super.buildPythonPackage {
38 "appenlight-client" = super.buildPythonPackage {
39 name = "appenlight-client-0.6.26";
39 name = "appenlight-client-0.6.26";
40 doCheck = false;
40 doCheck = false;
41 propagatedBuildInputs = [
41 propagatedBuildInputs = [
42 self."webob"
42 self."webob"
43 self."requests"
43 self."requests"
44 self."six"
44 self."six"
45 ];
45 ];
46 src = fetchurl {
46 src = fetchurl {
47 url = "https://files.pythonhosted.org/packages/2e/56/418fc10379b96e795ee39a15e69a730c222818af04c3821fa354eaa859ec/appenlight_client-0.6.26.tar.gz";
47 url = "https://files.pythonhosted.org/packages/2e/56/418fc10379b96e795ee39a15e69a730c222818af04c3821fa354eaa859ec/appenlight_client-0.6.26.tar.gz";
48 sha256 = "0s9xw3sb8s3pk73k78nnq4jil3q4mk6bczfa1fmgfx61kdxl2712";
48 sha256 = "0s9xw3sb8s3pk73k78nnq4jil3q4mk6bczfa1fmgfx61kdxl2712";
49 };
49 };
50 meta = {
50 meta = {
51 license = [ pkgs.lib.licenses.bsdOriginal ];
51 license = [ pkgs.lib.licenses.bsdOriginal ];
52 };
52 };
53 };
53 };
54 "asn1crypto" = super.buildPythonPackage {
54 "asn1crypto" = super.buildPythonPackage {
55 name = "asn1crypto-0.24.0";
55 name = "asn1crypto-0.24.0";
56 doCheck = false;
56 doCheck = false;
57 src = fetchurl {
57 src = fetchurl {
58 url = "https://files.pythonhosted.org/packages/fc/f1/8db7daa71f414ddabfa056c4ef792e1461ff655c2ae2928a2b675bfed6b4/asn1crypto-0.24.0.tar.gz";
58 url = "https://files.pythonhosted.org/packages/fc/f1/8db7daa71f414ddabfa056c4ef792e1461ff655c2ae2928a2b675bfed6b4/asn1crypto-0.24.0.tar.gz";
59 sha256 = "0jaf8rf9dx1lf23xfv2cdd5h52f1qr3w8k63985bc35g3d220p4x";
59 sha256 = "0jaf8rf9dx1lf23xfv2cdd5h52f1qr3w8k63985bc35g3d220p4x";
60 };
60 };
61 meta = {
61 meta = {
62 license = [ pkgs.lib.licenses.mit ];
62 license = [ pkgs.lib.licenses.mit ];
63 };
63 };
64 };
64 };
65 "atomicwrites" = super.buildPythonPackage {
65 "atomicwrites" = super.buildPythonPackage {
66 name = "atomicwrites-1.3.0";
66 name = "atomicwrites-1.3.0";
67 doCheck = false;
67 doCheck = false;
68 src = fetchurl {
68 src = fetchurl {
69 url = "https://files.pythonhosted.org/packages/ec/0f/cd484ac8820fed363b374af30049adc8fd13065720fd4f4c6be8a2309da7/atomicwrites-1.3.0.tar.gz";
69 url = "https://files.pythonhosted.org/packages/ec/0f/cd484ac8820fed363b374af30049adc8fd13065720fd4f4c6be8a2309da7/atomicwrites-1.3.0.tar.gz";
70 sha256 = "19ngcscdf3jsqmpcxn6zl5b6anmsajb6izp1smcd1n02midl9abm";
70 sha256 = "19ngcscdf3jsqmpcxn6zl5b6anmsajb6izp1smcd1n02midl9abm";
71 };
71 };
72 meta = {
72 meta = {
73 license = [ pkgs.lib.licenses.mit ];
73 license = [ pkgs.lib.licenses.mit ];
74 };
74 };
75 };
75 };
76 "attrs" = super.buildPythonPackage {
76 "attrs" = super.buildPythonPackage {
77 name = "attrs-19.1.0";
77 name = "attrs-19.1.0";
78 doCheck = false;
78 doCheck = false;
79 src = fetchurl {
79 src = fetchurl {
80 url = "https://files.pythonhosted.org/packages/cc/d9/931a24cc5394f19383fbbe3e1147a0291276afa43a0dc3ed0d6cd9fda813/attrs-19.1.0.tar.gz";
80 url = "https://files.pythonhosted.org/packages/cc/d9/931a24cc5394f19383fbbe3e1147a0291276afa43a0dc3ed0d6cd9fda813/attrs-19.1.0.tar.gz";
81 sha256 = "16g33zr5f449lqc5wgvzpknxryfzrfsxcr6kpgxwn7l5fkv71f7h";
81 sha256 = "16g33zr5f449lqc5wgvzpknxryfzrfsxcr6kpgxwn7l5fkv71f7h";
82 };
82 };
83 meta = {
83 meta = {
84 license = [ pkgs.lib.licenses.mit ];
84 license = [ pkgs.lib.licenses.mit ];
85 };
85 };
86 };
86 };
87 "babel" = super.buildPythonPackage {
87 "babel" = super.buildPythonPackage {
88 name = "babel-1.3";
88 name = "babel-1.3";
89 doCheck = false;
89 doCheck = false;
90 propagatedBuildInputs = [
90 propagatedBuildInputs = [
91 self."pytz"
91 self."pytz"
92 ];
92 ];
93 src = fetchurl {
93 src = fetchurl {
94 url = "https://files.pythonhosted.org/packages/33/27/e3978243a03a76398c384c83f7ca879bc6e8f1511233a621fcada135606e/Babel-1.3.tar.gz";
94 url = "https://files.pythonhosted.org/packages/33/27/e3978243a03a76398c384c83f7ca879bc6e8f1511233a621fcada135606e/Babel-1.3.tar.gz";
95 sha256 = "0bnin777lc53nxd1hp3apq410jj5wx92n08h7h4izpl4f4sx00lz";
95 sha256 = "0bnin777lc53nxd1hp3apq410jj5wx92n08h7h4izpl4f4sx00lz";
96 };
96 };
97 meta = {
97 meta = {
98 license = [ pkgs.lib.licenses.bsdOriginal ];
98 license = [ pkgs.lib.licenses.bsdOriginal ];
99 };
99 };
100 };
100 };
101 "backports.shutil-get-terminal-size" = super.buildPythonPackage {
101 "backports.shutil-get-terminal-size" = super.buildPythonPackage {
102 name = "backports.shutil-get-terminal-size-1.0.0";
102 name = "backports.shutil-get-terminal-size-1.0.0";
103 doCheck = false;
103 doCheck = false;
104 src = fetchurl {
104 src = fetchurl {
105 url = "https://files.pythonhosted.org/packages/ec/9c/368086faa9c016efce5da3e0e13ba392c9db79e3ab740b763fe28620b18b/backports.shutil_get_terminal_size-1.0.0.tar.gz";
105 url = "https://files.pythonhosted.org/packages/ec/9c/368086faa9c016efce5da3e0e13ba392c9db79e3ab740b763fe28620b18b/backports.shutil_get_terminal_size-1.0.0.tar.gz";
106 sha256 = "107cmn7g3jnbkp826zlj8rrj19fam301qvaqf0f3905f5217lgki";
106 sha256 = "107cmn7g3jnbkp826zlj8rrj19fam301qvaqf0f3905f5217lgki";
107 };
107 };
108 meta = {
108 meta = {
109 license = [ pkgs.lib.licenses.mit ];
109 license = [ pkgs.lib.licenses.mit ];
110 };
110 };
111 };
111 };
112 "beaker" = super.buildPythonPackage {
112 "beaker" = super.buildPythonPackage {
113 name = "beaker-1.9.1";
113 name = "beaker-1.9.1";
114 doCheck = false;
114 doCheck = false;
115 propagatedBuildInputs = [
115 propagatedBuildInputs = [
116 self."funcsigs"
116 self."funcsigs"
117 ];
117 ];
118 src = fetchurl {
118 src = fetchurl {
119 url = "https://files.pythonhosted.org/packages/ca/14/a626188d0d0c7b55dd7cf1902046c2743bd392a7078bb53073e13280eb1e/Beaker-1.9.1.tar.gz";
119 url = "https://files.pythonhosted.org/packages/ca/14/a626188d0d0c7b55dd7cf1902046c2743bd392a7078bb53073e13280eb1e/Beaker-1.9.1.tar.gz";
120 sha256 = "08arsn61r255lhz6hcpn2lsiqpg30clla805ysx06wmbhvb6w9rj";
120 sha256 = "08arsn61r255lhz6hcpn2lsiqpg30clla805ysx06wmbhvb6w9rj";
121 };
121 };
122 meta = {
122 meta = {
123 license = [ pkgs.lib.licenses.bsdOriginal ];
123 license = [ pkgs.lib.licenses.bsdOriginal ];
124 };
124 };
125 };
125 };
126 "beautifulsoup4" = super.buildPythonPackage {
126 "beautifulsoup4" = super.buildPythonPackage {
127 name = "beautifulsoup4-4.6.3";
127 name = "beautifulsoup4-4.6.3";
128 doCheck = false;
128 doCheck = false;
129 src = fetchurl {
129 src = fetchurl {
130 url = "https://files.pythonhosted.org/packages/88/df/86bffad6309f74f3ff85ea69344a078fc30003270c8df6894fca7a3c72ff/beautifulsoup4-4.6.3.tar.gz";
130 url = "https://files.pythonhosted.org/packages/88/df/86bffad6309f74f3ff85ea69344a078fc30003270c8df6894fca7a3c72ff/beautifulsoup4-4.6.3.tar.gz";
131 sha256 = "041dhalzjciw6qyzzq7a2k4h1yvyk76xigp35hv5ibnn448ydy4h";
131 sha256 = "041dhalzjciw6qyzzq7a2k4h1yvyk76xigp35hv5ibnn448ydy4h";
132 };
132 };
133 meta = {
133 meta = {
134 license = [ pkgs.lib.licenses.mit ];
134 license = [ pkgs.lib.licenses.mit ];
135 };
135 };
136 };
136 };
137 "billiard" = super.buildPythonPackage {
137 "billiard" = super.buildPythonPackage {
138 name = "billiard-3.6.1.0";
138 name = "billiard-3.6.1.0";
139 doCheck = false;
139 doCheck = false;
140 src = fetchurl {
140 src = fetchurl {
141 url = "https://files.pythonhosted.org/packages/68/1d/2aea8fbb0b1e1260a8a2e77352de2983d36d7ac01207cf14c2b9c6cc860e/billiard-3.6.1.0.tar.gz";
141 url = "https://files.pythonhosted.org/packages/68/1d/2aea8fbb0b1e1260a8a2e77352de2983d36d7ac01207cf14c2b9c6cc860e/billiard-3.6.1.0.tar.gz";
142 sha256 = "09hzy3aqi7visy4vmf4xiish61n0rq5nd3iwjydydps8yrs9r05q";
142 sha256 = "09hzy3aqi7visy4vmf4xiish61n0rq5nd3iwjydydps8yrs9r05q";
143 };
143 };
144 meta = {
144 meta = {
145 license = [ pkgs.lib.licenses.bsdOriginal ];
145 license = [ pkgs.lib.licenses.bsdOriginal ];
146 };
146 };
147 };
147 };
148 "bleach" = super.buildPythonPackage {
148 "bleach" = super.buildPythonPackage {
149 name = "bleach-3.1.0";
149 name = "bleach-3.1.0";
150 doCheck = false;
150 doCheck = false;
151 propagatedBuildInputs = [
151 propagatedBuildInputs = [
152 self."six"
152 self."six"
153 self."webencodings"
153 self."webencodings"
154 ];
154 ];
155 src = fetchurl {
155 src = fetchurl {
156 url = "https://files.pythonhosted.org/packages/78/5a/0df03e8735cd9c75167528299c738702437589b9c71a849489d00ffa82e8/bleach-3.1.0.tar.gz";
156 url = "https://files.pythonhosted.org/packages/78/5a/0df03e8735cd9c75167528299c738702437589b9c71a849489d00ffa82e8/bleach-3.1.0.tar.gz";
157 sha256 = "1yhrgrhkln8bd6gn3imj69g1h4xqah9gaz9q26crqr6gmmvpzprz";
157 sha256 = "1yhrgrhkln8bd6gn3imj69g1h4xqah9gaz9q26crqr6gmmvpzprz";
158 };
158 };
159 meta = {
159 meta = {
160 license = [ pkgs.lib.licenses.asl20 ];
160 license = [ pkgs.lib.licenses.asl20 ];
161 };
161 };
162 };
162 };
163 "bumpversion" = super.buildPythonPackage {
163 "bumpversion" = super.buildPythonPackage {
164 name = "bumpversion-0.5.3";
164 name = "bumpversion-0.5.3";
165 doCheck = false;
165 doCheck = false;
166 src = fetchurl {
166 src = fetchurl {
167 url = "https://files.pythonhosted.org/packages/14/41/8c9da3549f8e00c84f0432c3a8cf8ed6898374714676aab91501d48760db/bumpversion-0.5.3.tar.gz";
167 url = "https://files.pythonhosted.org/packages/14/41/8c9da3549f8e00c84f0432c3a8cf8ed6898374714676aab91501d48760db/bumpversion-0.5.3.tar.gz";
168 sha256 = "0zn7694yfipxg35ikkfh7kvgl2fissha3dnqad2c5bvsvmrwhi37";
168 sha256 = "0zn7694yfipxg35ikkfh7kvgl2fissha3dnqad2c5bvsvmrwhi37";
169 };
169 };
170 meta = {
170 meta = {
171 license = [ pkgs.lib.licenses.mit ];
171 license = [ pkgs.lib.licenses.mit ];
172 };
172 };
173 };
173 };
174 "celery" = super.buildPythonPackage {
174 "celery" = super.buildPythonPackage {
175 name = "celery-4.3.0";
175 name = "celery-4.3.0";
176 doCheck = false;
176 doCheck = false;
177 propagatedBuildInputs = [
177 propagatedBuildInputs = [
178 self."pytz"
178 self."pytz"
179 self."billiard"
179 self."billiard"
180 self."kombu"
180 self."kombu"
181 self."vine"
181 self."vine"
182 ];
182 ];
183 src = fetchurl {
183 src = fetchurl {
184 url = "https://files.pythonhosted.org/packages/a2/4b/d020836f751617e907e84753a41c92231cd4b673ff991b8ee9da52361323/celery-4.3.0.tar.gz";
184 url = "https://files.pythonhosted.org/packages/a2/4b/d020836f751617e907e84753a41c92231cd4b673ff991b8ee9da52361323/celery-4.3.0.tar.gz";
185 sha256 = "1y8y0gbgkwimpxqnxq2rm5qz2vy01fvjiybnpm00y5rzd2m34iac";
185 sha256 = "1y8y0gbgkwimpxqnxq2rm5qz2vy01fvjiybnpm00y5rzd2m34iac";
186 };
186 };
187 meta = {
187 meta = {
188 license = [ pkgs.lib.licenses.bsdOriginal ];
188 license = [ pkgs.lib.licenses.bsdOriginal ];
189 };
189 };
190 };
190 };
191 "cffi" = super.buildPythonPackage {
191 "cffi" = super.buildPythonPackage {
192 name = "cffi-1.12.3";
192 name = "cffi-1.12.3";
193 doCheck = false;
193 doCheck = false;
194 propagatedBuildInputs = [
194 propagatedBuildInputs = [
195 self."pycparser"
195 self."pycparser"
196 ];
196 ];
197 src = fetchurl {
197 src = fetchurl {
198 url = "https://files.pythonhosted.org/packages/93/1a/ab8c62b5838722f29f3daffcc8d4bd61844aa9b5f437341cc890ceee483b/cffi-1.12.3.tar.gz";
198 url = "https://files.pythonhosted.org/packages/93/1a/ab8c62b5838722f29f3daffcc8d4bd61844aa9b5f437341cc890ceee483b/cffi-1.12.3.tar.gz";
199 sha256 = "0x075521fxwv0mfp4cqzk7lvmw4n94bjw601qkcv314z5s182704";
199 sha256 = "0x075521fxwv0mfp4cqzk7lvmw4n94bjw601qkcv314z5s182704";
200 };
200 };
201 meta = {
201 meta = {
202 license = [ pkgs.lib.licenses.mit ];
202 license = [ pkgs.lib.licenses.mit ];
203 };
203 };
204 };
204 };
205 "chameleon" = super.buildPythonPackage {
205 "chameleon" = super.buildPythonPackage {
206 name = "chameleon-2.24";
206 name = "chameleon-2.24";
207 doCheck = false;
207 doCheck = false;
208 src = fetchurl {
208 src = fetchurl {
209 url = "https://files.pythonhosted.org/packages/5a/9e/637379ffa13c5172b5c0e704833ffea6bf51cec7567f93fd6e903d53ed74/Chameleon-2.24.tar.gz";
209 url = "https://files.pythonhosted.org/packages/5a/9e/637379ffa13c5172b5c0e704833ffea6bf51cec7567f93fd6e903d53ed74/Chameleon-2.24.tar.gz";
210 sha256 = "0ykqr7syxfa6h9adjfnsv1gdsca2xzm22vmic8859n0f0j09abj5";
210 sha256 = "0ykqr7syxfa6h9adjfnsv1gdsca2xzm22vmic8859n0f0j09abj5";
211 };
211 };
212 meta = {
212 meta = {
213 license = [ { fullName = "BSD-like (http://repoze.org/license.html)"; } ];
213 license = [ { fullName = "BSD-like (http://repoze.org/license.html)"; } ];
214 };
214 };
215 };
215 };
216 "channelstream" = super.buildPythonPackage {
216 "channelstream" = super.buildPythonPackage {
217 name = "channelstream-0.5.2";
217 name = "channelstream-0.5.2";
218 doCheck = false;
218 doCheck = false;
219 propagatedBuildInputs = [
219 propagatedBuildInputs = [
220 self."gevent"
220 self."gevent"
221 self."ws4py"
221 self."ws4py"
222 self."pyramid"
222 self."pyramid"
223 self."pyramid-jinja2"
223 self."pyramid-jinja2"
224 self."itsdangerous"
224 self."itsdangerous"
225 self."requests"
225 self."requests"
226 self."six"
226 self."six"
227 ];
227 ];
228 src = fetchurl {
228 src = fetchurl {
229 url = "https://files.pythonhosted.org/packages/2b/31/29a8e085cf5bf97fa88e7b947adabfc581a18a3463adf77fb6dada34a65f/channelstream-0.5.2.tar.gz";
229 url = "https://files.pythonhosted.org/packages/2b/31/29a8e085cf5bf97fa88e7b947adabfc581a18a3463adf77fb6dada34a65f/channelstream-0.5.2.tar.gz";
230 sha256 = "1qbm4xdl5hfkja683x546bncg3rqq8qv79w1m1a1wd48cqqzb6rm";
230 sha256 = "1qbm4xdl5hfkja683x546bncg3rqq8qv79w1m1a1wd48cqqzb6rm";
231 };
231 };
232 meta = {
232 meta = {
233 license = [ pkgs.lib.licenses.bsdOriginal ];
233 license = [ pkgs.lib.licenses.bsdOriginal ];
234 };
234 };
235 };
235 };
236 "click" = super.buildPythonPackage {
236 "click" = super.buildPythonPackage {
237 name = "click-7.0";
237 name = "click-7.0";
238 doCheck = false;
238 doCheck = false;
239 src = fetchurl {
239 src = fetchurl {
240 url = "https://files.pythonhosted.org/packages/f8/5c/f60e9d8a1e77005f664b76ff8aeaee5bc05d0a91798afd7f53fc998dbc47/Click-7.0.tar.gz";
240 url = "https://files.pythonhosted.org/packages/f8/5c/f60e9d8a1e77005f664b76ff8aeaee5bc05d0a91798afd7f53fc998dbc47/Click-7.0.tar.gz";
241 sha256 = "1mzjixd4vjbjvzb6vylki9w1556a9qmdh35kzmq6cign46av952v";
241 sha256 = "1mzjixd4vjbjvzb6vylki9w1556a9qmdh35kzmq6cign46av952v";
242 };
242 };
243 meta = {
243 meta = {
244 license = [ pkgs.lib.licenses.bsdOriginal ];
244 license = [ pkgs.lib.licenses.bsdOriginal ];
245 };
245 };
246 };
246 };
247 "colander" = super.buildPythonPackage {
247 "colander" = super.buildPythonPackage {
248 name = "colander-1.7.0";
248 name = "colander-1.7.0";
249 doCheck = false;
249 doCheck = false;
250 propagatedBuildInputs = [
250 propagatedBuildInputs = [
251 self."translationstring"
251 self."translationstring"
252 self."iso8601"
252 self."iso8601"
253 self."enum34"
253 self."enum34"
254 ];
254 ];
255 src = fetchurl {
255 src = fetchurl {
256 url = "https://files.pythonhosted.org/packages/db/e4/74ab06f54211917b41865cafc987ce511e35503de48da9bfe9358a1bdc3e/colander-1.7.0.tar.gz";
256 url = "https://files.pythonhosted.org/packages/db/e4/74ab06f54211917b41865cafc987ce511e35503de48da9bfe9358a1bdc3e/colander-1.7.0.tar.gz";
257 sha256 = "1wl1bqab307lbbcjx81i28s3yl6dlm4rf15fxawkjb6j48x1cn6p";
257 sha256 = "1wl1bqab307lbbcjx81i28s3yl6dlm4rf15fxawkjb6j48x1cn6p";
258 };
258 };
259 meta = {
259 meta = {
260 license = [ { fullName = "BSD-derived (http://www.repoze.org/LICENSE.txt)"; } ];
260 license = [ { fullName = "BSD-derived (http://www.repoze.org/LICENSE.txt)"; } ];
261 };
261 };
262 };
262 };
263 "configobj" = super.buildPythonPackage {
263 "configobj" = super.buildPythonPackage {
264 name = "configobj-5.0.6";
264 name = "configobj-5.0.6";
265 doCheck = false;
265 doCheck = false;
266 propagatedBuildInputs = [
266 propagatedBuildInputs = [
267 self."six"
267 self."six"
268 ];
268 ];
269 src = fetchurl {
269 src = fetchurl {
270 url = "https://code.rhodecode.com/upstream/configobj/artifacts/download/0-012de99a-b1e1-4f64-a5c0-07a98a41b324.tar.gz?md5=6a513f51fe04b2c18cf84c1395a7c626";
270 url = "https://code.rhodecode.com/upstream/configobj/artifacts/download/0-012de99a-b1e1-4f64-a5c0-07a98a41b324.tar.gz?md5=6a513f51fe04b2c18cf84c1395a7c626";
271 sha256 = "0kqfrdfr14mw8yd8qwq14dv2xghpkjmd3yjsy8dfcbvpcc17xnxp";
271 sha256 = "0kqfrdfr14mw8yd8qwq14dv2xghpkjmd3yjsy8dfcbvpcc17xnxp";
272 };
272 };
273 meta = {
273 meta = {
274 license = [ pkgs.lib.licenses.bsdOriginal ];
274 license = [ pkgs.lib.licenses.bsdOriginal ];
275 };
275 };
276 };
276 };
277 "configparser" = super.buildPythonPackage {
277 "configparser" = super.buildPythonPackage {
278 name = "configparser-4.0.2";
278 name = "configparser-4.0.2";
279 doCheck = false;
279 doCheck = false;
280 src = fetchurl {
280 src = fetchurl {
281 url = "https://files.pythonhosted.org/packages/16/4f/48975536bd488d3a272549eb795ac4a13a5f7fcdc8995def77fbef3532ee/configparser-4.0.2.tar.gz";
281 url = "https://files.pythonhosted.org/packages/16/4f/48975536bd488d3a272549eb795ac4a13a5f7fcdc8995def77fbef3532ee/configparser-4.0.2.tar.gz";
282 sha256 = "1priacxym85yjcf68hh38w55nqswaxp71ryjyfdk222kg9l85ln7";
282 sha256 = "1priacxym85yjcf68hh38w55nqswaxp71ryjyfdk222kg9l85ln7";
283 };
283 };
284 meta = {
284 meta = {
285 license = [ pkgs.lib.licenses.mit ];
285 license = [ pkgs.lib.licenses.mit ];
286 };
286 };
287 };
287 };
288 "contextlib2" = super.buildPythonPackage {
288 "contextlib2" = super.buildPythonPackage {
289 name = "contextlib2-0.6.0";
289 name = "contextlib2-0.6.0";
290 doCheck = false;
290 doCheck = false;
291 src = fetchurl {
291 src = fetchurl {
292 url = "https://files.pythonhosted.org/packages/f0/08/ac376929b2c51e2d5fd4b9fa1f72eca0162c990edc526bdd3b16449323ad/contextlib2-0.6.0.tar.gz";
292 url = "https://files.pythonhosted.org/packages/f0/08/ac376929b2c51e2d5fd4b9fa1f72eca0162c990edc526bdd3b16449323ad/contextlib2-0.6.0.tar.gz";
293 sha256 = "1lvy0xvh5kkx0cmv2y8rpxjrg909q920k05x7m8srjkpcxrsm5vi";
293 sha256 = "1lvy0xvh5kkx0cmv2y8rpxjrg909q920k05x7m8srjkpcxrsm5vi";
294 };
294 };
295 meta = {
295 meta = {
296 license = [ pkgs.lib.licenses.psfl ];
296 license = [ pkgs.lib.licenses.psfl ];
297 };
297 };
298 };
298 };
299 "cov-core" = super.buildPythonPackage {
299 "cov-core" = super.buildPythonPackage {
300 name = "cov-core-1.15.0";
300 name = "cov-core-1.15.0";
301 doCheck = false;
301 doCheck = false;
302 propagatedBuildInputs = [
302 propagatedBuildInputs = [
303 self."coverage"
303 self."coverage"
304 ];
304 ];
305 src = fetchurl {
305 src = fetchurl {
306 url = "https://files.pythonhosted.org/packages/4b/87/13e75a47b4ba1be06f29f6d807ca99638bedc6b57fa491cd3de891ca2923/cov-core-1.15.0.tar.gz";
306 url = "https://files.pythonhosted.org/packages/4b/87/13e75a47b4ba1be06f29f6d807ca99638bedc6b57fa491cd3de891ca2923/cov-core-1.15.0.tar.gz";
307 sha256 = "0k3np9ymh06yv1ib96sb6wfsxjkqhmik8qfsn119vnhga9ywc52a";
307 sha256 = "0k3np9ymh06yv1ib96sb6wfsxjkqhmik8qfsn119vnhga9ywc52a";
308 };
308 };
309 meta = {
309 meta = {
310 license = [ pkgs.lib.licenses.mit ];
310 license = [ pkgs.lib.licenses.mit ];
311 };
311 };
312 };
312 };
313 "coverage" = super.buildPythonPackage {
313 "coverage" = super.buildPythonPackage {
314 name = "coverage-4.5.4";
314 name = "coverage-4.5.4";
315 doCheck = false;
315 doCheck = false;
316 src = fetchurl {
316 src = fetchurl {
317 url = "https://files.pythonhosted.org/packages/85/d5/818d0e603685c4a613d56f065a721013e942088047ff1027a632948bdae6/coverage-4.5.4.tar.gz";
317 url = "https://files.pythonhosted.org/packages/85/d5/818d0e603685c4a613d56f065a721013e942088047ff1027a632948bdae6/coverage-4.5.4.tar.gz";
318 sha256 = "0p0j4di6h8k6ica7jwwj09azdcg4ycxq60i9qsskmsg94cd9yzg0";
318 sha256 = "0p0j4di6h8k6ica7jwwj09azdcg4ycxq60i9qsskmsg94cd9yzg0";
319 };
319 };
320 meta = {
320 meta = {
321 license = [ pkgs.lib.licenses.asl20 ];
321 license = [ pkgs.lib.licenses.asl20 ];
322 };
322 };
323 };
323 };
324 "cryptography" = super.buildPythonPackage {
324 "cryptography" = super.buildPythonPackage {
325 name = "cryptography-2.6.1";
325 name = "cryptography-2.6.1";
326 doCheck = false;
326 doCheck = false;
327 propagatedBuildInputs = [
327 propagatedBuildInputs = [
328 self."asn1crypto"
328 self."asn1crypto"
329 self."six"
329 self."six"
330 self."cffi"
330 self."cffi"
331 self."enum34"
331 self."enum34"
332 self."ipaddress"
332 self."ipaddress"
333 ];
333 ];
334 src = fetchurl {
334 src = fetchurl {
335 url = "https://files.pythonhosted.org/packages/07/ca/bc827c5e55918ad223d59d299fff92f3563476c3b00d0a9157d9c0217449/cryptography-2.6.1.tar.gz";
335 url = "https://files.pythonhosted.org/packages/07/ca/bc827c5e55918ad223d59d299fff92f3563476c3b00d0a9157d9c0217449/cryptography-2.6.1.tar.gz";
336 sha256 = "19iwz5avym5zl6jrrrkym1rdaa9h61j20ph4cswsqgv8xg5j3j16";
336 sha256 = "19iwz5avym5zl6jrrrkym1rdaa9h61j20ph4cswsqgv8xg5j3j16";
337 };
337 };
338 meta = {
338 meta = {
339 license = [ pkgs.lib.licenses.bsdOriginal { fullName = "BSD or Apache License, Version 2.0"; } pkgs.lib.licenses.asl20 ];
339 license = [ pkgs.lib.licenses.bsdOriginal { fullName = "BSD or Apache License, Version 2.0"; } pkgs.lib.licenses.asl20 ];
340 };
340 };
341 };
341 };
342 "cssselect" = super.buildPythonPackage {
342 "cssselect" = super.buildPythonPackage {
343 name = "cssselect-1.0.3";
343 name = "cssselect-1.0.3";
344 doCheck = false;
344 doCheck = false;
345 src = fetchurl {
345 src = fetchurl {
346 url = "https://files.pythonhosted.org/packages/52/ea/f31e1d2e9eb130fda2a631e22eac369dc644e8807345fbed5113f2d6f92b/cssselect-1.0.3.tar.gz";
346 url = "https://files.pythonhosted.org/packages/52/ea/f31e1d2e9eb130fda2a631e22eac369dc644e8807345fbed5113f2d6f92b/cssselect-1.0.3.tar.gz";
347 sha256 = "011jqa2jhmydhi0iz4v1w3cr540z5zas8g2bw8brdw4s4b2qnv86";
347 sha256 = "011jqa2jhmydhi0iz4v1w3cr540z5zas8g2bw8brdw4s4b2qnv86";
348 };
348 };
349 meta = {
349 meta = {
350 license = [ pkgs.lib.licenses.bsdOriginal ];
350 license = [ pkgs.lib.licenses.bsdOriginal ];
351 };
351 };
352 };
352 };
353 "decorator" = super.buildPythonPackage {
353 "decorator" = super.buildPythonPackage {
354 name = "decorator-4.1.2";
354 name = "decorator-4.1.2";
355 doCheck = false;
355 doCheck = false;
356 src = fetchurl {
356 src = fetchurl {
357 url = "https://files.pythonhosted.org/packages/bb/e0/f6e41e9091e130bf16d4437dabbac3993908e4d6485ecbc985ef1352db94/decorator-4.1.2.tar.gz";
357 url = "https://files.pythonhosted.org/packages/bb/e0/f6e41e9091e130bf16d4437dabbac3993908e4d6485ecbc985ef1352db94/decorator-4.1.2.tar.gz";
358 sha256 = "1d8npb11kxyi36mrvjdpcjij76l5zfyrz2f820brf0l0rcw4vdkw";
358 sha256 = "1d8npb11kxyi36mrvjdpcjij76l5zfyrz2f820brf0l0rcw4vdkw";
359 };
359 };
360 meta = {
360 meta = {
361 license = [ pkgs.lib.licenses.bsdOriginal { fullName = "new BSD License"; } ];
361 license = [ pkgs.lib.licenses.bsdOriginal { fullName = "new BSD License"; } ];
362 };
362 };
363 };
363 };
364 "deform" = super.buildPythonPackage {
364 "deform" = super.buildPythonPackage {
365 name = "deform-2.0.8";
365 name = "deform-2.0.8";
366 doCheck = false;
366 doCheck = false;
367 propagatedBuildInputs = [
367 propagatedBuildInputs = [
368 self."chameleon"
368 self."chameleon"
369 self."colander"
369 self."colander"
370 self."iso8601"
370 self."iso8601"
371 self."peppercorn"
371 self."peppercorn"
372 self."translationstring"
372 self."translationstring"
373 self."zope.deprecation"
373 self."zope.deprecation"
374 ];
374 ];
375 src = fetchurl {
375 src = fetchurl {
376 url = "https://files.pythonhosted.org/packages/21/d0/45fdf891a82722c02fc2da319cf2d1ae6b5abf9e470ad3762135a895a868/deform-2.0.8.tar.gz";
376 url = "https://files.pythonhosted.org/packages/21/d0/45fdf891a82722c02fc2da319cf2d1ae6b5abf9e470ad3762135a895a868/deform-2.0.8.tar.gz";
377 sha256 = "0wbjv98sib96649aqaygzxnrkclyy50qij2rha6fn1i4c86bfdl9";
377 sha256 = "0wbjv98sib96649aqaygzxnrkclyy50qij2rha6fn1i4c86bfdl9";
378 };
378 };
379 meta = {
379 meta = {
380 license = [ { fullName = "Repoze Public License"; } { fullName = "BSD-derived (http://www.repoze.org/LICENSE.txt)"; } ];
380 license = [ { fullName = "Repoze Public License"; } { fullName = "BSD-derived (http://www.repoze.org/LICENSE.txt)"; } ];
381 };
381 };
382 };
382 };
383 "defusedxml" = super.buildPythonPackage {
383 "defusedxml" = super.buildPythonPackage {
384 name = "defusedxml-0.6.0";
384 name = "defusedxml-0.6.0";
385 doCheck = false;
385 doCheck = false;
386 src = fetchurl {
386 src = fetchurl {
387 url = "https://files.pythonhosted.org/packages/a4/5f/f8aa58ca0cf01cbcee728abc9d88bfeb74e95e6cb4334cfd5bed5673ea77/defusedxml-0.6.0.tar.gz";
387 url = "https://files.pythonhosted.org/packages/a4/5f/f8aa58ca0cf01cbcee728abc9d88bfeb74e95e6cb4334cfd5bed5673ea77/defusedxml-0.6.0.tar.gz";
388 sha256 = "1xbp8fivl3wlbyg2jrvs4lalaqv1xp9a9f29p75wdx2s2d6h717n";
388 sha256 = "1xbp8fivl3wlbyg2jrvs4lalaqv1xp9a9f29p75wdx2s2d6h717n";
389 };
389 };
390 meta = {
390 meta = {
391 license = [ pkgs.lib.licenses.psfl ];
391 license = [ pkgs.lib.licenses.psfl ];
392 };
392 };
393 };
393 };
394 "dm.xmlsec.binding" = super.buildPythonPackage {
394 "dm.xmlsec.binding" = super.buildPythonPackage {
395 name = "dm.xmlsec.binding-1.3.7";
395 name = "dm.xmlsec.binding-1.3.7";
396 doCheck = false;
396 doCheck = false;
397 propagatedBuildInputs = [
397 propagatedBuildInputs = [
398 self."setuptools"
398 self."setuptools"
399 self."lxml"
399 self."lxml"
400 ];
400 ];
401 src = fetchurl {
401 src = fetchurl {
402 url = "https://files.pythonhosted.org/packages/2c/9e/7651982d50252692991acdae614af821fd6c79bc8dcd598ad71d55be8fc7/dm.xmlsec.binding-1.3.7.tar.gz";
402 url = "https://files.pythonhosted.org/packages/2c/9e/7651982d50252692991acdae614af821fd6c79bc8dcd598ad71d55be8fc7/dm.xmlsec.binding-1.3.7.tar.gz";
403 sha256 = "03jjjscx1pz2nc0dwiw9nia02qbz1c6f0f9zkyr8fmvys2n5jkb3";
403 sha256 = "03jjjscx1pz2nc0dwiw9nia02qbz1c6f0f9zkyr8fmvys2n5jkb3";
404 };
404 };
405 meta = {
405 meta = {
406 license = [ pkgs.lib.licenses.bsdOriginal ];
406 license = [ pkgs.lib.licenses.bsdOriginal ];
407 };
407 };
408 };
408 };
409 "docutils" = super.buildPythonPackage {
409 "docutils" = super.buildPythonPackage {
410 name = "docutils-0.14";
410 name = "docutils-0.14";
411 doCheck = false;
411 doCheck = false;
412 src = fetchurl {
412 src = fetchurl {
413 url = "https://files.pythonhosted.org/packages/84/f4/5771e41fdf52aabebbadecc9381d11dea0fa34e4759b4071244fa094804c/docutils-0.14.tar.gz";
413 url = "https://files.pythonhosted.org/packages/84/f4/5771e41fdf52aabebbadecc9381d11dea0fa34e4759b4071244fa094804c/docutils-0.14.tar.gz";
414 sha256 = "0x22fs3pdmr42kvz6c654756wja305qv6cx1zbhwlagvxgr4xrji";
414 sha256 = "0x22fs3pdmr42kvz6c654756wja305qv6cx1zbhwlagvxgr4xrji";
415 };
415 };
416 meta = {
416 meta = {
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 ];
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 "dogpile.cache" = super.buildPythonPackage {
420 "dogpile.cache" = super.buildPythonPackage {
421 name = "dogpile.cache-0.9.0";
421 name = "dogpile.cache-0.9.0";
422 doCheck = false;
422 doCheck = false;
423 propagatedBuildInputs = [
423 propagatedBuildInputs = [
424 self."decorator"
424 self."decorator"
425 ];
425 ];
426 src = fetchurl {
426 src = fetchurl {
427 url = "https://files.pythonhosted.org/packages/ac/6a/9ac405686a94b7f009a20a50070a5786b0e1aedc707b88d40d0c4b51a82e/dogpile.cache-0.9.0.tar.gz";
427 url = "https://files.pythonhosted.org/packages/ac/6a/9ac405686a94b7f009a20a50070a5786b0e1aedc707b88d40d0c4b51a82e/dogpile.cache-0.9.0.tar.gz";
428 sha256 = "0sr1fn6b4k5bh0cscd9yi8csqxvj4ngzildav58x5p694mc86j5k";
428 sha256 = "0sr1fn6b4k5bh0cscd9yi8csqxvj4ngzildav58x5p694mc86j5k";
429 };
429 };
430 meta = {
430 meta = {
431 license = [ pkgs.lib.licenses.bsdOriginal ];
431 license = [ pkgs.lib.licenses.bsdOriginal ];
432 };
432 };
433 };
433 };
434 "dogpile.core" = super.buildPythonPackage {
434 "dogpile.core" = super.buildPythonPackage {
435 name = "dogpile.core-0.4.1";
435 name = "dogpile.core-0.4.1";
436 doCheck = false;
436 doCheck = false;
437 src = fetchurl {
437 src = fetchurl {
438 url = "https://files.pythonhosted.org/packages/0e/77/e72abc04c22aedf874301861e5c1e761231c288b5de369c18be8f4b5c9bb/dogpile.core-0.4.1.tar.gz";
438 url = "https://files.pythonhosted.org/packages/0e/77/e72abc04c22aedf874301861e5c1e761231c288b5de369c18be8f4b5c9bb/dogpile.core-0.4.1.tar.gz";
439 sha256 = "0xpdvg4kr1isfkrh1rfsh7za4q5a5s6l2kf9wpvndbwf3aqjyrdy";
439 sha256 = "0xpdvg4kr1isfkrh1rfsh7za4q5a5s6l2kf9wpvndbwf3aqjyrdy";
440 };
440 };
441 meta = {
441 meta = {
442 license = [ pkgs.lib.licenses.bsdOriginal ];
442 license = [ pkgs.lib.licenses.bsdOriginal ];
443 };
443 };
444 };
444 };
445 "ecdsa" = super.buildPythonPackage {
445 "ecdsa" = super.buildPythonPackage {
446 name = "ecdsa-0.13.2";
446 name = "ecdsa-0.13.2";
447 doCheck = false;
447 doCheck = false;
448 src = fetchurl {
448 src = fetchurl {
449 url = "https://files.pythonhosted.org/packages/51/76/139bf6e9b7b6684d5891212cdbd9e0739f2bfc03f380a1a6ffa700f392ac/ecdsa-0.13.2.tar.gz";
449 url = "https://files.pythonhosted.org/packages/51/76/139bf6e9b7b6684d5891212cdbd9e0739f2bfc03f380a1a6ffa700f392ac/ecdsa-0.13.2.tar.gz";
450 sha256 = "116qaq7bh4lcynzi613960jhsnn19v0kmsqwahiwjfj14gx4y0sw";
450 sha256 = "116qaq7bh4lcynzi613960jhsnn19v0kmsqwahiwjfj14gx4y0sw";
451 };
451 };
452 meta = {
452 meta = {
453 license = [ pkgs.lib.licenses.mit ];
453 license = [ pkgs.lib.licenses.mit ];
454 };
454 };
455 };
455 };
456 "elasticsearch" = super.buildPythonPackage {
456 "elasticsearch" = super.buildPythonPackage {
457 name = "elasticsearch-6.3.1";
457 name = "elasticsearch-6.3.1";
458 doCheck = false;
458 doCheck = false;
459 propagatedBuildInputs = [
459 propagatedBuildInputs = [
460 self."urllib3"
460 self."urllib3"
461 ];
461 ];
462 src = fetchurl {
462 src = fetchurl {
463 url = "https://files.pythonhosted.org/packages/9d/ce/c4664e8380e379a9402ecfbaf158e56396da90d520daba21cfa840e0eb71/elasticsearch-6.3.1.tar.gz";
463 url = "https://files.pythonhosted.org/packages/9d/ce/c4664e8380e379a9402ecfbaf158e56396da90d520daba21cfa840e0eb71/elasticsearch-6.3.1.tar.gz";
464 sha256 = "12y93v0yn7a4xmf969239g8gb3l4cdkclfpbk1qc8hx5qkymrnma";
464 sha256 = "12y93v0yn7a4xmf969239g8gb3l4cdkclfpbk1qc8hx5qkymrnma";
465 };
465 };
466 meta = {
466 meta = {
467 license = [ pkgs.lib.licenses.asl20 ];
467 license = [ pkgs.lib.licenses.asl20 ];
468 };
468 };
469 };
469 };
470 "elasticsearch-dsl" = super.buildPythonPackage {
470 "elasticsearch-dsl" = super.buildPythonPackage {
471 name = "elasticsearch-dsl-6.3.1";
471 name = "elasticsearch-dsl-6.3.1";
472 doCheck = false;
472 doCheck = false;
473 propagatedBuildInputs = [
473 propagatedBuildInputs = [
474 self."six"
474 self."six"
475 self."python-dateutil"
475 self."python-dateutil"
476 self."elasticsearch"
476 self."elasticsearch"
477 self."ipaddress"
477 self."ipaddress"
478 ];
478 ];
479 src = fetchurl {
479 src = fetchurl {
480 url = "https://files.pythonhosted.org/packages/4c/0d/1549f50c591db6bb4e66cbcc8d34a6e537c3d89aa426b167c244fd46420a/elasticsearch-dsl-6.3.1.tar.gz";
480 url = "https://files.pythonhosted.org/packages/4c/0d/1549f50c591db6bb4e66cbcc8d34a6e537c3d89aa426b167c244fd46420a/elasticsearch-dsl-6.3.1.tar.gz";
481 sha256 = "1gh8a0shqi105k325hgwb9avrpdjh0mc6mxwfg9ba7g6lssb702z";
481 sha256 = "1gh8a0shqi105k325hgwb9avrpdjh0mc6mxwfg9ba7g6lssb702z";
482 };
482 };
483 meta = {
483 meta = {
484 license = [ pkgs.lib.licenses.asl20 ];
484 license = [ pkgs.lib.licenses.asl20 ];
485 };
485 };
486 };
486 };
487 "elasticsearch1" = super.buildPythonPackage {
487 "elasticsearch1" = super.buildPythonPackage {
488 name = "elasticsearch1-1.10.0";
488 name = "elasticsearch1-1.10.0";
489 doCheck = false;
489 doCheck = false;
490 propagatedBuildInputs = [
490 propagatedBuildInputs = [
491 self."urllib3"
491 self."urllib3"
492 ];
492 ];
493 src = fetchurl {
493 src = fetchurl {
494 url = "https://files.pythonhosted.org/packages/a6/eb/73e75f9681fa71e3157b8ee878534235d57f24ee64f0e77f8d995fb57076/elasticsearch1-1.10.0.tar.gz";
494 url = "https://files.pythonhosted.org/packages/a6/eb/73e75f9681fa71e3157b8ee878534235d57f24ee64f0e77f8d995fb57076/elasticsearch1-1.10.0.tar.gz";
495 sha256 = "0g89444kd5zwql4vbvyrmi2m6l6dcj6ga98j4hqxyyyz6z20aki2";
495 sha256 = "0g89444kd5zwql4vbvyrmi2m6l6dcj6ga98j4hqxyyyz6z20aki2";
496 };
496 };
497 meta = {
497 meta = {
498 license = [ pkgs.lib.licenses.asl20 ];
498 license = [ pkgs.lib.licenses.asl20 ];
499 };
499 };
500 };
500 };
501 "elasticsearch1-dsl" = super.buildPythonPackage {
501 "elasticsearch1-dsl" = super.buildPythonPackage {
502 name = "elasticsearch1-dsl-0.0.12";
502 name = "elasticsearch1-dsl-0.0.12";
503 doCheck = false;
503 doCheck = false;
504 propagatedBuildInputs = [
504 propagatedBuildInputs = [
505 self."six"
505 self."six"
506 self."python-dateutil"
506 self."python-dateutil"
507 self."elasticsearch1"
507 self."elasticsearch1"
508 ];
508 ];
509 src = fetchurl {
509 src = fetchurl {
510 url = "https://files.pythonhosted.org/packages/eb/9d/785342775cb10eddc9b8d7457d618a423b4f0b89d8b2b2d1bc27190d71db/elasticsearch1-dsl-0.0.12.tar.gz";
510 url = "https://files.pythonhosted.org/packages/eb/9d/785342775cb10eddc9b8d7457d618a423b4f0b89d8b2b2d1bc27190d71db/elasticsearch1-dsl-0.0.12.tar.gz";
511 sha256 = "0ig1ly39v93hba0z975wnhbmzwj28w6w1sqlr2g7cn5spp732bhk";
511 sha256 = "0ig1ly39v93hba0z975wnhbmzwj28w6w1sqlr2g7cn5spp732bhk";
512 };
512 };
513 meta = {
513 meta = {
514 license = [ pkgs.lib.licenses.asl20 ];
514 license = [ pkgs.lib.licenses.asl20 ];
515 };
515 };
516 };
516 };
517 "elasticsearch2" = super.buildPythonPackage {
517 "elasticsearch2" = super.buildPythonPackage {
518 name = "elasticsearch2-2.5.1";
518 name = "elasticsearch2-2.5.1";
519 doCheck = false;
519 doCheck = false;
520 propagatedBuildInputs = [
520 propagatedBuildInputs = [
521 self."urllib3"
521 self."urllib3"
522 ];
522 ];
523 src = fetchurl {
523 src = fetchurl {
524 url = "https://files.pythonhosted.org/packages/f6/09/f9b24aa6b1120bea371cd57ef6f57c7694cf16660469456a8be6c2bdbe22/elasticsearch2-2.5.1.tar.gz";
524 url = "https://files.pythonhosted.org/packages/f6/09/f9b24aa6b1120bea371cd57ef6f57c7694cf16660469456a8be6c2bdbe22/elasticsearch2-2.5.1.tar.gz";
525 sha256 = "19k2znpjfyp0hrq73cz7pjyj289040xpsxsm0xhh4jfh6y551g7k";
525 sha256 = "19k2znpjfyp0hrq73cz7pjyj289040xpsxsm0xhh4jfh6y551g7k";
526 };
526 };
527 meta = {
527 meta = {
528 license = [ pkgs.lib.licenses.asl20 ];
528 license = [ pkgs.lib.licenses.asl20 ];
529 };
529 };
530 };
530 };
531 "entrypoints" = super.buildPythonPackage {
531 "entrypoints" = super.buildPythonPackage {
532 name = "entrypoints-0.2.2";
532 name = "entrypoints-0.2.2";
533 doCheck = false;
533 doCheck = false;
534 propagatedBuildInputs = [
534 propagatedBuildInputs = [
535 self."configparser"
535 self."configparser"
536 ];
536 ];
537 src = fetchurl {
537 src = fetchurl {
538 url = "https://code.rhodecode.com/upstream/entrypoints/artifacts/download/0-8e9ee9e4-c4db-409c-b07e-81568fd1832d.tar.gz?md5=3a027b8ff1d257b91fe257de6c43357d";
538 url = "https://code.rhodecode.com/upstream/entrypoints/artifacts/download/0-8e9ee9e4-c4db-409c-b07e-81568fd1832d.tar.gz?md5=3a027b8ff1d257b91fe257de6c43357d";
539 sha256 = "0qih72n2myclanplqipqxpgpj9d2yhff1pz5d02zq1cfqyd173w5";
539 sha256 = "0qih72n2myclanplqipqxpgpj9d2yhff1pz5d02zq1cfqyd173w5";
540 };
540 };
541 meta = {
541 meta = {
542 license = [ pkgs.lib.licenses.mit ];
542 license = [ pkgs.lib.licenses.mit ];
543 };
543 };
544 };
544 };
545 "enum34" = super.buildPythonPackage {
545 "enum34" = super.buildPythonPackage {
546 name = "enum34-1.1.6";
546 name = "enum34-1.1.6";
547 doCheck = false;
547 doCheck = false;
548 src = fetchurl {
548 src = fetchurl {
549 url = "https://files.pythonhosted.org/packages/bf/3e/31d502c25302814a7c2f1d3959d2a3b3f78e509002ba91aea64993936876/enum34-1.1.6.tar.gz";
549 url = "https://files.pythonhosted.org/packages/bf/3e/31d502c25302814a7c2f1d3959d2a3b3f78e509002ba91aea64993936876/enum34-1.1.6.tar.gz";
550 sha256 = "1cgm5ng2gcfrkrm3hc22brl6chdmv67b9zvva9sfs7gn7dwc9n4a";
550 sha256 = "1cgm5ng2gcfrkrm3hc22brl6chdmv67b9zvva9sfs7gn7dwc9n4a";
551 };
551 };
552 meta = {
552 meta = {
553 license = [ pkgs.lib.licenses.bsdOriginal ];
553 license = [ pkgs.lib.licenses.bsdOriginal ];
554 };
554 };
555 };
555 };
556 "formencode" = super.buildPythonPackage {
556 "formencode" = super.buildPythonPackage {
557 name = "formencode-1.2.4";
557 name = "formencode-1.2.4";
558 doCheck = false;
558 doCheck = false;
559 src = fetchurl {
559 src = fetchurl {
560 url = "https://files.pythonhosted.org/packages/8e/59/0174271a6f004512e0201188593e6d319db139d14cb7490e488bbb078015/FormEncode-1.2.4.tar.gz";
560 url = "https://files.pythonhosted.org/packages/8e/59/0174271a6f004512e0201188593e6d319db139d14cb7490e488bbb078015/FormEncode-1.2.4.tar.gz";
561 sha256 = "1fgy04sdy4yry5xcjls3x3xy30dqwj58ycnkndim819jx0788w42";
561 sha256 = "1fgy04sdy4yry5xcjls3x3xy30dqwj58ycnkndim819jx0788w42";
562 };
562 };
563 meta = {
563 meta = {
564 license = [ pkgs.lib.licenses.psfl ];
564 license = [ pkgs.lib.licenses.psfl ];
565 };
565 };
566 };
566 };
567 "funcsigs" = super.buildPythonPackage {
567 "funcsigs" = super.buildPythonPackage {
568 name = "funcsigs-1.0.2";
568 name = "funcsigs-1.0.2";
569 doCheck = false;
569 doCheck = false;
570 src = fetchurl {
570 src = fetchurl {
571 url = "https://files.pythonhosted.org/packages/94/4a/db842e7a0545de1cdb0439bb80e6e42dfe82aaeaadd4072f2263a4fbed23/funcsigs-1.0.2.tar.gz";
571 url = "https://files.pythonhosted.org/packages/94/4a/db842e7a0545de1cdb0439bb80e6e42dfe82aaeaadd4072f2263a4fbed23/funcsigs-1.0.2.tar.gz";
572 sha256 = "0l4g5818ffyfmfs1a924811azhjj8ax9xd1cffr1mzd3ycn0zfx7";
572 sha256 = "0l4g5818ffyfmfs1a924811azhjj8ax9xd1cffr1mzd3ycn0zfx7";
573 };
573 };
574 meta = {
574 meta = {
575 license = [ { fullName = "ASL"; } pkgs.lib.licenses.asl20 ];
575 license = [ { fullName = "ASL"; } pkgs.lib.licenses.asl20 ];
576 };
576 };
577 };
577 };
578 "functools32" = super.buildPythonPackage {
578 "functools32" = super.buildPythonPackage {
579 name = "functools32-3.2.3.post2";
579 name = "functools32-3.2.3.post2";
580 doCheck = false;
580 doCheck = false;
581 src = fetchurl {
581 src = fetchurl {
582 url = "https://files.pythonhosted.org/packages/c5/60/6ac26ad05857c601308d8fb9e87fa36d0ebf889423f47c3502ef034365db/functools32-3.2.3-2.tar.gz";
582 url = "https://files.pythonhosted.org/packages/c5/60/6ac26ad05857c601308d8fb9e87fa36d0ebf889423f47c3502ef034365db/functools32-3.2.3-2.tar.gz";
583 sha256 = "0v8ya0b58x47wp216n1zamimv4iw57cxz3xxhzix52jkw3xks9gn";
583 sha256 = "0v8ya0b58x47wp216n1zamimv4iw57cxz3xxhzix52jkw3xks9gn";
584 };
584 };
585 meta = {
585 meta = {
586 license = [ pkgs.lib.licenses.psfl ];
586 license = [ pkgs.lib.licenses.psfl ];
587 };
587 };
588 };
588 };
589 "future" = super.buildPythonPackage {
589 "future" = super.buildPythonPackage {
590 name = "future-0.14.3";
590 name = "future-0.14.3";
591 doCheck = false;
591 doCheck = false;
592 src = fetchurl {
592 src = fetchurl {
593 url = "https://files.pythonhosted.org/packages/83/80/8ef3a11a15f8eaafafa0937b20c1b3f73527e69ab6b3fa1cf94a5a96aabb/future-0.14.3.tar.gz";
593 url = "https://files.pythonhosted.org/packages/83/80/8ef3a11a15f8eaafafa0937b20c1b3f73527e69ab6b3fa1cf94a5a96aabb/future-0.14.3.tar.gz";
594 sha256 = "1savk7jx7hal032f522c5ajhh8fra6gmnadrj9adv5qxi18pv1b2";
594 sha256 = "1savk7jx7hal032f522c5ajhh8fra6gmnadrj9adv5qxi18pv1b2";
595 };
595 };
596 meta = {
596 meta = {
597 license = [ { fullName = "OSI Approved"; } pkgs.lib.licenses.mit ];
597 license = [ { fullName = "OSI Approved"; } pkgs.lib.licenses.mit ];
598 };
598 };
599 };
599 };
600 "futures" = super.buildPythonPackage {
600 "futures" = super.buildPythonPackage {
601 name = "futures-3.0.2";
601 name = "futures-3.0.2";
602 doCheck = false;
602 doCheck = false;
603 src = fetchurl {
603 src = fetchurl {
604 url = "https://files.pythonhosted.org/packages/f8/e7/fc0fcbeb9193ba2d4de00b065e7fd5aecd0679e93ce95a07322b2b1434f4/futures-3.0.2.tar.gz";
604 url = "https://files.pythonhosted.org/packages/f8/e7/fc0fcbeb9193ba2d4de00b065e7fd5aecd0679e93ce95a07322b2b1434f4/futures-3.0.2.tar.gz";
605 sha256 = "0mz2pbgxbc2nbib1szifi07whjbfs4r02pv2z390z7p410awjgyw";
605 sha256 = "0mz2pbgxbc2nbib1szifi07whjbfs4r02pv2z390z7p410awjgyw";
606 };
606 };
607 meta = {
607 meta = {
608 license = [ pkgs.lib.licenses.bsdOriginal ];
608 license = [ pkgs.lib.licenses.bsdOriginal ];
609 };
609 };
610 };
610 };
611 "gevent" = super.buildPythonPackage {
611 "gevent" = super.buildPythonPackage {
612 name = "gevent-1.4.0";
612 name = "gevent-1.4.0";
613 doCheck = false;
613 doCheck = false;
614 propagatedBuildInputs = [
614 propagatedBuildInputs = [
615 self."greenlet"
615 self."greenlet"
616 ];
616 ];
617 src = fetchurl {
617 src = fetchurl {
618 url = "https://files.pythonhosted.org/packages/ed/27/6c49b70808f569b66ec7fac2e78f076e9b204db9cf5768740cff3d5a07ae/gevent-1.4.0.tar.gz";
618 url = "https://files.pythonhosted.org/packages/ed/27/6c49b70808f569b66ec7fac2e78f076e9b204db9cf5768740cff3d5a07ae/gevent-1.4.0.tar.gz";
619 sha256 = "1lchr4akw2jkm5v4kz7bdm4wv3knkfhbfn9vkkz4s5yrkcxzmdqy";
619 sha256 = "1lchr4akw2jkm5v4kz7bdm4wv3knkfhbfn9vkkz4s5yrkcxzmdqy";
620 };
620 };
621 meta = {
621 meta = {
622 license = [ pkgs.lib.licenses.mit ];
622 license = [ pkgs.lib.licenses.mit ];
623 };
623 };
624 };
624 };
625 "gnureadline" = super.buildPythonPackage {
625 "gnureadline" = super.buildPythonPackage {
626 name = "gnureadline-6.3.8";
626 name = "gnureadline-6.3.8";
627 doCheck = false;
627 doCheck = false;
628 src = fetchurl {
628 src = fetchurl {
629 url = "https://files.pythonhosted.org/packages/50/64/86085c823cd78f9df9d8e33dce0baa71618016f8860460b82cf6610e1eb3/gnureadline-6.3.8.tar.gz";
629 url = "https://files.pythonhosted.org/packages/50/64/86085c823cd78f9df9d8e33dce0baa71618016f8860460b82cf6610e1eb3/gnureadline-6.3.8.tar.gz";
630 sha256 = "0ddhj98x2nv45iz4aadk4b9m0b1kpsn1xhcbypn5cd556knhiqjq";
630 sha256 = "0ddhj98x2nv45iz4aadk4b9m0b1kpsn1xhcbypn5cd556knhiqjq";
631 };
631 };
632 meta = {
632 meta = {
633 license = [ { fullName = "GNU General Public License v3 (GPLv3)"; } pkgs.lib.licenses.gpl1 ];
633 license = [ { fullName = "GNU General Public License v3 (GPLv3)"; } pkgs.lib.licenses.gpl1 ];
634 };
634 };
635 };
635 };
636 "gprof2dot" = super.buildPythonPackage {
636 "gprof2dot" = super.buildPythonPackage {
637 name = "gprof2dot-2017.9.19";
637 name = "gprof2dot-2017.9.19";
638 doCheck = false;
638 doCheck = false;
639 src = fetchurl {
639 src = fetchurl {
640 url = "https://files.pythonhosted.org/packages/9d/36/f977122502979f3dfb50704979c9ed70e6b620787942b089bf1af15f5aba/gprof2dot-2017.9.19.tar.gz";
640 url = "https://files.pythonhosted.org/packages/9d/36/f977122502979f3dfb50704979c9ed70e6b620787942b089bf1af15f5aba/gprof2dot-2017.9.19.tar.gz";
641 sha256 = "17ih23ld2nzgc3xwgbay911l6lh96jp1zshmskm17n1gg2i7mg6f";
641 sha256 = "17ih23ld2nzgc3xwgbay911l6lh96jp1zshmskm17n1gg2i7mg6f";
642 };
642 };
643 meta = {
643 meta = {
644 license = [ { fullName = "GNU Lesser General Public License v3 or later (LGPLv3+)"; } { fullName = "LGPL"; } ];
644 license = [ { fullName = "GNU Lesser General Public License v3 or later (LGPLv3+)"; } { fullName = "LGPL"; } ];
645 };
645 };
646 };
646 };
647 "greenlet" = super.buildPythonPackage {
647 "greenlet" = super.buildPythonPackage {
648 name = "greenlet-0.4.15";
648 name = "greenlet-0.4.15";
649 doCheck = false;
649 doCheck = false;
650 src = fetchurl {
650 src = fetchurl {
651 url = "https://files.pythonhosted.org/packages/f8/e8/b30ae23b45f69aa3f024b46064c0ac8e5fcb4f22ace0dca8d6f9c8bbe5e7/greenlet-0.4.15.tar.gz";
651 url = "https://files.pythonhosted.org/packages/f8/e8/b30ae23b45f69aa3f024b46064c0ac8e5fcb4f22ace0dca8d6f9c8bbe5e7/greenlet-0.4.15.tar.gz";
652 sha256 = "1g4g1wwc472ds89zmqlpyan3fbnzpa8qm48z3z1y6mlk44z485ll";
652 sha256 = "1g4g1wwc472ds89zmqlpyan3fbnzpa8qm48z3z1y6mlk44z485ll";
653 };
653 };
654 meta = {
654 meta = {
655 license = [ pkgs.lib.licenses.mit ];
655 license = [ pkgs.lib.licenses.mit ];
656 };
656 };
657 };
657 };
658 "gunicorn" = super.buildPythonPackage {
658 "gunicorn" = super.buildPythonPackage {
659 name = "gunicorn-19.9.0";
659 name = "gunicorn-19.9.0";
660 doCheck = false;
660 doCheck = false;
661 src = fetchurl {
661 src = fetchurl {
662 url = "https://files.pythonhosted.org/packages/47/52/68ba8e5e8ba251e54006a49441f7ccabca83b6bef5aedacb4890596c7911/gunicorn-19.9.0.tar.gz";
662 url = "https://files.pythonhosted.org/packages/47/52/68ba8e5e8ba251e54006a49441f7ccabca83b6bef5aedacb4890596c7911/gunicorn-19.9.0.tar.gz";
663 sha256 = "1wzlf4xmn6qjirh5w81l6i6kqjnab1n1qqkh7zsj1yb6gh4n49ps";
663 sha256 = "1wzlf4xmn6qjirh5w81l6i6kqjnab1n1qqkh7zsj1yb6gh4n49ps";
664 };
664 };
665 meta = {
665 meta = {
666 license = [ pkgs.lib.licenses.mit ];
666 license = [ pkgs.lib.licenses.mit ];
667 };
667 };
668 };
668 };
669 "hupper" = super.buildPythonPackage {
669 "hupper" = super.buildPythonPackage {
670 name = "hupper-1.9.1";
670 name = "hupper-1.9.1";
671 doCheck = false;
671 doCheck = false;
672 src = fetchurl {
672 src = fetchurl {
673 url = "https://files.pythonhosted.org/packages/09/3a/4f215659f31eeffe364a984dba486bfa3907bfcc54b7013bdfe825cebb5f/hupper-1.9.1.tar.gz";
673 url = "https://files.pythonhosted.org/packages/09/3a/4f215659f31eeffe364a984dba486bfa3907bfcc54b7013bdfe825cebb5f/hupper-1.9.1.tar.gz";
674 sha256 = "0pyg879fv9mbwlnbzw2a3234qqycqs9l97h5mpkmk0bvxhi2471v";
674 sha256 = "0pyg879fv9mbwlnbzw2a3234qqycqs9l97h5mpkmk0bvxhi2471v";
675 };
675 };
676 meta = {
676 meta = {
677 license = [ pkgs.lib.licenses.mit ];
677 license = [ pkgs.lib.licenses.mit ];
678 };
678 };
679 };
679 };
680 "importlib-metadata" = super.buildPythonPackage {
680 "importlib-metadata" = super.buildPythonPackage {
681 name = "importlib-metadata-0.23";
681 name = "importlib-metadata-0.23";
682 doCheck = false;
682 doCheck = false;
683 propagatedBuildInputs = [
683 propagatedBuildInputs = [
684 self."zipp"
684 self."zipp"
685 self."contextlib2"
685 self."contextlib2"
686 self."configparser"
686 self."configparser"
687 self."pathlib2"
687 self."pathlib2"
688 ];
688 ];
689 src = fetchurl {
689 src = fetchurl {
690 url = "https://files.pythonhosted.org/packages/5d/44/636bcd15697791943e2dedda0dbe098d8530a38d113b202817133e0b06c0/importlib_metadata-0.23.tar.gz";
690 url = "https://files.pythonhosted.org/packages/5d/44/636bcd15697791943e2dedda0dbe098d8530a38d113b202817133e0b06c0/importlib_metadata-0.23.tar.gz";
691 sha256 = "09mdqdfv5rdrwz80jh9m379gxmvk2vhjfz0fg53hid00icvxf65a";
691 sha256 = "09mdqdfv5rdrwz80jh9m379gxmvk2vhjfz0fg53hid00icvxf65a";
692 };
692 };
693 meta = {
693 meta = {
694 license = [ pkgs.lib.licenses.asl20 ];
694 license = [ pkgs.lib.licenses.asl20 ];
695 };
695 };
696 };
696 };
697 "infrae.cache" = super.buildPythonPackage {
697 "infrae.cache" = super.buildPythonPackage {
698 name = "infrae.cache-1.0.1";
698 name = "infrae.cache-1.0.1";
699 doCheck = false;
699 doCheck = false;
700 propagatedBuildInputs = [
700 propagatedBuildInputs = [
701 self."beaker"
701 self."beaker"
702 self."repoze.lru"
702 self."repoze.lru"
703 ];
703 ];
704 src = fetchurl {
704 src = fetchurl {
705 url = "https://files.pythonhosted.org/packages/bb/f0/e7d5e984cf6592fd2807dc7bc44a93f9d18e04e6a61f87fdfb2622422d74/infrae.cache-1.0.1.tar.gz";
705 url = "https://files.pythonhosted.org/packages/bb/f0/e7d5e984cf6592fd2807dc7bc44a93f9d18e04e6a61f87fdfb2622422d74/infrae.cache-1.0.1.tar.gz";
706 sha256 = "1dvqsjn8vw253wz9d1pz17j79mf4bs53dvp2qxck2qdp1am1njw4";
706 sha256 = "1dvqsjn8vw253wz9d1pz17j79mf4bs53dvp2qxck2qdp1am1njw4";
707 };
707 };
708 meta = {
708 meta = {
709 license = [ pkgs.lib.licenses.zpl21 ];
709 license = [ pkgs.lib.licenses.zpl21 ];
710 };
710 };
711 };
711 };
712 "invoke" = super.buildPythonPackage {
712 "invoke" = super.buildPythonPackage {
713 name = "invoke-0.13.0";
713 name = "invoke-0.13.0";
714 doCheck = false;
714 doCheck = false;
715 src = fetchurl {
715 src = fetchurl {
716 url = "https://files.pythonhosted.org/packages/47/bf/d07ef52fa1ac645468858bbac7cb95b246a972a045e821493d17d89c81be/invoke-0.13.0.tar.gz";
716 url = "https://files.pythonhosted.org/packages/47/bf/d07ef52fa1ac645468858bbac7cb95b246a972a045e821493d17d89c81be/invoke-0.13.0.tar.gz";
717 sha256 = "0794vhgxfmkh0vzkkg5cfv1w82g3jc3xr18wim29far9qpx9468s";
717 sha256 = "0794vhgxfmkh0vzkkg5cfv1w82g3jc3xr18wim29far9qpx9468s";
718 };
718 };
719 meta = {
719 meta = {
720 license = [ pkgs.lib.licenses.bsdOriginal ];
720 license = [ pkgs.lib.licenses.bsdOriginal ];
721 };
721 };
722 };
722 };
723 "ipaddress" = super.buildPythonPackage {
723 "ipaddress" = super.buildPythonPackage {
724 name = "ipaddress-1.0.23";
724 name = "ipaddress-1.0.23";
725 doCheck = false;
725 doCheck = false;
726 src = fetchurl {
726 src = fetchurl {
727 url = "https://files.pythonhosted.org/packages/b9/9a/3e9da40ea28b8210dd6504d3fe9fe7e013b62bf45902b458d1cdc3c34ed9/ipaddress-1.0.23.tar.gz";
727 url = "https://files.pythonhosted.org/packages/b9/9a/3e9da40ea28b8210dd6504d3fe9fe7e013b62bf45902b458d1cdc3c34ed9/ipaddress-1.0.23.tar.gz";
728 sha256 = "1qp743h30s04m3cg3yk3fycad930jv17q7dsslj4mfw0jlvf1y5p";
728 sha256 = "1qp743h30s04m3cg3yk3fycad930jv17q7dsslj4mfw0jlvf1y5p";
729 };
729 };
730 meta = {
730 meta = {
731 license = [ pkgs.lib.licenses.psfl ];
731 license = [ pkgs.lib.licenses.psfl ];
732 };
732 };
733 };
733 };
734 "ipdb" = super.buildPythonPackage {
734 "ipdb" = super.buildPythonPackage {
735 name = "ipdb-0.12";
735 name = "ipdb-0.12";
736 doCheck = false;
736 doCheck = false;
737 propagatedBuildInputs = [
737 propagatedBuildInputs = [
738 self."setuptools"
738 self."setuptools"
739 self."ipython"
739 self."ipython"
740 ];
740 ];
741 src = fetchurl {
741 src = fetchurl {
742 url = "https://files.pythonhosted.org/packages/6d/43/c3c2e866a8803e196d6209595020a4a6db1a3c5d07c01455669497ae23d0/ipdb-0.12.tar.gz";
742 url = "https://files.pythonhosted.org/packages/6d/43/c3c2e866a8803e196d6209595020a4a6db1a3c5d07c01455669497ae23d0/ipdb-0.12.tar.gz";
743 sha256 = "1khr2n7xfy8hg65kj1bsrjq9g7656pp0ybfa8abpbzpdawji3qnw";
743 sha256 = "1khr2n7xfy8hg65kj1bsrjq9g7656pp0ybfa8abpbzpdawji3qnw";
744 };
744 };
745 meta = {
745 meta = {
746 license = [ pkgs.lib.licenses.bsdOriginal ];
746 license = [ pkgs.lib.licenses.bsdOriginal ];
747 };
747 };
748 };
748 };
749 "ipython" = super.buildPythonPackage {
749 "ipython" = super.buildPythonPackage {
750 name = "ipython-5.1.0";
750 name = "ipython-5.1.0";
751 doCheck = false;
751 doCheck = false;
752 propagatedBuildInputs = [
752 propagatedBuildInputs = [
753 self."setuptools"
753 self."setuptools"
754 self."decorator"
754 self."decorator"
755 self."pickleshare"
755 self."pickleshare"
756 self."simplegeneric"
756 self."simplegeneric"
757 self."traitlets"
757 self."traitlets"
758 self."prompt-toolkit"
758 self."prompt-toolkit"
759 self."pygments"
759 self."pygments"
760 self."pexpect"
760 self."pexpect"
761 self."backports.shutil-get-terminal-size"
761 self."backports.shutil-get-terminal-size"
762 self."pathlib2"
762 self."pathlib2"
763 self."pexpect"
763 self."pexpect"
764 ];
764 ];
765 src = fetchurl {
765 src = fetchurl {
766 url = "https://files.pythonhosted.org/packages/89/63/a9292f7cd9d0090a0f995e1167f3f17d5889dcbc9a175261719c513b9848/ipython-5.1.0.tar.gz";
766 url = "https://files.pythonhosted.org/packages/89/63/a9292f7cd9d0090a0f995e1167f3f17d5889dcbc9a175261719c513b9848/ipython-5.1.0.tar.gz";
767 sha256 = "0qdrf6aj9kvjczd5chj1my8y2iq09am9l8bb2a1334a52d76kx3y";
767 sha256 = "0qdrf6aj9kvjczd5chj1my8y2iq09am9l8bb2a1334a52d76kx3y";
768 };
768 };
769 meta = {
769 meta = {
770 license = [ pkgs.lib.licenses.bsdOriginal ];
770 license = [ pkgs.lib.licenses.bsdOriginal ];
771 };
771 };
772 };
772 };
773 "ipython-genutils" = super.buildPythonPackage {
773 "ipython-genutils" = super.buildPythonPackage {
774 name = "ipython-genutils-0.2.0";
774 name = "ipython-genutils-0.2.0";
775 doCheck = false;
775 doCheck = false;
776 src = fetchurl {
776 src = fetchurl {
777 url = "https://files.pythonhosted.org/packages/e8/69/fbeffffc05236398ebfcfb512b6d2511c622871dca1746361006da310399/ipython_genutils-0.2.0.tar.gz";
777 url = "https://files.pythonhosted.org/packages/e8/69/fbeffffc05236398ebfcfb512b6d2511c622871dca1746361006da310399/ipython_genutils-0.2.0.tar.gz";
778 sha256 = "1a4bc9y8hnvq6cp08qs4mckgm6i6ajpndp4g496rvvzcfmp12bpb";
778 sha256 = "1a4bc9y8hnvq6cp08qs4mckgm6i6ajpndp4g496rvvzcfmp12bpb";
779 };
779 };
780 meta = {
780 meta = {
781 license = [ pkgs.lib.licenses.bsdOriginal ];
781 license = [ pkgs.lib.licenses.bsdOriginal ];
782 };
782 };
783 };
783 };
784 "iso8601" = super.buildPythonPackage {
784 "iso8601" = super.buildPythonPackage {
785 name = "iso8601-0.1.12";
785 name = "iso8601-0.1.12";
786 doCheck = false;
786 doCheck = false;
787 src = fetchurl {
787 src = fetchurl {
788 url = "https://files.pythonhosted.org/packages/45/13/3db24895497345fb44c4248c08b16da34a9eb02643cea2754b21b5ed08b0/iso8601-0.1.12.tar.gz";
788 url = "https://files.pythonhosted.org/packages/45/13/3db24895497345fb44c4248c08b16da34a9eb02643cea2754b21b5ed08b0/iso8601-0.1.12.tar.gz";
789 sha256 = "10nyvvnrhw2w3p09v1ica4lgj6f4g9j3kkfx17qmraiq3w7b5i29";
789 sha256 = "10nyvvnrhw2w3p09v1ica4lgj6f4g9j3kkfx17qmraiq3w7b5i29";
790 };
790 };
791 meta = {
791 meta = {
792 license = [ pkgs.lib.licenses.mit ];
792 license = [ pkgs.lib.licenses.mit ];
793 };
793 };
794 };
794 };
795 "isodate" = super.buildPythonPackage {
795 "isodate" = super.buildPythonPackage {
796 name = "isodate-0.6.0";
796 name = "isodate-0.6.0";
797 doCheck = false;
797 doCheck = false;
798 propagatedBuildInputs = [
798 propagatedBuildInputs = [
799 self."six"
799 self."six"
800 ];
800 ];
801 src = fetchurl {
801 src = fetchurl {
802 url = "https://files.pythonhosted.org/packages/b1/80/fb8c13a4cd38eb5021dc3741a9e588e4d1de88d895c1910c6fc8a08b7a70/isodate-0.6.0.tar.gz";
802 url = "https://files.pythonhosted.org/packages/b1/80/fb8c13a4cd38eb5021dc3741a9e588e4d1de88d895c1910c6fc8a08b7a70/isodate-0.6.0.tar.gz";
803 sha256 = "1n7jkz68kk5pwni540pr5zdh99bf6ywydk1p5pdrqisrawylldif";
803 sha256 = "1n7jkz68kk5pwni540pr5zdh99bf6ywydk1p5pdrqisrawylldif";
804 };
804 };
805 meta = {
805 meta = {
806 license = [ pkgs.lib.licenses.bsdOriginal ];
806 license = [ pkgs.lib.licenses.bsdOriginal ];
807 };
807 };
808 };
808 };
809 "itsdangerous" = super.buildPythonPackage {
809 "itsdangerous" = super.buildPythonPackage {
810 name = "itsdangerous-0.24";
810 name = "itsdangerous-0.24";
811 doCheck = false;
811 doCheck = false;
812 src = fetchurl {
812 src = fetchurl {
813 url = "https://files.pythonhosted.org/packages/dc/b4/a60bcdba945c00f6d608d8975131ab3f25b22f2bcfe1dab221165194b2d4/itsdangerous-0.24.tar.gz";
813 url = "https://files.pythonhosted.org/packages/dc/b4/a60bcdba945c00f6d608d8975131ab3f25b22f2bcfe1dab221165194b2d4/itsdangerous-0.24.tar.gz";
814 sha256 = "06856q6x675ly542ig0plbqcyab6ksfzijlyf1hzhgg3sgwgrcyb";
814 sha256 = "06856q6x675ly542ig0plbqcyab6ksfzijlyf1hzhgg3sgwgrcyb";
815 };
815 };
816 meta = {
816 meta = {
817 license = [ pkgs.lib.licenses.bsdOriginal ];
817 license = [ pkgs.lib.licenses.bsdOriginal ];
818 };
818 };
819 };
819 };
820 "jinja2" = super.buildPythonPackage {
820 "jinja2" = super.buildPythonPackage {
821 name = "jinja2-2.9.6";
821 name = "jinja2-2.9.6";
822 doCheck = false;
822 doCheck = false;
823 propagatedBuildInputs = [
823 propagatedBuildInputs = [
824 self."markupsafe"
824 self."markupsafe"
825 ];
825 ];
826 src = fetchurl {
826 src = fetchurl {
827 url = "https://files.pythonhosted.org/packages/90/61/f820ff0076a2599dd39406dcb858ecb239438c02ce706c8e91131ab9c7f1/Jinja2-2.9.6.tar.gz";
827 url = "https://files.pythonhosted.org/packages/90/61/f820ff0076a2599dd39406dcb858ecb239438c02ce706c8e91131ab9c7f1/Jinja2-2.9.6.tar.gz";
828 sha256 = "1zzrkywhziqffrzks14kzixz7nd4yh2vc0fb04a68vfd2ai03anx";
828 sha256 = "1zzrkywhziqffrzks14kzixz7nd4yh2vc0fb04a68vfd2ai03anx";
829 };
829 };
830 meta = {
830 meta = {
831 license = [ pkgs.lib.licenses.bsdOriginal ];
831 license = [ pkgs.lib.licenses.bsdOriginal ];
832 };
832 };
833 };
833 };
834 "jsonschema" = super.buildPythonPackage {
834 "jsonschema" = super.buildPythonPackage {
835 name = "jsonschema-2.6.0";
835 name = "jsonschema-2.6.0";
836 doCheck = false;
836 doCheck = false;
837 propagatedBuildInputs = [
837 propagatedBuildInputs = [
838 self."functools32"
838 self."functools32"
839 ];
839 ];
840 src = fetchurl {
840 src = fetchurl {
841 url = "https://files.pythonhosted.org/packages/58/b9/171dbb07e18c6346090a37f03c7e74410a1a56123f847efed59af260a298/jsonschema-2.6.0.tar.gz";
841 url = "https://files.pythonhosted.org/packages/58/b9/171dbb07e18c6346090a37f03c7e74410a1a56123f847efed59af260a298/jsonschema-2.6.0.tar.gz";
842 sha256 = "00kf3zmpp9ya4sydffpifn0j0mzm342a2vzh82p6r0vh10cg7xbg";
842 sha256 = "00kf3zmpp9ya4sydffpifn0j0mzm342a2vzh82p6r0vh10cg7xbg";
843 };
843 };
844 meta = {
844 meta = {
845 license = [ pkgs.lib.licenses.mit ];
845 license = [ pkgs.lib.licenses.mit ];
846 };
846 };
847 };
847 };
848 "jupyter-client" = super.buildPythonPackage {
848 "jupyter-client" = super.buildPythonPackage {
849 name = "jupyter-client-5.0.0";
849 name = "jupyter-client-5.0.0";
850 doCheck = false;
850 doCheck = false;
851 propagatedBuildInputs = [
851 propagatedBuildInputs = [
852 self."traitlets"
852 self."traitlets"
853 self."jupyter-core"
853 self."jupyter-core"
854 self."pyzmq"
854 self."pyzmq"
855 self."python-dateutil"
855 self."python-dateutil"
856 ];
856 ];
857 src = fetchurl {
857 src = fetchurl {
858 url = "https://files.pythonhosted.org/packages/e5/6f/65412ed462202b90134b7e761b0b7e7f949e07a549c1755475333727b3d0/jupyter_client-5.0.0.tar.gz";
858 url = "https://files.pythonhosted.org/packages/e5/6f/65412ed462202b90134b7e761b0b7e7f949e07a549c1755475333727b3d0/jupyter_client-5.0.0.tar.gz";
859 sha256 = "0nxw4rqk4wsjhc87gjqd7pv89cb9dnimcfnmcmp85bmrvv1gjri7";
859 sha256 = "0nxw4rqk4wsjhc87gjqd7pv89cb9dnimcfnmcmp85bmrvv1gjri7";
860 };
860 };
861 meta = {
861 meta = {
862 license = [ pkgs.lib.licenses.bsdOriginal ];
862 license = [ pkgs.lib.licenses.bsdOriginal ];
863 };
863 };
864 };
864 };
865 "jupyter-core" = super.buildPythonPackage {
865 "jupyter-core" = super.buildPythonPackage {
866 name = "jupyter-core-4.5.0";
866 name = "jupyter-core-4.5.0";
867 doCheck = false;
867 doCheck = false;
868 propagatedBuildInputs = [
868 propagatedBuildInputs = [
869 self."traitlets"
869 self."traitlets"
870 ];
870 ];
871 src = fetchurl {
871 src = fetchurl {
872 url = "https://files.pythonhosted.org/packages/4a/de/ff4ca734656d17ebe0450807b59d728f45277e2e7f4b82bc9aae6cb82961/jupyter_core-4.5.0.tar.gz";
872 url = "https://files.pythonhosted.org/packages/4a/de/ff4ca734656d17ebe0450807b59d728f45277e2e7f4b82bc9aae6cb82961/jupyter_core-4.5.0.tar.gz";
873 sha256 = "1xr4pbghwk5hayn5wwnhb7z95380r45p79gf5if5pi1akwg7qvic";
873 sha256 = "1xr4pbghwk5hayn5wwnhb7z95380r45p79gf5if5pi1akwg7qvic";
874 };
874 };
875 meta = {
875 meta = {
876 license = [ pkgs.lib.licenses.bsdOriginal ];
876 license = [ pkgs.lib.licenses.bsdOriginal ];
877 };
877 };
878 };
878 };
879 "kombu" = super.buildPythonPackage {
879 "kombu" = super.buildPythonPackage {
880 name = "kombu-4.6.6";
880 name = "kombu-4.6.6";
881 doCheck = false;
881 doCheck = false;
882 propagatedBuildInputs = [
882 propagatedBuildInputs = [
883 self."amqp"
883 self."amqp"
884 self."importlib-metadata"
884 self."importlib-metadata"
885 ];
885 ];
886 src = fetchurl {
886 src = fetchurl {
887 url = "https://files.pythonhosted.org/packages/20/e6/bc2d9affba6138a1dc143f77fef253e9e08e238fa7c0688d917c09005e96/kombu-4.6.6.tar.gz";
887 url = "https://files.pythonhosted.org/packages/20/e6/bc2d9affba6138a1dc143f77fef253e9e08e238fa7c0688d917c09005e96/kombu-4.6.6.tar.gz";
888 sha256 = "11mxpcy8mg1l35bgbhba70v29bydr2hrhdbdlb4lg98m3m5vaq0p";
888 sha256 = "11mxpcy8mg1l35bgbhba70v29bydr2hrhdbdlb4lg98m3m5vaq0p";
889 };
889 };
890 meta = {
890 meta = {
891 license = [ pkgs.lib.licenses.bsdOriginal ];
891 license = [ pkgs.lib.licenses.bsdOriginal ];
892 };
892 };
893 };
893 };
894 "lxml" = super.buildPythonPackage {
894 "lxml" = super.buildPythonPackage {
895 name = "lxml-4.2.5";
895 name = "lxml-4.2.5";
896 doCheck = false;
896 doCheck = false;
897 src = fetchurl {
897 src = fetchurl {
898 url = "https://files.pythonhosted.org/packages/4b/20/ddf5eb3bd5c57582d2b4652b4bbcf8da301bdfe5d805cb94e805f4d7464d/lxml-4.2.5.tar.gz";
898 url = "https://files.pythonhosted.org/packages/4b/20/ddf5eb3bd5c57582d2b4652b4bbcf8da301bdfe5d805cb94e805f4d7464d/lxml-4.2.5.tar.gz";
899 sha256 = "0zw0y9hs0nflxhl9cs6ipwwh53szi3w2x06wl0k9cylyqac0cwin";
899 sha256 = "0zw0y9hs0nflxhl9cs6ipwwh53szi3w2x06wl0k9cylyqac0cwin";
900 };
900 };
901 meta = {
901 meta = {
902 license = [ pkgs.lib.licenses.bsdOriginal ];
902 license = [ pkgs.lib.licenses.bsdOriginal ];
903 };
903 };
904 };
904 };
905 "mako" = super.buildPythonPackage {
905 "mako" = super.buildPythonPackage {
906 name = "mako-1.1.0";
906 name = "mako-1.1.0";
907 doCheck = false;
907 doCheck = false;
908 propagatedBuildInputs = [
908 propagatedBuildInputs = [
909 self."markupsafe"
909 self."markupsafe"
910 ];
910 ];
911 src = fetchurl {
911 src = fetchurl {
912 url = "https://files.pythonhosted.org/packages/b0/3c/8dcd6883d009f7cae0f3157fb53e9afb05a0d3d33b3db1268ec2e6f4a56b/Mako-1.1.0.tar.gz";
912 url = "https://files.pythonhosted.org/packages/b0/3c/8dcd6883d009f7cae0f3157fb53e9afb05a0d3d33b3db1268ec2e6f4a56b/Mako-1.1.0.tar.gz";
913 sha256 = "0jqa3qfpykyn4fmkn0kh6043sfls7br8i2bsdbccazcvk9cijsd3";
913 sha256 = "0jqa3qfpykyn4fmkn0kh6043sfls7br8i2bsdbccazcvk9cijsd3";
914 };
914 };
915 meta = {
915 meta = {
916 license = [ pkgs.lib.licenses.mit ];
916 license = [ pkgs.lib.licenses.mit ];
917 };
917 };
918 };
918 };
919 "markdown" = super.buildPythonPackage {
919 "markdown" = super.buildPythonPackage {
920 name = "markdown-2.6.11";
920 name = "markdown-2.6.11";
921 doCheck = false;
921 doCheck = false;
922 src = fetchurl {
922 src = fetchurl {
923 url = "https://files.pythonhosted.org/packages/b3/73/fc5c850f44af5889192dff783b7b0d8f3fe8d30b65c8e3f78f8f0265fecf/Markdown-2.6.11.tar.gz";
923 url = "https://files.pythonhosted.org/packages/b3/73/fc5c850f44af5889192dff783b7b0d8f3fe8d30b65c8e3f78f8f0265fecf/Markdown-2.6.11.tar.gz";
924 sha256 = "108g80ryzykh8bj0i7jfp71510wrcixdi771lf2asyghgyf8cmm8";
924 sha256 = "108g80ryzykh8bj0i7jfp71510wrcixdi771lf2asyghgyf8cmm8";
925 };
925 };
926 meta = {
926 meta = {
927 license = [ pkgs.lib.licenses.bsdOriginal ];
927 license = [ pkgs.lib.licenses.bsdOriginal ];
928 };
928 };
929 };
929 };
930 "markupsafe" = super.buildPythonPackage {
930 "markupsafe" = super.buildPythonPackage {
931 name = "markupsafe-1.1.1";
931 name = "markupsafe-1.1.1";
932 doCheck = false;
932 doCheck = false;
933 src = fetchurl {
933 src = fetchurl {
934 url = "https://files.pythonhosted.org/packages/b9/2e/64db92e53b86efccfaea71321f597fa2e1b2bd3853d8ce658568f7a13094/MarkupSafe-1.1.1.tar.gz";
934 url = "https://files.pythonhosted.org/packages/b9/2e/64db92e53b86efccfaea71321f597fa2e1b2bd3853d8ce658568f7a13094/MarkupSafe-1.1.1.tar.gz";
935 sha256 = "0sqipg4fk7xbixqd8kq6rlkxj664d157bdwbh93farcphf92x1r9";
935 sha256 = "0sqipg4fk7xbixqd8kq6rlkxj664d157bdwbh93farcphf92x1r9";
936 };
936 };
937 meta = {
937 meta = {
938 license = [ pkgs.lib.licenses.bsdOriginal pkgs.lib.licenses.bsd3 ];
938 license = [ pkgs.lib.licenses.bsdOriginal pkgs.lib.licenses.bsd3 ];
939 };
939 };
940 };
940 };
941 "mistune" = super.buildPythonPackage {
941 "mistune" = super.buildPythonPackage {
942 name = "mistune-0.8.4";
942 name = "mistune-0.8.4";
943 doCheck = false;
943 doCheck = false;
944 src = fetchurl {
944 src = fetchurl {
945 url = "https://files.pythonhosted.org/packages/2d/a4/509f6e7783ddd35482feda27bc7f72e65b5e7dc910eca4ab2164daf9c577/mistune-0.8.4.tar.gz";
945 url = "https://files.pythonhosted.org/packages/2d/a4/509f6e7783ddd35482feda27bc7f72e65b5e7dc910eca4ab2164daf9c577/mistune-0.8.4.tar.gz";
946 sha256 = "0vkmsh0x480rni51lhyvigfdf06b9247z868pk3bal1wnnfl58sr";
946 sha256 = "0vkmsh0x480rni51lhyvigfdf06b9247z868pk3bal1wnnfl58sr";
947 };
947 };
948 meta = {
948 meta = {
949 license = [ pkgs.lib.licenses.bsdOriginal ];
949 license = [ pkgs.lib.licenses.bsdOriginal ];
950 };
950 };
951 };
951 };
952 "mock" = super.buildPythonPackage {
952 "mock" = super.buildPythonPackage {
953 name = "mock-3.0.5";
953 name = "mock-3.0.5";
954 doCheck = false;
954 doCheck = false;
955 propagatedBuildInputs = [
955 propagatedBuildInputs = [
956 self."six"
956 self."six"
957 self."funcsigs"
957 self."funcsigs"
958 ];
958 ];
959 src = fetchurl {
959 src = fetchurl {
960 url = "https://files.pythonhosted.org/packages/2e/ab/4fe657d78b270aa6a32f027849513b829b41b0f28d9d8d7f8c3d29ea559a/mock-3.0.5.tar.gz";
960 url = "https://files.pythonhosted.org/packages/2e/ab/4fe657d78b270aa6a32f027849513b829b41b0f28d9d8d7f8c3d29ea559a/mock-3.0.5.tar.gz";
961 sha256 = "1hrp6j0yrx2xzylfv02qa8kph661m6yq4p0mc8fnimch9j4psrc3";
961 sha256 = "1hrp6j0yrx2xzylfv02qa8kph661m6yq4p0mc8fnimch9j4psrc3";
962 };
962 };
963 meta = {
963 meta = {
964 license = [ pkgs.lib.licenses.bsdOriginal { fullName = "OSI Approved :: BSD License"; } ];
964 license = [ pkgs.lib.licenses.bsdOriginal { fullName = "OSI Approved :: BSD License"; } ];
965 };
965 };
966 };
966 };
967 "more-itertools" = super.buildPythonPackage {
967 "more-itertools" = super.buildPythonPackage {
968 name = "more-itertools-5.0.0";
968 name = "more-itertools-5.0.0";
969 doCheck = false;
969 doCheck = false;
970 propagatedBuildInputs = [
970 propagatedBuildInputs = [
971 self."six"
971 self."six"
972 ];
972 ];
973 src = fetchurl {
973 src = fetchurl {
974 url = "https://files.pythonhosted.org/packages/dd/26/30fc0d541d9fdf55faf5ba4b0fd68f81d5bd2447579224820ad525934178/more-itertools-5.0.0.tar.gz";
974 url = "https://files.pythonhosted.org/packages/dd/26/30fc0d541d9fdf55faf5ba4b0fd68f81d5bd2447579224820ad525934178/more-itertools-5.0.0.tar.gz";
975 sha256 = "1r12cm6mcdwdzz7d47a6g4l437xsvapdlgyhqay3i2nrlv03da9q";
975 sha256 = "1r12cm6mcdwdzz7d47a6g4l437xsvapdlgyhqay3i2nrlv03da9q";
976 };
976 };
977 meta = {
977 meta = {
978 license = [ pkgs.lib.licenses.mit ];
978 license = [ pkgs.lib.licenses.mit ];
979 };
979 };
980 };
980 };
981 "msgpack-python" = super.buildPythonPackage {
981 "msgpack-python" = super.buildPythonPackage {
982 name = "msgpack-python-0.5.6";
982 name = "msgpack-python-0.5.6";
983 doCheck = false;
983 doCheck = false;
984 src = fetchurl {
984 src = fetchurl {
985 url = "https://files.pythonhosted.org/packages/8a/20/6eca772d1a5830336f84aca1d8198e5a3f4715cd1c7fc36d3cc7f7185091/msgpack-python-0.5.6.tar.gz";
985 url = "https://files.pythonhosted.org/packages/8a/20/6eca772d1a5830336f84aca1d8198e5a3f4715cd1c7fc36d3cc7f7185091/msgpack-python-0.5.6.tar.gz";
986 sha256 = "16wh8qgybmfh4pjp8vfv78mdlkxfmcasg78lzlnm6nslsfkci31p";
986 sha256 = "16wh8qgybmfh4pjp8vfv78mdlkxfmcasg78lzlnm6nslsfkci31p";
987 };
987 };
988 meta = {
988 meta = {
989 license = [ pkgs.lib.licenses.asl20 ];
989 license = [ pkgs.lib.licenses.asl20 ];
990 };
990 };
991 };
991 };
992 "mysql-python" = super.buildPythonPackage {
992 "mysql-python" = super.buildPythonPackage {
993 name = "mysql-python-1.2.5";
993 name = "mysql-python-1.2.5";
994 doCheck = false;
994 doCheck = false;
995 src = fetchurl {
995 src = fetchurl {
996 url = "https://files.pythonhosted.org/packages/a5/e9/51b544da85a36a68debe7a7091f068d802fc515a3a202652828c73453cad/MySQL-python-1.2.5.zip";
996 url = "https://files.pythonhosted.org/packages/a5/e9/51b544da85a36a68debe7a7091f068d802fc515a3a202652828c73453cad/MySQL-python-1.2.5.zip";
997 sha256 = "0x0c2jg0bb3pp84njaqiic050qkyd7ymwhfvhipnimg58yv40441";
997 sha256 = "0x0c2jg0bb3pp84njaqiic050qkyd7ymwhfvhipnimg58yv40441";
998 };
998 };
999 meta = {
999 meta = {
1000 license = [ pkgs.lib.licenses.gpl1 ];
1000 license = [ pkgs.lib.licenses.gpl1 ];
1001 };
1001 };
1002 };
1002 };
1003 "nbconvert" = super.buildPythonPackage {
1003 "nbconvert" = super.buildPythonPackage {
1004 name = "nbconvert-5.3.1";
1004 name = "nbconvert-5.3.1";
1005 doCheck = false;
1005 doCheck = false;
1006 propagatedBuildInputs = [
1006 propagatedBuildInputs = [
1007 self."mistune"
1007 self."mistune"
1008 self."jinja2"
1008 self."jinja2"
1009 self."pygments"
1009 self."pygments"
1010 self."traitlets"
1010 self."traitlets"
1011 self."jupyter-core"
1011 self."jupyter-core"
1012 self."nbformat"
1012 self."nbformat"
1013 self."entrypoints"
1013 self."entrypoints"
1014 self."bleach"
1014 self."bleach"
1015 self."pandocfilters"
1015 self."pandocfilters"
1016 self."testpath"
1016 self."testpath"
1017 ];
1017 ];
1018 src = fetchurl {
1018 src = fetchurl {
1019 url = "https://files.pythonhosted.org/packages/b9/a4/d0a0938ad6f5eeb4dea4e73d255c617ef94b0b2849d51194c9bbdb838412/nbconvert-5.3.1.tar.gz";
1019 url = "https://files.pythonhosted.org/packages/b9/a4/d0a0938ad6f5eeb4dea4e73d255c617ef94b0b2849d51194c9bbdb838412/nbconvert-5.3.1.tar.gz";
1020 sha256 = "1f9dkvpx186xjm4xab0qbph588mncp4vqk3fmxrsnqs43mks9c8j";
1020 sha256 = "1f9dkvpx186xjm4xab0qbph588mncp4vqk3fmxrsnqs43mks9c8j";
1021 };
1021 };
1022 meta = {
1022 meta = {
1023 license = [ pkgs.lib.licenses.bsdOriginal ];
1023 license = [ pkgs.lib.licenses.bsdOriginal ];
1024 };
1024 };
1025 };
1025 };
1026 "nbformat" = super.buildPythonPackage {
1026 "nbformat" = super.buildPythonPackage {
1027 name = "nbformat-4.4.0";
1027 name = "nbformat-4.4.0";
1028 doCheck = false;
1028 doCheck = false;
1029 propagatedBuildInputs = [
1029 propagatedBuildInputs = [
1030 self."ipython-genutils"
1030 self."ipython-genutils"
1031 self."traitlets"
1031 self."traitlets"
1032 self."jsonschema"
1032 self."jsonschema"
1033 self."jupyter-core"
1033 self."jupyter-core"
1034 ];
1034 ];
1035 src = fetchurl {
1035 src = fetchurl {
1036 url = "https://files.pythonhosted.org/packages/6e/0e/160754f7ae3e984863f585a3743b0ed1702043a81245907c8fae2d537155/nbformat-4.4.0.tar.gz";
1036 url = "https://files.pythonhosted.org/packages/6e/0e/160754f7ae3e984863f585a3743b0ed1702043a81245907c8fae2d537155/nbformat-4.4.0.tar.gz";
1037 sha256 = "00nlf08h8yc4q73nphfvfhxrcnilaqanb8z0mdy6nxk0vzq4wjgp";
1037 sha256 = "00nlf08h8yc4q73nphfvfhxrcnilaqanb8z0mdy6nxk0vzq4wjgp";
1038 };
1038 };
1039 meta = {
1039 meta = {
1040 license = [ pkgs.lib.licenses.bsdOriginal ];
1040 license = [ pkgs.lib.licenses.bsdOriginal ];
1041 };
1041 };
1042 };
1042 };
1043 "packaging" = super.buildPythonPackage {
1043 "packaging" = super.buildPythonPackage {
1044 name = "packaging-19.2";
1044 name = "packaging-19.2";
1045 doCheck = false;
1045 doCheck = false;
1046 propagatedBuildInputs = [
1046 propagatedBuildInputs = [
1047 self."pyparsing"
1047 self."pyparsing"
1048 self."six"
1048 self."six"
1049 ];
1049 ];
1050 src = fetchurl {
1050 src = fetchurl {
1051 url = "https://files.pythonhosted.org/packages/5a/2f/449ded84226d0e2fda8da9252e5ee7731bdf14cd338f622dfcd9934e0377/packaging-19.2.tar.gz";
1051 url = "https://files.pythonhosted.org/packages/5a/2f/449ded84226d0e2fda8da9252e5ee7731bdf14cd338f622dfcd9934e0377/packaging-19.2.tar.gz";
1052 sha256 = "0izwlz9h0bw171a1chr311g2y7n657zjaf4mq4rgm8pp9lbj9f98";
1052 sha256 = "0izwlz9h0bw171a1chr311g2y7n657zjaf4mq4rgm8pp9lbj9f98";
1053 };
1053 };
1054 meta = {
1054 meta = {
1055 license = [ pkgs.lib.licenses.bsdOriginal { fullName = "BSD or Apache License, Version 2.0"; } pkgs.lib.licenses.asl20 ];
1055 license = [ pkgs.lib.licenses.bsdOriginal { fullName = "BSD or Apache License, Version 2.0"; } pkgs.lib.licenses.asl20 ];
1056 };
1056 };
1057 };
1057 };
1058 "pandocfilters" = super.buildPythonPackage {
1058 "pandocfilters" = super.buildPythonPackage {
1059 name = "pandocfilters-1.4.2";
1059 name = "pandocfilters-1.4.2";
1060 doCheck = false;
1060 doCheck = false;
1061 src = fetchurl {
1061 src = fetchurl {
1062 url = "https://files.pythonhosted.org/packages/4c/ea/236e2584af67bb6df960832731a6e5325fd4441de001767da328c33368ce/pandocfilters-1.4.2.tar.gz";
1062 url = "https://files.pythonhosted.org/packages/4c/ea/236e2584af67bb6df960832731a6e5325fd4441de001767da328c33368ce/pandocfilters-1.4.2.tar.gz";
1063 sha256 = "1a8d9b7s48gmq9zj0pmbyv2sivn5i7m6mybgpkk4jm5vd7hp1pdk";
1063 sha256 = "1a8d9b7s48gmq9zj0pmbyv2sivn5i7m6mybgpkk4jm5vd7hp1pdk";
1064 };
1064 };
1065 meta = {
1065 meta = {
1066 license = [ pkgs.lib.licenses.bsdOriginal ];
1066 license = [ pkgs.lib.licenses.bsdOriginal ];
1067 };
1067 };
1068 };
1068 };
1069 "paste" = super.buildPythonPackage {
1069 "paste" = super.buildPythonPackage {
1070 name = "paste-3.2.1";
1070 name = "paste-3.2.1";
1071 doCheck = false;
1071 doCheck = false;
1072 propagatedBuildInputs = [
1072 propagatedBuildInputs = [
1073 self."six"
1073 self."six"
1074 ];
1074 ];
1075 src = fetchurl {
1075 src = fetchurl {
1076 url = "https://files.pythonhosted.org/packages/0d/86/7008b5563594e8a63763f05212a3eb84c85f0b2eff834e5697716e56bca9/Paste-3.2.1.tar.gz";
1076 url = "https://files.pythonhosted.org/packages/0d/86/7008b5563594e8a63763f05212a3eb84c85f0b2eff834e5697716e56bca9/Paste-3.2.1.tar.gz";
1077 sha256 = "1vjxr8n1p31c9x9rh8g0f34yisa9028cxpvn36q7g1s0m2b9x71x";
1077 sha256 = "1vjxr8n1p31c9x9rh8g0f34yisa9028cxpvn36q7g1s0m2b9x71x";
1078 };
1078 };
1079 meta = {
1079 meta = {
1080 license = [ pkgs.lib.licenses.mit ];
1080 license = [ pkgs.lib.licenses.mit ];
1081 };
1081 };
1082 };
1082 };
1083 "pastedeploy" = super.buildPythonPackage {
1083 "pastedeploy" = super.buildPythonPackage {
1084 name = "pastedeploy-2.0.1";
1084 name = "pastedeploy-2.0.1";
1085 doCheck = false;
1085 doCheck = false;
1086 src = fetchurl {
1086 src = fetchurl {
1087 url = "https://files.pythonhosted.org/packages/19/a0/5623701df7e2478a68a1b685d1a84518024eef994cde7e4da8449a31616f/PasteDeploy-2.0.1.tar.gz";
1087 url = "https://files.pythonhosted.org/packages/19/a0/5623701df7e2478a68a1b685d1a84518024eef994cde7e4da8449a31616f/PasteDeploy-2.0.1.tar.gz";
1088 sha256 = "02imfbbx1mi2h546f3sr37m47dk9qizaqhzzlhx8bkzxa6fzn8yl";
1088 sha256 = "02imfbbx1mi2h546f3sr37m47dk9qizaqhzzlhx8bkzxa6fzn8yl";
1089 };
1089 };
1090 meta = {
1090 meta = {
1091 license = [ pkgs.lib.licenses.mit ];
1091 license = [ pkgs.lib.licenses.mit ];
1092 };
1092 };
1093 };
1093 };
1094 "pastescript" = super.buildPythonPackage {
1094 "pastescript" = super.buildPythonPackage {
1095 name = "pastescript-3.2.0";
1095 name = "pastescript-3.2.0";
1096 doCheck = false;
1096 doCheck = false;
1097 propagatedBuildInputs = [
1097 propagatedBuildInputs = [
1098 self."paste"
1098 self."paste"
1099 self."pastedeploy"
1099 self."pastedeploy"
1100 self."six"
1100 self."six"
1101 ];
1101 ];
1102 src = fetchurl {
1102 src = fetchurl {
1103 url = "https://files.pythonhosted.org/packages/ff/47/45c6f5a3cb8f5abf786fea98dbb8d02400a55768a9b623afb7df12346c61/PasteScript-3.2.0.tar.gz";
1103 url = "https://files.pythonhosted.org/packages/ff/47/45c6f5a3cb8f5abf786fea98dbb8d02400a55768a9b623afb7df12346c61/PasteScript-3.2.0.tar.gz";
1104 sha256 = "1b3jq7xh383nvrrlblk05m37345bv97xrhx77wshllba3h7mq3wv";
1104 sha256 = "1b3jq7xh383nvrrlblk05m37345bv97xrhx77wshllba3h7mq3wv";
1105 };
1105 };
1106 meta = {
1106 meta = {
1107 license = [ pkgs.lib.licenses.mit ];
1107 license = [ pkgs.lib.licenses.mit ];
1108 };
1108 };
1109 };
1109 };
1110 "pathlib2" = super.buildPythonPackage {
1110 "pathlib2" = super.buildPythonPackage {
1111 name = "pathlib2-2.3.5";
1111 name = "pathlib2-2.3.5";
1112 doCheck = false;
1112 doCheck = false;
1113 propagatedBuildInputs = [
1113 propagatedBuildInputs = [
1114 self."six"
1114 self."six"
1115 self."scandir"
1115 self."scandir"
1116 ];
1116 ];
1117 src = fetchurl {
1117 src = fetchurl {
1118 url = "https://files.pythonhosted.org/packages/94/d8/65c86584e7e97ef824a1845c72bbe95d79f5b306364fa778a3c3e401b309/pathlib2-2.3.5.tar.gz";
1118 url = "https://files.pythonhosted.org/packages/94/d8/65c86584e7e97ef824a1845c72bbe95d79f5b306364fa778a3c3e401b309/pathlib2-2.3.5.tar.gz";
1119 sha256 = "0s4qa8c082fdkb17izh4mfgwrjd1n5pya18wvrbwqdvvb5xs9nbc";
1119 sha256 = "0s4qa8c082fdkb17izh4mfgwrjd1n5pya18wvrbwqdvvb5xs9nbc";
1120 };
1120 };
1121 meta = {
1121 meta = {
1122 license = [ pkgs.lib.licenses.mit ];
1122 license = [ pkgs.lib.licenses.mit ];
1123 };
1123 };
1124 };
1124 };
1125 "peppercorn" = super.buildPythonPackage {
1125 "peppercorn" = super.buildPythonPackage {
1126 name = "peppercorn-0.6";
1126 name = "peppercorn-0.6";
1127 doCheck = false;
1127 doCheck = false;
1128 src = fetchurl {
1128 src = fetchurl {
1129 url = "https://files.pythonhosted.org/packages/e4/77/93085de7108cdf1a0b092ff443872a8f9442c736d7ddebdf2f27627935f4/peppercorn-0.6.tar.gz";
1129 url = "https://files.pythonhosted.org/packages/e4/77/93085de7108cdf1a0b092ff443872a8f9442c736d7ddebdf2f27627935f4/peppercorn-0.6.tar.gz";
1130 sha256 = "1ip4bfwcpwkq9hz2dai14k2cyabvwrnvcvrcmzxmqm04g8fnimwn";
1130 sha256 = "1ip4bfwcpwkq9hz2dai14k2cyabvwrnvcvrcmzxmqm04g8fnimwn";
1131 };
1131 };
1132 meta = {
1132 meta = {
1133 license = [ { fullName = "BSD-derived (http://www.repoze.org/LICENSE.txt)"; } ];
1133 license = [ { fullName = "BSD-derived (http://www.repoze.org/LICENSE.txt)"; } ];
1134 };
1134 };
1135 };
1135 };
1136 "pexpect" = super.buildPythonPackage {
1136 "pexpect" = super.buildPythonPackage {
1137 name = "pexpect-4.7.0";
1137 name = "pexpect-4.7.0";
1138 doCheck = false;
1138 doCheck = false;
1139 propagatedBuildInputs = [
1139 propagatedBuildInputs = [
1140 self."ptyprocess"
1140 self."ptyprocess"
1141 ];
1141 ];
1142 src = fetchurl {
1142 src = fetchurl {
1143 url = "https://files.pythonhosted.org/packages/1c/b1/362a0d4235496cb42c33d1d8732b5e2c607b0129ad5fdd76f5a583b9fcb3/pexpect-4.7.0.tar.gz";
1143 url = "https://files.pythonhosted.org/packages/1c/b1/362a0d4235496cb42c33d1d8732b5e2c607b0129ad5fdd76f5a583b9fcb3/pexpect-4.7.0.tar.gz";
1144 sha256 = "1sv2rri15zwhds85a4kamwh9pj49qcxv7m4miyr4jfpfwv81yb4y";
1144 sha256 = "1sv2rri15zwhds85a4kamwh9pj49qcxv7m4miyr4jfpfwv81yb4y";
1145 };
1145 };
1146 meta = {
1146 meta = {
1147 license = [ pkgs.lib.licenses.isc { fullName = "ISC License (ISCL)"; } ];
1147 license = [ pkgs.lib.licenses.isc { fullName = "ISC License (ISCL)"; } ];
1148 };
1148 };
1149 };
1149 };
1150 "pickleshare" = super.buildPythonPackage {
1150 "pickleshare" = super.buildPythonPackage {
1151 name = "pickleshare-0.7.5";
1151 name = "pickleshare-0.7.5";
1152 doCheck = false;
1152 doCheck = false;
1153 propagatedBuildInputs = [
1153 propagatedBuildInputs = [
1154 self."pathlib2"
1154 self."pathlib2"
1155 ];
1155 ];
1156 src = fetchurl {
1156 src = fetchurl {
1157 url = "https://files.pythonhosted.org/packages/d8/b6/df3c1c9b616e9c0edbc4fbab6ddd09df9535849c64ba51fcb6531c32d4d8/pickleshare-0.7.5.tar.gz";
1157 url = "https://files.pythonhosted.org/packages/d8/b6/df3c1c9b616e9c0edbc4fbab6ddd09df9535849c64ba51fcb6531c32d4d8/pickleshare-0.7.5.tar.gz";
1158 sha256 = "1jmghg3c53yp1i8cm6pcrm280ayi8621rwyav9fac7awjr3kss47";
1158 sha256 = "1jmghg3c53yp1i8cm6pcrm280ayi8621rwyav9fac7awjr3kss47";
1159 };
1159 };
1160 meta = {
1160 meta = {
1161 license = [ pkgs.lib.licenses.mit ];
1161 license = [ pkgs.lib.licenses.mit ];
1162 };
1162 };
1163 };
1163 };
1164 "plaster" = super.buildPythonPackage {
1164 "plaster" = super.buildPythonPackage {
1165 name = "plaster-1.0";
1165 name = "plaster-1.0";
1166 doCheck = false;
1166 doCheck = false;
1167 propagatedBuildInputs = [
1167 propagatedBuildInputs = [
1168 self."setuptools"
1168 self."setuptools"
1169 ];
1169 ];
1170 src = fetchurl {
1170 src = fetchurl {
1171 url = "https://files.pythonhosted.org/packages/37/e1/56d04382d718d32751017d32f351214384e529b794084eee20bb52405563/plaster-1.0.tar.gz";
1171 url = "https://files.pythonhosted.org/packages/37/e1/56d04382d718d32751017d32f351214384e529b794084eee20bb52405563/plaster-1.0.tar.gz";
1172 sha256 = "1hy8k0nv2mxq94y5aysk6hjk9ryb4bsd13g83m60hcyzxz3wflc3";
1172 sha256 = "1hy8k0nv2mxq94y5aysk6hjk9ryb4bsd13g83m60hcyzxz3wflc3";
1173 };
1173 };
1174 meta = {
1174 meta = {
1175 license = [ pkgs.lib.licenses.mit ];
1175 license = [ pkgs.lib.licenses.mit ];
1176 };
1176 };
1177 };
1177 };
1178 "plaster-pastedeploy" = super.buildPythonPackage {
1178 "plaster-pastedeploy" = super.buildPythonPackage {
1179 name = "plaster-pastedeploy-0.7";
1179 name = "plaster-pastedeploy-0.7";
1180 doCheck = false;
1180 doCheck = false;
1181 propagatedBuildInputs = [
1181 propagatedBuildInputs = [
1182 self."pastedeploy"
1182 self."pastedeploy"
1183 self."plaster"
1183 self."plaster"
1184 ];
1184 ];
1185 src = fetchurl {
1185 src = fetchurl {
1186 url = "https://files.pythonhosted.org/packages/99/69/2d3bc33091249266a1bd3cf24499e40ab31d54dffb4a7d76fe647950b98c/plaster_pastedeploy-0.7.tar.gz";
1186 url = "https://files.pythonhosted.org/packages/99/69/2d3bc33091249266a1bd3cf24499e40ab31d54dffb4a7d76fe647950b98c/plaster_pastedeploy-0.7.tar.gz";
1187 sha256 = "1zg7gcsvc1kzay1ry5p699rg2qavfsxqwl17mqxzr0gzw6j9679r";
1187 sha256 = "1zg7gcsvc1kzay1ry5p699rg2qavfsxqwl17mqxzr0gzw6j9679r";
1188 };
1188 };
1189 meta = {
1189 meta = {
1190 license = [ pkgs.lib.licenses.mit ];
1190 license = [ pkgs.lib.licenses.mit ];
1191 };
1191 };
1192 };
1192 };
1193 "pluggy" = super.buildPythonPackage {
1193 "pluggy" = super.buildPythonPackage {
1194 name = "pluggy-0.13.0";
1194 name = "pluggy-0.13.0";
1195 doCheck = false;
1195 doCheck = false;
1196 propagatedBuildInputs = [
1196 propagatedBuildInputs = [
1197 self."importlib-metadata"
1197 self."importlib-metadata"
1198 ];
1198 ];
1199 src = fetchurl {
1199 src = fetchurl {
1200 url = "https://files.pythonhosted.org/packages/d7/9d/ae82a5facf2dd89f557a33ad18eb68e5ac7b7a75cf52bf6a208f29077ecf/pluggy-0.13.0.tar.gz";
1200 url = "https://files.pythonhosted.org/packages/d7/9d/ae82a5facf2dd89f557a33ad18eb68e5ac7b7a75cf52bf6a208f29077ecf/pluggy-0.13.0.tar.gz";
1201 sha256 = "0d4gsvb4kjqhiqqi4bbsdp7s1xlyl5phibcw1q1mrpd65xia2pzs";
1201 sha256 = "0d4gsvb4kjqhiqqi4bbsdp7s1xlyl5phibcw1q1mrpd65xia2pzs";
1202 };
1202 };
1203 meta = {
1203 meta = {
1204 license = [ pkgs.lib.licenses.mit ];
1204 license = [ pkgs.lib.licenses.mit ];
1205 };
1205 };
1206 };
1206 };
1207 "prompt-toolkit" = super.buildPythonPackage {
1207 "prompt-toolkit" = super.buildPythonPackage {
1208 name = "prompt-toolkit-1.0.18";
1208 name = "prompt-toolkit-1.0.18";
1209 doCheck = false;
1209 doCheck = false;
1210 propagatedBuildInputs = [
1210 propagatedBuildInputs = [
1211 self."six"
1211 self."six"
1212 self."wcwidth"
1212 self."wcwidth"
1213 ];
1213 ];
1214 src = fetchurl {
1214 src = fetchurl {
1215 url = "https://files.pythonhosted.org/packages/c5/64/c170e5b1913b540bf0c8ab7676b21fdd1d25b65ddeb10025c6ca43cccd4c/prompt_toolkit-1.0.18.tar.gz";
1215 url = "https://files.pythonhosted.org/packages/c5/64/c170e5b1913b540bf0c8ab7676b21fdd1d25b65ddeb10025c6ca43cccd4c/prompt_toolkit-1.0.18.tar.gz";
1216 sha256 = "09h1153wgr5x2ny7ds0w2m81n3bb9j8hjb8sjfnrg506r01clkyx";
1216 sha256 = "09h1153wgr5x2ny7ds0w2m81n3bb9j8hjb8sjfnrg506r01clkyx";
1217 };
1217 };
1218 meta = {
1218 meta = {
1219 license = [ pkgs.lib.licenses.bsdOriginal ];
1219 license = [ pkgs.lib.licenses.bsdOriginal ];
1220 };
1220 };
1221 };
1221 };
1222 "psutil" = super.buildPythonPackage {
1222 "psutil" = super.buildPythonPackage {
1223 name = "psutil-5.6.5";
1223 name = "psutil-5.6.5";
1224 doCheck = false;
1224 doCheck = false;
1225 src = fetchurl {
1225 src = fetchurl {
1226 url = "https://files.pythonhosted.org/packages/03/9a/95c4b3d0424426e5fd94b5302ff74cea44d5d4f53466e1228ac8e73e14b4/psutil-5.6.5.tar.gz";
1226 url = "https://files.pythonhosted.org/packages/03/9a/95c4b3d0424426e5fd94b5302ff74cea44d5d4f53466e1228ac8e73e14b4/psutil-5.6.5.tar.gz";
1227 sha256 = "0isil5jxwwd8awz54qk28rpgjg43i5l6yl70g40vxwa4r4m56lfh";
1227 sha256 = "0isil5jxwwd8awz54qk28rpgjg43i5l6yl70g40vxwa4r4m56lfh";
1228 };
1228 };
1229 meta = {
1229 meta = {
1230 license = [ pkgs.lib.licenses.bsdOriginal ];
1230 license = [ pkgs.lib.licenses.bsdOriginal ];
1231 };
1231 };
1232 };
1232 };
1233 "psycopg2" = super.buildPythonPackage {
1233 "psycopg2" = super.buildPythonPackage {
1234 name = "psycopg2-2.8.4";
1234 name = "psycopg2-2.8.4";
1235 doCheck = false;
1235 doCheck = false;
1236 src = fetchurl {
1236 src = fetchurl {
1237 url = "https://files.pythonhosted.org/packages/84/d7/6a93c99b5ba4d4d22daa3928b983cec66df4536ca50b22ce5dcac65e4e71/psycopg2-2.8.4.tar.gz";
1237 url = "https://files.pythonhosted.org/packages/84/d7/6a93c99b5ba4d4d22daa3928b983cec66df4536ca50b22ce5dcac65e4e71/psycopg2-2.8.4.tar.gz";
1238 sha256 = "1djvh98pi4hjd8rxbq8qzc63bg8v78k33yg6pl99wak61b6fb67q";
1238 sha256 = "1djvh98pi4hjd8rxbq8qzc63bg8v78k33yg6pl99wak61b6fb67q";
1239 };
1239 };
1240 meta = {
1240 meta = {
1241 license = [ pkgs.lib.licenses.zpl21 { fullName = "GNU Library or Lesser General Public License (LGPL)"; } { fullName = "LGPL with exceptions or ZPL"; } ];
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 "ptyprocess" = super.buildPythonPackage {
1244 "ptyprocess" = super.buildPythonPackage {
1245 name = "ptyprocess-0.6.0";
1245 name = "ptyprocess-0.6.0";
1246 doCheck = false;
1246 doCheck = false;
1247 src = fetchurl {
1247 src = fetchurl {
1248 url = "https://files.pythonhosted.org/packages/7d/2d/e4b8733cf79b7309d84c9081a4ab558c89d8c89da5961bf4ddb050ca1ce0/ptyprocess-0.6.0.tar.gz";
1248 url = "https://files.pythonhosted.org/packages/7d/2d/e4b8733cf79b7309d84c9081a4ab558c89d8c89da5961bf4ddb050ca1ce0/ptyprocess-0.6.0.tar.gz";
1249 sha256 = "1h4lcd3w5nrxnsk436ar7fwkiy5rfn5wj2xwy9l0r4mdqnf2jgwj";
1249 sha256 = "1h4lcd3w5nrxnsk436ar7fwkiy5rfn5wj2xwy9l0r4mdqnf2jgwj";
1250 };
1250 };
1251 meta = {
1251 meta = {
1252 license = [ ];
1252 license = [ ];
1253 };
1253 };
1254 };
1254 };
1255 "py" = super.buildPythonPackage {
1255 "py" = super.buildPythonPackage {
1256 name = "py-1.8.0";
1256 name = "py-1.8.0";
1257 doCheck = false;
1257 doCheck = false;
1258 src = fetchurl {
1258 src = fetchurl {
1259 url = "https://files.pythonhosted.org/packages/f1/5a/87ca5909f400a2de1561f1648883af74345fe96349f34f737cdfc94eba8c/py-1.8.0.tar.gz";
1259 url = "https://files.pythonhosted.org/packages/f1/5a/87ca5909f400a2de1561f1648883af74345fe96349f34f737cdfc94eba8c/py-1.8.0.tar.gz";
1260 sha256 = "0lsy1gajva083pzc7csj1cvbmminb7b4l6a0prdzyb3fd829nqyw";
1260 sha256 = "0lsy1gajva083pzc7csj1cvbmminb7b4l6a0prdzyb3fd829nqyw";
1261 };
1261 };
1262 meta = {
1262 meta = {
1263 license = [ pkgs.lib.licenses.mit ];
1263 license = [ pkgs.lib.licenses.mit ];
1264 };
1264 };
1265 };
1265 };
1266 "py-bcrypt" = super.buildPythonPackage {
1266 "py-bcrypt" = super.buildPythonPackage {
1267 name = "py-bcrypt-0.4";
1267 name = "py-bcrypt-0.4";
1268 doCheck = false;
1268 doCheck = false;
1269 src = fetchurl {
1269 src = fetchurl {
1270 url = "https://files.pythonhosted.org/packages/68/b1/1c3068c5c4d2e35c48b38dcc865301ebfdf45f54507086ac65ced1fd3b3d/py-bcrypt-0.4.tar.gz";
1270 url = "https://files.pythonhosted.org/packages/68/b1/1c3068c5c4d2e35c48b38dcc865301ebfdf45f54507086ac65ced1fd3b3d/py-bcrypt-0.4.tar.gz";
1271 sha256 = "0y6smdggwi5s72v6p1nn53dg6w05hna3d264cq6kas0lap73p8az";
1271 sha256 = "0y6smdggwi5s72v6p1nn53dg6w05hna3d264cq6kas0lap73p8az";
1272 };
1272 };
1273 meta = {
1273 meta = {
1274 license = [ pkgs.lib.licenses.bsdOriginal ];
1274 license = [ pkgs.lib.licenses.bsdOriginal ];
1275 };
1275 };
1276 };
1276 };
1277 "py-gfm" = super.buildPythonPackage {
1277 "py-gfm" = super.buildPythonPackage {
1278 name = "py-gfm-0.1.4";
1278 name = "py-gfm-0.1.4";
1279 doCheck = false;
1279 doCheck = false;
1280 propagatedBuildInputs = [
1280 propagatedBuildInputs = [
1281 self."setuptools"
1281 self."setuptools"
1282 self."markdown"
1282 self."markdown"
1283 ];
1283 ];
1284 src = fetchurl {
1284 src = fetchurl {
1285 url = "https://files.pythonhosted.org/packages/06/ee/004a03a1d92bb386dae44f6dd087db541bc5093374f1637d4d4ae5596cc2/py-gfm-0.1.4.tar.gz";
1285 url = "https://files.pythonhosted.org/packages/06/ee/004a03a1d92bb386dae44f6dd087db541bc5093374f1637d4d4ae5596cc2/py-gfm-0.1.4.tar.gz";
1286 sha256 = "0zip06g2isivx8fzgqd4n9qzsa22c25jas1rsb7m2rnjg72m0rzg";
1286 sha256 = "0zip06g2isivx8fzgqd4n9qzsa22c25jas1rsb7m2rnjg72m0rzg";
1287 };
1287 };
1288 meta = {
1288 meta = {
1289 license = [ pkgs.lib.licenses.bsdOriginal ];
1289 license = [ pkgs.lib.licenses.bsdOriginal ];
1290 };
1290 };
1291 };
1291 };
1292 "pyasn1" = super.buildPythonPackage {
1292 "pyasn1" = super.buildPythonPackage {
1293 name = "pyasn1-0.4.7";
1293 name = "pyasn1-0.4.7";
1294 doCheck = false;
1294 doCheck = false;
1295 src = fetchurl {
1295 src = fetchurl {
1296 url = "https://files.pythonhosted.org/packages/ca/f8/2a60a2c88a97558bdd289b6dc9eb75b00bd90ff34155d681ba6dbbcb46b2/pyasn1-0.4.7.tar.gz";
1296 url = "https://files.pythonhosted.org/packages/ca/f8/2a60a2c88a97558bdd289b6dc9eb75b00bd90ff34155d681ba6dbbcb46b2/pyasn1-0.4.7.tar.gz";
1297 sha256 = "0146ryp4g09ycy8p3l2vigmgfg42n4gb8whgg8cysrhxr9b56jd9";
1297 sha256 = "0146ryp4g09ycy8p3l2vigmgfg42n4gb8whgg8cysrhxr9b56jd9";
1298 };
1298 };
1299 meta = {
1299 meta = {
1300 license = [ pkgs.lib.licenses.bsdOriginal ];
1300 license = [ pkgs.lib.licenses.bsdOriginal ];
1301 };
1301 };
1302 };
1302 };
1303 "pyasn1-modules" = super.buildPythonPackage {
1303 "pyasn1-modules" = super.buildPythonPackage {
1304 name = "pyasn1-modules-0.2.6";
1304 name = "pyasn1-modules-0.2.6";
1305 doCheck = false;
1305 doCheck = false;
1306 propagatedBuildInputs = [
1306 propagatedBuildInputs = [
1307 self."pyasn1"
1307 self."pyasn1"
1308 ];
1308 ];
1309 src = fetchurl {
1309 src = fetchurl {
1310 url = "https://files.pythonhosted.org/packages/f1/a9/a1ef72a0e43feff643cf0130a08123dea76205e7a0dda37e3efb5f054a31/pyasn1-modules-0.2.6.tar.gz";
1310 url = "https://files.pythonhosted.org/packages/f1/a9/a1ef72a0e43feff643cf0130a08123dea76205e7a0dda37e3efb5f054a31/pyasn1-modules-0.2.6.tar.gz";
1311 sha256 = "08hph9j1r018drnrny29l7dl2q0cin78csswrhwrh8jmq61pmha3";
1311 sha256 = "08hph9j1r018drnrny29l7dl2q0cin78csswrhwrh8jmq61pmha3";
1312 };
1312 };
1313 meta = {
1313 meta = {
1314 license = [ pkgs.lib.licenses.bsdOriginal pkgs.lib.licenses.bsd2 ];
1314 license = [ pkgs.lib.licenses.bsdOriginal pkgs.lib.licenses.bsd2 ];
1315 };
1315 };
1316 };
1316 };
1317 "pycparser" = super.buildPythonPackage {
1317 "pycparser" = super.buildPythonPackage {
1318 name = "pycparser-2.19";
1318 name = "pycparser-2.19";
1319 doCheck = false;
1319 doCheck = false;
1320 src = fetchurl {
1320 src = fetchurl {
1321 url = "https://files.pythonhosted.org/packages/68/9e/49196946aee219aead1290e00d1e7fdeab8567783e83e1b9ab5585e6206a/pycparser-2.19.tar.gz";
1321 url = "https://files.pythonhosted.org/packages/68/9e/49196946aee219aead1290e00d1e7fdeab8567783e83e1b9ab5585e6206a/pycparser-2.19.tar.gz";
1322 sha256 = "1cr5dcj9628lkz1qlwq3fv97c25363qppkmcayqvd05dpy573259";
1322 sha256 = "1cr5dcj9628lkz1qlwq3fv97c25363qppkmcayqvd05dpy573259";
1323 };
1323 };
1324 meta = {
1324 meta = {
1325 license = [ pkgs.lib.licenses.bsdOriginal ];
1325 license = [ pkgs.lib.licenses.bsdOriginal ];
1326 };
1326 };
1327 };
1327 };
1328 "pycrypto" = super.buildPythonPackage {
1328 "pycrypto" = super.buildPythonPackage {
1329 name = "pycrypto-2.6.1";
1329 name = "pycrypto-2.6.1";
1330 doCheck = false;
1330 doCheck = false;
1331 src = fetchurl {
1331 src = fetchurl {
1332 url = "https://files.pythonhosted.org/packages/60/db/645aa9af249f059cc3a368b118de33889219e0362141e75d4eaf6f80f163/pycrypto-2.6.1.tar.gz";
1332 url = "https://files.pythonhosted.org/packages/60/db/645aa9af249f059cc3a368b118de33889219e0362141e75d4eaf6f80f163/pycrypto-2.6.1.tar.gz";
1333 sha256 = "0g0ayql5b9mkjam8hym6zyg6bv77lbh66rv1fyvgqb17kfc1xkpj";
1333 sha256 = "0g0ayql5b9mkjam8hym6zyg6bv77lbh66rv1fyvgqb17kfc1xkpj";
1334 };
1334 };
1335 meta = {
1335 meta = {
1336 license = [ pkgs.lib.licenses.publicDomain ];
1336 license = [ pkgs.lib.licenses.publicDomain ];
1337 };
1337 };
1338 };
1338 };
1339 "pycurl" = super.buildPythonPackage {
1339 "pycurl" = super.buildPythonPackage {
1340 name = "pycurl-7.43.0.3";
1340 name = "pycurl-7.43.0.3";
1341 doCheck = false;
1341 doCheck = false;
1342 src = fetchurl {
1342 src = fetchurl {
1343 url = "https://files.pythonhosted.org/packages/ac/b3/0f3979633b7890bab6098d84c84467030b807a1e2b31f5d30103af5a71ca/pycurl-7.43.0.3.tar.gz";
1343 url = "https://files.pythonhosted.org/packages/ac/b3/0f3979633b7890bab6098d84c84467030b807a1e2b31f5d30103af5a71ca/pycurl-7.43.0.3.tar.gz";
1344 sha256 = "13nsvqhvnmnvfk75s8iynqsgszyv06cjp4drd3psi7zpbh63623g";
1344 sha256 = "13nsvqhvnmnvfk75s8iynqsgszyv06cjp4drd3psi7zpbh63623g";
1345 };
1345 };
1346 meta = {
1346 meta = {
1347 license = [ pkgs.lib.licenses.mit { fullName = "LGPL/MIT"; } { fullName = "GNU Library or Lesser General Public License (LGPL)"; } ];
1347 license = [ pkgs.lib.licenses.mit { fullName = "LGPL/MIT"; } { fullName = "GNU Library or Lesser General Public License (LGPL)"; } ];
1348 };
1348 };
1349 };
1349 };
1350 "pygments" = super.buildPythonPackage {
1350 "pygments" = super.buildPythonPackage {
1351 name = "pygments-2.4.2";
1351 name = "pygments-2.4.2";
1352 doCheck = false;
1352 doCheck = false;
1353 src = fetchurl {
1353 src = fetchurl {
1354 url = "https://files.pythonhosted.org/packages/7e/ae/26808275fc76bf2832deb10d3a3ed3107bc4de01b85dcccbe525f2cd6d1e/Pygments-2.4.2.tar.gz";
1354 url = "https://files.pythonhosted.org/packages/7e/ae/26808275fc76bf2832deb10d3a3ed3107bc4de01b85dcccbe525f2cd6d1e/Pygments-2.4.2.tar.gz";
1355 sha256 = "15v2sqm5g12bqa0c7wikfh9ck2nl97ayizy1hpqhmws5gqalq748";
1355 sha256 = "15v2sqm5g12bqa0c7wikfh9ck2nl97ayizy1hpqhmws5gqalq748";
1356 };
1356 };
1357 meta = {
1357 meta = {
1358 license = [ pkgs.lib.licenses.bsdOriginal ];
1358 license = [ pkgs.lib.licenses.bsdOriginal ];
1359 };
1359 };
1360 };
1360 };
1361 "pymysql" = super.buildPythonPackage {
1361 "pymysql" = super.buildPythonPackage {
1362 name = "pymysql-0.8.1";
1362 name = "pymysql-0.8.1";
1363 doCheck = false;
1363 doCheck = false;
1364 src = fetchurl {
1364 src = fetchurl {
1365 url = "https://files.pythonhosted.org/packages/44/39/6bcb83cae0095a31b6be4511707fdf2009d3e29903a55a0494d3a9a2fac0/PyMySQL-0.8.1.tar.gz";
1365 url = "https://files.pythonhosted.org/packages/44/39/6bcb83cae0095a31b6be4511707fdf2009d3e29903a55a0494d3a9a2fac0/PyMySQL-0.8.1.tar.gz";
1366 sha256 = "0a96crz55bw4h6myh833skrli7b0ck89m3x673y2z2ryy7zrpq9l";
1366 sha256 = "0a96crz55bw4h6myh833skrli7b0ck89m3x673y2z2ryy7zrpq9l";
1367 };
1367 };
1368 meta = {
1368 meta = {
1369 license = [ pkgs.lib.licenses.mit ];
1369 license = [ pkgs.lib.licenses.mit ];
1370 };
1370 };
1371 };
1371 };
1372 "pyotp" = super.buildPythonPackage {
1372 "pyotp" = super.buildPythonPackage {
1373 name = "pyotp-2.3.0";
1373 name = "pyotp-2.3.0";
1374 doCheck = false;
1374 doCheck = false;
1375 src = fetchurl {
1375 src = fetchurl {
1376 url = "https://files.pythonhosted.org/packages/f7/15/395c4945ea6bc37e8811280bb675615cb4c2b2c1cd70bdc43329da91a386/pyotp-2.3.0.tar.gz";
1376 url = "https://files.pythonhosted.org/packages/f7/15/395c4945ea6bc37e8811280bb675615cb4c2b2c1cd70bdc43329da91a386/pyotp-2.3.0.tar.gz";
1377 sha256 = "18d13ikra1iq0xyfqfm72zhgwxi2qi9ps6z1a6zmqp4qrn57wlzw";
1377 sha256 = "18d13ikra1iq0xyfqfm72zhgwxi2qi9ps6z1a6zmqp4qrn57wlzw";
1378 };
1378 };
1379 meta = {
1379 meta = {
1380 license = [ pkgs.lib.licenses.mit ];
1380 license = [ pkgs.lib.licenses.mit ];
1381 };
1381 };
1382 };
1382 };
1383 "pyparsing" = super.buildPythonPackage {
1383 "pyparsing" = super.buildPythonPackage {
1384 name = "pyparsing-2.4.2";
1384 name = "pyparsing-2.4.2";
1385 doCheck = false;
1385 doCheck = false;
1386 src = fetchurl {
1386 src = fetchurl {
1387 url = "https://files.pythonhosted.org/packages/7e/24/eaa8d7003aee23eda270099eeec754d7bf4399f75c6a011ef948304f66a2/pyparsing-2.4.2.tar.gz";
1387 url = "https://files.pythonhosted.org/packages/7e/24/eaa8d7003aee23eda270099eeec754d7bf4399f75c6a011ef948304f66a2/pyparsing-2.4.2.tar.gz";
1388 sha256 = "106xmhd2xn5bv6blsbqaa6wciwcq2c9i1pq1riw6s83y76wsg63g";
1388 sha256 = "106xmhd2xn5bv6blsbqaa6wciwcq2c9i1pq1riw6s83y76wsg63g";
1389 };
1389 };
1390 meta = {
1390 meta = {
1391 license = [ pkgs.lib.licenses.mit ];
1391 license = [ pkgs.lib.licenses.mit ];
1392 };
1392 };
1393 };
1393 };
1394 "pyramid" = super.buildPythonPackage {
1394 "pyramid" = super.buildPythonPackage {
1395 name = "pyramid-1.10.4";
1395 name = "pyramid-1.10.4";
1396 doCheck = false;
1396 doCheck = false;
1397 propagatedBuildInputs = [
1397 propagatedBuildInputs = [
1398 self."hupper"
1398 self."hupper"
1399 self."plaster"
1399 self."plaster"
1400 self."plaster-pastedeploy"
1400 self."plaster-pastedeploy"
1401 self."setuptools"
1401 self."setuptools"
1402 self."translationstring"
1402 self."translationstring"
1403 self."venusian"
1403 self."venusian"
1404 self."webob"
1404 self."webob"
1405 self."zope.deprecation"
1405 self."zope.deprecation"
1406 self."zope.interface"
1406 self."zope.interface"
1407 self."repoze.lru"
1407 self."repoze.lru"
1408 ];
1408 ];
1409 src = fetchurl {
1409 src = fetchurl {
1410 url = "https://files.pythonhosted.org/packages/c2/43/1ae701c9c6bb3a434358e678a5e72c96e8aa55cf4cb1d2fa2041b5dd38b7/pyramid-1.10.4.tar.gz";
1410 url = "https://files.pythonhosted.org/packages/c2/43/1ae701c9c6bb3a434358e678a5e72c96e8aa55cf4cb1d2fa2041b5dd38b7/pyramid-1.10.4.tar.gz";
1411 sha256 = "0rkxs1ajycg2zh1c94xlmls56mx5m161sn8112skj0amza6cn36q";
1411 sha256 = "0rkxs1ajycg2zh1c94xlmls56mx5m161sn8112skj0amza6cn36q";
1412 };
1412 };
1413 meta = {
1413 meta = {
1414 license = [ { fullName = "Repoze Public License"; } { fullName = "BSD-derived (http://www.repoze.org/LICENSE.txt)"; } ];
1414 license = [ { fullName = "Repoze Public License"; } { fullName = "BSD-derived (http://www.repoze.org/LICENSE.txt)"; } ];
1415 };
1415 };
1416 };
1416 };
1417 "pyramid-debugtoolbar" = super.buildPythonPackage {
1417 "pyramid-debugtoolbar" = super.buildPythonPackage {
1418 name = "pyramid-debugtoolbar-4.5.1";
1418 name = "pyramid-debugtoolbar-4.5.1";
1419 doCheck = false;
1419 doCheck = false;
1420 propagatedBuildInputs = [
1420 propagatedBuildInputs = [
1421 self."pyramid"
1421 self."pyramid"
1422 self."pyramid-mako"
1422 self."pyramid-mako"
1423 self."repoze.lru"
1423 self."repoze.lru"
1424 self."pygments"
1424 self."pygments"
1425 self."ipaddress"
1425 self."ipaddress"
1426 ];
1426 ];
1427 src = fetchurl {
1427 src = fetchurl {
1428 url = "https://files.pythonhosted.org/packages/88/21/74e7fa52edc74667e29403bd0cb4f2bb74dc4014711de313868001bf639f/pyramid_debugtoolbar-4.5.1.tar.gz";
1428 url = "https://files.pythonhosted.org/packages/88/21/74e7fa52edc74667e29403bd0cb4f2bb74dc4014711de313868001bf639f/pyramid_debugtoolbar-4.5.1.tar.gz";
1429 sha256 = "0hgf6i1fzvq43m9vjdmb24nnv8fwp7sdzrx9bcwrgpy24n07am9a";
1429 sha256 = "0hgf6i1fzvq43m9vjdmb24nnv8fwp7sdzrx9bcwrgpy24n07am9a";
1430 };
1430 };
1431 meta = {
1431 meta = {
1432 license = [ { fullName = "Repoze Public License"; } pkgs.lib.licenses.bsdOriginal ];
1432 license = [ { fullName = "Repoze Public License"; } pkgs.lib.licenses.bsdOriginal ];
1433 };
1433 };
1434 };
1434 };
1435 "pyramid-jinja2" = super.buildPythonPackage {
1435 "pyramid-jinja2" = super.buildPythonPackage {
1436 name = "pyramid-jinja2-2.7";
1436 name = "pyramid-jinja2-2.7";
1437 doCheck = false;
1437 doCheck = false;
1438 propagatedBuildInputs = [
1438 propagatedBuildInputs = [
1439 self."pyramid"
1439 self."pyramid"
1440 self."zope.deprecation"
1440 self."zope.deprecation"
1441 self."jinja2"
1441 self."jinja2"
1442 self."markupsafe"
1442 self."markupsafe"
1443 ];
1443 ];
1444 src = fetchurl {
1444 src = fetchurl {
1445 url = "https://files.pythonhosted.org/packages/d8/80/d60a7233823de22ce77bd864a8a83736a1fe8b49884b08303a2e68b2c853/pyramid_jinja2-2.7.tar.gz";
1445 url = "https://files.pythonhosted.org/packages/d8/80/d60a7233823de22ce77bd864a8a83736a1fe8b49884b08303a2e68b2c853/pyramid_jinja2-2.7.tar.gz";
1446 sha256 = "1sz5s0pp5jqhf4w22w9527yz8hgdi4mhr6apd6vw1gm5clghh8aw";
1446 sha256 = "1sz5s0pp5jqhf4w22w9527yz8hgdi4mhr6apd6vw1gm5clghh8aw";
1447 };
1447 };
1448 meta = {
1448 meta = {
1449 license = [ { fullName = "Repoze Public License"; } { fullName = "BSD-derived (http://www.repoze.org/LICENSE.txt)"; } ];
1449 license = [ { fullName = "Repoze Public License"; } { fullName = "BSD-derived (http://www.repoze.org/LICENSE.txt)"; } ];
1450 };
1450 };
1451 };
1451 };
1452 "pyramid-mailer" = super.buildPythonPackage {
1452 "pyramid-mailer" = super.buildPythonPackage {
1453 name = "pyramid-mailer-0.15.1";
1453 name = "pyramid-mailer-0.15.1";
1454 doCheck = false;
1454 doCheck = false;
1455 propagatedBuildInputs = [
1455 propagatedBuildInputs = [
1456 self."pyramid"
1456 self."pyramid"
1457 self."repoze.sendmail"
1457 self."repoze.sendmail"
1458 self."transaction"
1458 self."transaction"
1459 ];
1459 ];
1460 src = fetchurl {
1460 src = fetchurl {
1461 url = "https://files.pythonhosted.org/packages/a0/f2/6febf5459dff4d7e653314d575469ad2e11b9d2af2c3606360e1c67202f2/pyramid_mailer-0.15.1.tar.gz";
1461 url = "https://files.pythonhosted.org/packages/a0/f2/6febf5459dff4d7e653314d575469ad2e11b9d2af2c3606360e1c67202f2/pyramid_mailer-0.15.1.tar.gz";
1462 sha256 = "16vg8jb203jgb7b0hd6wllfqvp542qh2ry1gjai2m6qpv5agy2pc";
1462 sha256 = "16vg8jb203jgb7b0hd6wllfqvp542qh2ry1gjai2m6qpv5agy2pc";
1463 };
1463 };
1464 meta = {
1464 meta = {
1465 license = [ pkgs.lib.licenses.bsdOriginal ];
1465 license = [ pkgs.lib.licenses.bsdOriginal ];
1466 };
1466 };
1467 };
1467 };
1468 "pyramid-mako" = super.buildPythonPackage {
1468 "pyramid-mako" = super.buildPythonPackage {
1469 name = "pyramid-mako-1.1.0";
1469 name = "pyramid-mako-1.1.0";
1470 doCheck = false;
1470 doCheck = false;
1471 propagatedBuildInputs = [
1471 propagatedBuildInputs = [
1472 self."pyramid"
1472 self."pyramid"
1473 self."mako"
1473 self."mako"
1474 ];
1474 ];
1475 src = fetchurl {
1475 src = fetchurl {
1476 url = "https://files.pythonhosted.org/packages/63/7b/5e2af68f675071a6bad148c1c393928f0ef5fcd94e95cbf53b89d6471a83/pyramid_mako-1.1.0.tar.gz";
1476 url = "https://files.pythonhosted.org/packages/63/7b/5e2af68f675071a6bad148c1c393928f0ef5fcd94e95cbf53b89d6471a83/pyramid_mako-1.1.0.tar.gz";
1477 sha256 = "1qj0m091mnii86j2q1d82yir22nha361rvhclvg3s70z8iiwhrh0";
1477 sha256 = "1qj0m091mnii86j2q1d82yir22nha361rvhclvg3s70z8iiwhrh0";
1478 };
1478 };
1479 meta = {
1479 meta = {
1480 license = [ { fullName = "Repoze Public License"; } { fullName = "BSD-derived (http://www.repoze.org/LICENSE.txt)"; } ];
1480 license = [ { fullName = "Repoze Public License"; } { fullName = "BSD-derived (http://www.repoze.org/LICENSE.txt)"; } ];
1481 };
1481 };
1482 };
1482 };
1483 "pysqlite" = super.buildPythonPackage {
1483 "pysqlite" = super.buildPythonPackage {
1484 name = "pysqlite-2.8.3";
1484 name = "pysqlite-2.8.3";
1485 doCheck = false;
1485 doCheck = false;
1486 src = fetchurl {
1486 src = fetchurl {
1487 url = "https://files.pythonhosted.org/packages/42/02/981b6703e3c83c5b25a829c6e77aad059f9481b0bbacb47e6e8ca12bd731/pysqlite-2.8.3.tar.gz";
1487 url = "https://files.pythonhosted.org/packages/42/02/981b6703e3c83c5b25a829c6e77aad059f9481b0bbacb47e6e8ca12bd731/pysqlite-2.8.3.tar.gz";
1488 sha256 = "1424gwq9sil2ffmnizk60q36vydkv8rxs6m7xs987kz8cdc37lqp";
1488 sha256 = "1424gwq9sil2ffmnizk60q36vydkv8rxs6m7xs987kz8cdc37lqp";
1489 };
1489 };
1490 meta = {
1490 meta = {
1491 license = [ { fullName = "zlib/libpng License"; } { fullName = "zlib/libpng license"; } ];
1491 license = [ { fullName = "zlib/libpng License"; } { fullName = "zlib/libpng license"; } ];
1492 };
1492 };
1493 };
1493 };
1494 "pytest" = super.buildPythonPackage {
1494 "pytest" = super.buildPythonPackage {
1495 name = "pytest-4.6.5";
1495 name = "pytest-4.6.5";
1496 doCheck = false;
1496 doCheck = false;
1497 propagatedBuildInputs = [
1497 propagatedBuildInputs = [
1498 self."py"
1498 self."py"
1499 self."six"
1499 self."six"
1500 self."packaging"
1500 self."packaging"
1501 self."attrs"
1501 self."attrs"
1502 self."atomicwrites"
1502 self."atomicwrites"
1503 self."pluggy"
1503 self."pluggy"
1504 self."importlib-metadata"
1504 self."importlib-metadata"
1505 self."wcwidth"
1505 self."wcwidth"
1506 self."funcsigs"
1506 self."funcsigs"
1507 self."pathlib2"
1507 self."pathlib2"
1508 self."more-itertools"
1508 self."more-itertools"
1509 ];
1509 ];
1510 src = fetchurl {
1510 src = fetchurl {
1511 url = "https://files.pythonhosted.org/packages/2a/c6/1d1f32f6a5009900521b12e6560fb6b7245b0d4bc3fb771acd63d10e30e1/pytest-4.6.5.tar.gz";
1511 url = "https://files.pythonhosted.org/packages/2a/c6/1d1f32f6a5009900521b12e6560fb6b7245b0d4bc3fb771acd63d10e30e1/pytest-4.6.5.tar.gz";
1512 sha256 = "0iykwwfp4h181nd7rsihh2120b0rkawlw7rvbl19sgfspncr3hwg";
1512 sha256 = "0iykwwfp4h181nd7rsihh2120b0rkawlw7rvbl19sgfspncr3hwg";
1513 };
1513 };
1514 meta = {
1514 meta = {
1515 license = [ pkgs.lib.licenses.mit ];
1515 license = [ pkgs.lib.licenses.mit ];
1516 };
1516 };
1517 };
1517 };
1518 "pytest-cov" = super.buildPythonPackage {
1518 "pytest-cov" = super.buildPythonPackage {
1519 name = "pytest-cov-2.7.1";
1519 name = "pytest-cov-2.7.1";
1520 doCheck = false;
1520 doCheck = false;
1521 propagatedBuildInputs = [
1521 propagatedBuildInputs = [
1522 self."pytest"
1522 self."pytest"
1523 self."coverage"
1523 self."coverage"
1524 ];
1524 ];
1525 src = fetchurl {
1525 src = fetchurl {
1526 url = "https://files.pythonhosted.org/packages/bb/0f/3db7ff86801883b21d5353b258c994b1b8e2abbc804e2273b8d0fd19004b/pytest-cov-2.7.1.tar.gz";
1526 url = "https://files.pythonhosted.org/packages/bb/0f/3db7ff86801883b21d5353b258c994b1b8e2abbc804e2273b8d0fd19004b/pytest-cov-2.7.1.tar.gz";
1527 sha256 = "0filvmmyqm715azsl09ql8hy2x7h286n6d8z5x42a1wpvvys83p0";
1527 sha256 = "0filvmmyqm715azsl09ql8hy2x7h286n6d8z5x42a1wpvvys83p0";
1528 };
1528 };
1529 meta = {
1529 meta = {
1530 license = [ pkgs.lib.licenses.bsdOriginal pkgs.lib.licenses.mit ];
1530 license = [ pkgs.lib.licenses.bsdOriginal pkgs.lib.licenses.mit ];
1531 };
1531 };
1532 };
1532 };
1533 "pytest-profiling" = super.buildPythonPackage {
1533 "pytest-profiling" = super.buildPythonPackage {
1534 name = "pytest-profiling-1.7.0";
1534 name = "pytest-profiling-1.7.0";
1535 doCheck = false;
1535 doCheck = false;
1536 propagatedBuildInputs = [
1536 propagatedBuildInputs = [
1537 self."six"
1537 self."six"
1538 self."pytest"
1538 self."pytest"
1539 self."gprof2dot"
1539 self."gprof2dot"
1540 ];
1540 ];
1541 src = fetchurl {
1541 src = fetchurl {
1542 url = "https://files.pythonhosted.org/packages/39/70/22a4b33739f07f1732a63e33bbfbf68e0fa58cfba9d200e76d01921eddbf/pytest-profiling-1.7.0.tar.gz";
1542 url = "https://files.pythonhosted.org/packages/39/70/22a4b33739f07f1732a63e33bbfbf68e0fa58cfba9d200e76d01921eddbf/pytest-profiling-1.7.0.tar.gz";
1543 sha256 = "0abz9gi26jpcfdzgsvwad91555lpgdc8kbymicmms8k2fqa8z4wk";
1543 sha256 = "0abz9gi26jpcfdzgsvwad91555lpgdc8kbymicmms8k2fqa8z4wk";
1544 };
1544 };
1545 meta = {
1545 meta = {
1546 license = [ pkgs.lib.licenses.mit ];
1546 license = [ pkgs.lib.licenses.mit ];
1547 };
1547 };
1548 };
1548 };
1549 "pytest-runner" = super.buildPythonPackage {
1549 "pytest-runner" = super.buildPythonPackage {
1550 name = "pytest-runner-5.1";
1550 name = "pytest-runner-5.1";
1551 doCheck = false;
1551 doCheck = false;
1552 src = fetchurl {
1552 src = fetchurl {
1553 url = "https://files.pythonhosted.org/packages/d9/6d/4b41a74b31720e25abd4799be72d54811da4b4d0233e38b75864dcc1f7ad/pytest-runner-5.1.tar.gz";
1553 url = "https://files.pythonhosted.org/packages/d9/6d/4b41a74b31720e25abd4799be72d54811da4b4d0233e38b75864dcc1f7ad/pytest-runner-5.1.tar.gz";
1554 sha256 = "0ykfcnpp8c22winj63qzc07l5axwlc9ikl8vn05sc32gv3417815";
1554 sha256 = "0ykfcnpp8c22winj63qzc07l5axwlc9ikl8vn05sc32gv3417815";
1555 };
1555 };
1556 meta = {
1556 meta = {
1557 license = [ pkgs.lib.licenses.mit ];
1557 license = [ pkgs.lib.licenses.mit ];
1558 };
1558 };
1559 };
1559 };
1560 "pytest-sugar" = super.buildPythonPackage {
1560 "pytest-sugar" = super.buildPythonPackage {
1561 name = "pytest-sugar-0.9.2";
1561 name = "pytest-sugar-0.9.2";
1562 doCheck = false;
1562 doCheck = false;
1563 propagatedBuildInputs = [
1563 propagatedBuildInputs = [
1564 self."pytest"
1564 self."pytest"
1565 self."termcolor"
1565 self."termcolor"
1566 self."packaging"
1566 self."packaging"
1567 ];
1567 ];
1568 src = fetchurl {
1568 src = fetchurl {
1569 url = "https://files.pythonhosted.org/packages/55/59/f02f78d1c80f7e03e23177f60624c8106d4f23d124c921df103f65692464/pytest-sugar-0.9.2.tar.gz";
1569 url = "https://files.pythonhosted.org/packages/55/59/f02f78d1c80f7e03e23177f60624c8106d4f23d124c921df103f65692464/pytest-sugar-0.9.2.tar.gz";
1570 sha256 = "1asq7yc4g8bx2sn7yy974mhc9ywvaihasjab4inkirdwn9s7mn7w";
1570 sha256 = "1asq7yc4g8bx2sn7yy974mhc9ywvaihasjab4inkirdwn9s7mn7w";
1571 };
1571 };
1572 meta = {
1572 meta = {
1573 license = [ pkgs.lib.licenses.bsdOriginal ];
1573 license = [ pkgs.lib.licenses.bsdOriginal ];
1574 };
1574 };
1575 };
1575 };
1576 "pytest-timeout" = super.buildPythonPackage {
1576 "pytest-timeout" = super.buildPythonPackage {
1577 name = "pytest-timeout-1.3.3";
1577 name = "pytest-timeout-1.3.3";
1578 doCheck = false;
1578 doCheck = false;
1579 propagatedBuildInputs = [
1579 propagatedBuildInputs = [
1580 self."pytest"
1580 self."pytest"
1581 ];
1581 ];
1582 src = fetchurl {
1582 src = fetchurl {
1583 url = "https://files.pythonhosted.org/packages/13/48/7a166eaa29c1dca6cc253e3ba5773ff2e4aa4f567c1ea3905808e95ac5c1/pytest-timeout-1.3.3.tar.gz";
1583 url = "https://files.pythonhosted.org/packages/13/48/7a166eaa29c1dca6cc253e3ba5773ff2e4aa4f567c1ea3905808e95ac5c1/pytest-timeout-1.3.3.tar.gz";
1584 sha256 = "1cczcjhw4xx5sjkhxlhc5c1bkr7x6fcyx12wrnvwfckshdvblc2a";
1584 sha256 = "1cczcjhw4xx5sjkhxlhc5c1bkr7x6fcyx12wrnvwfckshdvblc2a";
1585 };
1585 };
1586 meta = {
1586 meta = {
1587 license = [ pkgs.lib.licenses.mit { fullName = "DFSG approved"; } ];
1587 license = [ pkgs.lib.licenses.mit { fullName = "DFSG approved"; } ];
1588 };
1588 };
1589 };
1589 };
1590 "python-dateutil" = super.buildPythonPackage {
1590 "python-dateutil" = super.buildPythonPackage {
1591 name = "python-dateutil-2.8.1";
1591 name = "python-dateutil-2.8.1";
1592 doCheck = false;
1592 doCheck = false;
1593 propagatedBuildInputs = [
1593 propagatedBuildInputs = [
1594 self."six"
1594 self."six"
1595 ];
1595 ];
1596 src = fetchurl {
1596 src = fetchurl {
1597 url = "https://files.pythonhosted.org/packages/be/ed/5bbc91f03fa4c839c4c7360375da77f9659af5f7086b7a7bdda65771c8e0/python-dateutil-2.8.1.tar.gz";
1597 url = "https://files.pythonhosted.org/packages/be/ed/5bbc91f03fa4c839c4c7360375da77f9659af5f7086b7a7bdda65771c8e0/python-dateutil-2.8.1.tar.gz";
1598 sha256 = "0g42w7k5007iv9dam6gnja2ry8ydwirh99mgdll35s12pyfzxsvk";
1598 sha256 = "0g42w7k5007iv9dam6gnja2ry8ydwirh99mgdll35s12pyfzxsvk";
1599 };
1599 };
1600 meta = {
1600 meta = {
1601 license = [ pkgs.lib.licenses.bsdOriginal pkgs.lib.licenses.asl20 { fullName = "Dual License"; } ];
1601 license = [ pkgs.lib.licenses.bsdOriginal pkgs.lib.licenses.asl20 { fullName = "Dual License"; } ];
1602 };
1602 };
1603 };
1603 };
1604 "python-editor" = super.buildPythonPackage {
1604 "python-editor" = super.buildPythonPackage {
1605 name = "python-editor-1.0.4";
1605 name = "python-editor-1.0.4";
1606 doCheck = false;
1606 doCheck = false;
1607 src = fetchurl {
1607 src = fetchurl {
1608 url = "https://files.pythonhosted.org/packages/0a/85/78f4a216d28343a67b7397c99825cff336330893f00601443f7c7b2f2234/python-editor-1.0.4.tar.gz";
1608 url = "https://files.pythonhosted.org/packages/0a/85/78f4a216d28343a67b7397c99825cff336330893f00601443f7c7b2f2234/python-editor-1.0.4.tar.gz";
1609 sha256 = "0yrjh8w72ivqxi4i7xsg5b1vz15x8fg51xra7c3bgfyxqnyadzai";
1609 sha256 = "0yrjh8w72ivqxi4i7xsg5b1vz15x8fg51xra7c3bgfyxqnyadzai";
1610 };
1610 };
1611 meta = {
1611 meta = {
1612 license = [ pkgs.lib.licenses.asl20 { fullName = "Apache"; } ];
1612 license = [ pkgs.lib.licenses.asl20 { fullName = "Apache"; } ];
1613 };
1613 };
1614 };
1614 };
1615 "python-ldap" = super.buildPythonPackage {
1615 "python-ldap" = super.buildPythonPackage {
1616 name = "python-ldap-3.1.0";
1616 name = "python-ldap-3.1.0";
1617 doCheck = false;
1617 doCheck = false;
1618 propagatedBuildInputs = [
1618 propagatedBuildInputs = [
1619 self."pyasn1"
1619 self."pyasn1"
1620 self."pyasn1-modules"
1620 self."pyasn1-modules"
1621 ];
1621 ];
1622 src = fetchurl {
1622 src = fetchurl {
1623 url = "https://files.pythonhosted.org/packages/7f/1c/28d721dff2fcd2fef9d55b40df63a00be26ec8a11e8c6fc612ae642f9cfd/python-ldap-3.1.0.tar.gz";
1623 url = "https://files.pythonhosted.org/packages/7f/1c/28d721dff2fcd2fef9d55b40df63a00be26ec8a11e8c6fc612ae642f9cfd/python-ldap-3.1.0.tar.gz";
1624 sha256 = "1i97nwfnraylyn0myxlf3vciicrf5h6fymrcff9c00k581wmx5s1";
1624 sha256 = "1i97nwfnraylyn0myxlf3vciicrf5h6fymrcff9c00k581wmx5s1";
1625 };
1625 };
1626 meta = {
1626 meta = {
1627 license = [ pkgs.lib.licenses.psfl ];
1627 license = [ pkgs.lib.licenses.psfl ];
1628 };
1628 };
1629 };
1629 };
1630 "python-memcached" = super.buildPythonPackage {
1630 "python-memcached" = super.buildPythonPackage {
1631 name = "python-memcached-1.59";
1631 name = "python-memcached-1.59";
1632 doCheck = false;
1632 doCheck = false;
1633 propagatedBuildInputs = [
1633 propagatedBuildInputs = [
1634 self."six"
1634 self."six"
1635 ];
1635 ];
1636 src = fetchurl {
1636 src = fetchurl {
1637 url = "https://files.pythonhosted.org/packages/90/59/5faf6e3cd8a568dd4f737ddae4f2e54204fd8c51f90bf8df99aca6c22318/python-memcached-1.59.tar.gz";
1637 url = "https://files.pythonhosted.org/packages/90/59/5faf6e3cd8a568dd4f737ddae4f2e54204fd8c51f90bf8df99aca6c22318/python-memcached-1.59.tar.gz";
1638 sha256 = "0kvyapavbirk2x3n1jx4yb9nyigrj1s3x15nm3qhpvhkpqvqdqm2";
1638 sha256 = "0kvyapavbirk2x3n1jx4yb9nyigrj1s3x15nm3qhpvhkpqvqdqm2";
1639 };
1639 };
1640 meta = {
1640 meta = {
1641 license = [ pkgs.lib.licenses.psfl ];
1641 license = [ pkgs.lib.licenses.psfl ];
1642 };
1642 };
1643 };
1643 };
1644 "python-pam" = super.buildPythonPackage {
1644 "python-pam" = super.buildPythonPackage {
1645 name = "python-pam-1.8.4";
1645 name = "python-pam-1.8.4";
1646 doCheck = false;
1646 doCheck = false;
1647 src = fetchurl {
1647 src = fetchurl {
1648 url = "https://files.pythonhosted.org/packages/01/16/544d01cae9f28e0292dbd092b6b8b0bf222b528f362ee768a5bed2140111/python-pam-1.8.4.tar.gz";
1648 url = "https://files.pythonhosted.org/packages/01/16/544d01cae9f28e0292dbd092b6b8b0bf222b528f362ee768a5bed2140111/python-pam-1.8.4.tar.gz";
1649 sha256 = "16whhc0vr7gxsbzvsnq65nq8fs3wwmx755cavm8kkczdkz4djmn8";
1649 sha256 = "16whhc0vr7gxsbzvsnq65nq8fs3wwmx755cavm8kkczdkz4djmn8";
1650 };
1650 };
1651 meta = {
1651 meta = {
1652 license = [ { fullName = "License :: OSI Approved :: MIT License"; } pkgs.lib.licenses.mit ];
1652 license = [ { fullName = "License :: OSI Approved :: MIT License"; } pkgs.lib.licenses.mit ];
1653 };
1653 };
1654 };
1654 };
1655 "python-saml" = super.buildPythonPackage {
1655 "python-saml" = super.buildPythonPackage {
1656 name = "python-saml-2.4.2";
1656 name = "python-saml-2.4.2";
1657 doCheck = false;
1657 doCheck = false;
1658 propagatedBuildInputs = [
1658 propagatedBuildInputs = [
1659 self."dm.xmlsec.binding"
1659 self."dm.xmlsec.binding"
1660 self."isodate"
1660 self."isodate"
1661 self."defusedxml"
1661 self."defusedxml"
1662 ];
1662 ];
1663 src = fetchurl {
1663 src = fetchurl {
1664 url = "https://files.pythonhosted.org/packages/79/a8/a6611017e0883102fd5e2b73c9d90691b8134e38247c04ee1531d3dc647c/python-saml-2.4.2.tar.gz";
1664 url = "https://files.pythonhosted.org/packages/79/a8/a6611017e0883102fd5e2b73c9d90691b8134e38247c04ee1531d3dc647c/python-saml-2.4.2.tar.gz";
1665 sha256 = "0dls4hwvf13yg7x5yfjrghbywg8g38vn5vr0rsf70hli3ydbfm43";
1665 sha256 = "0dls4hwvf13yg7x5yfjrghbywg8g38vn5vr0rsf70hli3ydbfm43";
1666 };
1666 };
1667 meta = {
1667 meta = {
1668 license = [ pkgs.lib.licenses.mit ];
1668 license = [ pkgs.lib.licenses.mit ];
1669 };
1669 };
1670 };
1670 };
1671 "pytz" = super.buildPythonPackage {
1671 "pytz" = super.buildPythonPackage {
1672 name = "pytz-2019.2";
1672 name = "pytz-2019.2";
1673 doCheck = false;
1673 doCheck = false;
1674 src = fetchurl {
1674 src = fetchurl {
1675 url = "https://files.pythonhosted.org/packages/27/c0/fbd352ca76050952a03db776d241959d5a2ee1abddfeb9e2a53fdb489be4/pytz-2019.2.tar.gz";
1675 url = "https://files.pythonhosted.org/packages/27/c0/fbd352ca76050952a03db776d241959d5a2ee1abddfeb9e2a53fdb489be4/pytz-2019.2.tar.gz";
1676 sha256 = "0ckb27hhjc8i8gcdvk4d9avld62b7k52yjijc60s2m3y8cpb7h16";
1676 sha256 = "0ckb27hhjc8i8gcdvk4d9avld62b7k52yjijc60s2m3y8cpb7h16";
1677 };
1677 };
1678 meta = {
1678 meta = {
1679 license = [ pkgs.lib.licenses.mit ];
1679 license = [ pkgs.lib.licenses.mit ];
1680 };
1680 };
1681 };
1681 };
1682 "pyzmq" = super.buildPythonPackage {
1682 "pyzmq" = super.buildPythonPackage {
1683 name = "pyzmq-14.6.0";
1683 name = "pyzmq-14.6.0";
1684 doCheck = false;
1684 doCheck = false;
1685 src = fetchurl {
1685 src = fetchurl {
1686 url = "https://files.pythonhosted.org/packages/8a/3b/5463d5a9d712cd8bbdac335daece0d69f6a6792da4e3dd89956c0db4e4e6/pyzmq-14.6.0.tar.gz";
1686 url = "https://files.pythonhosted.org/packages/8a/3b/5463d5a9d712cd8bbdac335daece0d69f6a6792da4e3dd89956c0db4e4e6/pyzmq-14.6.0.tar.gz";
1687 sha256 = "1frmbjykvhmdg64g7sn20c9fpamrsfxwci1nhhg8q7jgz5pq0ikp";
1687 sha256 = "1frmbjykvhmdg64g7sn20c9fpamrsfxwci1nhhg8q7jgz5pq0ikp";
1688 };
1688 };
1689 meta = {
1689 meta = {
1690 license = [ pkgs.lib.licenses.bsdOriginal { fullName = "LGPL+BSD"; } { fullName = "GNU Library or Lesser General Public License (LGPL)"; } ];
1690 license = [ pkgs.lib.licenses.bsdOriginal { fullName = "LGPL+BSD"; } { fullName = "GNU Library or Lesser General Public License (LGPL)"; } ];
1691 };
1691 };
1692 };
1692 };
1693 "redis" = super.buildPythonPackage {
1693 "redis" = super.buildPythonPackage {
1694 name = "redis-3.3.11";
1694 name = "redis-3.3.11";
1695 doCheck = false;
1695 doCheck = false;
1696 src = fetchurl {
1696 src = fetchurl {
1697 url = "https://files.pythonhosted.org/packages/06/ca/00557c74279d2f256d3c42cabf237631355f3a132e4c74c2000e6647ad98/redis-3.3.11.tar.gz";
1697 url = "https://files.pythonhosted.org/packages/06/ca/00557c74279d2f256d3c42cabf237631355f3a132e4c74c2000e6647ad98/redis-3.3.11.tar.gz";
1698 sha256 = "1hicqbi5xl92hhml82awrr2rxl9jar5fp8nbcycj9qgmsdwc43wd";
1698 sha256 = "1hicqbi5xl92hhml82awrr2rxl9jar5fp8nbcycj9qgmsdwc43wd";
1699 };
1699 };
1700 meta = {
1700 meta = {
1701 license = [ pkgs.lib.licenses.mit ];
1701 license = [ pkgs.lib.licenses.mit ];
1702 };
1702 };
1703 };
1703 };
1704 "repoze.lru" = super.buildPythonPackage {
1704 "repoze.lru" = super.buildPythonPackage {
1705 name = "repoze.lru-0.7";
1705 name = "repoze.lru-0.7";
1706 doCheck = false;
1706 doCheck = false;
1707 src = fetchurl {
1707 src = fetchurl {
1708 url = "https://files.pythonhosted.org/packages/12/bc/595a77c4b5e204847fdf19268314ef59c85193a9dc9f83630fc459c0fee5/repoze.lru-0.7.tar.gz";
1708 url = "https://files.pythonhosted.org/packages/12/bc/595a77c4b5e204847fdf19268314ef59c85193a9dc9f83630fc459c0fee5/repoze.lru-0.7.tar.gz";
1709 sha256 = "0xzz1aw2smy8hdszrq8yhnklx6w1r1mf55061kalw3iq35gafa84";
1709 sha256 = "0xzz1aw2smy8hdszrq8yhnklx6w1r1mf55061kalw3iq35gafa84";
1710 };
1710 };
1711 meta = {
1711 meta = {
1712 license = [ { fullName = "Repoze Public License"; } { fullName = "BSD-derived (http://www.repoze.org/LICENSE.txt)"; } ];
1712 license = [ { fullName = "Repoze Public License"; } { fullName = "BSD-derived (http://www.repoze.org/LICENSE.txt)"; } ];
1713 };
1713 };
1714 };
1714 };
1715 "repoze.sendmail" = super.buildPythonPackage {
1715 "repoze.sendmail" = super.buildPythonPackage {
1716 name = "repoze.sendmail-4.4.1";
1716 name = "repoze.sendmail-4.4.1";
1717 doCheck = false;
1717 doCheck = false;
1718 propagatedBuildInputs = [
1718 propagatedBuildInputs = [
1719 self."setuptools"
1719 self."setuptools"
1720 self."zope.interface"
1720 self."zope.interface"
1721 self."transaction"
1721 self."transaction"
1722 ];
1722 ];
1723 src = fetchurl {
1723 src = fetchurl {
1724 url = "https://files.pythonhosted.org/packages/12/4e/8ef1fd5c42765d712427b9c391419a77bd48877886d2cbc5e9f23c8cad9b/repoze.sendmail-4.4.1.tar.gz";
1724 url = "https://files.pythonhosted.org/packages/12/4e/8ef1fd5c42765d712427b9c391419a77bd48877886d2cbc5e9f23c8cad9b/repoze.sendmail-4.4.1.tar.gz";
1725 sha256 = "096ln02jr2afk7ab9j2czxqv2ryqq7m86ah572nqplx52iws73ks";
1725 sha256 = "096ln02jr2afk7ab9j2czxqv2ryqq7m86ah572nqplx52iws73ks";
1726 };
1726 };
1727 meta = {
1727 meta = {
1728 license = [ pkgs.lib.licenses.zpl21 ];
1728 license = [ pkgs.lib.licenses.zpl21 ];
1729 };
1729 };
1730 };
1730 };
1731 "requests" = super.buildPythonPackage {
1731 "requests" = super.buildPythonPackage {
1732 name = "requests-2.9.1";
1732 name = "requests-2.9.1";
1733 doCheck = false;
1733 doCheck = false;
1734 src = fetchurl {
1734 src = fetchurl {
1735 url = "https://files.pythonhosted.org/packages/f9/6d/07c44fb1ebe04d069459a189e7dab9e4abfe9432adcd4477367c25332748/requests-2.9.1.tar.gz";
1735 url = "https://files.pythonhosted.org/packages/f9/6d/07c44fb1ebe04d069459a189e7dab9e4abfe9432adcd4477367c25332748/requests-2.9.1.tar.gz";
1736 sha256 = "0zsqrzlybf25xscgi7ja4s48y2abf9wvjkn47wh984qgs1fq2xy5";
1736 sha256 = "0zsqrzlybf25xscgi7ja4s48y2abf9wvjkn47wh984qgs1fq2xy5";
1737 };
1737 };
1738 meta = {
1738 meta = {
1739 license = [ pkgs.lib.licenses.asl20 ];
1739 license = [ pkgs.lib.licenses.asl20 ];
1740 };
1740 };
1741 };
1741 };
1742 "rhodecode-enterprise-ce" = super.buildPythonPackage {
1742 "rhodecode-enterprise-ce" = super.buildPythonPackage {
1743 name = "rhodecode-enterprise-ce-4.18.0";
1743 name = "rhodecode-enterprise-ce-4.18.0";
1744 buildInputs = [
1744 buildInputs = [
1745 self."pytest"
1745 self."pytest"
1746 self."py"
1746 self."py"
1747 self."pytest-cov"
1747 self."pytest-cov"
1748 self."pytest-sugar"
1748 self."pytest-sugar"
1749 self."pytest-runner"
1749 self."pytest-runner"
1750 self."pytest-profiling"
1750 self."pytest-profiling"
1751 self."pytest-timeout"
1751 self."pytest-timeout"
1752 self."gprof2dot"
1752 self."gprof2dot"
1753 self."mock"
1753 self."mock"
1754 self."cov-core"
1754 self."cov-core"
1755 self."coverage"
1755 self."coverage"
1756 self."webtest"
1756 self."webtest"
1757 self."beautifulsoup4"
1757 self."beautifulsoup4"
1758 self."configobj"
1758 self."configobj"
1759 ];
1759 ];
1760 doCheck = true;
1760 doCheck = true;
1761 propagatedBuildInputs = [
1761 propagatedBuildInputs = [
1762 self."amqp"
1762 self."amqp"
1763 self."babel"
1763 self."babel"
1764 self."beaker"
1764 self."beaker"
1765 self."bleach"
1765 self."bleach"
1766 self."celery"
1766 self."celery"
1767 self."channelstream"
1767 self."channelstream"
1768 self."click"
1768 self."click"
1769 self."colander"
1769 self."colander"
1770 self."configobj"
1770 self."configobj"
1771 self."cssselect"
1771 self."cssselect"
1772 self."cryptography"
1772 self."cryptography"
1773 self."decorator"
1773 self."decorator"
1774 self."deform"
1774 self."deform"
1775 self."docutils"
1775 self."docutils"
1776 self."dogpile.cache"
1776 self."dogpile.cache"
1777 self."dogpile.core"
1777 self."dogpile.core"
1778 self."formencode"
1778 self."formencode"
1779 self."future"
1779 self."future"
1780 self."futures"
1780 self."futures"
1781 self."infrae.cache"
1781 self."infrae.cache"
1782 self."iso8601"
1782 self."iso8601"
1783 self."itsdangerous"
1783 self."itsdangerous"
1784 self."kombu"
1784 self."kombu"
1785 self."lxml"
1785 self."lxml"
1786 self."mako"
1786 self."mako"
1787 self."markdown"
1787 self."markdown"
1788 self."markupsafe"
1788 self."markupsafe"
1789 self."msgpack-python"
1789 self."msgpack-python"
1790 self."pyotp"
1790 self."pyotp"
1791 self."packaging"
1791 self."packaging"
1792 self."pathlib2"
1792 self."pathlib2"
1793 self."paste"
1793 self."paste"
1794 self."pastedeploy"
1794 self."pastedeploy"
1795 self."pastescript"
1795 self."pastescript"
1796 self."peppercorn"
1796 self."peppercorn"
1797 self."psutil"
1797 self."psutil"
1798 self."py-bcrypt"
1798 self."py-bcrypt"
1799 self."pycurl"
1799 self."pycurl"
1800 self."pycrypto"
1800 self."pycrypto"
1801 self."pygments"
1801 self."pygments"
1802 self."pyparsing"
1802 self."pyparsing"
1803 self."pyramid-debugtoolbar"
1803 self."pyramid-debugtoolbar"
1804 self."pyramid-mako"
1804 self."pyramid-mako"
1805 self."pyramid"
1805 self."pyramid"
1806 self."pyramid-mailer"
1806 self."pyramid-mailer"
1807 self."python-dateutil"
1807 self."python-dateutil"
1808 self."python-ldap"
1808 self."python-ldap"
1809 self."python-memcached"
1809 self."python-memcached"
1810 self."python-pam"
1810 self."python-pam"
1811 self."python-saml"
1811 self."python-saml"
1812 self."pytz"
1812 self."pytz"
1813 self."tzlocal"
1813 self."tzlocal"
1814 self."pyzmq"
1814 self."pyzmq"
1815 self."py-gfm"
1815 self."py-gfm"
1816 self."redis"
1816 self."redis"
1817 self."repoze.lru"
1817 self."repoze.lru"
1818 self."requests"
1818 self."requests"
1819 self."routes"
1819 self."routes"
1820 self."simplejson"
1820 self."simplejson"
1821 self."six"
1821 self."six"
1822 self."sqlalchemy"
1822 self."sqlalchemy"
1823 self."sshpubkeys"
1823 self."sshpubkeys"
1824 self."subprocess32"
1824 self."subprocess32"
1825 self."supervisor"
1825 self."supervisor"
1826 self."translationstring"
1826 self."translationstring"
1827 self."urllib3"
1827 self."urllib3"
1828 self."urlobject"
1828 self."urlobject"
1829 self."venusian"
1829 self."venusian"
1830 self."weberror"
1830 self."weberror"
1831 self."webhelpers2"
1831 self."webhelpers2"
1832 self."webhelpers"
1833 self."webob"
1832 self."webob"
1834 self."whoosh"
1833 self."whoosh"
1835 self."wsgiref"
1834 self."wsgiref"
1836 self."zope.cachedescriptors"
1835 self."zope.cachedescriptors"
1837 self."zope.deprecation"
1836 self."zope.deprecation"
1838 self."zope.event"
1837 self."zope.event"
1839 self."zope.interface"
1838 self."zope.interface"
1840 self."mysql-python"
1839 self."mysql-python"
1841 self."pymysql"
1840 self."pymysql"
1842 self."pysqlite"
1841 self."pysqlite"
1843 self."psycopg2"
1842 self."psycopg2"
1844 self."nbconvert"
1843 self."nbconvert"
1845 self."nbformat"
1844 self."nbformat"
1846 self."jupyter-client"
1845 self."jupyter-client"
1847 self."jupyter-core"
1846 self."jupyter-core"
1848 self."alembic"
1847 self."alembic"
1849 self."invoke"
1848 self."invoke"
1850 self."bumpversion"
1849 self."bumpversion"
1851 self."gevent"
1850 self."gevent"
1852 self."greenlet"
1851 self."greenlet"
1853 self."gunicorn"
1852 self."gunicorn"
1854 self."waitress"
1853 self."waitress"
1855 self."ipdb"
1854 self."ipdb"
1856 self."ipython"
1855 self."ipython"
1857 self."rhodecode-tools"
1856 self."rhodecode-tools"
1858 self."appenlight-client"
1857 self."appenlight-client"
1859 self."pytest"
1858 self."pytest"
1860 self."py"
1859 self."py"
1861 self."pytest-cov"
1860 self."pytest-cov"
1862 self."pytest-sugar"
1861 self."pytest-sugar"
1863 self."pytest-runner"
1862 self."pytest-runner"
1864 self."pytest-profiling"
1863 self."pytest-profiling"
1865 self."pytest-timeout"
1864 self."pytest-timeout"
1866 self."gprof2dot"
1865 self."gprof2dot"
1867 self."mock"
1866 self."mock"
1868 self."cov-core"
1867 self."cov-core"
1869 self."coverage"
1868 self."coverage"
1870 self."webtest"
1869 self."webtest"
1871 self."beautifulsoup4"
1870 self."beautifulsoup4"
1872 ];
1871 ];
1873 src = ./.;
1872 src = ./.;
1874 meta = {
1873 meta = {
1875 license = [ { fullName = "Affero GNU General Public License v3 or later (AGPLv3+)"; } { fullName = "AGPLv3, and Commercial License"; } ];
1874 license = [ { fullName = "Affero GNU General Public License v3 or later (AGPLv3+)"; } { fullName = "AGPLv3, and Commercial License"; } ];
1876 };
1875 };
1877 };
1876 };
1878 "rhodecode-tools" = super.buildPythonPackage {
1877 "rhodecode-tools" = super.buildPythonPackage {
1879 name = "rhodecode-tools-1.3.0";
1878 name = "rhodecode-tools-1.3.0";
1880 doCheck = false;
1879 doCheck = false;
1881 propagatedBuildInputs = [
1880 propagatedBuildInputs = [
1882 self."click"
1881 self."click"
1883 self."future"
1882 self."future"
1884 self."six"
1883 self."six"
1885 self."mako"
1884 self."mako"
1886 self."markupsafe"
1885 self."markupsafe"
1887 self."requests"
1886 self."requests"
1888 self."urllib3"
1887 self."urllib3"
1889 self."whoosh"
1888 self."whoosh"
1890 self."elasticsearch"
1889 self."elasticsearch"
1891 self."elasticsearch-dsl"
1890 self."elasticsearch-dsl"
1892 self."elasticsearch2"
1891 self."elasticsearch2"
1893 self."elasticsearch1-dsl"
1892 self."elasticsearch1-dsl"
1894 ];
1893 ];
1895 src = fetchurl {
1894 src = fetchurl {
1896 url = "https://code.rhodecode.com/rhodecode-tools-ce/artifacts/download/0-2e546668-61c4-42b2-ae03-7850a2dbb846.tar.gz?sha256=0938220f33264b0514dc63207ce1f77e38b1b1fd93fd4f68d34243ab62ecc853";
1895 url = "https://code.rhodecode.com/rhodecode-tools-ce/artifacts/download/0-2e546668-61c4-42b2-ae03-7850a2dbb846.tar.gz?sha256=0938220f33264b0514dc63207ce1f77e38b1b1fd93fd4f68d34243ab62ecc853";
1897 sha256 = "0ly8xiianhs2sdl4zzckznqv2f3yyzhpq833vha0ajr66c7j4f09";
1896 sha256 = "0ly8xiianhs2sdl4zzckznqv2f3yyzhpq833vha0ajr66c7j4f09";
1898 };
1897 };
1899 meta = {
1898 meta = {
1900 license = [ { fullName = "Apache 2.0 and Proprietary"; } ];
1899 license = [ { fullName = "Apache 2.0 and Proprietary"; } ];
1901 };
1900 };
1902 };
1901 };
1903 "routes" = super.buildPythonPackage {
1902 "routes" = super.buildPythonPackage {
1904 name = "routes-2.4.1";
1903 name = "routes-2.4.1";
1905 doCheck = false;
1904 doCheck = false;
1906 propagatedBuildInputs = [
1905 propagatedBuildInputs = [
1907 self."six"
1906 self."six"
1908 self."repoze.lru"
1907 self."repoze.lru"
1909 ];
1908 ];
1910 src = fetchurl {
1909 src = fetchurl {
1911 url = "https://files.pythonhosted.org/packages/33/38/ea827837e68d9c7dde4cff7ec122a93c319f0effc08ce92a17095576603f/Routes-2.4.1.tar.gz";
1910 url = "https://files.pythonhosted.org/packages/33/38/ea827837e68d9c7dde4cff7ec122a93c319f0effc08ce92a17095576603f/Routes-2.4.1.tar.gz";
1912 sha256 = "1zamff3m0kc4vyfniyhxpkkcqv1rrgnmh37ykxv34nna1ws47vi6";
1911 sha256 = "1zamff3m0kc4vyfniyhxpkkcqv1rrgnmh37ykxv34nna1ws47vi6";
1913 };
1912 };
1914 meta = {
1913 meta = {
1915 license = [ pkgs.lib.licenses.mit ];
1914 license = [ pkgs.lib.licenses.mit ];
1916 };
1915 };
1917 };
1916 };
1918 "scandir" = super.buildPythonPackage {
1917 "scandir" = super.buildPythonPackage {
1919 name = "scandir-1.10.0";
1918 name = "scandir-1.10.0";
1920 doCheck = false;
1919 doCheck = false;
1921 src = fetchurl {
1920 src = fetchurl {
1922 url = "https://files.pythonhosted.org/packages/df/f5/9c052db7bd54d0cbf1bc0bb6554362bba1012d03e5888950a4f5c5dadc4e/scandir-1.10.0.tar.gz";
1921 url = "https://files.pythonhosted.org/packages/df/f5/9c052db7bd54d0cbf1bc0bb6554362bba1012d03e5888950a4f5c5dadc4e/scandir-1.10.0.tar.gz";
1923 sha256 = "1bkqwmf056pkchf05ywbnf659wqlp6lljcdb0y88wr9f0vv32ijd";
1922 sha256 = "1bkqwmf056pkchf05ywbnf659wqlp6lljcdb0y88wr9f0vv32ijd";
1924 };
1923 };
1925 meta = {
1924 meta = {
1926 license = [ pkgs.lib.licenses.bsdOriginal { fullName = "New BSD License"; } ];
1925 license = [ pkgs.lib.licenses.bsdOriginal { fullName = "New BSD License"; } ];
1927 };
1926 };
1928 };
1927 };
1929 "setproctitle" = super.buildPythonPackage {
1928 "setproctitle" = super.buildPythonPackage {
1930 name = "setproctitle-1.1.10";
1929 name = "setproctitle-1.1.10";
1931 doCheck = false;
1930 doCheck = false;
1932 src = fetchurl {
1931 src = fetchurl {
1933 url = "https://files.pythonhosted.org/packages/5a/0d/dc0d2234aacba6cf1a729964383e3452c52096dc695581248b548786f2b3/setproctitle-1.1.10.tar.gz";
1932 url = "https://files.pythonhosted.org/packages/5a/0d/dc0d2234aacba6cf1a729964383e3452c52096dc695581248b548786f2b3/setproctitle-1.1.10.tar.gz";
1934 sha256 = "163kplw9dcrw0lffq1bvli5yws3rngpnvrxrzdw89pbphjjvg0v2";
1933 sha256 = "163kplw9dcrw0lffq1bvli5yws3rngpnvrxrzdw89pbphjjvg0v2";
1935 };
1934 };
1936 meta = {
1935 meta = {
1937 license = [ pkgs.lib.licenses.bsdOriginal ];
1936 license = [ pkgs.lib.licenses.bsdOriginal ];
1938 };
1937 };
1939 };
1938 };
1940 "setuptools" = super.buildPythonPackage {
1939 "setuptools" = super.buildPythonPackage {
1941 name = "setuptools-41.6.0";
1940 name = "setuptools-41.6.0";
1942 doCheck = false;
1941 doCheck = false;
1943 src = fetchurl {
1942 src = fetchurl {
1944 url = "https://files.pythonhosted.org/packages/11/0a/7f13ef5cd932a107cd4c0f3ebc9d831d9b78e1a0e8c98a098ca17b1d7d97/setuptools-41.6.0.zip";
1943 url = "https://files.pythonhosted.org/packages/11/0a/7f13ef5cd932a107cd4c0f3ebc9d831d9b78e1a0e8c98a098ca17b1d7d97/setuptools-41.6.0.zip";
1945 sha256 = "08nplghlnlb4qkhb9hp1lqqqlnh1qingdj8fi6w6rlfwj6rn3yka";
1944 sha256 = "08nplghlnlb4qkhb9hp1lqqqlnh1qingdj8fi6w6rlfwj6rn3yka";
1946 };
1945 };
1947 meta = {
1946 meta = {
1948 license = [ pkgs.lib.licenses.mit ];
1947 license = [ pkgs.lib.licenses.mit ];
1949 };
1948 };
1950 };
1949 };
1951 "simplegeneric" = super.buildPythonPackage {
1950 "simplegeneric" = super.buildPythonPackage {
1952 name = "simplegeneric-0.8.1";
1951 name = "simplegeneric-0.8.1";
1953 doCheck = false;
1952 doCheck = false;
1954 src = fetchurl {
1953 src = fetchurl {
1955 url = "https://files.pythonhosted.org/packages/3d/57/4d9c9e3ae9a255cd4e1106bb57e24056d3d0709fc01b2e3e345898e49d5b/simplegeneric-0.8.1.zip";
1954 url = "https://files.pythonhosted.org/packages/3d/57/4d9c9e3ae9a255cd4e1106bb57e24056d3d0709fc01b2e3e345898e49d5b/simplegeneric-0.8.1.zip";
1956 sha256 = "0wwi1c6md4vkbcsfsf8dklf3vr4mcdj4mpxkanwgb6jb1432x5yw";
1955 sha256 = "0wwi1c6md4vkbcsfsf8dklf3vr4mcdj4mpxkanwgb6jb1432x5yw";
1957 };
1956 };
1958 meta = {
1957 meta = {
1959 license = [ pkgs.lib.licenses.zpl21 ];
1958 license = [ pkgs.lib.licenses.zpl21 ];
1960 };
1959 };
1961 };
1960 };
1962 "simplejson" = super.buildPythonPackage {
1961 "simplejson" = super.buildPythonPackage {
1963 name = "simplejson-3.16.0";
1962 name = "simplejson-3.16.0";
1964 doCheck = false;
1963 doCheck = false;
1965 src = fetchurl {
1964 src = fetchurl {
1966 url = "https://files.pythonhosted.org/packages/e3/24/c35fb1c1c315fc0fffe61ea00d3f88e85469004713dab488dee4f35b0aff/simplejson-3.16.0.tar.gz";
1965 url = "https://files.pythonhosted.org/packages/e3/24/c35fb1c1c315fc0fffe61ea00d3f88e85469004713dab488dee4f35b0aff/simplejson-3.16.0.tar.gz";
1967 sha256 = "19cws1syk8jzq2pw43878dv6fjkb0ifvjpx0i9aajix6kc9jkwxi";
1966 sha256 = "19cws1syk8jzq2pw43878dv6fjkb0ifvjpx0i9aajix6kc9jkwxi";
1968 };
1967 };
1969 meta = {
1968 meta = {
1970 license = [ { fullName = "Academic Free License (AFL)"; } pkgs.lib.licenses.mit ];
1969 license = [ { fullName = "Academic Free License (AFL)"; } pkgs.lib.licenses.mit ];
1971 };
1970 };
1972 };
1971 };
1973 "six" = super.buildPythonPackage {
1972 "six" = super.buildPythonPackage {
1974 name = "six-1.11.0";
1973 name = "six-1.11.0";
1975 doCheck = false;
1974 doCheck = false;
1976 src = fetchurl {
1975 src = fetchurl {
1977 url = "https://files.pythonhosted.org/packages/16/d8/bc6316cf98419719bd59c91742194c111b6f2e85abac88e496adefaf7afe/six-1.11.0.tar.gz";
1976 url = "https://files.pythonhosted.org/packages/16/d8/bc6316cf98419719bd59c91742194c111b6f2e85abac88e496adefaf7afe/six-1.11.0.tar.gz";
1978 sha256 = "1scqzwc51c875z23phj48gircqjgnn3af8zy2izjwmnlxrxsgs3h";
1977 sha256 = "1scqzwc51c875z23phj48gircqjgnn3af8zy2izjwmnlxrxsgs3h";
1979 };
1978 };
1980 meta = {
1979 meta = {
1981 license = [ pkgs.lib.licenses.mit ];
1980 license = [ pkgs.lib.licenses.mit ];
1982 };
1981 };
1983 };
1982 };
1984 "sqlalchemy" = super.buildPythonPackage {
1983 "sqlalchemy" = super.buildPythonPackage {
1985 name = "sqlalchemy-1.3.11";
1984 name = "sqlalchemy-1.3.11";
1986 doCheck = false;
1985 doCheck = false;
1987 src = fetchurl {
1986 src = fetchurl {
1988 url = "https://files.pythonhosted.org/packages/34/5c/0e1d7ad0ca52544bb12f9cb8d5cc454af45821c92160ffedd38db0a317f6/SQLAlchemy-1.3.11.tar.gz";
1987 url = "https://files.pythonhosted.org/packages/34/5c/0e1d7ad0ca52544bb12f9cb8d5cc454af45821c92160ffedd38db0a317f6/SQLAlchemy-1.3.11.tar.gz";
1989 sha256 = "12izpqqgy738ndn7qqn962qxi8qw2xb9vg2i880x12paklg599dg";
1988 sha256 = "12izpqqgy738ndn7qqn962qxi8qw2xb9vg2i880x12paklg599dg";
1990 };
1989 };
1991 meta = {
1990 meta = {
1992 license = [ pkgs.lib.licenses.mit ];
1991 license = [ pkgs.lib.licenses.mit ];
1993 };
1992 };
1994 };
1993 };
1995 "sshpubkeys" = super.buildPythonPackage {
1994 "sshpubkeys" = super.buildPythonPackage {
1996 name = "sshpubkeys-3.1.0";
1995 name = "sshpubkeys-3.1.0";
1997 doCheck = false;
1996 doCheck = false;
1998 propagatedBuildInputs = [
1997 propagatedBuildInputs = [
1999 self."cryptography"
1998 self."cryptography"
2000 self."ecdsa"
1999 self."ecdsa"
2001 ];
2000 ];
2002 src = fetchurl {
2001 src = fetchurl {
2003 url = "https://files.pythonhosted.org/packages/00/23/f7508a12007c96861c3da811992f14283d79c819d71a217b3e12d5196649/sshpubkeys-3.1.0.tar.gz";
2002 url = "https://files.pythonhosted.org/packages/00/23/f7508a12007c96861c3da811992f14283d79c819d71a217b3e12d5196649/sshpubkeys-3.1.0.tar.gz";
2004 sha256 = "105g2li04nm1hb15a2y6hm9m9k7fbrkd5l3gy12w3kgcmsf3k25k";
2003 sha256 = "105g2li04nm1hb15a2y6hm9m9k7fbrkd5l3gy12w3kgcmsf3k25k";
2005 };
2004 };
2006 meta = {
2005 meta = {
2007 license = [ pkgs.lib.licenses.bsdOriginal ];
2006 license = [ pkgs.lib.licenses.bsdOriginal ];
2008 };
2007 };
2009 };
2008 };
2010 "subprocess32" = super.buildPythonPackage {
2009 "subprocess32" = super.buildPythonPackage {
2011 name = "subprocess32-3.5.4";
2010 name = "subprocess32-3.5.4";
2012 doCheck = false;
2011 doCheck = false;
2013 src = fetchurl {
2012 src = fetchurl {
2014 url = "https://files.pythonhosted.org/packages/32/c8/564be4d12629b912ea431f1a50eb8b3b9d00f1a0b1ceff17f266be190007/subprocess32-3.5.4.tar.gz";
2013 url = "https://files.pythonhosted.org/packages/32/c8/564be4d12629b912ea431f1a50eb8b3b9d00f1a0b1ceff17f266be190007/subprocess32-3.5.4.tar.gz";
2015 sha256 = "17f7mvwx2271s1wrl0qac3wjqqnrqag866zs3qc8v5wp0k43fagb";
2014 sha256 = "17f7mvwx2271s1wrl0qac3wjqqnrqag866zs3qc8v5wp0k43fagb";
2016 };
2015 };
2017 meta = {
2016 meta = {
2018 license = [ pkgs.lib.licenses.psfl ];
2017 license = [ pkgs.lib.licenses.psfl ];
2019 };
2018 };
2020 };
2019 };
2021 "supervisor" = super.buildPythonPackage {
2020 "supervisor" = super.buildPythonPackage {
2022 name = "supervisor-4.1.0";
2021 name = "supervisor-4.1.0";
2023 doCheck = false;
2022 doCheck = false;
2024 src = fetchurl {
2023 src = fetchurl {
2025 url = "https://files.pythonhosted.org/packages/de/87/ee1ad8fa533a4b5f2c7623f4a2b585d3c1947af7bed8e65bc7772274320e/supervisor-4.1.0.tar.gz";
2024 url = "https://files.pythonhosted.org/packages/de/87/ee1ad8fa533a4b5f2c7623f4a2b585d3c1947af7bed8e65bc7772274320e/supervisor-4.1.0.tar.gz";
2026 sha256 = "10q36sa1jqljyyyl7cif52akpygl5kmlqq9x91hmx53f8zh6zj1d";
2025 sha256 = "10q36sa1jqljyyyl7cif52akpygl5kmlqq9x91hmx53f8zh6zj1d";
2027 };
2026 };
2028 meta = {
2027 meta = {
2029 license = [ { fullName = "BSD-derived (http://www.repoze.org/LICENSE.txt)"; } ];
2028 license = [ { fullName = "BSD-derived (http://www.repoze.org/LICENSE.txt)"; } ];
2030 };
2029 };
2031 };
2030 };
2032 "tempita" = super.buildPythonPackage {
2031 "tempita" = super.buildPythonPackage {
2033 name = "tempita-0.5.2";
2032 name = "tempita-0.5.2";
2034 doCheck = false;
2033 doCheck = false;
2035 src = fetchurl {
2034 src = fetchurl {
2036 url = "https://files.pythonhosted.org/packages/56/c8/8ed6eee83dbddf7b0fc64dd5d4454bc05e6ccaafff47991f73f2894d9ff4/Tempita-0.5.2.tar.gz";
2035 url = "https://files.pythonhosted.org/packages/56/c8/8ed6eee83dbddf7b0fc64dd5d4454bc05e6ccaafff47991f73f2894d9ff4/Tempita-0.5.2.tar.gz";
2037 sha256 = "177wwq45slfyajd8csy477bmdmzipyw0dm7i85k3akb7m85wzkna";
2036 sha256 = "177wwq45slfyajd8csy477bmdmzipyw0dm7i85k3akb7m85wzkna";
2038 };
2037 };
2039 meta = {
2038 meta = {
2040 license = [ pkgs.lib.licenses.mit ];
2039 license = [ pkgs.lib.licenses.mit ];
2041 };
2040 };
2042 };
2041 };
2043 "termcolor" = super.buildPythonPackage {
2042 "termcolor" = super.buildPythonPackage {
2044 name = "termcolor-1.1.0";
2043 name = "termcolor-1.1.0";
2045 doCheck = false;
2044 doCheck = false;
2046 src = fetchurl {
2045 src = fetchurl {
2047 url = "https://files.pythonhosted.org/packages/8a/48/a76be51647d0eb9f10e2a4511bf3ffb8cc1e6b14e9e4fab46173aa79f981/termcolor-1.1.0.tar.gz";
2046 url = "https://files.pythonhosted.org/packages/8a/48/a76be51647d0eb9f10e2a4511bf3ffb8cc1e6b14e9e4fab46173aa79f981/termcolor-1.1.0.tar.gz";
2048 sha256 = "0fv1vq14rpqwgazxg4981904lfyp84mnammw7y046491cv76jv8x";
2047 sha256 = "0fv1vq14rpqwgazxg4981904lfyp84mnammw7y046491cv76jv8x";
2049 };
2048 };
2050 meta = {
2049 meta = {
2051 license = [ pkgs.lib.licenses.mit ];
2050 license = [ pkgs.lib.licenses.mit ];
2052 };
2051 };
2053 };
2052 };
2054 "testpath" = super.buildPythonPackage {
2053 "testpath" = super.buildPythonPackage {
2055 name = "testpath-0.4.4";
2054 name = "testpath-0.4.4";
2056 doCheck = false;
2055 doCheck = false;
2057 src = fetchurl {
2056 src = fetchurl {
2058 url = "https://files.pythonhosted.org/packages/2c/b3/5d57205e896d8998d77ad12aa42ebce75cd97d8b9a97d00ba078c4c9ffeb/testpath-0.4.4.tar.gz";
2057 url = "https://files.pythonhosted.org/packages/2c/b3/5d57205e896d8998d77ad12aa42ebce75cd97d8b9a97d00ba078c4c9ffeb/testpath-0.4.4.tar.gz";
2059 sha256 = "0zpcmq22dz79ipvvsfnw1ykpjcaj6xyzy7ws77s5b5ql3hka7q30";
2058 sha256 = "0zpcmq22dz79ipvvsfnw1ykpjcaj6xyzy7ws77s5b5ql3hka7q30";
2060 };
2059 };
2061 meta = {
2060 meta = {
2062 license = [ ];
2061 license = [ ];
2063 };
2062 };
2064 };
2063 };
2065 "traitlets" = super.buildPythonPackage {
2064 "traitlets" = super.buildPythonPackage {
2066 name = "traitlets-4.3.3";
2065 name = "traitlets-4.3.3";
2067 doCheck = false;
2066 doCheck = false;
2068 propagatedBuildInputs = [
2067 propagatedBuildInputs = [
2069 self."ipython-genutils"
2068 self."ipython-genutils"
2070 self."six"
2069 self."six"
2071 self."decorator"
2070 self."decorator"
2072 self."enum34"
2071 self."enum34"
2073 ];
2072 ];
2074 src = fetchurl {
2073 src = fetchurl {
2075 url = "https://files.pythonhosted.org/packages/75/b0/43deb021bc943f18f07cbe3dac1d681626a48997b7ffa1e7fb14ef922b21/traitlets-4.3.3.tar.gz";
2074 url = "https://files.pythonhosted.org/packages/75/b0/43deb021bc943f18f07cbe3dac1d681626a48997b7ffa1e7fb14ef922b21/traitlets-4.3.3.tar.gz";
2076 sha256 = "1xsrwgivpkxlbr4dfndfsi098s29yqgswgjc1qqn69yxklvfw8yh";
2075 sha256 = "1xsrwgivpkxlbr4dfndfsi098s29yqgswgjc1qqn69yxklvfw8yh";
2077 };
2076 };
2078 meta = {
2077 meta = {
2079 license = [ pkgs.lib.licenses.bsdOriginal ];
2078 license = [ pkgs.lib.licenses.bsdOriginal ];
2080 };
2079 };
2081 };
2080 };
2082 "transaction" = super.buildPythonPackage {
2081 "transaction" = super.buildPythonPackage {
2083 name = "transaction-2.4.0";
2082 name = "transaction-2.4.0";
2084 doCheck = false;
2083 doCheck = false;
2085 propagatedBuildInputs = [
2084 propagatedBuildInputs = [
2086 self."zope.interface"
2085 self."zope.interface"
2087 ];
2086 ];
2088 src = fetchurl {
2087 src = fetchurl {
2089 url = "https://files.pythonhosted.org/packages/9d/7d/0e8af0d059e052b9dcf2bb5a08aad20ae3e238746bdd3f8701a60969b363/transaction-2.4.0.tar.gz";
2088 url = "https://files.pythonhosted.org/packages/9d/7d/0e8af0d059e052b9dcf2bb5a08aad20ae3e238746bdd3f8701a60969b363/transaction-2.4.0.tar.gz";
2090 sha256 = "17wz1y524ca07vr03yddy8dv0gbscs06dbdywmllxv5rc725jq3j";
2089 sha256 = "17wz1y524ca07vr03yddy8dv0gbscs06dbdywmllxv5rc725jq3j";
2091 };
2090 };
2092 meta = {
2091 meta = {
2093 license = [ pkgs.lib.licenses.zpl21 ];
2092 license = [ pkgs.lib.licenses.zpl21 ];
2094 };
2093 };
2095 };
2094 };
2096 "translationstring" = super.buildPythonPackage {
2095 "translationstring" = super.buildPythonPackage {
2097 name = "translationstring-1.3";
2096 name = "translationstring-1.3";
2098 doCheck = false;
2097 doCheck = false;
2099 src = fetchurl {
2098 src = fetchurl {
2100 url = "https://files.pythonhosted.org/packages/5e/eb/bee578cc150b44c653b63f5ebe258b5d0d812ddac12497e5f80fcad5d0b4/translationstring-1.3.tar.gz";
2099 url = "https://files.pythonhosted.org/packages/5e/eb/bee578cc150b44c653b63f5ebe258b5d0d812ddac12497e5f80fcad5d0b4/translationstring-1.3.tar.gz";
2101 sha256 = "0bdpcnd9pv0131dl08h4zbcwmgc45lyvq3pa224xwan5b3x4rr2f";
2100 sha256 = "0bdpcnd9pv0131dl08h4zbcwmgc45lyvq3pa224xwan5b3x4rr2f";
2102 };
2101 };
2103 meta = {
2102 meta = {
2104 license = [ { fullName = "BSD-like (http://repoze.org/license.html)"; } ];
2103 license = [ { fullName = "BSD-like (http://repoze.org/license.html)"; } ];
2105 };
2104 };
2106 };
2105 };
2107 "tzlocal" = super.buildPythonPackage {
2106 "tzlocal" = super.buildPythonPackage {
2108 name = "tzlocal-1.5.1";
2107 name = "tzlocal-1.5.1";
2109 doCheck = false;
2108 doCheck = false;
2110 propagatedBuildInputs = [
2109 propagatedBuildInputs = [
2111 self."pytz"
2110 self."pytz"
2112 ];
2111 ];
2113 src = fetchurl {
2112 src = fetchurl {
2114 url = "https://files.pythonhosted.org/packages/cb/89/e3687d3ed99bc882793f82634e9824e62499fdfdc4b1ae39e211c5b05017/tzlocal-1.5.1.tar.gz";
2113 url = "https://files.pythonhosted.org/packages/cb/89/e3687d3ed99bc882793f82634e9824e62499fdfdc4b1ae39e211c5b05017/tzlocal-1.5.1.tar.gz";
2115 sha256 = "0kiciwiqx0bv0fbc913idxibc4ygg4cb7f8rcpd9ij2shi4bigjf";
2114 sha256 = "0kiciwiqx0bv0fbc913idxibc4ygg4cb7f8rcpd9ij2shi4bigjf";
2116 };
2115 };
2117 meta = {
2116 meta = {
2118 license = [ pkgs.lib.licenses.mit ];
2117 license = [ pkgs.lib.licenses.mit ];
2119 };
2118 };
2120 };
2119 };
2121 "urllib3" = super.buildPythonPackage {
2120 "urllib3" = super.buildPythonPackage {
2122 name = "urllib3-1.24.1";
2121 name = "urllib3-1.24.1";
2123 doCheck = false;
2122 doCheck = false;
2124 src = fetchurl {
2123 src = fetchurl {
2125 url = "https://files.pythonhosted.org/packages/b1/53/37d82ab391393565f2f831b8eedbffd57db5a718216f82f1a8b4d381a1c1/urllib3-1.24.1.tar.gz";
2124 url = "https://files.pythonhosted.org/packages/b1/53/37d82ab391393565f2f831b8eedbffd57db5a718216f82f1a8b4d381a1c1/urllib3-1.24.1.tar.gz";
2126 sha256 = "08lwd9f3hqznyf32vnzwvp87pchx062nkbgyrf67rwlkgj0jk5fy";
2125 sha256 = "08lwd9f3hqznyf32vnzwvp87pchx062nkbgyrf67rwlkgj0jk5fy";
2127 };
2126 };
2128 meta = {
2127 meta = {
2129 license = [ pkgs.lib.licenses.mit ];
2128 license = [ pkgs.lib.licenses.mit ];
2130 };
2129 };
2131 };
2130 };
2132 "urlobject" = super.buildPythonPackage {
2131 "urlobject" = super.buildPythonPackage {
2133 name = "urlobject-2.4.3";
2132 name = "urlobject-2.4.3";
2134 doCheck = false;
2133 doCheck = false;
2135 src = fetchurl {
2134 src = fetchurl {
2136 url = "https://files.pythonhosted.org/packages/e2/b8/1d0a916f4b34c4618846e6da0e4eeaa8fcb4a2f39e006434fe38acb74b34/URLObject-2.4.3.tar.gz";
2135 url = "https://files.pythonhosted.org/packages/e2/b8/1d0a916f4b34c4618846e6da0e4eeaa8fcb4a2f39e006434fe38acb74b34/URLObject-2.4.3.tar.gz";
2137 sha256 = "1ahc8ficzfvr2avln71immfh4ls0zyv6cdaa5xmkdj5rd87f5cj7";
2136 sha256 = "1ahc8ficzfvr2avln71immfh4ls0zyv6cdaa5xmkdj5rd87f5cj7";
2138 };
2137 };
2139 meta = {
2138 meta = {
2140 license = [ pkgs.lib.licenses.publicDomain ];
2139 license = [ pkgs.lib.licenses.publicDomain ];
2141 };
2140 };
2142 };
2141 };
2143 "venusian" = super.buildPythonPackage {
2142 "venusian" = super.buildPythonPackage {
2144 name = "venusian-1.2.0";
2143 name = "venusian-1.2.0";
2145 doCheck = false;
2144 doCheck = false;
2146 src = fetchurl {
2145 src = fetchurl {
2147 url = "https://files.pythonhosted.org/packages/7e/6f/40a9d43ac77cb51cb62be5b5662d170f43f8037bdc4eab56336c4ca92bb7/venusian-1.2.0.tar.gz";
2146 url = "https://files.pythonhosted.org/packages/7e/6f/40a9d43ac77cb51cb62be5b5662d170f43f8037bdc4eab56336c4ca92bb7/venusian-1.2.0.tar.gz";
2148 sha256 = "0ghyx66g8ikx9nx1mnwqvdcqm11i1vlq0hnvwl50s48bp22q5v34";
2147 sha256 = "0ghyx66g8ikx9nx1mnwqvdcqm11i1vlq0hnvwl50s48bp22q5v34";
2149 };
2148 };
2150 meta = {
2149 meta = {
2151 license = [ { fullName = "BSD-derived (http://www.repoze.org/LICENSE.txt)"; } ];
2150 license = [ { fullName = "BSD-derived (http://www.repoze.org/LICENSE.txt)"; } ];
2152 };
2151 };
2153 };
2152 };
2154 "vine" = super.buildPythonPackage {
2153 "vine" = super.buildPythonPackage {
2155 name = "vine-1.3.0";
2154 name = "vine-1.3.0";
2156 doCheck = false;
2155 doCheck = false;
2157 src = fetchurl {
2156 src = fetchurl {
2158 url = "https://files.pythonhosted.org/packages/1c/e1/79fb8046e607dd6c2ad05c9b8ebac9d0bd31d086a08f02699e96fc5b3046/vine-1.3.0.tar.gz";
2157 url = "https://files.pythonhosted.org/packages/1c/e1/79fb8046e607dd6c2ad05c9b8ebac9d0bd31d086a08f02699e96fc5b3046/vine-1.3.0.tar.gz";
2159 sha256 = "11ydsbhl1vabndc2r979dv61s6j2b0giq6dgvryifvq1m7bycghk";
2158 sha256 = "11ydsbhl1vabndc2r979dv61s6j2b0giq6dgvryifvq1m7bycghk";
2160 };
2159 };
2161 meta = {
2160 meta = {
2162 license = [ pkgs.lib.licenses.bsdOriginal ];
2161 license = [ pkgs.lib.licenses.bsdOriginal ];
2163 };
2162 };
2164 };
2163 };
2165 "waitress" = super.buildPythonPackage {
2164 "waitress" = super.buildPythonPackage {
2166 name = "waitress-1.3.1";
2165 name = "waitress-1.3.1";
2167 doCheck = false;
2166 doCheck = false;
2168 src = fetchurl {
2167 src = fetchurl {
2169 url = "https://files.pythonhosted.org/packages/a6/e6/708da7bba65898e5d759ade8391b1077e49d07be0b0223c39f5be04def56/waitress-1.3.1.tar.gz";
2168 url = "https://files.pythonhosted.org/packages/a6/e6/708da7bba65898e5d759ade8391b1077e49d07be0b0223c39f5be04def56/waitress-1.3.1.tar.gz";
2170 sha256 = "1iysl8ka3l4cdrr0r19fh1cv28q41mwpvgsb81ji7k4shkb0k3i7";
2169 sha256 = "1iysl8ka3l4cdrr0r19fh1cv28q41mwpvgsb81ji7k4shkb0k3i7";
2171 };
2170 };
2172 meta = {
2171 meta = {
2173 license = [ pkgs.lib.licenses.zpl21 ];
2172 license = [ pkgs.lib.licenses.zpl21 ];
2174 };
2173 };
2175 };
2174 };
2176 "wcwidth" = super.buildPythonPackage {
2175 "wcwidth" = super.buildPythonPackage {
2177 name = "wcwidth-0.1.7";
2176 name = "wcwidth-0.1.7";
2178 doCheck = false;
2177 doCheck = false;
2179 src = fetchurl {
2178 src = fetchurl {
2180 url = "https://files.pythonhosted.org/packages/55/11/e4a2bb08bb450fdbd42cc709dd40de4ed2c472cf0ccb9e64af22279c5495/wcwidth-0.1.7.tar.gz";
2179 url = "https://files.pythonhosted.org/packages/55/11/e4a2bb08bb450fdbd42cc709dd40de4ed2c472cf0ccb9e64af22279c5495/wcwidth-0.1.7.tar.gz";
2181 sha256 = "0pn6dflzm609m4r3i8ik5ni9ijjbb5fa3vg1n7hn6vkd49r77wrx";
2180 sha256 = "0pn6dflzm609m4r3i8ik5ni9ijjbb5fa3vg1n7hn6vkd49r77wrx";
2182 };
2181 };
2183 meta = {
2182 meta = {
2184 license = [ pkgs.lib.licenses.mit ];
2183 license = [ pkgs.lib.licenses.mit ];
2185 };
2184 };
2186 };
2185 };
2187 "webencodings" = super.buildPythonPackage {
2186 "webencodings" = super.buildPythonPackage {
2188 name = "webencodings-0.5.1";
2187 name = "webencodings-0.5.1";
2189 doCheck = false;
2188 doCheck = false;
2190 src = fetchurl {
2189 src = fetchurl {
2191 url = "https://files.pythonhosted.org/packages/0b/02/ae6ceac1baeda530866a85075641cec12989bd8d31af6d5ab4a3e8c92f47/webencodings-0.5.1.tar.gz";
2190 url = "https://files.pythonhosted.org/packages/0b/02/ae6ceac1baeda530866a85075641cec12989bd8d31af6d5ab4a3e8c92f47/webencodings-0.5.1.tar.gz";
2192 sha256 = "08qrgrc4hrximb2gqnl69g01s93rhf2842jfxdjljc1dbwj1qsmk";
2191 sha256 = "08qrgrc4hrximb2gqnl69g01s93rhf2842jfxdjljc1dbwj1qsmk";
2193 };
2192 };
2194 meta = {
2193 meta = {
2195 license = [ pkgs.lib.licenses.bsdOriginal ];
2194 license = [ pkgs.lib.licenses.bsdOriginal ];
2196 };
2195 };
2197 };
2196 };
2198 "weberror" = super.buildPythonPackage {
2197 "weberror" = super.buildPythonPackage {
2199 name = "weberror-0.10.3";
2198 name = "weberror-0.10.3";
2200 doCheck = false;
2199 doCheck = false;
2201 propagatedBuildInputs = [
2200 propagatedBuildInputs = [
2202 self."webob"
2201 self."webob"
2203 self."tempita"
2202 self."tempita"
2204 self."pygments"
2203 self."pygments"
2205 self."paste"
2204 self."paste"
2206 ];
2205 ];
2207 src = fetchurl {
2206 src = fetchurl {
2208 url = "https://files.pythonhosted.org/packages/35/76/e7e5c2ce7e9c7f31b54c1ff295a495886d1279a002557d74dd8957346a79/WebError-0.10.3.tar.gz";
2207 url = "https://files.pythonhosted.org/packages/35/76/e7e5c2ce7e9c7f31b54c1ff295a495886d1279a002557d74dd8957346a79/WebError-0.10.3.tar.gz";
2209 sha256 = "0frg4kvycqpj5bi8asfqfs6bxsr2cvjvb6b56c4d1ai1z57kbjx6";
2208 sha256 = "0frg4kvycqpj5bi8asfqfs6bxsr2cvjvb6b56c4d1ai1z57kbjx6";
2210 };
2209 };
2211 meta = {
2210 meta = {
2212 license = [ pkgs.lib.licenses.mit ];
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 "webhelpers2" = super.buildPythonPackage {
2214 "webhelpers2" = super.buildPythonPackage {
2230 name = "webhelpers2-2.0";
2215 name = "webhelpers2-2.0";
2231 doCheck = false;
2216 doCheck = false;
2232 propagatedBuildInputs = [
2217 propagatedBuildInputs = [
2233 self."markupsafe"
2218 self."markupsafe"
2234 self."six"
2219 self."six"
2235 ];
2220 ];
2236 src = fetchurl {
2221 src = fetchurl {
2237 url = "https://files.pythonhosted.org/packages/ff/30/56342c6ea522439e3662427c8d7b5e5b390dff4ff2dc92d8afcb8ab68b75/WebHelpers2-2.0.tar.gz";
2222 url = "https://files.pythonhosted.org/packages/ff/30/56342c6ea522439e3662427c8d7b5e5b390dff4ff2dc92d8afcb8ab68b75/WebHelpers2-2.0.tar.gz";
2238 sha256 = "0aphva1qmxh83n01p53f5fd43m4srzbnfbz5ajvbx9aj2aipwmcs";
2223 sha256 = "0aphva1qmxh83n01p53f5fd43m4srzbnfbz5ajvbx9aj2aipwmcs";
2239 };
2224 };
2240 meta = {
2225 meta = {
2241 license = [ pkgs.lib.licenses.mit ];
2226 license = [ pkgs.lib.licenses.mit ];
2242 };
2227 };
2243 };
2228 };
2244 "webob" = super.buildPythonPackage {
2229 "webob" = super.buildPythonPackage {
2245 name = "webob-1.8.5";
2230 name = "webob-1.8.5";
2246 doCheck = false;
2231 doCheck = false;
2247 src = fetchurl {
2232 src = fetchurl {
2248 url = "https://files.pythonhosted.org/packages/9d/1a/0c89c070ee2829c934cb6c7082287c822e28236a4fcf90063e6be7c35532/WebOb-1.8.5.tar.gz";
2233 url = "https://files.pythonhosted.org/packages/9d/1a/0c89c070ee2829c934cb6c7082287c822e28236a4fcf90063e6be7c35532/WebOb-1.8.5.tar.gz";
2249 sha256 = "11khpzaxc88q31v25ic330gsf56fwmbdc9b30br8mvp0fmwspah5";
2234 sha256 = "11khpzaxc88q31v25ic330gsf56fwmbdc9b30br8mvp0fmwspah5";
2250 };
2235 };
2251 meta = {
2236 meta = {
2252 license = [ pkgs.lib.licenses.mit ];
2237 license = [ pkgs.lib.licenses.mit ];
2253 };
2238 };
2254 };
2239 };
2255 "webtest" = super.buildPythonPackage {
2240 "webtest" = super.buildPythonPackage {
2256 name = "webtest-2.0.33";
2241 name = "webtest-2.0.33";
2257 doCheck = false;
2242 doCheck = false;
2258 propagatedBuildInputs = [
2243 propagatedBuildInputs = [
2259 self."six"
2244 self."six"
2260 self."webob"
2245 self."webob"
2261 self."waitress"
2246 self."waitress"
2262 self."beautifulsoup4"
2247 self."beautifulsoup4"
2263 ];
2248 ];
2264 src = fetchurl {
2249 src = fetchurl {
2265 url = "https://files.pythonhosted.org/packages/a8/b0/ffc9413b637dbe26e291429bb0f6ed731e518d0cd03da28524a8fe2e8a8f/WebTest-2.0.33.tar.gz";
2250 url = "https://files.pythonhosted.org/packages/a8/b0/ffc9413b637dbe26e291429bb0f6ed731e518d0cd03da28524a8fe2e8a8f/WebTest-2.0.33.tar.gz";
2266 sha256 = "1l3z0cwqslsf4rcrhi2gr8kdfh74wn2dw76376i4g9i38gz8wd21";
2251 sha256 = "1l3z0cwqslsf4rcrhi2gr8kdfh74wn2dw76376i4g9i38gz8wd21";
2267 };
2252 };
2268 meta = {
2253 meta = {
2269 license = [ pkgs.lib.licenses.mit ];
2254 license = [ pkgs.lib.licenses.mit ];
2270 };
2255 };
2271 };
2256 };
2272 "whoosh" = super.buildPythonPackage {
2257 "whoosh" = super.buildPythonPackage {
2273 name = "whoosh-2.7.4";
2258 name = "whoosh-2.7.4";
2274 doCheck = false;
2259 doCheck = false;
2275 src = fetchurl {
2260 src = fetchurl {
2276 url = "https://files.pythonhosted.org/packages/25/2b/6beed2107b148edc1321da0d489afc4617b9ed317ef7b72d4993cad9b684/Whoosh-2.7.4.tar.gz";
2261 url = "https://files.pythonhosted.org/packages/25/2b/6beed2107b148edc1321da0d489afc4617b9ed317ef7b72d4993cad9b684/Whoosh-2.7.4.tar.gz";
2277 sha256 = "10qsqdjpbc85fykc1vgcs8xwbgn4l2l52c8d83xf1q59pwyn79bw";
2262 sha256 = "10qsqdjpbc85fykc1vgcs8xwbgn4l2l52c8d83xf1q59pwyn79bw";
2278 };
2263 };
2279 meta = {
2264 meta = {
2280 license = [ pkgs.lib.licenses.bsdOriginal pkgs.lib.licenses.bsd2 ];
2265 license = [ pkgs.lib.licenses.bsdOriginal pkgs.lib.licenses.bsd2 ];
2281 };
2266 };
2282 };
2267 };
2283 "ws4py" = super.buildPythonPackage {
2268 "ws4py" = super.buildPythonPackage {
2284 name = "ws4py-0.5.1";
2269 name = "ws4py-0.5.1";
2285 doCheck = false;
2270 doCheck = false;
2286 src = fetchurl {
2271 src = fetchurl {
2287 url = "https://files.pythonhosted.org/packages/53/20/4019a739b2eefe9282d3822ef6a225250af964b117356971bd55e274193c/ws4py-0.5.1.tar.gz";
2272 url = "https://files.pythonhosted.org/packages/53/20/4019a739b2eefe9282d3822ef6a225250af964b117356971bd55e274193c/ws4py-0.5.1.tar.gz";
2288 sha256 = "10slbbf2jm4hpr92jx7kh7mhf48sjl01v2w4d8z3f1p0ybbp7l19";
2273 sha256 = "10slbbf2jm4hpr92jx7kh7mhf48sjl01v2w4d8z3f1p0ybbp7l19";
2289 };
2274 };
2290 meta = {
2275 meta = {
2291 license = [ pkgs.lib.licenses.bsdOriginal ];
2276 license = [ pkgs.lib.licenses.bsdOriginal ];
2292 };
2277 };
2293 };
2278 };
2294 "wsgiref" = super.buildPythonPackage {
2279 "wsgiref" = super.buildPythonPackage {
2295 name = "wsgiref-0.1.2";
2280 name = "wsgiref-0.1.2";
2296 doCheck = false;
2281 doCheck = false;
2297 src = fetchurl {
2282 src = fetchurl {
2298 url = "https://files.pythonhosted.org/packages/41/9e/309259ce8dff8c596e8c26df86dbc4e848b9249fd36797fd60be456f03fc/wsgiref-0.1.2.zip";
2283 url = "https://files.pythonhosted.org/packages/41/9e/309259ce8dff8c596e8c26df86dbc4e848b9249fd36797fd60be456f03fc/wsgiref-0.1.2.zip";
2299 sha256 = "0y8fyjmpq7vwwm4x732w97qbkw78rjwal5409k04cw4m03411rn7";
2284 sha256 = "0y8fyjmpq7vwwm4x732w97qbkw78rjwal5409k04cw4m03411rn7";
2300 };
2285 };
2301 meta = {
2286 meta = {
2302 license = [ { fullName = "PSF or ZPL"; } ];
2287 license = [ { fullName = "PSF or ZPL"; } ];
2303 };
2288 };
2304 };
2289 };
2305 "zipp" = super.buildPythonPackage {
2290 "zipp" = super.buildPythonPackage {
2306 name = "zipp-0.6.0";
2291 name = "zipp-0.6.0";
2307 doCheck = false;
2292 doCheck = false;
2308 propagatedBuildInputs = [
2293 propagatedBuildInputs = [
2309 self."more-itertools"
2294 self."more-itertools"
2310 ];
2295 ];
2311 src = fetchurl {
2296 src = fetchurl {
2312 url = "https://files.pythonhosted.org/packages/57/dd/585d728479d97d25aeeb9aa470d36a4ad8d0ba5610f84e14770128ce6ff7/zipp-0.6.0.tar.gz";
2297 url = "https://files.pythonhosted.org/packages/57/dd/585d728479d97d25aeeb9aa470d36a4ad8d0ba5610f84e14770128ce6ff7/zipp-0.6.0.tar.gz";
2313 sha256 = "13ndkf7vklw978a4gdl1yfvn8hch28429a0iam67sg4nrp5v261p";
2298 sha256 = "13ndkf7vklw978a4gdl1yfvn8hch28429a0iam67sg4nrp5v261p";
2314 };
2299 };
2315 meta = {
2300 meta = {
2316 license = [ pkgs.lib.licenses.mit ];
2301 license = [ pkgs.lib.licenses.mit ];
2317 };
2302 };
2318 };
2303 };
2319 "zope.cachedescriptors" = super.buildPythonPackage {
2304 "zope.cachedescriptors" = super.buildPythonPackage {
2320 name = "zope.cachedescriptors-4.3.1";
2305 name = "zope.cachedescriptors-4.3.1";
2321 doCheck = false;
2306 doCheck = false;
2322 propagatedBuildInputs = [
2307 propagatedBuildInputs = [
2323 self."setuptools"
2308 self."setuptools"
2324 ];
2309 ];
2325 src = fetchurl {
2310 src = fetchurl {
2326 url = "https://files.pythonhosted.org/packages/2f/89/ebe1890cc6d3291ebc935558fa764d5fffe571018dbbee200e9db78762cb/zope.cachedescriptors-4.3.1.tar.gz";
2311 url = "https://files.pythonhosted.org/packages/2f/89/ebe1890cc6d3291ebc935558fa764d5fffe571018dbbee200e9db78762cb/zope.cachedescriptors-4.3.1.tar.gz";
2327 sha256 = "0jhr3m5p74c6r7k8iv0005b8bfsialih9d7zl5vx38rf5xq1lk8z";
2312 sha256 = "0jhr3m5p74c6r7k8iv0005b8bfsialih9d7zl5vx38rf5xq1lk8z";
2328 };
2313 };
2329 meta = {
2314 meta = {
2330 license = [ pkgs.lib.licenses.zpl21 ];
2315 license = [ pkgs.lib.licenses.zpl21 ];
2331 };
2316 };
2332 };
2317 };
2333 "zope.deprecation" = super.buildPythonPackage {
2318 "zope.deprecation" = super.buildPythonPackage {
2334 name = "zope.deprecation-4.4.0";
2319 name = "zope.deprecation-4.4.0";
2335 doCheck = false;
2320 doCheck = false;
2336 propagatedBuildInputs = [
2321 propagatedBuildInputs = [
2337 self."setuptools"
2322 self."setuptools"
2338 ];
2323 ];
2339 src = fetchurl {
2324 src = fetchurl {
2340 url = "https://files.pythonhosted.org/packages/34/da/46e92d32d545dd067b9436279d84c339e8b16de2ca393d7b892bc1e1e9fd/zope.deprecation-4.4.0.tar.gz";
2325 url = "https://files.pythonhosted.org/packages/34/da/46e92d32d545dd067b9436279d84c339e8b16de2ca393d7b892bc1e1e9fd/zope.deprecation-4.4.0.tar.gz";
2341 sha256 = "1pz2cv7gv9y1r3m0bdv7ks1alagmrn5msm5spwdzkb2by0w36i8d";
2326 sha256 = "1pz2cv7gv9y1r3m0bdv7ks1alagmrn5msm5spwdzkb2by0w36i8d";
2342 };
2327 };
2343 meta = {
2328 meta = {
2344 license = [ pkgs.lib.licenses.zpl21 ];
2329 license = [ pkgs.lib.licenses.zpl21 ];
2345 };
2330 };
2346 };
2331 };
2347 "zope.event" = super.buildPythonPackage {
2332 "zope.event" = super.buildPythonPackage {
2348 name = "zope.event-4.4";
2333 name = "zope.event-4.4";
2349 doCheck = false;
2334 doCheck = false;
2350 propagatedBuildInputs = [
2335 propagatedBuildInputs = [
2351 self."setuptools"
2336 self."setuptools"
2352 ];
2337 ];
2353 src = fetchurl {
2338 src = fetchurl {
2354 url = "https://files.pythonhosted.org/packages/4c/b2/51c0369adcf5be2334280eed230192ab3b03f81f8efda9ddea6f65cc7b32/zope.event-4.4.tar.gz";
2339 url = "https://files.pythonhosted.org/packages/4c/b2/51c0369adcf5be2334280eed230192ab3b03f81f8efda9ddea6f65cc7b32/zope.event-4.4.tar.gz";
2355 sha256 = "1ksbc726av9xacml6jhcfyn828hlhb9xlddpx6fcvnlvmpmpvhk9";
2340 sha256 = "1ksbc726av9xacml6jhcfyn828hlhb9xlddpx6fcvnlvmpmpvhk9";
2356 };
2341 };
2357 meta = {
2342 meta = {
2358 license = [ pkgs.lib.licenses.zpl21 ];
2343 license = [ pkgs.lib.licenses.zpl21 ];
2359 };
2344 };
2360 };
2345 };
2361 "zope.interface" = super.buildPythonPackage {
2346 "zope.interface" = super.buildPythonPackage {
2362 name = "zope.interface-4.6.0";
2347 name = "zope.interface-4.6.0";
2363 doCheck = false;
2348 doCheck = false;
2364 propagatedBuildInputs = [
2349 propagatedBuildInputs = [
2365 self."setuptools"
2350 self."setuptools"
2366 ];
2351 ];
2367 src = fetchurl {
2352 src = fetchurl {
2368 url = "https://files.pythonhosted.org/packages/4e/d0/c9d16bd5b38de44a20c6dc5d5ed80a49626fafcb3db9f9efdc2a19026db6/zope.interface-4.6.0.tar.gz";
2353 url = "https://files.pythonhosted.org/packages/4e/d0/c9d16bd5b38de44a20c6dc5d5ed80a49626fafcb3db9f9efdc2a19026db6/zope.interface-4.6.0.tar.gz";
2369 sha256 = "1rgh2x3rcl9r0v0499kf78xy86rnmanajf4ywmqb943wpk50sg8v";
2354 sha256 = "1rgh2x3rcl9r0v0499kf78xy86rnmanajf4ywmqb943wpk50sg8v";
2370 };
2355 };
2371 meta = {
2356 meta = {
2372 license = [ pkgs.lib.licenses.zpl21 ];
2357 license = [ pkgs.lib.licenses.zpl21 ];
2373 };
2358 };
2374 };
2359 };
2375
2360
2376 ### Test requirements
2361 ### Test requirements
2377
2362
2378
2363
2379 }
2364 }
@@ -1,123 +1,122 b''
1 ## dependencies
1 ## dependencies
2
2
3 amqp==2.5.2
3 amqp==2.5.2
4 babel==1.3
4 babel==1.3
5 beaker==1.9.1
5 beaker==1.9.1
6 bleach==3.1.0
6 bleach==3.1.0
7 celery==4.3.0
7 celery==4.3.0
8 channelstream==0.5.2
8 channelstream==0.5.2
9 click==7.0
9 click==7.0
10 colander==1.7.0
10 colander==1.7.0
11 # our custom configobj
11 # our custom configobj
12 https://code.rhodecode.com/upstream/configobj/artifacts/download/0-012de99a-b1e1-4f64-a5c0-07a98a41b324.tar.gz?md5=6a513f51fe04b2c18cf84c1395a7c626#egg=configobj==5.0.6
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 cssselect==1.0.3
13 cssselect==1.0.3
14 cryptography==2.6.1
14 cryptography==2.6.1
15 decorator==4.1.2
15 decorator==4.1.2
16 deform==2.0.8
16 deform==2.0.8
17 docutils==0.14.0
17 docutils==0.14.0
18 dogpile.cache==0.9.0
18 dogpile.cache==0.9.0
19 dogpile.core==0.4.1
19 dogpile.core==0.4.1
20 formencode==1.2.4
20 formencode==1.2.4
21 future==0.14.3
21 future==0.14.3
22 futures==3.0.2
22 futures==3.0.2
23 infrae.cache==1.0.1
23 infrae.cache==1.0.1
24 iso8601==0.1.12
24 iso8601==0.1.12
25 itsdangerous==0.24
25 itsdangerous==0.24
26 kombu==4.6.6
26 kombu==4.6.6
27 lxml==4.2.5
27 lxml==4.2.5
28 mako==1.1.0
28 mako==1.1.0
29 markdown==2.6.11
29 markdown==2.6.11
30 markupsafe==1.1.1
30 markupsafe==1.1.1
31 msgpack-python==0.5.6
31 msgpack-python==0.5.6
32 pyotp==2.3.0
32 pyotp==2.3.0
33 packaging==19.2
33 packaging==19.2
34 pathlib2==2.3.5
34 pathlib2==2.3.5
35 paste==3.2.1
35 paste==3.2.1
36 pastedeploy==2.0.1
36 pastedeploy==2.0.1
37 pastescript==3.2.0
37 pastescript==3.2.0
38 peppercorn==0.6
38 peppercorn==0.6
39 psutil==5.6.5
39 psutil==5.6.5
40 py-bcrypt==0.4
40 py-bcrypt==0.4
41 pycurl==7.43.0.3
41 pycurl==7.43.0.3
42 pycrypto==2.6.1
42 pycrypto==2.6.1
43 pygments==2.4.2
43 pygments==2.4.2
44 pyparsing==2.4.2
44 pyparsing==2.4.2
45 pyramid-debugtoolbar==4.5.1
45 pyramid-debugtoolbar==4.5.1
46 pyramid-mako==1.1.0
46 pyramid-mako==1.1.0
47 pyramid==1.10.4
47 pyramid==1.10.4
48 pyramid_mailer==0.15.1
48 pyramid_mailer==0.15.1
49 python-dateutil==2.8.1
49 python-dateutil==2.8.1
50 python-ldap==3.1.0
50 python-ldap==3.1.0
51 python-memcached==1.59
51 python-memcached==1.59
52 python-pam==1.8.4
52 python-pam==1.8.4
53 python-saml==2.4.2
53 python-saml==2.4.2
54 pytz==2019.2
54 pytz==2019.2
55 tzlocal==1.5.1
55 tzlocal==1.5.1
56 pyzmq==14.6.0
56 pyzmq==14.6.0
57 py-gfm==0.1.4
57 py-gfm==0.1.4
58 redis==3.3.11
58 redis==3.3.11
59 repoze.lru==0.7
59 repoze.lru==0.7
60 requests==2.9.1
60 requests==2.9.1
61 routes==2.4.1
61 routes==2.4.1
62 simplejson==3.16.0
62 simplejson==3.16.0
63 six==1.11.0
63 six==1.11.0
64 sqlalchemy==1.3.11
64 sqlalchemy==1.3.11
65 sshpubkeys==3.1.0
65 sshpubkeys==3.1.0
66 subprocess32==3.5.4
66 subprocess32==3.5.4
67 supervisor==4.1.0
67 supervisor==4.1.0
68 translationstring==1.3
68 translationstring==1.3
69 urllib3==1.24.1
69 urllib3==1.24.1
70 urlobject==2.4.3
70 urlobject==2.4.3
71 venusian==1.2.0
71 venusian==1.2.0
72 weberror==0.10.3
72 weberror==0.10.3
73 webhelpers2==2.0
73 webhelpers2==2.0
74 webhelpers==1.3
75 webob==1.8.5
74 webob==1.8.5
76 whoosh==2.7.4
75 whoosh==2.7.4
77 wsgiref==0.1.2
76 wsgiref==0.1.2
78 zope.cachedescriptors==4.3.1
77 zope.cachedescriptors==4.3.1
79 zope.deprecation==4.4.0
78 zope.deprecation==4.4.0
80 zope.event==4.4.0
79 zope.event==4.4.0
81 zope.interface==4.6.0
80 zope.interface==4.6.0
82
81
83 # DB drivers
82 # DB drivers
84 mysql-python==1.2.5
83 mysql-python==1.2.5
85 pymysql==0.8.1
84 pymysql==0.8.1
86 pysqlite==2.8.3
85 pysqlite==2.8.3
87 psycopg2==2.8.4
86 psycopg2==2.8.4
88
87
89 # IPYTHON RENDERING
88 # IPYTHON RENDERING
90 # entrypoints backport, pypi version doesn't support egg installs
89 # entrypoints backport, pypi version doesn't support egg installs
91 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
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 nbconvert==5.3.1
91 nbconvert==5.3.1
93 nbformat==4.4.0
92 nbformat==4.4.0
94 jupyter-client==5.0.0
93 jupyter-client==5.0.0
95 jupyter-core==4.5.0
94 jupyter-core==4.5.0
96
95
97 ## cli tools
96 ## cli tools
98 alembic==1.3.1
97 alembic==1.3.1
99 invoke==0.13.0
98 invoke==0.13.0
100 bumpversion==0.5.3
99 bumpversion==0.5.3
101
100
102 ## http servers
101 ## http servers
103 gevent==1.4.0
102 gevent==1.4.0
104 greenlet==0.4.15
103 greenlet==0.4.15
105 gunicorn==19.9.0
104 gunicorn==19.9.0
106 waitress==1.3.1
105 waitress==1.3.1
107
106
108 ## debug
107 ## debug
109 ipdb==0.12.0
108 ipdb==0.12.0
110 ipython==5.1.0
109 ipython==5.1.0
111
110
112 ## rhodecode-tools, special case
111 ## rhodecode-tools, special case
113 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
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 ## appenlight
115 ## appenlight
117 appenlight-client==0.6.26
116 appenlight-client==0.6.26
118
117
119 ## test related requirements
118 ## test related requirements
120 -r requirements_test.txt
119 -r requirements_test.txt
121
120
122 ## uncomment to add the debug libraries
121 ## uncomment to add the debug libraries
123 #-r requirements_debug.txt
122 #-r requirements_debug.txt
@@ -1,90 +1,93 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2016-2019 RhodeCode GmbH
3 # Copyright (C) 2016-2019 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import logging
21 import logging
22
22
23 from pyramid.httpexceptions import HTTPNotFound
23 from pyramid.httpexceptions import HTTPNotFound
24 from pyramid.view import view_config
24 from pyramid.view import view_config
25
25
26 from rhodecode.apps._base import BaseAppView
26 from rhodecode.apps._base import BaseAppView
27 from rhodecode.model.db import joinedload, UserLog
27 from rhodecode.model.db import joinedload, UserLog
28 from rhodecode.lib.user_log_filter import user_log_filter
28 from rhodecode.lib.user_log_filter import user_log_filter
29 from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator
29 from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator
30 from rhodecode.lib.utils2 import safe_int
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 log = logging.getLogger(__name__)
33 log = logging.getLogger(__name__)
34
34
35
35
36 class AdminAuditLogsView(BaseAppView):
36 class AdminAuditLogsView(BaseAppView):
37 def load_default_context(self):
37 def load_default_context(self):
38 c = self._get_local_tmpl_context()
38 c = self._get_local_tmpl_context()
39 return c
39 return c
40
40
41 @LoginRequired()
41 @LoginRequired()
42 @HasPermissionAllDecorator('hg.admin')
42 @HasPermissionAllDecorator('hg.admin')
43 @view_config(
43 @view_config(
44 route_name='admin_audit_logs', request_method='GET',
44 route_name='admin_audit_logs', request_method='GET',
45 renderer='rhodecode:templates/admin/admin_audit_logs.mako')
45 renderer='rhodecode:templates/admin/admin_audit_logs.mako')
46 def admin_audit_logs(self):
46 def admin_audit_logs(self):
47 c = self.load_default_context()
47 c = self.load_default_context()
48
48
49 users_log = UserLog.query()\
49 users_log = UserLog.query()\
50 .options(joinedload(UserLog.user))\
50 .options(joinedload(UserLog.user))\
51 .options(joinedload(UserLog.repository))
51 .options(joinedload(UserLog.repository))
52
52
53 # FILTERING
53 # FILTERING
54 c.search_term = self.request.GET.get('filter')
54 c.search_term = self.request.GET.get('filter')
55 try:
55 try:
56 users_log = user_log_filter(users_log, c.search_term)
56 users_log = user_log_filter(users_log, c.search_term)
57 except Exception:
57 except Exception:
58 # we want this to crash for now
58 # we want this to crash for now
59 raise
59 raise
60
60
61 users_log = users_log.order_by(UserLog.action_date.desc())
61 users_log = users_log.order_by(UserLog.action_date.desc())
62
62
63 p = safe_int(self.request.GET.get('page', 1), 1)
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 if c.search_term:
69 if c.search_term:
67 kw['filter'] = c.search_term
70 query_params['filter'] = c.search_term
68 return self.request.current_route_path(_query=kw)
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,
73 c.audit_logs = SqlPage(users_log, page=p, items_per_page=10,
71 url=url_generator)
74 url_maker=url_generator)
72 return self._get_template_context(c)
75 return self._get_template_context(c)
73
76
74 @LoginRequired()
77 @LoginRequired()
75 @HasPermissionAllDecorator('hg.admin')
78 @HasPermissionAllDecorator('hg.admin')
76 @view_config(
79 @view_config(
77 route_name='admin_audit_log_entry', request_method='GET',
80 route_name='admin_audit_log_entry', request_method='GET',
78 renderer='rhodecode:templates/admin/admin_audit_log_entry.mako')
81 renderer='rhodecode:templates/admin/admin_audit_log_entry.mako')
79 def admin_audit_log_entry(self):
82 def admin_audit_log_entry(self):
80 c = self.load_default_context()
83 c = self.load_default_context()
81 audit_log_id = self.request.matchdict['audit_log_id']
84 audit_log_id = self.request.matchdict['audit_log_id']
82
85
83 c.audit_log_entry = UserLog.query()\
86 c.audit_log_entry = UserLog.query()\
84 .options(joinedload(UserLog.user))\
87 .options(joinedload(UserLog.user))\
85 .options(joinedload(UserLog.repository))\
88 .options(joinedload(UserLog.repository))\
86 .filter(UserLog.user_log_id == audit_log_id).scalar()
89 .filter(UserLog.user_log_id == audit_log_id).scalar()
87 if not c.audit_log_entry:
90 if not c.audit_log_entry:
88 raise HTTPNotFound()
91 raise HTTPNotFound()
89
92
90 return self._get_template_context(c)
93 return self._get_template_context(c)
@@ -1,1329 +1,1333 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2016-2019 RhodeCode GmbH
3 # Copyright (C) 2016-2019 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import logging
21 import logging
22 import datetime
22 import datetime
23 import formencode
23 import formencode
24 import formencode.htmlfill
24 import formencode.htmlfill
25
25
26 from pyramid.httpexceptions import HTTPFound
26 from pyramid.httpexceptions import HTTPFound
27 from pyramid.view import view_config
27 from pyramid.view import view_config
28 from pyramid.renderers import render
28 from pyramid.renderers import render
29 from pyramid.response import Response
29 from pyramid.response import Response
30
30
31 from rhodecode import events
31 from rhodecode import events
32 from rhodecode.apps._base import BaseAppView, DataGridAppView, UserAppView
32 from rhodecode.apps._base import BaseAppView, DataGridAppView, UserAppView
33 from rhodecode.apps.ssh_support import SshKeyFileChangeEvent
33 from rhodecode.apps.ssh_support import SshKeyFileChangeEvent
34 from rhodecode.authentication.base import get_authn_registry, RhodeCodeExternalAuthPlugin
34 from rhodecode.authentication.base import get_authn_registry, RhodeCodeExternalAuthPlugin
35 from rhodecode.authentication.plugins import auth_rhodecode
35 from rhodecode.authentication.plugins import auth_rhodecode
36 from rhodecode.events import trigger
36 from rhodecode.events import trigger
37 from rhodecode.model.db import true
37 from rhodecode.model.db import true
38
38
39 from rhodecode.lib import audit_logger, rc_cache
39 from rhodecode.lib import audit_logger, rc_cache
40 from rhodecode.lib.exceptions import (
40 from rhodecode.lib.exceptions import (
41 UserCreationError, UserOwnsReposException, UserOwnsRepoGroupsException,
41 UserCreationError, UserOwnsReposException, UserOwnsRepoGroupsException,
42 UserOwnsUserGroupsException, DefaultUserException)
42 UserOwnsUserGroupsException, DefaultUserException)
43 from rhodecode.lib.ext_json import json
43 from rhodecode.lib.ext_json import json
44 from rhodecode.lib.auth import (
44 from rhodecode.lib.auth import (
45 LoginRequired, HasPermissionAllDecorator, CSRFRequired)
45 LoginRequired, HasPermissionAllDecorator, CSRFRequired)
46 from rhodecode.lib import helpers as h
46 from rhodecode.lib import helpers as h
47 from rhodecode.lib.helpers import SqlPage
47 from rhodecode.lib.utils2 import safe_int, safe_unicode, AttributeDict
48 from rhodecode.lib.utils2 import safe_int, safe_unicode, AttributeDict
48 from rhodecode.model.auth_token import AuthTokenModel
49 from rhodecode.model.auth_token import AuthTokenModel
49 from rhodecode.model.forms import (
50 from rhodecode.model.forms import (
50 UserForm, UserIndividualPermissionsForm, UserPermissionsForm,
51 UserForm, UserIndividualPermissionsForm, UserPermissionsForm,
51 UserExtraEmailForm, UserExtraIpForm)
52 UserExtraEmailForm, UserExtraIpForm)
52 from rhodecode.model.permission import PermissionModel
53 from rhodecode.model.permission import PermissionModel
53 from rhodecode.model.repo_group import RepoGroupModel
54 from rhodecode.model.repo_group import RepoGroupModel
54 from rhodecode.model.ssh_key import SshKeyModel
55 from rhodecode.model.ssh_key import SshKeyModel
55 from rhodecode.model.user import UserModel
56 from rhodecode.model.user import UserModel
56 from rhodecode.model.user_group import UserGroupModel
57 from rhodecode.model.user_group import UserGroupModel
57 from rhodecode.model.db import (
58 from rhodecode.model.db import (
58 or_, coalesce,IntegrityError, User, UserGroup, UserIpMap, UserEmailMap,
59 or_, coalesce,IntegrityError, User, UserGroup, UserIpMap, UserEmailMap,
59 UserApiKeys, UserSshKeys, RepoGroup)
60 UserApiKeys, UserSshKeys, RepoGroup)
60 from rhodecode.model.meta import Session
61 from rhodecode.model.meta import Session
61
62
62 log = logging.getLogger(__name__)
63 log = logging.getLogger(__name__)
63
64
64
65
65 class AdminUsersView(BaseAppView, DataGridAppView):
66 class AdminUsersView(BaseAppView, DataGridAppView):
66
67
67 def load_default_context(self):
68 def load_default_context(self):
68 c = self._get_local_tmpl_context()
69 c = self._get_local_tmpl_context()
69 return c
70 return c
70
71
71 @LoginRequired()
72 @LoginRequired()
72 @HasPermissionAllDecorator('hg.admin')
73 @HasPermissionAllDecorator('hg.admin')
73 @view_config(
74 @view_config(
74 route_name='users', request_method='GET',
75 route_name='users', request_method='GET',
75 renderer='rhodecode:templates/admin/users/users.mako')
76 renderer='rhodecode:templates/admin/users/users.mako')
76 def users_list(self):
77 def users_list(self):
77 c = self.load_default_context()
78 c = self.load_default_context()
78 return self._get_template_context(c)
79 return self._get_template_context(c)
79
80
80 @LoginRequired()
81 @LoginRequired()
81 @HasPermissionAllDecorator('hg.admin')
82 @HasPermissionAllDecorator('hg.admin')
82 @view_config(
83 @view_config(
83 # renderer defined below
84 # renderer defined below
84 route_name='users_data', request_method='GET',
85 route_name='users_data', request_method='GET',
85 renderer='json_ext', xhr=True)
86 renderer='json_ext', xhr=True)
86 def users_list_data(self):
87 def users_list_data(self):
87 self.load_default_context()
88 self.load_default_context()
88 column_map = {
89 column_map = {
89 'first_name': 'name',
90 'first_name': 'name',
90 'last_name': 'lastname',
91 'last_name': 'lastname',
91 }
92 }
92 draw, start, limit = self._extract_chunk(self.request)
93 draw, start, limit = self._extract_chunk(self.request)
93 search_q, order_by, order_dir = self._extract_ordering(
94 search_q, order_by, order_dir = self._extract_ordering(
94 self.request, column_map=column_map)
95 self.request, column_map=column_map)
95 _render = self.request.get_partial_renderer(
96 _render = self.request.get_partial_renderer(
96 'rhodecode:templates/data_table/_dt_elements.mako')
97 'rhodecode:templates/data_table/_dt_elements.mako')
97
98
98 def user_actions(user_id, username):
99 def user_actions(user_id, username):
99 return _render("user_actions", user_id, username)
100 return _render("user_actions", user_id, username)
100
101
101 users_data_total_count = User.query()\
102 users_data_total_count = User.query()\
102 .filter(User.username != User.DEFAULT_USER) \
103 .filter(User.username != User.DEFAULT_USER) \
103 .count()
104 .count()
104
105
105 users_data_total_inactive_count = User.query()\
106 users_data_total_inactive_count = User.query()\
106 .filter(User.username != User.DEFAULT_USER) \
107 .filter(User.username != User.DEFAULT_USER) \
107 .filter(User.active != true())\
108 .filter(User.active != true())\
108 .count()
109 .count()
109
110
110 # json generate
111 # json generate
111 base_q = User.query().filter(User.username != User.DEFAULT_USER)
112 base_q = User.query().filter(User.username != User.DEFAULT_USER)
112 base_inactive_q = base_q.filter(User.active != true())
113 base_inactive_q = base_q.filter(User.active != true())
113
114
114 if search_q:
115 if search_q:
115 like_expression = u'%{}%'.format(safe_unicode(search_q))
116 like_expression = u'%{}%'.format(safe_unicode(search_q))
116 base_q = base_q.filter(or_(
117 base_q = base_q.filter(or_(
117 User.username.ilike(like_expression),
118 User.username.ilike(like_expression),
118 User._email.ilike(like_expression),
119 User._email.ilike(like_expression),
119 User.name.ilike(like_expression),
120 User.name.ilike(like_expression),
120 User.lastname.ilike(like_expression),
121 User.lastname.ilike(like_expression),
121 ))
122 ))
122 base_inactive_q = base_q.filter(User.active != true())
123 base_inactive_q = base_q.filter(User.active != true())
123
124
124 users_data_total_filtered_count = base_q.count()
125 users_data_total_filtered_count = base_q.count()
125 users_data_total_filtered_inactive_count = base_inactive_q.count()
126 users_data_total_filtered_inactive_count = base_inactive_q.count()
126
127
127 sort_col = getattr(User, order_by, None)
128 sort_col = getattr(User, order_by, None)
128 if sort_col:
129 if sort_col:
129 if order_dir == 'asc':
130 if order_dir == 'asc':
130 # handle null values properly to order by NULL last
131 # handle null values properly to order by NULL last
131 if order_by in ['last_activity']:
132 if order_by in ['last_activity']:
132 sort_col = coalesce(sort_col, datetime.date.max)
133 sort_col = coalesce(sort_col, datetime.date.max)
133 sort_col = sort_col.asc()
134 sort_col = sort_col.asc()
134 else:
135 else:
135 # handle null values properly to order by NULL last
136 # handle null values properly to order by NULL last
136 if order_by in ['last_activity']:
137 if order_by in ['last_activity']:
137 sort_col = coalesce(sort_col, datetime.date.min)
138 sort_col = coalesce(sort_col, datetime.date.min)
138 sort_col = sort_col.desc()
139 sort_col = sort_col.desc()
139
140
140 base_q = base_q.order_by(sort_col)
141 base_q = base_q.order_by(sort_col)
141 base_q = base_q.offset(start).limit(limit)
142 base_q = base_q.offset(start).limit(limit)
142
143
143 users_list = base_q.all()
144 users_list = base_q.all()
144
145
145 users_data = []
146 users_data = []
146 for user in users_list:
147 for user in users_list:
147 users_data.append({
148 users_data.append({
148 "username": h.gravatar_with_user(self.request, user.username),
149 "username": h.gravatar_with_user(self.request, user.username),
149 "email": user.email,
150 "email": user.email,
150 "first_name": user.first_name,
151 "first_name": user.first_name,
151 "last_name": user.last_name,
152 "last_name": user.last_name,
152 "last_login": h.format_date(user.last_login),
153 "last_login": h.format_date(user.last_login),
153 "last_activity": h.format_date(user.last_activity),
154 "last_activity": h.format_date(user.last_activity),
154 "active": h.bool2icon(user.active),
155 "active": h.bool2icon(user.active),
155 "active_raw": user.active,
156 "active_raw": user.active,
156 "admin": h.bool2icon(user.admin),
157 "admin": h.bool2icon(user.admin),
157 "extern_type": user.extern_type,
158 "extern_type": user.extern_type,
158 "extern_name": user.extern_name,
159 "extern_name": user.extern_name,
159 "action": user_actions(user.user_id, user.username),
160 "action": user_actions(user.user_id, user.username),
160 })
161 })
161 data = ({
162 data = ({
162 'draw': draw,
163 'draw': draw,
163 'data': users_data,
164 'data': users_data,
164 'recordsTotal': users_data_total_count,
165 'recordsTotal': users_data_total_count,
165 'recordsFiltered': users_data_total_filtered_count,
166 'recordsFiltered': users_data_total_filtered_count,
166 'recordsTotalInactive': users_data_total_inactive_count,
167 'recordsTotalInactive': users_data_total_inactive_count,
167 'recordsFilteredInactive': users_data_total_filtered_inactive_count
168 'recordsFilteredInactive': users_data_total_filtered_inactive_count
168 })
169 })
169
170
170 return data
171 return data
171
172
172 def _set_personal_repo_group_template_vars(self, c_obj):
173 def _set_personal_repo_group_template_vars(self, c_obj):
173 DummyUser = AttributeDict({
174 DummyUser = AttributeDict({
174 'username': '${username}',
175 'username': '${username}',
175 'user_id': '${user_id}',
176 'user_id': '${user_id}',
176 })
177 })
177 c_obj.default_create_repo_group = RepoGroupModel() \
178 c_obj.default_create_repo_group = RepoGroupModel() \
178 .get_default_create_personal_repo_group()
179 .get_default_create_personal_repo_group()
179 c_obj.personal_repo_group_name = RepoGroupModel() \
180 c_obj.personal_repo_group_name = RepoGroupModel() \
180 .get_personal_group_name(DummyUser)
181 .get_personal_group_name(DummyUser)
181
182
182 @LoginRequired()
183 @LoginRequired()
183 @HasPermissionAllDecorator('hg.admin')
184 @HasPermissionAllDecorator('hg.admin')
184 @view_config(
185 @view_config(
185 route_name='users_new', request_method='GET',
186 route_name='users_new', request_method='GET',
186 renderer='rhodecode:templates/admin/users/user_add.mako')
187 renderer='rhodecode:templates/admin/users/user_add.mako')
187 def users_new(self):
188 def users_new(self):
188 _ = self.request.translate
189 _ = self.request.translate
189 c = self.load_default_context()
190 c = self.load_default_context()
190 c.default_extern_type = auth_rhodecode.RhodeCodeAuthPlugin.uid
191 c.default_extern_type = auth_rhodecode.RhodeCodeAuthPlugin.uid
191 self._set_personal_repo_group_template_vars(c)
192 self._set_personal_repo_group_template_vars(c)
192 return self._get_template_context(c)
193 return self._get_template_context(c)
193
194
194 @LoginRequired()
195 @LoginRequired()
195 @HasPermissionAllDecorator('hg.admin')
196 @HasPermissionAllDecorator('hg.admin')
196 @CSRFRequired()
197 @CSRFRequired()
197 @view_config(
198 @view_config(
198 route_name='users_create', request_method='POST',
199 route_name='users_create', request_method='POST',
199 renderer='rhodecode:templates/admin/users/user_add.mako')
200 renderer='rhodecode:templates/admin/users/user_add.mako')
200 def users_create(self):
201 def users_create(self):
201 _ = self.request.translate
202 _ = self.request.translate
202 c = self.load_default_context()
203 c = self.load_default_context()
203 c.default_extern_type = auth_rhodecode.RhodeCodeAuthPlugin.uid
204 c.default_extern_type = auth_rhodecode.RhodeCodeAuthPlugin.uid
204 user_model = UserModel()
205 user_model = UserModel()
205 user_form = UserForm(self.request.translate)()
206 user_form = UserForm(self.request.translate)()
206 try:
207 try:
207 form_result = user_form.to_python(dict(self.request.POST))
208 form_result = user_form.to_python(dict(self.request.POST))
208 user = user_model.create(form_result)
209 user = user_model.create(form_result)
209 Session().flush()
210 Session().flush()
210 creation_data = user.get_api_data()
211 creation_data = user.get_api_data()
211 username = form_result['username']
212 username = form_result['username']
212
213
213 audit_logger.store_web(
214 audit_logger.store_web(
214 'user.create', action_data={'data': creation_data},
215 'user.create', action_data={'data': creation_data},
215 user=c.rhodecode_user)
216 user=c.rhodecode_user)
216
217
217 user_link = h.link_to(
218 user_link = h.link_to(
218 h.escape(username),
219 h.escape(username),
219 h.route_path('user_edit', user_id=user.user_id))
220 h.route_path('user_edit', user_id=user.user_id))
220 h.flash(h.literal(_('Created user %(user_link)s')
221 h.flash(h.literal(_('Created user %(user_link)s')
221 % {'user_link': user_link}), category='success')
222 % {'user_link': user_link}), category='success')
222 Session().commit()
223 Session().commit()
223 except formencode.Invalid as errors:
224 except formencode.Invalid as errors:
224 self._set_personal_repo_group_template_vars(c)
225 self._set_personal_repo_group_template_vars(c)
225 data = render(
226 data = render(
226 'rhodecode:templates/admin/users/user_add.mako',
227 'rhodecode:templates/admin/users/user_add.mako',
227 self._get_template_context(c), self.request)
228 self._get_template_context(c), self.request)
228 html = formencode.htmlfill.render(
229 html = formencode.htmlfill.render(
229 data,
230 data,
230 defaults=errors.value,
231 defaults=errors.value,
231 errors=errors.error_dict or {},
232 errors=errors.error_dict or {},
232 prefix_error=False,
233 prefix_error=False,
233 encoding="UTF-8",
234 encoding="UTF-8",
234 force_defaults=False
235 force_defaults=False
235 )
236 )
236 return Response(html)
237 return Response(html)
237 except UserCreationError as e:
238 except UserCreationError as e:
238 h.flash(e, 'error')
239 h.flash(e, 'error')
239 except Exception:
240 except Exception:
240 log.exception("Exception creation of user")
241 log.exception("Exception creation of user")
241 h.flash(_('Error occurred during creation of user %s')
242 h.flash(_('Error occurred during creation of user %s')
242 % self.request.POST.get('username'), category='error')
243 % self.request.POST.get('username'), category='error')
243 raise HTTPFound(h.route_path('users'))
244 raise HTTPFound(h.route_path('users'))
244
245
245
246
246 class UsersView(UserAppView):
247 class UsersView(UserAppView):
247 ALLOW_SCOPED_TOKENS = False
248 ALLOW_SCOPED_TOKENS = False
248 """
249 """
249 This view has alternative version inside EE, if modified please take a look
250 This view has alternative version inside EE, if modified please take a look
250 in there as well.
251 in there as well.
251 """
252 """
252
253
253 def get_auth_plugins(self):
254 def get_auth_plugins(self):
254 valid_plugins = []
255 valid_plugins = []
255 authn_registry = get_authn_registry(self.request.registry)
256 authn_registry = get_authn_registry(self.request.registry)
256 for plugin in authn_registry.get_plugins_for_authentication():
257 for plugin in authn_registry.get_plugins_for_authentication():
257 if isinstance(plugin, RhodeCodeExternalAuthPlugin):
258 if isinstance(plugin, RhodeCodeExternalAuthPlugin):
258 valid_plugins.append(plugin)
259 valid_plugins.append(plugin)
259 elif plugin.name == 'rhodecode':
260 elif plugin.name == 'rhodecode':
260 valid_plugins.append(plugin)
261 valid_plugins.append(plugin)
261
262
262 # extend our choices if user has set a bound plugin which isn't enabled at the
263 # extend our choices if user has set a bound plugin which isn't enabled at the
263 # moment
264 # moment
264 extern_type = self.db_user.extern_type
265 extern_type = self.db_user.extern_type
265 if extern_type not in [x.uid for x in valid_plugins]:
266 if extern_type not in [x.uid for x in valid_plugins]:
266 try:
267 try:
267 plugin = authn_registry.get_plugin_by_uid(extern_type)
268 plugin = authn_registry.get_plugin_by_uid(extern_type)
268 if plugin:
269 if plugin:
269 valid_plugins.append(plugin)
270 valid_plugins.append(plugin)
270
271
271 except Exception:
272 except Exception:
272 log.exception(
273 log.exception(
273 'Could not extend user plugins with `{}`'.format(extern_type))
274 'Could not extend user plugins with `{}`'.format(extern_type))
274 return valid_plugins
275 return valid_plugins
275
276
276 def load_default_context(self):
277 def load_default_context(self):
277 req = self.request
278 req = self.request
278
279
279 c = self._get_local_tmpl_context()
280 c = self._get_local_tmpl_context()
280 c.allow_scoped_tokens = self.ALLOW_SCOPED_TOKENS
281 c.allow_scoped_tokens = self.ALLOW_SCOPED_TOKENS
281 c.allowed_languages = [
282 c.allowed_languages = [
282 ('en', 'English (en)'),
283 ('en', 'English (en)'),
283 ('de', 'German (de)'),
284 ('de', 'German (de)'),
284 ('fr', 'French (fr)'),
285 ('fr', 'French (fr)'),
285 ('it', 'Italian (it)'),
286 ('it', 'Italian (it)'),
286 ('ja', 'Japanese (ja)'),
287 ('ja', 'Japanese (ja)'),
287 ('pl', 'Polish (pl)'),
288 ('pl', 'Polish (pl)'),
288 ('pt', 'Portuguese (pt)'),
289 ('pt', 'Portuguese (pt)'),
289 ('ru', 'Russian (ru)'),
290 ('ru', 'Russian (ru)'),
290 ('zh', 'Chinese (zh)'),
291 ('zh', 'Chinese (zh)'),
291 ]
292 ]
292
293
293 c.allowed_extern_types = [
294 c.allowed_extern_types = [
294 (x.uid, x.get_display_name()) for x in self.get_auth_plugins()
295 (x.uid, x.get_display_name()) for x in self.get_auth_plugins()
295 ]
296 ]
296
297
297 c.available_permissions = req.registry.settings['available_permissions']
298 c.available_permissions = req.registry.settings['available_permissions']
298 PermissionModel().set_global_permission_choices(
299 PermissionModel().set_global_permission_choices(
299 c, gettext_translator=req.translate)
300 c, gettext_translator=req.translate)
300
301
301 return c
302 return c
302
303
303 @LoginRequired()
304 @LoginRequired()
304 @HasPermissionAllDecorator('hg.admin')
305 @HasPermissionAllDecorator('hg.admin')
305 @CSRFRequired()
306 @CSRFRequired()
306 @view_config(
307 @view_config(
307 route_name='user_update', request_method='POST',
308 route_name='user_update', request_method='POST',
308 renderer='rhodecode:templates/admin/users/user_edit.mako')
309 renderer='rhodecode:templates/admin/users/user_edit.mako')
309 def user_update(self):
310 def user_update(self):
310 _ = self.request.translate
311 _ = self.request.translate
311 c = self.load_default_context()
312 c = self.load_default_context()
312
313
313 user_id = self.db_user_id
314 user_id = self.db_user_id
314 c.user = self.db_user
315 c.user = self.db_user
315
316
316 c.active = 'profile'
317 c.active = 'profile'
317 c.extern_type = c.user.extern_type
318 c.extern_type = c.user.extern_type
318 c.extern_name = c.user.extern_name
319 c.extern_name = c.user.extern_name
319 c.perm_user = c.user.AuthUser(ip_addr=self.request.remote_addr)
320 c.perm_user = c.user.AuthUser(ip_addr=self.request.remote_addr)
320 available_languages = [x[0] for x in c.allowed_languages]
321 available_languages = [x[0] for x in c.allowed_languages]
321 _form = UserForm(self.request.translate, edit=True,
322 _form = UserForm(self.request.translate, edit=True,
322 available_languages=available_languages,
323 available_languages=available_languages,
323 old_data={'user_id': user_id,
324 old_data={'user_id': user_id,
324 'email': c.user.email})()
325 'email': c.user.email})()
325 form_result = {}
326 form_result = {}
326 old_values = c.user.get_api_data()
327 old_values = c.user.get_api_data()
327 try:
328 try:
328 form_result = _form.to_python(dict(self.request.POST))
329 form_result = _form.to_python(dict(self.request.POST))
329 skip_attrs = ['extern_name']
330 skip_attrs = ['extern_name']
330 # TODO: plugin should define if username can be updated
331 # TODO: plugin should define if username can be updated
331 if c.extern_type != "rhodecode":
332 if c.extern_type != "rhodecode":
332 # forbid updating username for external accounts
333 # forbid updating username for external accounts
333 skip_attrs.append('username')
334 skip_attrs.append('username')
334
335
335 UserModel().update_user(
336 UserModel().update_user(
336 user_id, skip_attrs=skip_attrs, **form_result)
337 user_id, skip_attrs=skip_attrs, **form_result)
337
338
338 audit_logger.store_web(
339 audit_logger.store_web(
339 'user.edit', action_data={'old_data': old_values},
340 'user.edit', action_data={'old_data': old_values},
340 user=c.rhodecode_user)
341 user=c.rhodecode_user)
341
342
342 Session().commit()
343 Session().commit()
343 h.flash(_('User updated successfully'), category='success')
344 h.flash(_('User updated successfully'), category='success')
344 except formencode.Invalid as errors:
345 except formencode.Invalid as errors:
345 data = render(
346 data = render(
346 'rhodecode:templates/admin/users/user_edit.mako',
347 'rhodecode:templates/admin/users/user_edit.mako',
347 self._get_template_context(c), self.request)
348 self._get_template_context(c), self.request)
348 html = formencode.htmlfill.render(
349 html = formencode.htmlfill.render(
349 data,
350 data,
350 defaults=errors.value,
351 defaults=errors.value,
351 errors=errors.error_dict or {},
352 errors=errors.error_dict or {},
352 prefix_error=False,
353 prefix_error=False,
353 encoding="UTF-8",
354 encoding="UTF-8",
354 force_defaults=False
355 force_defaults=False
355 )
356 )
356 return Response(html)
357 return Response(html)
357 except UserCreationError as e:
358 except UserCreationError as e:
358 h.flash(e, 'error')
359 h.flash(e, 'error')
359 except Exception:
360 except Exception:
360 log.exception("Exception updating user")
361 log.exception("Exception updating user")
361 h.flash(_('Error occurred during update of user %s')
362 h.flash(_('Error occurred during update of user %s')
362 % form_result.get('username'), category='error')
363 % form_result.get('username'), category='error')
363 raise HTTPFound(h.route_path('user_edit', user_id=user_id))
364 raise HTTPFound(h.route_path('user_edit', user_id=user_id))
364
365
365 @LoginRequired()
366 @LoginRequired()
366 @HasPermissionAllDecorator('hg.admin')
367 @HasPermissionAllDecorator('hg.admin')
367 @CSRFRequired()
368 @CSRFRequired()
368 @view_config(
369 @view_config(
369 route_name='user_delete', request_method='POST',
370 route_name='user_delete', request_method='POST',
370 renderer='rhodecode:templates/admin/users/user_edit.mako')
371 renderer='rhodecode:templates/admin/users/user_edit.mako')
371 def user_delete(self):
372 def user_delete(self):
372 _ = self.request.translate
373 _ = self.request.translate
373 c = self.load_default_context()
374 c = self.load_default_context()
374 c.user = self.db_user
375 c.user = self.db_user
375
376
376 _repos = c.user.repositories
377 _repos = c.user.repositories
377 _repo_groups = c.user.repository_groups
378 _repo_groups = c.user.repository_groups
378 _user_groups = c.user.user_groups
379 _user_groups = c.user.user_groups
379 _artifacts = c.user.artifacts
380 _artifacts = c.user.artifacts
380
381
381 handle_repos = None
382 handle_repos = None
382 handle_repo_groups = None
383 handle_repo_groups = None
383 handle_user_groups = None
384 handle_user_groups = None
384 handle_artifacts = None
385 handle_artifacts = None
385
386
386 # calls for flash of handle based on handle case detach or delete
387 # calls for flash of handle based on handle case detach or delete
387 def set_handle_flash_repos():
388 def set_handle_flash_repos():
388 handle = handle_repos
389 handle = handle_repos
389 if handle == 'detach':
390 if handle == 'detach':
390 h.flash(_('Detached %s repositories') % len(_repos),
391 h.flash(_('Detached %s repositories') % len(_repos),
391 category='success')
392 category='success')
392 elif handle == 'delete':
393 elif handle == 'delete':
393 h.flash(_('Deleted %s repositories') % len(_repos),
394 h.flash(_('Deleted %s repositories') % len(_repos),
394 category='success')
395 category='success')
395
396
396 def set_handle_flash_repo_groups():
397 def set_handle_flash_repo_groups():
397 handle = handle_repo_groups
398 handle = handle_repo_groups
398 if handle == 'detach':
399 if handle == 'detach':
399 h.flash(_('Detached %s repository groups') % len(_repo_groups),
400 h.flash(_('Detached %s repository groups') % len(_repo_groups),
400 category='success')
401 category='success')
401 elif handle == 'delete':
402 elif handle == 'delete':
402 h.flash(_('Deleted %s repository groups') % len(_repo_groups),
403 h.flash(_('Deleted %s repository groups') % len(_repo_groups),
403 category='success')
404 category='success')
404
405
405 def set_handle_flash_user_groups():
406 def set_handle_flash_user_groups():
406 handle = handle_user_groups
407 handle = handle_user_groups
407 if handle == 'detach':
408 if handle == 'detach':
408 h.flash(_('Detached %s user groups') % len(_user_groups),
409 h.flash(_('Detached %s user groups') % len(_user_groups),
409 category='success')
410 category='success')
410 elif handle == 'delete':
411 elif handle == 'delete':
411 h.flash(_('Deleted %s user groups') % len(_user_groups),
412 h.flash(_('Deleted %s user groups') % len(_user_groups),
412 category='success')
413 category='success')
413
414
414 def set_handle_flash_artifacts():
415 def set_handle_flash_artifacts():
415 handle = handle_artifacts
416 handle = handle_artifacts
416 if handle == 'detach':
417 if handle == 'detach':
417 h.flash(_('Detached %s artifacts') % len(_artifacts),
418 h.flash(_('Detached %s artifacts') % len(_artifacts),
418 category='success')
419 category='success')
419 elif handle == 'delete':
420 elif handle == 'delete':
420 h.flash(_('Deleted %s artifacts') % len(_artifacts),
421 h.flash(_('Deleted %s artifacts') % len(_artifacts),
421 category='success')
422 category='success')
422
423
423 if _repos and self.request.POST.get('user_repos'):
424 if _repos and self.request.POST.get('user_repos'):
424 handle_repos = self.request.POST['user_repos']
425 handle_repos = self.request.POST['user_repos']
425
426
426 if _repo_groups and self.request.POST.get('user_repo_groups'):
427 if _repo_groups and self.request.POST.get('user_repo_groups'):
427 handle_repo_groups = self.request.POST['user_repo_groups']
428 handle_repo_groups = self.request.POST['user_repo_groups']
428
429
429 if _user_groups and self.request.POST.get('user_user_groups'):
430 if _user_groups and self.request.POST.get('user_user_groups'):
430 handle_user_groups = self.request.POST['user_user_groups']
431 handle_user_groups = self.request.POST['user_user_groups']
431
432
432 if _artifacts and self.request.POST.get('user_artifacts'):
433 if _artifacts and self.request.POST.get('user_artifacts'):
433 handle_artifacts = self.request.POST['user_artifacts']
434 handle_artifacts = self.request.POST['user_artifacts']
434
435
435 old_values = c.user.get_api_data()
436 old_values = c.user.get_api_data()
436
437
437 try:
438 try:
438 UserModel().delete(c.user, handle_repos=handle_repos,
439 UserModel().delete(c.user, handle_repos=handle_repos,
439 handle_repo_groups=handle_repo_groups,
440 handle_repo_groups=handle_repo_groups,
440 handle_user_groups=handle_user_groups,
441 handle_user_groups=handle_user_groups,
441 handle_artifacts=handle_artifacts)
442 handle_artifacts=handle_artifacts)
442
443
443 audit_logger.store_web(
444 audit_logger.store_web(
444 'user.delete', action_data={'old_data': old_values},
445 'user.delete', action_data={'old_data': old_values},
445 user=c.rhodecode_user)
446 user=c.rhodecode_user)
446
447
447 Session().commit()
448 Session().commit()
448 set_handle_flash_repos()
449 set_handle_flash_repos()
449 set_handle_flash_repo_groups()
450 set_handle_flash_repo_groups()
450 set_handle_flash_user_groups()
451 set_handle_flash_user_groups()
451 set_handle_flash_artifacts()
452 set_handle_flash_artifacts()
452 username = h.escape(old_values['username'])
453 username = h.escape(old_values['username'])
453 h.flash(_('Successfully deleted user `{}`').format(username), category='success')
454 h.flash(_('Successfully deleted user `{}`').format(username), category='success')
454 except (UserOwnsReposException, UserOwnsRepoGroupsException,
455 except (UserOwnsReposException, UserOwnsRepoGroupsException,
455 UserOwnsUserGroupsException, DefaultUserException) as e:
456 UserOwnsUserGroupsException, DefaultUserException) as e:
456 h.flash(e, category='warning')
457 h.flash(e, category='warning')
457 except Exception:
458 except Exception:
458 log.exception("Exception during deletion of user")
459 log.exception("Exception during deletion of user")
459 h.flash(_('An error occurred during deletion of user'),
460 h.flash(_('An error occurred during deletion of user'),
460 category='error')
461 category='error')
461 raise HTTPFound(h.route_path('users'))
462 raise HTTPFound(h.route_path('users'))
462
463
463 @LoginRequired()
464 @LoginRequired()
464 @HasPermissionAllDecorator('hg.admin')
465 @HasPermissionAllDecorator('hg.admin')
465 @view_config(
466 @view_config(
466 route_name='user_edit', request_method='GET',
467 route_name='user_edit', request_method='GET',
467 renderer='rhodecode:templates/admin/users/user_edit.mako')
468 renderer='rhodecode:templates/admin/users/user_edit.mako')
468 def user_edit(self):
469 def user_edit(self):
469 _ = self.request.translate
470 _ = self.request.translate
470 c = self.load_default_context()
471 c = self.load_default_context()
471 c.user = self.db_user
472 c.user = self.db_user
472
473
473 c.active = 'profile'
474 c.active = 'profile'
474 c.extern_type = c.user.extern_type
475 c.extern_type = c.user.extern_type
475 c.extern_name = c.user.extern_name
476 c.extern_name = c.user.extern_name
476 c.perm_user = c.user.AuthUser(ip_addr=self.request.remote_addr)
477 c.perm_user = c.user.AuthUser(ip_addr=self.request.remote_addr)
477
478
478 defaults = c.user.get_dict()
479 defaults = c.user.get_dict()
479 defaults.update({'language': c.user.user_data.get('language')})
480 defaults.update({'language': c.user.user_data.get('language')})
480
481
481 data = render(
482 data = render(
482 'rhodecode:templates/admin/users/user_edit.mako',
483 'rhodecode:templates/admin/users/user_edit.mako',
483 self._get_template_context(c), self.request)
484 self._get_template_context(c), self.request)
484 html = formencode.htmlfill.render(
485 html = formencode.htmlfill.render(
485 data,
486 data,
486 defaults=defaults,
487 defaults=defaults,
487 encoding="UTF-8",
488 encoding="UTF-8",
488 force_defaults=False
489 force_defaults=False
489 )
490 )
490 return Response(html)
491 return Response(html)
491
492
492 @LoginRequired()
493 @LoginRequired()
493 @HasPermissionAllDecorator('hg.admin')
494 @HasPermissionAllDecorator('hg.admin')
494 @view_config(
495 @view_config(
495 route_name='user_edit_advanced', request_method='GET',
496 route_name='user_edit_advanced', request_method='GET',
496 renderer='rhodecode:templates/admin/users/user_edit.mako')
497 renderer='rhodecode:templates/admin/users/user_edit.mako')
497 def user_edit_advanced(self):
498 def user_edit_advanced(self):
498 _ = self.request.translate
499 _ = self.request.translate
499 c = self.load_default_context()
500 c = self.load_default_context()
500
501
501 user_id = self.db_user_id
502 user_id = self.db_user_id
502 c.user = self.db_user
503 c.user = self.db_user
503
504
504 c.active = 'advanced'
505 c.active = 'advanced'
505 c.personal_repo_group = RepoGroup.get_user_personal_repo_group(user_id)
506 c.personal_repo_group = RepoGroup.get_user_personal_repo_group(user_id)
506 c.personal_repo_group_name = RepoGroupModel()\
507 c.personal_repo_group_name = RepoGroupModel()\
507 .get_personal_group_name(c.user)
508 .get_personal_group_name(c.user)
508
509
509 c.user_to_review_rules = sorted(
510 c.user_to_review_rules = sorted(
510 (x.user for x in c.user.user_review_rules),
511 (x.user for x in c.user.user_review_rules),
511 key=lambda u: u.username.lower())
512 key=lambda u: u.username.lower())
512
513
513 c.first_admin = User.get_first_super_admin()
514 c.first_admin = User.get_first_super_admin()
514 defaults = c.user.get_dict()
515 defaults = c.user.get_dict()
515
516
516 # Interim workaround if the user participated on any pull requests as a
517 # Interim workaround if the user participated on any pull requests as a
517 # reviewer.
518 # reviewer.
518 has_review = len(c.user.reviewer_pull_requests)
519 has_review = len(c.user.reviewer_pull_requests)
519 c.can_delete_user = not has_review
520 c.can_delete_user = not has_review
520 c.can_delete_user_message = ''
521 c.can_delete_user_message = ''
521 inactive_link = h.link_to(
522 inactive_link = h.link_to(
522 'inactive', h.route_path('user_edit', user_id=user_id, _anchor='active'))
523 'inactive', h.route_path('user_edit', user_id=user_id, _anchor='active'))
523 if has_review == 1:
524 if has_review == 1:
524 c.can_delete_user_message = h.literal(_(
525 c.can_delete_user_message = h.literal(_(
525 'The user participates as reviewer in {} pull request and '
526 'The user participates as reviewer in {} pull request and '
526 'cannot be deleted. \nYou can set the user to '
527 'cannot be deleted. \nYou can set the user to '
527 '"{}" instead of deleting it.').format(
528 '"{}" instead of deleting it.').format(
528 has_review, inactive_link))
529 has_review, inactive_link))
529 elif has_review:
530 elif has_review:
530 c.can_delete_user_message = h.literal(_(
531 c.can_delete_user_message = h.literal(_(
531 'The user participates as reviewer in {} pull requests and '
532 'The user participates as reviewer in {} pull requests and '
532 'cannot be deleted. \nYou can set the user to '
533 'cannot be deleted. \nYou can set the user to '
533 '"{}" instead of deleting it.').format(
534 '"{}" instead of deleting it.').format(
534 has_review, inactive_link))
535 has_review, inactive_link))
535
536
536 data = render(
537 data = render(
537 'rhodecode:templates/admin/users/user_edit.mako',
538 'rhodecode:templates/admin/users/user_edit.mako',
538 self._get_template_context(c), self.request)
539 self._get_template_context(c), self.request)
539 html = formencode.htmlfill.render(
540 html = formencode.htmlfill.render(
540 data,
541 data,
541 defaults=defaults,
542 defaults=defaults,
542 encoding="UTF-8",
543 encoding="UTF-8",
543 force_defaults=False
544 force_defaults=False
544 )
545 )
545 return Response(html)
546 return Response(html)
546
547
547 @LoginRequired()
548 @LoginRequired()
548 @HasPermissionAllDecorator('hg.admin')
549 @HasPermissionAllDecorator('hg.admin')
549 @view_config(
550 @view_config(
550 route_name='user_edit_global_perms', request_method='GET',
551 route_name='user_edit_global_perms', request_method='GET',
551 renderer='rhodecode:templates/admin/users/user_edit.mako')
552 renderer='rhodecode:templates/admin/users/user_edit.mako')
552 def user_edit_global_perms(self):
553 def user_edit_global_perms(self):
553 _ = self.request.translate
554 _ = self.request.translate
554 c = self.load_default_context()
555 c = self.load_default_context()
555 c.user = self.db_user
556 c.user = self.db_user
556
557
557 c.active = 'global_perms'
558 c.active = 'global_perms'
558
559
559 c.default_user = User.get_default_user()
560 c.default_user = User.get_default_user()
560 defaults = c.user.get_dict()
561 defaults = c.user.get_dict()
561 defaults.update(c.default_user.get_default_perms(suffix='_inherited'))
562 defaults.update(c.default_user.get_default_perms(suffix='_inherited'))
562 defaults.update(c.default_user.get_default_perms())
563 defaults.update(c.default_user.get_default_perms())
563 defaults.update(c.user.get_default_perms())
564 defaults.update(c.user.get_default_perms())
564
565
565 data = render(
566 data = render(
566 'rhodecode:templates/admin/users/user_edit.mako',
567 'rhodecode:templates/admin/users/user_edit.mako',
567 self._get_template_context(c), self.request)
568 self._get_template_context(c), self.request)
568 html = formencode.htmlfill.render(
569 html = formencode.htmlfill.render(
569 data,
570 data,
570 defaults=defaults,
571 defaults=defaults,
571 encoding="UTF-8",
572 encoding="UTF-8",
572 force_defaults=False
573 force_defaults=False
573 )
574 )
574 return Response(html)
575 return Response(html)
575
576
576 @LoginRequired()
577 @LoginRequired()
577 @HasPermissionAllDecorator('hg.admin')
578 @HasPermissionAllDecorator('hg.admin')
578 @CSRFRequired()
579 @CSRFRequired()
579 @view_config(
580 @view_config(
580 route_name='user_edit_global_perms_update', request_method='POST',
581 route_name='user_edit_global_perms_update', request_method='POST',
581 renderer='rhodecode:templates/admin/users/user_edit.mako')
582 renderer='rhodecode:templates/admin/users/user_edit.mako')
582 def user_edit_global_perms_update(self):
583 def user_edit_global_perms_update(self):
583 _ = self.request.translate
584 _ = self.request.translate
584 c = self.load_default_context()
585 c = self.load_default_context()
585
586
586 user_id = self.db_user_id
587 user_id = self.db_user_id
587 c.user = self.db_user
588 c.user = self.db_user
588
589
589 c.active = 'global_perms'
590 c.active = 'global_perms'
590 try:
591 try:
591 # first stage that verifies the checkbox
592 # first stage that verifies the checkbox
592 _form = UserIndividualPermissionsForm(self.request.translate)
593 _form = UserIndividualPermissionsForm(self.request.translate)
593 form_result = _form.to_python(dict(self.request.POST))
594 form_result = _form.to_python(dict(self.request.POST))
594 inherit_perms = form_result['inherit_default_permissions']
595 inherit_perms = form_result['inherit_default_permissions']
595 c.user.inherit_default_permissions = inherit_perms
596 c.user.inherit_default_permissions = inherit_perms
596 Session().add(c.user)
597 Session().add(c.user)
597
598
598 if not inherit_perms:
599 if not inherit_perms:
599 # only update the individual ones if we un check the flag
600 # only update the individual ones if we un check the flag
600 _form = UserPermissionsForm(
601 _form = UserPermissionsForm(
601 self.request.translate,
602 self.request.translate,
602 [x[0] for x in c.repo_create_choices],
603 [x[0] for x in c.repo_create_choices],
603 [x[0] for x in c.repo_create_on_write_choices],
604 [x[0] for x in c.repo_create_on_write_choices],
604 [x[0] for x in c.repo_group_create_choices],
605 [x[0] for x in c.repo_group_create_choices],
605 [x[0] for x in c.user_group_create_choices],
606 [x[0] for x in c.user_group_create_choices],
606 [x[0] for x in c.fork_choices],
607 [x[0] for x in c.fork_choices],
607 [x[0] for x in c.inherit_default_permission_choices])()
608 [x[0] for x in c.inherit_default_permission_choices])()
608
609
609 form_result = _form.to_python(dict(self.request.POST))
610 form_result = _form.to_python(dict(self.request.POST))
610 form_result.update({'perm_user_id': c.user.user_id})
611 form_result.update({'perm_user_id': c.user.user_id})
611
612
612 PermissionModel().update_user_permissions(form_result)
613 PermissionModel().update_user_permissions(form_result)
613
614
614 # TODO(marcink): implement global permissions
615 # TODO(marcink): implement global permissions
615 # audit_log.store_web('user.edit.permissions')
616 # audit_log.store_web('user.edit.permissions')
616
617
617 Session().commit()
618 Session().commit()
618
619
619 h.flash(_('User global permissions updated successfully'),
620 h.flash(_('User global permissions updated successfully'),
620 category='success')
621 category='success')
621
622
622 except formencode.Invalid as errors:
623 except formencode.Invalid as errors:
623 data = render(
624 data = render(
624 'rhodecode:templates/admin/users/user_edit.mako',
625 'rhodecode:templates/admin/users/user_edit.mako',
625 self._get_template_context(c), self.request)
626 self._get_template_context(c), self.request)
626 html = formencode.htmlfill.render(
627 html = formencode.htmlfill.render(
627 data,
628 data,
628 defaults=errors.value,
629 defaults=errors.value,
629 errors=errors.error_dict or {},
630 errors=errors.error_dict or {},
630 prefix_error=False,
631 prefix_error=False,
631 encoding="UTF-8",
632 encoding="UTF-8",
632 force_defaults=False
633 force_defaults=False
633 )
634 )
634 return Response(html)
635 return Response(html)
635 except Exception:
636 except Exception:
636 log.exception("Exception during permissions saving")
637 log.exception("Exception during permissions saving")
637 h.flash(_('An error occurred during permissions saving'),
638 h.flash(_('An error occurred during permissions saving'),
638 category='error')
639 category='error')
639
640
640 affected_user_ids = [user_id]
641 affected_user_ids = [user_id]
641 PermissionModel().trigger_permission_flush(affected_user_ids)
642 PermissionModel().trigger_permission_flush(affected_user_ids)
642 raise HTTPFound(h.route_path('user_edit_global_perms', user_id=user_id))
643 raise HTTPFound(h.route_path('user_edit_global_perms', user_id=user_id))
643
644
644 @LoginRequired()
645 @LoginRequired()
645 @HasPermissionAllDecorator('hg.admin')
646 @HasPermissionAllDecorator('hg.admin')
646 @CSRFRequired()
647 @CSRFRequired()
647 @view_config(
648 @view_config(
648 route_name='user_enable_force_password_reset', request_method='POST',
649 route_name='user_enable_force_password_reset', request_method='POST',
649 renderer='rhodecode:templates/admin/users/user_edit.mako')
650 renderer='rhodecode:templates/admin/users/user_edit.mako')
650 def user_enable_force_password_reset(self):
651 def user_enable_force_password_reset(self):
651 _ = self.request.translate
652 _ = self.request.translate
652 c = self.load_default_context()
653 c = self.load_default_context()
653
654
654 user_id = self.db_user_id
655 user_id = self.db_user_id
655 c.user = self.db_user
656 c.user = self.db_user
656
657
657 try:
658 try:
658 c.user.update_userdata(force_password_change=True)
659 c.user.update_userdata(force_password_change=True)
659
660
660 msg = _('Force password change enabled for user')
661 msg = _('Force password change enabled for user')
661 audit_logger.store_web('user.edit.password_reset.enabled',
662 audit_logger.store_web('user.edit.password_reset.enabled',
662 user=c.rhodecode_user)
663 user=c.rhodecode_user)
663
664
664 Session().commit()
665 Session().commit()
665 h.flash(msg, category='success')
666 h.flash(msg, category='success')
666 except Exception:
667 except Exception:
667 log.exception("Exception during password reset for user")
668 log.exception("Exception during password reset for user")
668 h.flash(_('An error occurred during password reset for user'),
669 h.flash(_('An error occurred during password reset for user'),
669 category='error')
670 category='error')
670
671
671 raise HTTPFound(h.route_path('user_edit_advanced', user_id=user_id))
672 raise HTTPFound(h.route_path('user_edit_advanced', user_id=user_id))
672
673
673 @LoginRequired()
674 @LoginRequired()
674 @HasPermissionAllDecorator('hg.admin')
675 @HasPermissionAllDecorator('hg.admin')
675 @CSRFRequired()
676 @CSRFRequired()
676 @view_config(
677 @view_config(
677 route_name='user_disable_force_password_reset', request_method='POST',
678 route_name='user_disable_force_password_reset', request_method='POST',
678 renderer='rhodecode:templates/admin/users/user_edit.mako')
679 renderer='rhodecode:templates/admin/users/user_edit.mako')
679 def user_disable_force_password_reset(self):
680 def user_disable_force_password_reset(self):
680 _ = self.request.translate
681 _ = self.request.translate
681 c = self.load_default_context()
682 c = self.load_default_context()
682
683
683 user_id = self.db_user_id
684 user_id = self.db_user_id
684 c.user = self.db_user
685 c.user = self.db_user
685
686
686 try:
687 try:
687 c.user.update_userdata(force_password_change=False)
688 c.user.update_userdata(force_password_change=False)
688
689
689 msg = _('Force password change disabled for user')
690 msg = _('Force password change disabled for user')
690 audit_logger.store_web(
691 audit_logger.store_web(
691 'user.edit.password_reset.disabled',
692 'user.edit.password_reset.disabled',
692 user=c.rhodecode_user)
693 user=c.rhodecode_user)
693
694
694 Session().commit()
695 Session().commit()
695 h.flash(msg, category='success')
696 h.flash(msg, category='success')
696 except Exception:
697 except Exception:
697 log.exception("Exception during password reset for user")
698 log.exception("Exception during password reset for user")
698 h.flash(_('An error occurred during password reset for user'),
699 h.flash(_('An error occurred during password reset for user'),
699 category='error')
700 category='error')
700
701
701 raise HTTPFound(h.route_path('user_edit_advanced', user_id=user_id))
702 raise HTTPFound(h.route_path('user_edit_advanced', user_id=user_id))
702
703
703 @LoginRequired()
704 @LoginRequired()
704 @HasPermissionAllDecorator('hg.admin')
705 @HasPermissionAllDecorator('hg.admin')
705 @CSRFRequired()
706 @CSRFRequired()
706 @view_config(
707 @view_config(
707 route_name='user_create_personal_repo_group', request_method='POST',
708 route_name='user_create_personal_repo_group', request_method='POST',
708 renderer='rhodecode:templates/admin/users/user_edit.mako')
709 renderer='rhodecode:templates/admin/users/user_edit.mako')
709 def user_create_personal_repo_group(self):
710 def user_create_personal_repo_group(self):
710 """
711 """
711 Create personal repository group for this user
712 Create personal repository group for this user
712 """
713 """
713 from rhodecode.model.repo_group import RepoGroupModel
714 from rhodecode.model.repo_group import RepoGroupModel
714
715
715 _ = self.request.translate
716 _ = self.request.translate
716 c = self.load_default_context()
717 c = self.load_default_context()
717
718
718 user_id = self.db_user_id
719 user_id = self.db_user_id
719 c.user = self.db_user
720 c.user = self.db_user
720
721
721 personal_repo_group = RepoGroup.get_user_personal_repo_group(
722 personal_repo_group = RepoGroup.get_user_personal_repo_group(
722 c.user.user_id)
723 c.user.user_id)
723 if personal_repo_group:
724 if personal_repo_group:
724 raise HTTPFound(h.route_path('user_edit_advanced', user_id=user_id))
725 raise HTTPFound(h.route_path('user_edit_advanced', user_id=user_id))
725
726
726 personal_repo_group_name = RepoGroupModel().get_personal_group_name(c.user)
727 personal_repo_group_name = RepoGroupModel().get_personal_group_name(c.user)
727 named_personal_group = RepoGroup.get_by_group_name(
728 named_personal_group = RepoGroup.get_by_group_name(
728 personal_repo_group_name)
729 personal_repo_group_name)
729 try:
730 try:
730
731
731 if named_personal_group and named_personal_group.user_id == c.user.user_id:
732 if named_personal_group and named_personal_group.user_id == c.user.user_id:
732 # migrate the same named group, and mark it as personal
733 # migrate the same named group, and mark it as personal
733 named_personal_group.personal = True
734 named_personal_group.personal = True
734 Session().add(named_personal_group)
735 Session().add(named_personal_group)
735 Session().commit()
736 Session().commit()
736 msg = _('Linked repository group `%s` as personal' % (
737 msg = _('Linked repository group `%s` as personal' % (
737 personal_repo_group_name,))
738 personal_repo_group_name,))
738 h.flash(msg, category='success')
739 h.flash(msg, category='success')
739 elif not named_personal_group:
740 elif not named_personal_group:
740 RepoGroupModel().create_personal_repo_group(c.user)
741 RepoGroupModel().create_personal_repo_group(c.user)
741
742
742 msg = _('Created repository group `%s`' % (
743 msg = _('Created repository group `%s`' % (
743 personal_repo_group_name,))
744 personal_repo_group_name,))
744 h.flash(msg, category='success')
745 h.flash(msg, category='success')
745 else:
746 else:
746 msg = _('Repository group `%s` is already taken' % (
747 msg = _('Repository group `%s` is already taken' % (
747 personal_repo_group_name,))
748 personal_repo_group_name,))
748 h.flash(msg, category='warning')
749 h.flash(msg, category='warning')
749 except Exception:
750 except Exception:
750 log.exception("Exception during repository group creation")
751 log.exception("Exception during repository group creation")
751 msg = _(
752 msg = _(
752 'An error occurred during repository group creation for user')
753 'An error occurred during repository group creation for user')
753 h.flash(msg, category='error')
754 h.flash(msg, category='error')
754 Session().rollback()
755 Session().rollback()
755
756
756 raise HTTPFound(h.route_path('user_edit_advanced', user_id=user_id))
757 raise HTTPFound(h.route_path('user_edit_advanced', user_id=user_id))
757
758
758 @LoginRequired()
759 @LoginRequired()
759 @HasPermissionAllDecorator('hg.admin')
760 @HasPermissionAllDecorator('hg.admin')
760 @view_config(
761 @view_config(
761 route_name='edit_user_auth_tokens', request_method='GET',
762 route_name='edit_user_auth_tokens', request_method='GET',
762 renderer='rhodecode:templates/admin/users/user_edit.mako')
763 renderer='rhodecode:templates/admin/users/user_edit.mako')
763 def auth_tokens(self):
764 def auth_tokens(self):
764 _ = self.request.translate
765 _ = self.request.translate
765 c = self.load_default_context()
766 c = self.load_default_context()
766 c.user = self.db_user
767 c.user = self.db_user
767
768
768 c.active = 'auth_tokens'
769 c.active = 'auth_tokens'
769
770
770 c.lifetime_values = AuthTokenModel.get_lifetime_values(translator=_)
771 c.lifetime_values = AuthTokenModel.get_lifetime_values(translator=_)
771 c.role_values = [
772 c.role_values = [
772 (x, AuthTokenModel.cls._get_role_name(x))
773 (x, AuthTokenModel.cls._get_role_name(x))
773 for x in AuthTokenModel.cls.ROLES]
774 for x in AuthTokenModel.cls.ROLES]
774 c.role_options = [(c.role_values, _("Role"))]
775 c.role_options = [(c.role_values, _("Role"))]
775 c.user_auth_tokens = AuthTokenModel().get_auth_tokens(
776 c.user_auth_tokens = AuthTokenModel().get_auth_tokens(
776 c.user.user_id, show_expired=True)
777 c.user.user_id, show_expired=True)
777 c.role_vcs = AuthTokenModel.cls.ROLE_VCS
778 c.role_vcs = AuthTokenModel.cls.ROLE_VCS
778 return self._get_template_context(c)
779 return self._get_template_context(c)
779
780
780 def maybe_attach_token_scope(self, token):
781 def maybe_attach_token_scope(self, token):
781 # implemented in EE edition
782 # implemented in EE edition
782 pass
783 pass
783
784
784 @LoginRequired()
785 @LoginRequired()
785 @HasPermissionAllDecorator('hg.admin')
786 @HasPermissionAllDecorator('hg.admin')
786 @CSRFRequired()
787 @CSRFRequired()
787 @view_config(
788 @view_config(
788 route_name='edit_user_auth_tokens_add', request_method='POST')
789 route_name='edit_user_auth_tokens_add', request_method='POST')
789 def auth_tokens_add(self):
790 def auth_tokens_add(self):
790 _ = self.request.translate
791 _ = self.request.translate
791 c = self.load_default_context()
792 c = self.load_default_context()
792
793
793 user_id = self.db_user_id
794 user_id = self.db_user_id
794 c.user = self.db_user
795 c.user = self.db_user
795
796
796 user_data = c.user.get_api_data()
797 user_data = c.user.get_api_data()
797 lifetime = safe_int(self.request.POST.get('lifetime'), -1)
798 lifetime = safe_int(self.request.POST.get('lifetime'), -1)
798 description = self.request.POST.get('description')
799 description = self.request.POST.get('description')
799 role = self.request.POST.get('role')
800 role = self.request.POST.get('role')
800
801
801 token = UserModel().add_auth_token(
802 token = UserModel().add_auth_token(
802 user=c.user.user_id,
803 user=c.user.user_id,
803 lifetime_minutes=lifetime, role=role, description=description,
804 lifetime_minutes=lifetime, role=role, description=description,
804 scope_callback=self.maybe_attach_token_scope)
805 scope_callback=self.maybe_attach_token_scope)
805 token_data = token.get_api_data()
806 token_data = token.get_api_data()
806
807
807 audit_logger.store_web(
808 audit_logger.store_web(
808 'user.edit.token.add', action_data={
809 'user.edit.token.add', action_data={
809 'data': {'token': token_data, 'user': user_data}},
810 'data': {'token': token_data, 'user': user_data}},
810 user=self._rhodecode_user, )
811 user=self._rhodecode_user, )
811 Session().commit()
812 Session().commit()
812
813
813 h.flash(_("Auth token successfully created"), category='success')
814 h.flash(_("Auth token successfully created"), category='success')
814 return HTTPFound(h.route_path('edit_user_auth_tokens', user_id=user_id))
815 return HTTPFound(h.route_path('edit_user_auth_tokens', user_id=user_id))
815
816
816 @LoginRequired()
817 @LoginRequired()
817 @HasPermissionAllDecorator('hg.admin')
818 @HasPermissionAllDecorator('hg.admin')
818 @CSRFRequired()
819 @CSRFRequired()
819 @view_config(
820 @view_config(
820 route_name='edit_user_auth_tokens_delete', request_method='POST')
821 route_name='edit_user_auth_tokens_delete', request_method='POST')
821 def auth_tokens_delete(self):
822 def auth_tokens_delete(self):
822 _ = self.request.translate
823 _ = self.request.translate
823 c = self.load_default_context()
824 c = self.load_default_context()
824
825
825 user_id = self.db_user_id
826 user_id = self.db_user_id
826 c.user = self.db_user
827 c.user = self.db_user
827
828
828 user_data = c.user.get_api_data()
829 user_data = c.user.get_api_data()
829
830
830 del_auth_token = self.request.POST.get('del_auth_token')
831 del_auth_token = self.request.POST.get('del_auth_token')
831
832
832 if del_auth_token:
833 if del_auth_token:
833 token = UserApiKeys.get_or_404(del_auth_token)
834 token = UserApiKeys.get_or_404(del_auth_token)
834 token_data = token.get_api_data()
835 token_data = token.get_api_data()
835
836
836 AuthTokenModel().delete(del_auth_token, c.user.user_id)
837 AuthTokenModel().delete(del_auth_token, c.user.user_id)
837 audit_logger.store_web(
838 audit_logger.store_web(
838 'user.edit.token.delete', action_data={
839 'user.edit.token.delete', action_data={
839 'data': {'token': token_data, 'user': user_data}},
840 'data': {'token': token_data, 'user': user_data}},
840 user=self._rhodecode_user,)
841 user=self._rhodecode_user,)
841 Session().commit()
842 Session().commit()
842 h.flash(_("Auth token successfully deleted"), category='success')
843 h.flash(_("Auth token successfully deleted"), category='success')
843
844
844 return HTTPFound(h.route_path('edit_user_auth_tokens', user_id=user_id))
845 return HTTPFound(h.route_path('edit_user_auth_tokens', user_id=user_id))
845
846
846 @LoginRequired()
847 @LoginRequired()
847 @HasPermissionAllDecorator('hg.admin')
848 @HasPermissionAllDecorator('hg.admin')
848 @view_config(
849 @view_config(
849 route_name='edit_user_ssh_keys', request_method='GET',
850 route_name='edit_user_ssh_keys', request_method='GET',
850 renderer='rhodecode:templates/admin/users/user_edit.mako')
851 renderer='rhodecode:templates/admin/users/user_edit.mako')
851 def ssh_keys(self):
852 def ssh_keys(self):
852 _ = self.request.translate
853 _ = self.request.translate
853 c = self.load_default_context()
854 c = self.load_default_context()
854 c.user = self.db_user
855 c.user = self.db_user
855
856
856 c.active = 'ssh_keys'
857 c.active = 'ssh_keys'
857 c.default_key = self.request.GET.get('default_key')
858 c.default_key = self.request.GET.get('default_key')
858 c.user_ssh_keys = SshKeyModel().get_ssh_keys(c.user.user_id)
859 c.user_ssh_keys = SshKeyModel().get_ssh_keys(c.user.user_id)
859 return self._get_template_context(c)
860 return self._get_template_context(c)
860
861
861 @LoginRequired()
862 @LoginRequired()
862 @HasPermissionAllDecorator('hg.admin')
863 @HasPermissionAllDecorator('hg.admin')
863 @view_config(
864 @view_config(
864 route_name='edit_user_ssh_keys_generate_keypair', request_method='GET',
865 route_name='edit_user_ssh_keys_generate_keypair', request_method='GET',
865 renderer='rhodecode:templates/admin/users/user_edit.mako')
866 renderer='rhodecode:templates/admin/users/user_edit.mako')
866 def ssh_keys_generate_keypair(self):
867 def ssh_keys_generate_keypair(self):
867 _ = self.request.translate
868 _ = self.request.translate
868 c = self.load_default_context()
869 c = self.load_default_context()
869
870
870 c.user = self.db_user
871 c.user = self.db_user
871
872
872 c.active = 'ssh_keys_generate'
873 c.active = 'ssh_keys_generate'
873 comment = 'RhodeCode-SSH {}'.format(c.user.email or '')
874 comment = 'RhodeCode-SSH {}'.format(c.user.email or '')
874 c.private, c.public = SshKeyModel().generate_keypair(comment=comment)
875 c.private, c.public = SshKeyModel().generate_keypair(comment=comment)
875
876
876 return self._get_template_context(c)
877 return self._get_template_context(c)
877
878
878 @LoginRequired()
879 @LoginRequired()
879 @HasPermissionAllDecorator('hg.admin')
880 @HasPermissionAllDecorator('hg.admin')
880 @CSRFRequired()
881 @CSRFRequired()
881 @view_config(
882 @view_config(
882 route_name='edit_user_ssh_keys_add', request_method='POST')
883 route_name='edit_user_ssh_keys_add', request_method='POST')
883 def ssh_keys_add(self):
884 def ssh_keys_add(self):
884 _ = self.request.translate
885 _ = self.request.translate
885 c = self.load_default_context()
886 c = self.load_default_context()
886
887
887 user_id = self.db_user_id
888 user_id = self.db_user_id
888 c.user = self.db_user
889 c.user = self.db_user
889
890
890 user_data = c.user.get_api_data()
891 user_data = c.user.get_api_data()
891 key_data = self.request.POST.get('key_data')
892 key_data = self.request.POST.get('key_data')
892 description = self.request.POST.get('description')
893 description = self.request.POST.get('description')
893
894
894 fingerprint = 'unknown'
895 fingerprint = 'unknown'
895 try:
896 try:
896 if not key_data:
897 if not key_data:
897 raise ValueError('Please add a valid public key')
898 raise ValueError('Please add a valid public key')
898
899
899 key = SshKeyModel().parse_key(key_data.strip())
900 key = SshKeyModel().parse_key(key_data.strip())
900 fingerprint = key.hash_md5()
901 fingerprint = key.hash_md5()
901
902
902 ssh_key = SshKeyModel().create(
903 ssh_key = SshKeyModel().create(
903 c.user.user_id, fingerprint, key.keydata, description)
904 c.user.user_id, fingerprint, key.keydata, description)
904 ssh_key_data = ssh_key.get_api_data()
905 ssh_key_data = ssh_key.get_api_data()
905
906
906 audit_logger.store_web(
907 audit_logger.store_web(
907 'user.edit.ssh_key.add', action_data={
908 'user.edit.ssh_key.add', action_data={
908 'data': {'ssh_key': ssh_key_data, 'user': user_data}},
909 'data': {'ssh_key': ssh_key_data, 'user': user_data}},
909 user=self._rhodecode_user, )
910 user=self._rhodecode_user, )
910 Session().commit()
911 Session().commit()
911
912
912 # Trigger an event on change of keys.
913 # Trigger an event on change of keys.
913 trigger(SshKeyFileChangeEvent(), self.request.registry)
914 trigger(SshKeyFileChangeEvent(), self.request.registry)
914
915
915 h.flash(_("Ssh Key successfully created"), category='success')
916 h.flash(_("Ssh Key successfully created"), category='success')
916
917
917 except IntegrityError:
918 except IntegrityError:
918 log.exception("Exception during ssh key saving")
919 log.exception("Exception during ssh key saving")
919 err = 'Such key with fingerprint `{}` already exists, ' \
920 err = 'Such key with fingerprint `{}` already exists, ' \
920 'please use a different one'.format(fingerprint)
921 'please use a different one'.format(fingerprint)
921 h.flash(_('An error occurred during ssh key saving: {}').format(err),
922 h.flash(_('An error occurred during ssh key saving: {}').format(err),
922 category='error')
923 category='error')
923 except Exception as e:
924 except Exception as e:
924 log.exception("Exception during ssh key saving")
925 log.exception("Exception during ssh key saving")
925 h.flash(_('An error occurred during ssh key saving: {}').format(e),
926 h.flash(_('An error occurred during ssh key saving: {}').format(e),
926 category='error')
927 category='error')
927
928
928 return HTTPFound(
929 return HTTPFound(
929 h.route_path('edit_user_ssh_keys', user_id=user_id))
930 h.route_path('edit_user_ssh_keys', user_id=user_id))
930
931
931 @LoginRequired()
932 @LoginRequired()
932 @HasPermissionAllDecorator('hg.admin')
933 @HasPermissionAllDecorator('hg.admin')
933 @CSRFRequired()
934 @CSRFRequired()
934 @view_config(
935 @view_config(
935 route_name='edit_user_ssh_keys_delete', request_method='POST')
936 route_name='edit_user_ssh_keys_delete', request_method='POST')
936 def ssh_keys_delete(self):
937 def ssh_keys_delete(self):
937 _ = self.request.translate
938 _ = self.request.translate
938 c = self.load_default_context()
939 c = self.load_default_context()
939
940
940 user_id = self.db_user_id
941 user_id = self.db_user_id
941 c.user = self.db_user
942 c.user = self.db_user
942
943
943 user_data = c.user.get_api_data()
944 user_data = c.user.get_api_data()
944
945
945 del_ssh_key = self.request.POST.get('del_ssh_key')
946 del_ssh_key = self.request.POST.get('del_ssh_key')
946
947
947 if del_ssh_key:
948 if del_ssh_key:
948 ssh_key = UserSshKeys.get_or_404(del_ssh_key)
949 ssh_key = UserSshKeys.get_or_404(del_ssh_key)
949 ssh_key_data = ssh_key.get_api_data()
950 ssh_key_data = ssh_key.get_api_data()
950
951
951 SshKeyModel().delete(del_ssh_key, c.user.user_id)
952 SshKeyModel().delete(del_ssh_key, c.user.user_id)
952 audit_logger.store_web(
953 audit_logger.store_web(
953 'user.edit.ssh_key.delete', action_data={
954 'user.edit.ssh_key.delete', action_data={
954 'data': {'ssh_key': ssh_key_data, 'user': user_data}},
955 'data': {'ssh_key': ssh_key_data, 'user': user_data}},
955 user=self._rhodecode_user,)
956 user=self._rhodecode_user,)
956 Session().commit()
957 Session().commit()
957 # Trigger an event on change of keys.
958 # Trigger an event on change of keys.
958 trigger(SshKeyFileChangeEvent(), self.request.registry)
959 trigger(SshKeyFileChangeEvent(), self.request.registry)
959 h.flash(_("Ssh key successfully deleted"), category='success')
960 h.flash(_("Ssh key successfully deleted"), category='success')
960
961
961 return HTTPFound(h.route_path('edit_user_ssh_keys', user_id=user_id))
962 return HTTPFound(h.route_path('edit_user_ssh_keys', user_id=user_id))
962
963
963 @LoginRequired()
964 @LoginRequired()
964 @HasPermissionAllDecorator('hg.admin')
965 @HasPermissionAllDecorator('hg.admin')
965 @view_config(
966 @view_config(
966 route_name='edit_user_emails', request_method='GET',
967 route_name='edit_user_emails', request_method='GET',
967 renderer='rhodecode:templates/admin/users/user_edit.mako')
968 renderer='rhodecode:templates/admin/users/user_edit.mako')
968 def emails(self):
969 def emails(self):
969 _ = self.request.translate
970 _ = self.request.translate
970 c = self.load_default_context()
971 c = self.load_default_context()
971 c.user = self.db_user
972 c.user = self.db_user
972
973
973 c.active = 'emails'
974 c.active = 'emails'
974 c.user_email_map = UserEmailMap.query() \
975 c.user_email_map = UserEmailMap.query() \
975 .filter(UserEmailMap.user == c.user).all()
976 .filter(UserEmailMap.user == c.user).all()
976
977
977 return self._get_template_context(c)
978 return self._get_template_context(c)
978
979
979 @LoginRequired()
980 @LoginRequired()
980 @HasPermissionAllDecorator('hg.admin')
981 @HasPermissionAllDecorator('hg.admin')
981 @CSRFRequired()
982 @CSRFRequired()
982 @view_config(
983 @view_config(
983 route_name='edit_user_emails_add', request_method='POST')
984 route_name='edit_user_emails_add', request_method='POST')
984 def emails_add(self):
985 def emails_add(self):
985 _ = self.request.translate
986 _ = self.request.translate
986 c = self.load_default_context()
987 c = self.load_default_context()
987
988
988 user_id = self.db_user_id
989 user_id = self.db_user_id
989 c.user = self.db_user
990 c.user = self.db_user
990
991
991 email = self.request.POST.get('new_email')
992 email = self.request.POST.get('new_email')
992 user_data = c.user.get_api_data()
993 user_data = c.user.get_api_data()
993 try:
994 try:
994
995
995 form = UserExtraEmailForm(self.request.translate)()
996 form = UserExtraEmailForm(self.request.translate)()
996 data = form.to_python({'email': email})
997 data = form.to_python({'email': email})
997 email = data['email']
998 email = data['email']
998
999
999 UserModel().add_extra_email(c.user.user_id, email)
1000 UserModel().add_extra_email(c.user.user_id, email)
1000 audit_logger.store_web(
1001 audit_logger.store_web(
1001 'user.edit.email.add',
1002 'user.edit.email.add',
1002 action_data={'email': email, 'user': user_data},
1003 action_data={'email': email, 'user': user_data},
1003 user=self._rhodecode_user)
1004 user=self._rhodecode_user)
1004 Session().commit()
1005 Session().commit()
1005 h.flash(_("Added new email address `%s` for user account") % email,
1006 h.flash(_("Added new email address `%s` for user account") % email,
1006 category='success')
1007 category='success')
1007 except formencode.Invalid as error:
1008 except formencode.Invalid as error:
1008 h.flash(h.escape(error.error_dict['email']), category='error')
1009 h.flash(h.escape(error.error_dict['email']), category='error')
1009 except IntegrityError:
1010 except IntegrityError:
1010 log.warning("Email %s already exists", email)
1011 log.warning("Email %s already exists", email)
1011 h.flash(_('Email `{}` is already registered for another user.').format(email),
1012 h.flash(_('Email `{}` is already registered for another user.').format(email),
1012 category='error')
1013 category='error')
1013 except Exception:
1014 except Exception:
1014 log.exception("Exception during email saving")
1015 log.exception("Exception during email saving")
1015 h.flash(_('An error occurred during email saving'),
1016 h.flash(_('An error occurred during email saving'),
1016 category='error')
1017 category='error')
1017 raise HTTPFound(h.route_path('edit_user_emails', user_id=user_id))
1018 raise HTTPFound(h.route_path('edit_user_emails', user_id=user_id))
1018
1019
1019 @LoginRequired()
1020 @LoginRequired()
1020 @HasPermissionAllDecorator('hg.admin')
1021 @HasPermissionAllDecorator('hg.admin')
1021 @CSRFRequired()
1022 @CSRFRequired()
1022 @view_config(
1023 @view_config(
1023 route_name='edit_user_emails_delete', request_method='POST')
1024 route_name='edit_user_emails_delete', request_method='POST')
1024 def emails_delete(self):
1025 def emails_delete(self):
1025 _ = self.request.translate
1026 _ = self.request.translate
1026 c = self.load_default_context()
1027 c = self.load_default_context()
1027
1028
1028 user_id = self.db_user_id
1029 user_id = self.db_user_id
1029 c.user = self.db_user
1030 c.user = self.db_user
1030
1031
1031 email_id = self.request.POST.get('del_email_id')
1032 email_id = self.request.POST.get('del_email_id')
1032 user_model = UserModel()
1033 user_model = UserModel()
1033
1034
1034 email = UserEmailMap.query().get(email_id).email
1035 email = UserEmailMap.query().get(email_id).email
1035 user_data = c.user.get_api_data()
1036 user_data = c.user.get_api_data()
1036 user_model.delete_extra_email(c.user.user_id, email_id)
1037 user_model.delete_extra_email(c.user.user_id, email_id)
1037 audit_logger.store_web(
1038 audit_logger.store_web(
1038 'user.edit.email.delete',
1039 'user.edit.email.delete',
1039 action_data={'email': email, 'user': user_data},
1040 action_data={'email': email, 'user': user_data},
1040 user=self._rhodecode_user)
1041 user=self._rhodecode_user)
1041 Session().commit()
1042 Session().commit()
1042 h.flash(_("Removed email address from user account"),
1043 h.flash(_("Removed email address from user account"),
1043 category='success')
1044 category='success')
1044 raise HTTPFound(h.route_path('edit_user_emails', user_id=user_id))
1045 raise HTTPFound(h.route_path('edit_user_emails', user_id=user_id))
1045
1046
1046 @LoginRequired()
1047 @LoginRequired()
1047 @HasPermissionAllDecorator('hg.admin')
1048 @HasPermissionAllDecorator('hg.admin')
1048 @view_config(
1049 @view_config(
1049 route_name='edit_user_ips', request_method='GET',
1050 route_name='edit_user_ips', request_method='GET',
1050 renderer='rhodecode:templates/admin/users/user_edit.mako')
1051 renderer='rhodecode:templates/admin/users/user_edit.mako')
1051 def ips(self):
1052 def ips(self):
1052 _ = self.request.translate
1053 _ = self.request.translate
1053 c = self.load_default_context()
1054 c = self.load_default_context()
1054 c.user = self.db_user
1055 c.user = self.db_user
1055
1056
1056 c.active = 'ips'
1057 c.active = 'ips'
1057 c.user_ip_map = UserIpMap.query() \
1058 c.user_ip_map = UserIpMap.query() \
1058 .filter(UserIpMap.user == c.user).all()
1059 .filter(UserIpMap.user == c.user).all()
1059
1060
1060 c.inherit_default_ips = c.user.inherit_default_permissions
1061 c.inherit_default_ips = c.user.inherit_default_permissions
1061 c.default_user_ip_map = UserIpMap.query() \
1062 c.default_user_ip_map = UserIpMap.query() \
1062 .filter(UserIpMap.user == User.get_default_user()).all()
1063 .filter(UserIpMap.user == User.get_default_user()).all()
1063
1064
1064 return self._get_template_context(c)
1065 return self._get_template_context(c)
1065
1066
1066 @LoginRequired()
1067 @LoginRequired()
1067 @HasPermissionAllDecorator('hg.admin')
1068 @HasPermissionAllDecorator('hg.admin')
1068 @CSRFRequired()
1069 @CSRFRequired()
1069 @view_config(
1070 @view_config(
1070 route_name='edit_user_ips_add', request_method='POST')
1071 route_name='edit_user_ips_add', request_method='POST')
1071 # NOTE(marcink): this view is allowed for default users, as we can
1072 # NOTE(marcink): this view is allowed for default users, as we can
1072 # edit their IP white list
1073 # edit their IP white list
1073 def ips_add(self):
1074 def ips_add(self):
1074 _ = self.request.translate
1075 _ = self.request.translate
1075 c = self.load_default_context()
1076 c = self.load_default_context()
1076
1077
1077 user_id = self.db_user_id
1078 user_id = self.db_user_id
1078 c.user = self.db_user
1079 c.user = self.db_user
1079
1080
1080 user_model = UserModel()
1081 user_model = UserModel()
1081 desc = self.request.POST.get('description')
1082 desc = self.request.POST.get('description')
1082 try:
1083 try:
1083 ip_list = user_model.parse_ip_range(
1084 ip_list = user_model.parse_ip_range(
1084 self.request.POST.get('new_ip'))
1085 self.request.POST.get('new_ip'))
1085 except Exception as e:
1086 except Exception as e:
1086 ip_list = []
1087 ip_list = []
1087 log.exception("Exception during ip saving")
1088 log.exception("Exception during ip saving")
1088 h.flash(_('An error occurred during ip saving:%s' % (e,)),
1089 h.flash(_('An error occurred during ip saving:%s' % (e,)),
1089 category='error')
1090 category='error')
1090 added = []
1091 added = []
1091 user_data = c.user.get_api_data()
1092 user_data = c.user.get_api_data()
1092 for ip in ip_list:
1093 for ip in ip_list:
1093 try:
1094 try:
1094 form = UserExtraIpForm(self.request.translate)()
1095 form = UserExtraIpForm(self.request.translate)()
1095 data = form.to_python({'ip': ip})
1096 data = form.to_python({'ip': ip})
1096 ip = data['ip']
1097 ip = data['ip']
1097
1098
1098 user_model.add_extra_ip(c.user.user_id, ip, desc)
1099 user_model.add_extra_ip(c.user.user_id, ip, desc)
1099 audit_logger.store_web(
1100 audit_logger.store_web(
1100 'user.edit.ip.add',
1101 'user.edit.ip.add',
1101 action_data={'ip': ip, 'user': user_data},
1102 action_data={'ip': ip, 'user': user_data},
1102 user=self._rhodecode_user)
1103 user=self._rhodecode_user)
1103 Session().commit()
1104 Session().commit()
1104 added.append(ip)
1105 added.append(ip)
1105 except formencode.Invalid as error:
1106 except formencode.Invalid as error:
1106 msg = error.error_dict['ip']
1107 msg = error.error_dict['ip']
1107 h.flash(msg, category='error')
1108 h.flash(msg, category='error')
1108 except Exception:
1109 except Exception:
1109 log.exception("Exception during ip saving")
1110 log.exception("Exception during ip saving")
1110 h.flash(_('An error occurred during ip saving'),
1111 h.flash(_('An error occurred during ip saving'),
1111 category='error')
1112 category='error')
1112 if added:
1113 if added:
1113 h.flash(
1114 h.flash(
1114 _("Added ips %s to user whitelist") % (', '.join(ip_list), ),
1115 _("Added ips %s to user whitelist") % (', '.join(ip_list), ),
1115 category='success')
1116 category='success')
1116 if 'default_user' in self.request.POST:
1117 if 'default_user' in self.request.POST:
1117 # case for editing global IP list we do it for 'DEFAULT' user
1118 # case for editing global IP list we do it for 'DEFAULT' user
1118 raise HTTPFound(h.route_path('admin_permissions_ips'))
1119 raise HTTPFound(h.route_path('admin_permissions_ips'))
1119 raise HTTPFound(h.route_path('edit_user_ips', user_id=user_id))
1120 raise HTTPFound(h.route_path('edit_user_ips', user_id=user_id))
1120
1121
1121 @LoginRequired()
1122 @LoginRequired()
1122 @HasPermissionAllDecorator('hg.admin')
1123 @HasPermissionAllDecorator('hg.admin')
1123 @CSRFRequired()
1124 @CSRFRequired()
1124 @view_config(
1125 @view_config(
1125 route_name='edit_user_ips_delete', request_method='POST')
1126 route_name='edit_user_ips_delete', request_method='POST')
1126 # NOTE(marcink): this view is allowed for default users, as we can
1127 # NOTE(marcink): this view is allowed for default users, as we can
1127 # edit their IP white list
1128 # edit their IP white list
1128 def ips_delete(self):
1129 def ips_delete(self):
1129 _ = self.request.translate
1130 _ = self.request.translate
1130 c = self.load_default_context()
1131 c = self.load_default_context()
1131
1132
1132 user_id = self.db_user_id
1133 user_id = self.db_user_id
1133 c.user = self.db_user
1134 c.user = self.db_user
1134
1135
1135 ip_id = self.request.POST.get('del_ip_id')
1136 ip_id = self.request.POST.get('del_ip_id')
1136 user_model = UserModel()
1137 user_model = UserModel()
1137 user_data = c.user.get_api_data()
1138 user_data = c.user.get_api_data()
1138 ip = UserIpMap.query().get(ip_id).ip_addr
1139 ip = UserIpMap.query().get(ip_id).ip_addr
1139 user_model.delete_extra_ip(c.user.user_id, ip_id)
1140 user_model.delete_extra_ip(c.user.user_id, ip_id)
1140 audit_logger.store_web(
1141 audit_logger.store_web(
1141 'user.edit.ip.delete', action_data={'ip': ip, 'user': user_data},
1142 'user.edit.ip.delete', action_data={'ip': ip, 'user': user_data},
1142 user=self._rhodecode_user)
1143 user=self._rhodecode_user)
1143 Session().commit()
1144 Session().commit()
1144 h.flash(_("Removed ip address from user whitelist"), category='success')
1145 h.flash(_("Removed ip address from user whitelist"), category='success')
1145
1146
1146 if 'default_user' in self.request.POST:
1147 if 'default_user' in self.request.POST:
1147 # case for editing global IP list we do it for 'DEFAULT' user
1148 # case for editing global IP list we do it for 'DEFAULT' user
1148 raise HTTPFound(h.route_path('admin_permissions_ips'))
1149 raise HTTPFound(h.route_path('admin_permissions_ips'))
1149 raise HTTPFound(h.route_path('edit_user_ips', user_id=user_id))
1150 raise HTTPFound(h.route_path('edit_user_ips', user_id=user_id))
1150
1151
1151 @LoginRequired()
1152 @LoginRequired()
1152 @HasPermissionAllDecorator('hg.admin')
1153 @HasPermissionAllDecorator('hg.admin')
1153 @view_config(
1154 @view_config(
1154 route_name='edit_user_groups_management', request_method='GET',
1155 route_name='edit_user_groups_management', request_method='GET',
1155 renderer='rhodecode:templates/admin/users/user_edit.mako')
1156 renderer='rhodecode:templates/admin/users/user_edit.mako')
1156 def groups_management(self):
1157 def groups_management(self):
1157 c = self.load_default_context()
1158 c = self.load_default_context()
1158 c.user = self.db_user
1159 c.user = self.db_user
1159 c.data = c.user.group_member
1160 c.data = c.user.group_member
1160
1161
1161 groups = [UserGroupModel.get_user_groups_as_dict(group.users_group)
1162 groups = [UserGroupModel.get_user_groups_as_dict(group.users_group)
1162 for group in c.user.group_member]
1163 for group in c.user.group_member]
1163 c.groups = json.dumps(groups)
1164 c.groups = json.dumps(groups)
1164 c.active = 'groups'
1165 c.active = 'groups'
1165
1166
1166 return self._get_template_context(c)
1167 return self._get_template_context(c)
1167
1168
1168 @LoginRequired()
1169 @LoginRequired()
1169 @HasPermissionAllDecorator('hg.admin')
1170 @HasPermissionAllDecorator('hg.admin')
1170 @CSRFRequired()
1171 @CSRFRequired()
1171 @view_config(
1172 @view_config(
1172 route_name='edit_user_groups_management_updates', request_method='POST')
1173 route_name='edit_user_groups_management_updates', request_method='POST')
1173 def groups_management_updates(self):
1174 def groups_management_updates(self):
1174 _ = self.request.translate
1175 _ = self.request.translate
1175 c = self.load_default_context()
1176 c = self.load_default_context()
1176
1177
1177 user_id = self.db_user_id
1178 user_id = self.db_user_id
1178 c.user = self.db_user
1179 c.user = self.db_user
1179
1180
1180 user_groups = set(self.request.POST.getall('users_group_id'))
1181 user_groups = set(self.request.POST.getall('users_group_id'))
1181 user_groups_objects = []
1182 user_groups_objects = []
1182
1183
1183 for ugid in user_groups:
1184 for ugid in user_groups:
1184 user_groups_objects.append(
1185 user_groups_objects.append(
1185 UserGroupModel().get_group(safe_int(ugid)))
1186 UserGroupModel().get_group(safe_int(ugid)))
1186 user_group_model = UserGroupModel()
1187 user_group_model = UserGroupModel()
1187 added_to_groups, removed_from_groups = \
1188 added_to_groups, removed_from_groups = \
1188 user_group_model.change_groups(c.user, user_groups_objects)
1189 user_group_model.change_groups(c.user, user_groups_objects)
1189
1190
1190 user_data = c.user.get_api_data()
1191 user_data = c.user.get_api_data()
1191 for user_group_id in added_to_groups:
1192 for user_group_id in added_to_groups:
1192 user_group = UserGroup.get(user_group_id)
1193 user_group = UserGroup.get(user_group_id)
1193 old_values = user_group.get_api_data()
1194 old_values = user_group.get_api_data()
1194 audit_logger.store_web(
1195 audit_logger.store_web(
1195 'user_group.edit.member.add',
1196 'user_group.edit.member.add',
1196 action_data={'user': user_data, 'old_data': old_values},
1197 action_data={'user': user_data, 'old_data': old_values},
1197 user=self._rhodecode_user)
1198 user=self._rhodecode_user)
1198
1199
1199 for user_group_id in removed_from_groups:
1200 for user_group_id in removed_from_groups:
1200 user_group = UserGroup.get(user_group_id)
1201 user_group = UserGroup.get(user_group_id)
1201 old_values = user_group.get_api_data()
1202 old_values = user_group.get_api_data()
1202 audit_logger.store_web(
1203 audit_logger.store_web(
1203 'user_group.edit.member.delete',
1204 'user_group.edit.member.delete',
1204 action_data={'user': user_data, 'old_data': old_values},
1205 action_data={'user': user_data, 'old_data': old_values},
1205 user=self._rhodecode_user)
1206 user=self._rhodecode_user)
1206
1207
1207 Session().commit()
1208 Session().commit()
1208 c.active = 'user_groups_management'
1209 c.active = 'user_groups_management'
1209 h.flash(_("Groups successfully changed"), category='success')
1210 h.flash(_("Groups successfully changed"), category='success')
1210
1211
1211 return HTTPFound(h.route_path(
1212 return HTTPFound(h.route_path(
1212 'edit_user_groups_management', user_id=user_id))
1213 'edit_user_groups_management', user_id=user_id))
1213
1214
1214 @LoginRequired()
1215 @LoginRequired()
1215 @HasPermissionAllDecorator('hg.admin')
1216 @HasPermissionAllDecorator('hg.admin')
1216 @view_config(
1217 @view_config(
1217 route_name='edit_user_audit_logs', request_method='GET',
1218 route_name='edit_user_audit_logs', request_method='GET',
1218 renderer='rhodecode:templates/admin/users/user_edit.mako')
1219 renderer='rhodecode:templates/admin/users/user_edit.mako')
1219 def user_audit_logs(self):
1220 def user_audit_logs(self):
1220 _ = self.request.translate
1221 _ = self.request.translate
1221 c = self.load_default_context()
1222 c = self.load_default_context()
1222 c.user = self.db_user
1223 c.user = self.db_user
1223
1224
1224 c.active = 'audit'
1225 c.active = 'audit'
1225
1226
1226 p = safe_int(self.request.GET.get('page', 1), 1)
1227 p = safe_int(self.request.GET.get('page', 1), 1)
1227
1228
1228 filter_term = self.request.GET.get('filter')
1229 filter_term = self.request.GET.get('filter')
1229 user_log = UserModel().get_user_log(c.user, filter_term)
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 if filter_term:
1236 if filter_term:
1233 kw['filter'] = filter_term
1237 query_params['filter'] = filter_term
1234 return self.request.current_route_path(_query=kw)
1238 return self.request.current_route_path(_query=query_params)
1235
1239
1236 c.audit_logs = h.Page(
1240 c.audit_logs = SqlPage(
1237 user_log, page=p, items_per_page=10, url=url_generator)
1241 user_log, page=p, items_per_page=10, url_maker=url_generator)
1238 c.filter_term = filter_term
1242 c.filter_term = filter_term
1239 return self._get_template_context(c)
1243 return self._get_template_context(c)
1240
1244
1241 @LoginRequired()
1245 @LoginRequired()
1242 @HasPermissionAllDecorator('hg.admin')
1246 @HasPermissionAllDecorator('hg.admin')
1243 @view_config(
1247 @view_config(
1244 route_name='edit_user_audit_logs_download', request_method='GET',
1248 route_name='edit_user_audit_logs_download', request_method='GET',
1245 renderer='string')
1249 renderer='string')
1246 def user_audit_logs_download(self):
1250 def user_audit_logs_download(self):
1247 _ = self.request.translate
1251 _ = self.request.translate
1248 c = self.load_default_context()
1252 c = self.load_default_context()
1249 c.user = self.db_user
1253 c.user = self.db_user
1250
1254
1251 user_log = UserModel().get_user_log(c.user, filter_term=None)
1255 user_log = UserModel().get_user_log(c.user, filter_term=None)
1252
1256
1253 audit_log_data = {}
1257 audit_log_data = {}
1254 for entry in user_log:
1258 for entry in user_log:
1255 audit_log_data[entry.user_log_id] = entry.get_dict()
1259 audit_log_data[entry.user_log_id] = entry.get_dict()
1256
1260
1257 response = Response(json.dumps(audit_log_data, indent=4))
1261 response = Response(json.dumps(audit_log_data, indent=4))
1258 response.content_disposition = str(
1262 response.content_disposition = str(
1259 'attachment; filename=%s' % 'user_{}_audit_logs.json'.format(c.user.user_id))
1263 'attachment; filename=%s' % 'user_{}_audit_logs.json'.format(c.user.user_id))
1260 response.content_type = 'application/json'
1264 response.content_type = 'application/json'
1261
1265
1262 return response
1266 return response
1263
1267
1264 @LoginRequired()
1268 @LoginRequired()
1265 @HasPermissionAllDecorator('hg.admin')
1269 @HasPermissionAllDecorator('hg.admin')
1266 @view_config(
1270 @view_config(
1267 route_name='edit_user_perms_summary', request_method='GET',
1271 route_name='edit_user_perms_summary', request_method='GET',
1268 renderer='rhodecode:templates/admin/users/user_edit.mako')
1272 renderer='rhodecode:templates/admin/users/user_edit.mako')
1269 def user_perms_summary(self):
1273 def user_perms_summary(self):
1270 _ = self.request.translate
1274 _ = self.request.translate
1271 c = self.load_default_context()
1275 c = self.load_default_context()
1272 c.user = self.db_user
1276 c.user = self.db_user
1273
1277
1274 c.active = 'perms_summary'
1278 c.active = 'perms_summary'
1275 c.perm_user = c.user.AuthUser(ip_addr=self.request.remote_addr)
1279 c.perm_user = c.user.AuthUser(ip_addr=self.request.remote_addr)
1276
1280
1277 return self._get_template_context(c)
1281 return self._get_template_context(c)
1278
1282
1279 @LoginRequired()
1283 @LoginRequired()
1280 @HasPermissionAllDecorator('hg.admin')
1284 @HasPermissionAllDecorator('hg.admin')
1281 @view_config(
1285 @view_config(
1282 route_name='edit_user_perms_summary_json', request_method='GET',
1286 route_name='edit_user_perms_summary_json', request_method='GET',
1283 renderer='json_ext')
1287 renderer='json_ext')
1284 def user_perms_summary_json(self):
1288 def user_perms_summary_json(self):
1285 self.load_default_context()
1289 self.load_default_context()
1286 perm_user = self.db_user.AuthUser(ip_addr=self.request.remote_addr)
1290 perm_user = self.db_user.AuthUser(ip_addr=self.request.remote_addr)
1287
1291
1288 return perm_user.permissions
1292 return perm_user.permissions
1289
1293
1290 @LoginRequired()
1294 @LoginRequired()
1291 @HasPermissionAllDecorator('hg.admin')
1295 @HasPermissionAllDecorator('hg.admin')
1292 @view_config(
1296 @view_config(
1293 route_name='edit_user_caches', request_method='GET',
1297 route_name='edit_user_caches', request_method='GET',
1294 renderer='rhodecode:templates/admin/users/user_edit.mako')
1298 renderer='rhodecode:templates/admin/users/user_edit.mako')
1295 def user_caches(self):
1299 def user_caches(self):
1296 _ = self.request.translate
1300 _ = self.request.translate
1297 c = self.load_default_context()
1301 c = self.load_default_context()
1298 c.user = self.db_user
1302 c.user = self.db_user
1299
1303
1300 c.active = 'caches'
1304 c.active = 'caches'
1301 c.perm_user = c.user.AuthUser(ip_addr=self.request.remote_addr)
1305 c.perm_user = c.user.AuthUser(ip_addr=self.request.remote_addr)
1302
1306
1303 cache_namespace_uid = 'cache_user_auth.{}'.format(self.db_user.user_id)
1307 cache_namespace_uid = 'cache_user_auth.{}'.format(self.db_user.user_id)
1304 c.region = rc_cache.get_or_create_region('cache_perms', cache_namespace_uid)
1308 c.region = rc_cache.get_or_create_region('cache_perms', cache_namespace_uid)
1305 c.backend = c.region.backend
1309 c.backend = c.region.backend
1306 c.user_keys = sorted(c.region.backend.list_keys(prefix=cache_namespace_uid))
1310 c.user_keys = sorted(c.region.backend.list_keys(prefix=cache_namespace_uid))
1307
1311
1308 return self._get_template_context(c)
1312 return self._get_template_context(c)
1309
1313
1310 @LoginRequired()
1314 @LoginRequired()
1311 @HasPermissionAllDecorator('hg.admin')
1315 @HasPermissionAllDecorator('hg.admin')
1312 @CSRFRequired()
1316 @CSRFRequired()
1313 @view_config(
1317 @view_config(
1314 route_name='edit_user_caches_update', request_method='POST')
1318 route_name='edit_user_caches_update', request_method='POST')
1315 def user_caches_update(self):
1319 def user_caches_update(self):
1316 _ = self.request.translate
1320 _ = self.request.translate
1317 c = self.load_default_context()
1321 c = self.load_default_context()
1318 c.user = self.db_user
1322 c.user = self.db_user
1319
1323
1320 c.active = 'caches'
1324 c.active = 'caches'
1321 c.perm_user = c.user.AuthUser(ip_addr=self.request.remote_addr)
1325 c.perm_user = c.user.AuthUser(ip_addr=self.request.remote_addr)
1322
1326
1323 cache_namespace_uid = 'cache_user_auth.{}'.format(self.db_user.user_id)
1327 cache_namespace_uid = 'cache_user_auth.{}'.format(self.db_user.user_id)
1324 del_keys = rc_cache.clear_cache_namespace('cache_perms', cache_namespace_uid)
1328 del_keys = rc_cache.clear_cache_namespace('cache_perms', cache_namespace_uid)
1325
1329
1326 h.flash(_("Deleted {} cache keys").format(del_keys), category='success')
1330 h.flash(_("Deleted {} cache keys").format(del_keys), category='success')
1327
1331
1328 return HTTPFound(h.route_path(
1332 return HTTPFound(h.route_path(
1329 'edit_user_caches', user_id=c.user.user_id))
1333 'edit_user_caches', user_id=c.user.user_id))
@@ -1,387 +1,388 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2019 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21
21
22 import logging
22 import logging
23 import itertools
23 import itertools
24
24
25 from webhelpers.feedgenerator import Atom1Feed, Rss201rev2Feed
25 from webhelpers.feedgenerator import Atom1Feed, Rss201rev2Feed
26
26
27 from pyramid.view import view_config
27 from pyramid.view import view_config
28 from pyramid.httpexceptions import HTTPBadRequest
28 from pyramid.httpexceptions import HTTPBadRequest
29 from pyramid.response import Response
29 from pyramid.response import Response
30 from pyramid.renderers import render
30 from pyramid.renderers import render
31
31
32 from rhodecode.apps._base import BaseAppView
32 from rhodecode.apps._base import BaseAppView
33 from rhodecode.model.db import (
33 from rhodecode.model.db import (
34 or_, joinedload, Repository, UserLog, UserFollowing, User, UserApiKeys)
34 or_, joinedload, Repository, UserLog, UserFollowing, User, UserApiKeys)
35 from rhodecode.model.meta import Session
35 from rhodecode.model.meta import Session
36 import rhodecode.lib.helpers as h
36 import rhodecode.lib.helpers as h
37 from rhodecode.lib.helpers import Page
37 from rhodecode.lib.helpers import SqlPage
38 from rhodecode.lib.user_log_filter import user_log_filter
38 from rhodecode.lib.user_log_filter import user_log_filter
39 from rhodecode.lib.auth import LoginRequired, NotAnonymous, CSRFRequired, HasRepoPermissionAny
39 from rhodecode.lib.auth import LoginRequired, NotAnonymous, CSRFRequired, HasRepoPermissionAny
40 from rhodecode.lib.utils2 import safe_int, AttributeDict, md5_safe
40 from rhodecode.lib.utils2 import safe_int, AttributeDict, md5_safe
41 from rhodecode.model.scm import ScmModel
41 from rhodecode.model.scm import ScmModel
42
42
43 log = logging.getLogger(__name__)
43 log = logging.getLogger(__name__)
44
44
45
45
46 class JournalView(BaseAppView):
46 class JournalView(BaseAppView):
47
47
48 def load_default_context(self):
48 def load_default_context(self):
49 c = self._get_local_tmpl_context(include_app_defaults=True)
49 c = self._get_local_tmpl_context(include_app_defaults=True)
50
50
51 self._load_defaults(c.rhodecode_name)
51 self._load_defaults(c.rhodecode_name)
52
52
53 # TODO(marcink): what is this, why we need a global register ?
53 # TODO(marcink): what is this, why we need a global register ?
54 c.search_term = self.request.GET.get('filter') or ''
54 c.search_term = self.request.GET.get('filter') or ''
55 return c
55 return c
56
56
57 def _get_config(self, rhodecode_name):
57 def _get_config(self, rhodecode_name):
58 import rhodecode
58 import rhodecode
59 config = rhodecode.CONFIG
59 config = rhodecode.CONFIG
60
60
61 return {
61 return {
62 'language': 'en-us',
62 'language': 'en-us',
63 'feed_ttl': '5', # TTL of feed,
63 'feed_ttl': '5', # TTL of feed,
64 'feed_items_per_page':
64 'feed_items_per_page':
65 safe_int(config.get('rss_items_per_page', 20)),
65 safe_int(config.get('rss_items_per_page', 20)),
66 'rhodecode_name': rhodecode_name
66 'rhodecode_name': rhodecode_name
67 }
67 }
68
68
69 def _load_defaults(self, rhodecode_name):
69 def _load_defaults(self, rhodecode_name):
70 config = self._get_config(rhodecode_name)
70 config = self._get_config(rhodecode_name)
71 # common values for feeds
71 # common values for feeds
72 self.language = config["language"]
72 self.language = config["language"]
73 self.ttl = config["feed_ttl"]
73 self.ttl = config["feed_ttl"]
74 self.feed_items_per_page = config['feed_items_per_page']
74 self.feed_items_per_page = config['feed_items_per_page']
75 self.rhodecode_name = config['rhodecode_name']
75 self.rhodecode_name = config['rhodecode_name']
76
76
77 def _get_daily_aggregate(self, journal):
77 def _get_daily_aggregate(self, journal):
78 groups = []
78 groups = []
79 for k, g in itertools.groupby(journal, lambda x: x.action_as_day):
79 for k, g in itertools.groupby(journal, lambda x: x.action_as_day):
80 user_group = []
80 user_group = []
81 # groupby username if it's a present value, else
81 # groupby username if it's a present value, else
82 # fallback to journal username
82 # fallback to journal username
83 for _, g2 in itertools.groupby(
83 for _, g2 in itertools.groupby(
84 list(g), lambda x: x.user.username if x.user else x.username):
84 list(g), lambda x: x.user.username if x.user else x.username):
85 l = list(g2)
85 l = list(g2)
86 user_group.append((l[0].user, l))
86 user_group.append((l[0].user, l))
87
87
88 groups.append((k, user_group,))
88 groups.append((k, user_group,))
89
89
90 return groups
90 return groups
91
91
92 def _get_journal_data(self, following_repos, search_term):
92 def _get_journal_data(self, following_repos, search_term):
93 repo_ids = [x.follows_repository.repo_id for x in following_repos
93 repo_ids = [x.follows_repository.repo_id for x in following_repos
94 if x.follows_repository is not None]
94 if x.follows_repository is not None]
95 user_ids = [x.follows_user.user_id for x in following_repos
95 user_ids = [x.follows_user.user_id for x in following_repos
96 if x.follows_user is not None]
96 if x.follows_user is not None]
97
97
98 filtering_criterion = None
98 filtering_criterion = None
99
99
100 if repo_ids and user_ids:
100 if repo_ids and user_ids:
101 filtering_criterion = or_(UserLog.repository_id.in_(repo_ids),
101 filtering_criterion = or_(UserLog.repository_id.in_(repo_ids),
102 UserLog.user_id.in_(user_ids))
102 UserLog.user_id.in_(user_ids))
103 if repo_ids and not user_ids:
103 if repo_ids and not user_ids:
104 filtering_criterion = UserLog.repository_id.in_(repo_ids)
104 filtering_criterion = UserLog.repository_id.in_(repo_ids)
105 if not repo_ids and user_ids:
105 if not repo_ids and user_ids:
106 filtering_criterion = UserLog.user_id.in_(user_ids)
106 filtering_criterion = UserLog.user_id.in_(user_ids)
107 if filtering_criterion is not None:
107 if filtering_criterion is not None:
108 journal = Session().query(UserLog)\
108 journal = Session().query(UserLog)\
109 .options(joinedload(UserLog.user))\
109 .options(joinedload(UserLog.user))\
110 .options(joinedload(UserLog.repository))
110 .options(joinedload(UserLog.repository))
111 # filter
111 # filter
112 try:
112 try:
113 journal = user_log_filter(journal, search_term)
113 journal = user_log_filter(journal, search_term)
114 except Exception:
114 except Exception:
115 # we want this to crash for now
115 # we want this to crash for now
116 raise
116 raise
117 journal = journal.filter(filtering_criterion)\
117 journal = journal.filter(filtering_criterion)\
118 .order_by(UserLog.action_date.desc())
118 .order_by(UserLog.action_date.desc())
119 else:
119 else:
120 journal = []
120 journal = []
121
121
122 return journal
122 return journal
123
123
124 def feed_uid(self, entry_id):
124 def feed_uid(self, entry_id):
125 return '{}:{}'.format('journal', md5_safe(entry_id))
125 return '{}:{}'.format('journal', md5_safe(entry_id))
126
126
127 def _atom_feed(self, repos, search_term, public=True):
127 def _atom_feed(self, repos, search_term, public=True):
128 _ = self.request.translate
128 _ = self.request.translate
129 journal = self._get_journal_data(repos, search_term)
129 journal = self._get_journal_data(repos, search_term)
130 if public:
130 if public:
131 _link = h.route_url('journal_public_atom')
131 _link = h.route_url('journal_public_atom')
132 _desc = '%s %s %s' % (self.rhodecode_name, _('public journal'),
132 _desc = '%s %s %s' % (self.rhodecode_name, _('public journal'),
133 'atom feed')
133 'atom feed')
134 else:
134 else:
135 _link = h.route_url('journal_atom')
135 _link = h.route_url('journal_atom')
136 _desc = '%s %s %s' % (self.rhodecode_name, _('journal'), 'atom feed')
136 _desc = '%s %s %s' % (self.rhodecode_name, _('journal'), 'atom feed')
137
137
138 feed = Atom1Feed(
138 feed = Atom1Feed(
139 title=_desc, link=_link, description=_desc,
139 title=_desc, link=_link, description=_desc,
140 language=self.language, ttl=self.ttl)
140 language=self.language, ttl=self.ttl)
141
141
142 for entry in journal[:self.feed_items_per_page]:
142 for entry in journal[:self.feed_items_per_page]:
143 user = entry.user
143 user = entry.user
144 if user is None:
144 if user is None:
145 # fix deleted users
145 # fix deleted users
146 user = AttributeDict({'short_contact': entry.username,
146 user = AttributeDict({'short_contact': entry.username,
147 'email': '',
147 'email': '',
148 'full_contact': ''})
148 'full_contact': ''})
149 action, action_extra, ico = h.action_parser(
149 action, action_extra, ico = h.action_parser(
150 self.request, entry, feed=True)
150 self.request, entry, feed=True)
151 title = "%s - %s %s" % (user.short_contact, action(),
151 title = "%s - %s %s" % (user.short_contact, action(),
152 entry.repository.repo_name)
152 entry.repository.repo_name)
153 desc = action_extra()
153 desc = action_extra()
154 _url = h.route_url('home')
154 _url = h.route_url('home')
155 if entry.repository is not None:
155 if entry.repository is not None:
156 _url = h.route_url('repo_commits',
156 _url = h.route_url('repo_commits',
157 repo_name=entry.repository.repo_name)
157 repo_name=entry.repository.repo_name)
158
158
159 feed.add_item(
159 feed.add_item(
160 unique_id=self.feed_uid(entry.user_log_id),
160 unique_id=self.feed_uid(entry.user_log_id),
161 title=title,
161 title=title,
162 pubdate=entry.action_date,
162 pubdate=entry.action_date,
163 link=_url,
163 link=_url,
164 author_email=user.email,
164 author_email=user.email,
165 author_name=user.full_contact,
165 author_name=user.full_contact,
166 description=desc)
166 description=desc)
167
167
168 response = Response(feed.writeString('utf-8'))
168 response = Response(feed.writeString('utf-8'))
169 response.content_type = feed.mime_type
169 response.content_type = feed.mime_type
170 return response
170 return response
171
171
172 def _rss_feed(self, repos, search_term, public=True):
172 def _rss_feed(self, repos, search_term, public=True):
173 _ = self.request.translate
173 _ = self.request.translate
174 journal = self._get_journal_data(repos, search_term)
174 journal = self._get_journal_data(repos, search_term)
175 if public:
175 if public:
176 _link = h.route_url('journal_public_atom')
176 _link = h.route_url('journal_public_atom')
177 _desc = '%s %s %s' % (
177 _desc = '%s %s %s' % (
178 self.rhodecode_name, _('public journal'), 'rss feed')
178 self.rhodecode_name, _('public journal'), 'rss feed')
179 else:
179 else:
180 _link = h.route_url('journal_atom')
180 _link = h.route_url('journal_atom')
181 _desc = '%s %s %s' % (
181 _desc = '%s %s %s' % (
182 self.rhodecode_name, _('journal'), 'rss feed')
182 self.rhodecode_name, _('journal'), 'rss feed')
183
183
184 feed = Rss201rev2Feed(
184 feed = Rss201rev2Feed(
185 title=_desc, link=_link, description=_desc,
185 title=_desc, link=_link, description=_desc,
186 language=self.language, ttl=self.ttl)
186 language=self.language, ttl=self.ttl)
187
187
188 for entry in journal[:self.feed_items_per_page]:
188 for entry in journal[:self.feed_items_per_page]:
189 user = entry.user
189 user = entry.user
190 if user is None:
190 if user is None:
191 # fix deleted users
191 # fix deleted users
192 user = AttributeDict({'short_contact': entry.username,
192 user = AttributeDict({'short_contact': entry.username,
193 'email': '',
193 'email': '',
194 'full_contact': ''})
194 'full_contact': ''})
195 action, action_extra, ico = h.action_parser(
195 action, action_extra, ico = h.action_parser(
196 self.request, entry, feed=True)
196 self.request, entry, feed=True)
197 title = "%s - %s %s" % (user.short_contact, action(),
197 title = "%s - %s %s" % (user.short_contact, action(),
198 entry.repository.repo_name)
198 entry.repository.repo_name)
199 desc = action_extra()
199 desc = action_extra()
200 _url = h.route_url('home')
200 _url = h.route_url('home')
201 if entry.repository is not None:
201 if entry.repository is not None:
202 _url = h.route_url('repo_commits',
202 _url = h.route_url('repo_commits',
203 repo_name=entry.repository.repo_name)
203 repo_name=entry.repository.repo_name)
204
204
205 feed.add_item(
205 feed.add_item(
206 unique_id=self.feed_uid(entry.user_log_id),
206 unique_id=self.feed_uid(entry.user_log_id),
207 title=title,
207 title=title,
208 pubdate=entry.action_date,
208 pubdate=entry.action_date,
209 link=_url,
209 link=_url,
210 author_email=user.email,
210 author_email=user.email,
211 author_name=user.full_contact,
211 author_name=user.full_contact,
212 description=desc)
212 description=desc)
213
213
214 response = Response(feed.writeString('utf-8'))
214 response = Response(feed.writeString('utf-8'))
215 response.content_type = feed.mime_type
215 response.content_type = feed.mime_type
216 return response
216 return response
217
217
218 @LoginRequired()
218 @LoginRequired()
219 @NotAnonymous()
219 @NotAnonymous()
220 @view_config(
220 @view_config(
221 route_name='journal', request_method='GET',
221 route_name='journal', request_method='GET',
222 renderer=None)
222 renderer=None)
223 def journal(self):
223 def journal(self):
224 c = self.load_default_context()
224 c = self.load_default_context()
225
225
226 p = safe_int(self.request.GET.get('page', 1), 1)
226 p = safe_int(self.request.GET.get('page', 1), 1)
227 c.user = User.get(self._rhodecode_user.user_id)
227 c.user = User.get(self._rhodecode_user.user_id)
228 following = Session().query(UserFollowing)\
228 following = Session().query(UserFollowing)\
229 .filter(UserFollowing.user_id == self._rhodecode_user.user_id)\
229 .filter(UserFollowing.user_id == self._rhodecode_user.user_id)\
230 .options(joinedload(UserFollowing.follows_repository))\
230 .options(joinedload(UserFollowing.follows_repository))\
231 .all()
231 .all()
232
232
233 journal = self._get_journal_data(following, c.search_term)
233 journal = self._get_journal_data(following, c.search_term)
234
234
235 def url_generator(**kw):
235 def url_generator(page_num):
236 query_params = {
236 query_params = {
237 'page': page_num,
237 'filter': c.search_term
238 'filter': c.search_term
238 }
239 }
239 query_params.update(kw)
240 return self.request.current_route_path(_query=query_params)
240 return self.request.current_route_path(_query=query_params)
241
241
242 c.journal_pager = Page(
242 c.journal_pager = SqlPage(
243 journal, page=p, items_per_page=20, url=url_generator)
243 journal, page=p, items_per_page=20, url_maker=url_generator)
244 c.journal_day_aggreagate = self._get_daily_aggregate(c.journal_pager)
244 c.journal_day_aggreagate = self._get_daily_aggregate(c.journal_pager)
245
245
246 c.journal_data = render(
246 c.journal_data = render(
247 'rhodecode:templates/journal/journal_data.mako',
247 'rhodecode:templates/journal/journal_data.mako',
248 self._get_template_context(c), self.request)
248 self._get_template_context(c), self.request)
249
249
250 if self.request.is_xhr:
250 if self.request.is_xhr:
251 return Response(c.journal_data)
251 return Response(c.journal_data)
252
252
253 html = render(
253 html = render(
254 'rhodecode:templates/journal/journal.mako',
254 'rhodecode:templates/journal/journal.mako',
255 self._get_template_context(c), self.request)
255 self._get_template_context(c), self.request)
256 return Response(html)
256 return Response(html)
257
257
258 @LoginRequired(auth_token_access=[UserApiKeys.ROLE_FEED])
258 @LoginRequired(auth_token_access=[UserApiKeys.ROLE_FEED])
259 @NotAnonymous()
259 @NotAnonymous()
260 @view_config(
260 @view_config(
261 route_name='journal_atom', request_method='GET',
261 route_name='journal_atom', request_method='GET',
262 renderer=None)
262 renderer=None)
263 def journal_atom(self):
263 def journal_atom(self):
264 """
264 """
265 Produce an atom-1.0 feed via feedgenerator module
265 Produce an atom-1.0 feed via feedgenerator module
266 """
266 """
267 c = self.load_default_context()
267 c = self.load_default_context()
268 following_repos = Session().query(UserFollowing)\
268 following_repos = Session().query(UserFollowing)\
269 .filter(UserFollowing.user_id == self._rhodecode_user.user_id)\
269 .filter(UserFollowing.user_id == self._rhodecode_user.user_id)\
270 .options(joinedload(UserFollowing.follows_repository))\
270 .options(joinedload(UserFollowing.follows_repository))\
271 .all()
271 .all()
272 return self._atom_feed(following_repos, c.search_term, public=False)
272 return self._atom_feed(following_repos, c.search_term, public=False)
273
273
274 @LoginRequired(auth_token_access=[UserApiKeys.ROLE_FEED])
274 @LoginRequired(auth_token_access=[UserApiKeys.ROLE_FEED])
275 @NotAnonymous()
275 @NotAnonymous()
276 @view_config(
276 @view_config(
277 route_name='journal_rss', request_method='GET',
277 route_name='journal_rss', request_method='GET',
278 renderer=None)
278 renderer=None)
279 def journal_rss(self):
279 def journal_rss(self):
280 """
280 """
281 Produce an rss feed via feedgenerator module
281 Produce an rss feed via feedgenerator module
282 """
282 """
283 c = self.load_default_context()
283 c = self.load_default_context()
284 following_repos = Session().query(UserFollowing)\
284 following_repos = Session().query(UserFollowing)\
285 .filter(UserFollowing.user_id == self._rhodecode_user.user_id)\
285 .filter(UserFollowing.user_id == self._rhodecode_user.user_id)\
286 .options(joinedload(UserFollowing.follows_repository))\
286 .options(joinedload(UserFollowing.follows_repository))\
287 .all()
287 .all()
288 return self._rss_feed(following_repos, c.search_term, public=False)
288 return self._rss_feed(following_repos, c.search_term, public=False)
289
289
290 @LoginRequired()
290 @LoginRequired()
291 @NotAnonymous()
291 @NotAnonymous()
292 @CSRFRequired()
292 @CSRFRequired()
293 @view_config(
293 @view_config(
294 route_name='toggle_following', request_method='POST',
294 route_name='toggle_following', request_method='POST',
295 renderer='json_ext')
295 renderer='json_ext')
296 def toggle_following(self):
296 def toggle_following(self):
297 user_id = self.request.POST.get('follows_user_id')
297 user_id = self.request.POST.get('follows_user_id')
298 if user_id:
298 if user_id:
299 try:
299 try:
300 ScmModel().toggle_following_user(user_id, self._rhodecode_user.user_id)
300 ScmModel().toggle_following_user(user_id, self._rhodecode_user.user_id)
301 Session().commit()
301 Session().commit()
302 return 'ok'
302 return 'ok'
303 except Exception:
303 except Exception:
304 raise HTTPBadRequest()
304 raise HTTPBadRequest()
305
305
306 repo_id = self.request.POST.get('follows_repo_id')
306 repo_id = self.request.POST.get('follows_repo_id')
307 repo = Repository.get_or_404(repo_id)
307 repo = Repository.get_or_404(repo_id)
308 perm_set = ['repository.read', 'repository.write', 'repository.admin']
308 perm_set = ['repository.read', 'repository.write', 'repository.admin']
309 has_perm = HasRepoPermissionAny(*perm_set)(repo.repo_name, 'RepoWatch check')
309 has_perm = HasRepoPermissionAny(*perm_set)(repo.repo_name, 'RepoWatch check')
310 if repo and has_perm:
310 if repo and has_perm:
311 try:
311 try:
312 ScmModel().toggle_following_repo(repo_id, self._rhodecode_user.user_id)
312 ScmModel().toggle_following_repo(repo_id, self._rhodecode_user.user_id)
313 Session().commit()
313 Session().commit()
314 return 'ok'
314 return 'ok'
315 except Exception:
315 except Exception:
316 raise HTTPBadRequest()
316 raise HTTPBadRequest()
317
317
318 raise HTTPBadRequest()
318 raise HTTPBadRequest()
319
319
320 @LoginRequired()
320 @LoginRequired()
321 @view_config(
321 @view_config(
322 route_name='journal_public', request_method='GET',
322 route_name='journal_public', request_method='GET',
323 renderer=None)
323 renderer=None)
324 def journal_public(self):
324 def journal_public(self):
325 c = self.load_default_context()
325 c = self.load_default_context()
326 # Return a rendered template
326 # Return a rendered template
327 p = safe_int(self.request.GET.get('page', 1), 1)
327 p = safe_int(self.request.GET.get('page', 1), 1)
328
328
329 c.following = Session().query(UserFollowing)\
329 c.following = Session().query(UserFollowing)\
330 .filter(UserFollowing.user_id == self._rhodecode_user.user_id)\
330 .filter(UserFollowing.user_id == self._rhodecode_user.user_id)\
331 .options(joinedload(UserFollowing.follows_repository))\
331 .options(joinedload(UserFollowing.follows_repository))\
332 .all()
332 .all()
333
333
334 journal = self._get_journal_data(c.following, c.search_term)
334 journal = self._get_journal_data(c.following, c.search_term)
335
335
336 def url_generator(**kw):
336 def url_generator(page_num):
337 query_params = {}
337 query_params = {
338 query_params.update(kw)
338 'page': page_num
339 }
339 return self.request.current_route_path(_query=query_params)
340 return self.request.current_route_path(_query=query_params)
340
341
341 c.journal_pager = Page(
342 c.journal_pager = SqlPage(
342 journal, page=p, items_per_page=20, url=url_generator)
343 journal, page=p, items_per_page=20, url_maker=url_generator)
343 c.journal_day_aggreagate = self._get_daily_aggregate(c.journal_pager)
344 c.journal_day_aggreagate = self._get_daily_aggregate(c.journal_pager)
344
345
345 c.journal_data = render(
346 c.journal_data = render(
346 'rhodecode:templates/journal/journal_data.mako',
347 'rhodecode:templates/journal/journal_data.mako',
347 self._get_template_context(c), self.request)
348 self._get_template_context(c), self.request)
348
349
349 if self.request.is_xhr:
350 if self.request.is_xhr:
350 return Response(c.journal_data)
351 return Response(c.journal_data)
351
352
352 html = render(
353 html = render(
353 'rhodecode:templates/journal/public_journal.mako',
354 'rhodecode:templates/journal/public_journal.mako',
354 self._get_template_context(c), self.request)
355 self._get_template_context(c), self.request)
355 return Response(html)
356 return Response(html)
356
357
357 @LoginRequired(auth_token_access=[UserApiKeys.ROLE_FEED])
358 @LoginRequired(auth_token_access=[UserApiKeys.ROLE_FEED])
358 @view_config(
359 @view_config(
359 route_name='journal_public_atom', request_method='GET',
360 route_name='journal_public_atom', request_method='GET',
360 renderer=None)
361 renderer=None)
361 def journal_public_atom(self):
362 def journal_public_atom(self):
362 """
363 """
363 Produce an atom-1.0 feed via feedgenerator module
364 Produce an atom-1.0 feed via feedgenerator module
364 """
365 """
365 c = self.load_default_context()
366 c = self.load_default_context()
366 following_repos = Session().query(UserFollowing)\
367 following_repos = Session().query(UserFollowing)\
367 .filter(UserFollowing.user_id == self._rhodecode_user.user_id)\
368 .filter(UserFollowing.user_id == self._rhodecode_user.user_id)\
368 .options(joinedload(UserFollowing.follows_repository))\
369 .options(joinedload(UserFollowing.follows_repository))\
369 .all()
370 .all()
370
371
371 return self._atom_feed(following_repos, c.search_term)
372 return self._atom_feed(following_repos, c.search_term)
372
373
373 @LoginRequired(auth_token_access=[UserApiKeys.ROLE_FEED])
374 @LoginRequired(auth_token_access=[UserApiKeys.ROLE_FEED])
374 @view_config(
375 @view_config(
375 route_name='journal_public_rss', request_method='GET',
376 route_name='journal_public_rss', request_method='GET',
376 renderer=None)
377 renderer=None)
377 def journal_public_rss(self):
378 def journal_public_rss(self):
378 """
379 """
379 Produce an rss2 feed via feedgenerator module
380 Produce an rss2 feed via feedgenerator module
380 """
381 """
381 c = self.load_default_context()
382 c = self.load_default_context()
382 following_repos = Session().query(UserFollowing)\
383 following_repos = Session().query(UserFollowing)\
383 .filter(UserFollowing.user_id == self._rhodecode_user.user_id)\
384 .filter(UserFollowing.user_id == self._rhodecode_user.user_id)\
384 .options(joinedload(UserFollowing.follows_repository))\
385 .options(joinedload(UserFollowing.follows_repository))\
385 .all()
386 .all()
386
387
387 return self._rss_feed(following_repos, c.search_term)
388 return self._rss_feed(following_repos, c.search_term)
@@ -1,198 +1,201 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2019 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import logging
21 import logging
22
22
23 from pyramid.httpexceptions import (
23 from pyramid.httpexceptions import (
24 HTTPFound, HTTPNotFound, HTTPInternalServerError)
24 HTTPFound, HTTPNotFound, HTTPInternalServerError)
25 from pyramid.view import view_config
25 from pyramid.view import view_config
26
26
27 from rhodecode.apps._base import BaseAppView
27 from rhodecode.apps._base import BaseAppView
28 from rhodecode.lib.auth import LoginRequired, NotAnonymous, CSRFRequired
28 from rhodecode.lib.auth import LoginRequired, NotAnonymous, CSRFRequired
29
29
30 from rhodecode.lib import helpers as h
30 from rhodecode.lib import helpers as h
31 from rhodecode.lib.helpers import Page
31 from rhodecode.lib.helpers import SqlPage
32 from rhodecode.lib.utils2 import safe_int
32 from rhodecode.lib.utils2 import safe_int
33 from rhodecode.model.db import Notification
33 from rhodecode.model.db import Notification
34 from rhodecode.model.notification import NotificationModel
34 from rhodecode.model.notification import NotificationModel
35 from rhodecode.model.meta import Session
35 from rhodecode.model.meta import Session
36
36
37
37
38 log = logging.getLogger(__name__)
38 log = logging.getLogger(__name__)
39
39
40
40
41 class MyAccountNotificationsView(BaseAppView):
41 class MyAccountNotificationsView(BaseAppView):
42
42
43 def load_default_context(self):
43 def load_default_context(self):
44 c = self._get_local_tmpl_context()
44 c = self._get_local_tmpl_context()
45 c.user = c.auth_user.get_instance()
45 c.user = c.auth_user.get_instance()
46
46
47 return c
47 return c
48
48
49 def _has_permissions(self, notification):
49 def _has_permissions(self, notification):
50 def is_owner():
50 def is_owner():
51 user_id = self._rhodecode_db_user.user_id
51 user_id = self._rhodecode_db_user.user_id
52 for user_notification in notification.notifications_to_users:
52 for user_notification in notification.notifications_to_users:
53 if user_notification.user.user_id == user_id:
53 if user_notification.user.user_id == user_id:
54 return True
54 return True
55 return False
55 return False
56 return h.HasPermissionAny('hg.admin')() or is_owner()
56 return h.HasPermissionAny('hg.admin')() or is_owner()
57
57
58 @LoginRequired()
58 @LoginRequired()
59 @NotAnonymous()
59 @NotAnonymous()
60 @view_config(
60 @view_config(
61 route_name='notifications_show_all', request_method='GET',
61 route_name='notifications_show_all', request_method='GET',
62 renderer='rhodecode:templates/admin/notifications/notifications_show_all.mako')
62 renderer='rhodecode:templates/admin/notifications/notifications_show_all.mako')
63 def notifications_show_all(self):
63 def notifications_show_all(self):
64 c = self.load_default_context()
64 c = self.load_default_context()
65
65
66 c.unread_count = NotificationModel().get_unread_cnt_for_user(
66 c.unread_count = NotificationModel().get_unread_cnt_for_user(
67 self._rhodecode_db_user.user_id)
67 self._rhodecode_db_user.user_id)
68
68
69 _current_filter = self.request.GET.getall('type') or ['unread']
69 _current_filter = self.request.GET.getall('type') or ['unread']
70
70
71 notifications = NotificationModel().get_for_user(
71 notifications = NotificationModel().get_for_user(
72 self._rhodecode_db_user.user_id,
72 self._rhodecode_db_user.user_id,
73 filter_=_current_filter)
73 filter_=_current_filter)
74
74
75 p = safe_int(self.request.GET.get('page', 1), 1)
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 _query = self.request.GET.mixed()
81 _query = self.request.GET.mixed()
79 _query.update(kw)
82 query_params.update(_query)
80 return self.request.current_route_path(_query=_query)
83 return self.request.current_route_path(_query=query_params)
81
84
82 c.notifications = Page(notifications, page=p, items_per_page=10,
85 c.notifications = SqlPage(notifications, page=p, items_per_page=10,
83 url=url_generator)
86 url_maker=url_generator)
84
87
85 c.unread_type = 'unread'
88 c.unread_type = 'unread'
86 c.all_type = 'all'
89 c.all_type = 'all'
87 c.pull_request_type = Notification.TYPE_PULL_REQUEST
90 c.pull_request_type = Notification.TYPE_PULL_REQUEST
88 c.comment_type = [Notification.TYPE_CHANGESET_COMMENT,
91 c.comment_type = [Notification.TYPE_CHANGESET_COMMENT,
89 Notification.TYPE_PULL_REQUEST_COMMENT]
92 Notification.TYPE_PULL_REQUEST_COMMENT]
90
93
91 c.current_filter = 'unread' # default filter
94 c.current_filter = 'unread' # default filter
92
95
93 if _current_filter == [c.pull_request_type]:
96 if _current_filter == [c.pull_request_type]:
94 c.current_filter = 'pull_request'
97 c.current_filter = 'pull_request'
95 elif _current_filter == c.comment_type:
98 elif _current_filter == c.comment_type:
96 c.current_filter = 'comment'
99 c.current_filter = 'comment'
97 elif _current_filter == [c.unread_type]:
100 elif _current_filter == [c.unread_type]:
98 c.current_filter = 'unread'
101 c.current_filter = 'unread'
99 elif _current_filter == [c.all_type]:
102 elif _current_filter == [c.all_type]:
100 c.current_filter = 'all'
103 c.current_filter = 'all'
101 return self._get_template_context(c)
104 return self._get_template_context(c)
102
105
103 @LoginRequired()
106 @LoginRequired()
104 @NotAnonymous()
107 @NotAnonymous()
105 @CSRFRequired()
108 @CSRFRequired()
106 @view_config(
109 @view_config(
107 route_name='notifications_mark_all_read', request_method='POST',
110 route_name='notifications_mark_all_read', request_method='POST',
108 renderer='rhodecode:templates/admin/notifications/notifications_show_all.mako')
111 renderer='rhodecode:templates/admin/notifications/notifications_show_all.mako')
109 def notifications_mark_all_read(self):
112 def notifications_mark_all_read(self):
110 NotificationModel().mark_all_read_for_user(
113 NotificationModel().mark_all_read_for_user(
111 self._rhodecode_db_user.user_id,
114 self._rhodecode_db_user.user_id,
112 filter_=self.request.GET.getall('type'))
115 filter_=self.request.GET.getall('type'))
113 Session().commit()
116 Session().commit()
114 raise HTTPFound(h.route_path('notifications_show_all'))
117 raise HTTPFound(h.route_path('notifications_show_all'))
115
118
116 @LoginRequired()
119 @LoginRequired()
117 @NotAnonymous()
120 @NotAnonymous()
118 @view_config(
121 @view_config(
119 route_name='notifications_show', request_method='GET',
122 route_name='notifications_show', request_method='GET',
120 renderer='rhodecode:templates/admin/notifications/notifications_show.mako')
123 renderer='rhodecode:templates/admin/notifications/notifications_show.mako')
121 def notifications_show(self):
124 def notifications_show(self):
122 c = self.load_default_context()
125 c = self.load_default_context()
123 notification_id = self.request.matchdict['notification_id']
126 notification_id = self.request.matchdict['notification_id']
124 notification = Notification.get_or_404(notification_id)
127 notification = Notification.get_or_404(notification_id)
125
128
126 if not self._has_permissions(notification):
129 if not self._has_permissions(notification):
127 log.debug('User %s does not have permission to access notification',
130 log.debug('User %s does not have permission to access notification',
128 self._rhodecode_user)
131 self._rhodecode_user)
129 raise HTTPNotFound()
132 raise HTTPNotFound()
130
133
131 u_notification = NotificationModel().get_user_notification(
134 u_notification = NotificationModel().get_user_notification(
132 self._rhodecode_db_user.user_id, notification)
135 self._rhodecode_db_user.user_id, notification)
133 if not u_notification:
136 if not u_notification:
134 log.debug('User %s notification does not exist',
137 log.debug('User %s notification does not exist',
135 self._rhodecode_user)
138 self._rhodecode_user)
136 raise HTTPNotFound()
139 raise HTTPNotFound()
137
140
138 # when opening this notification, mark it as read for this use
141 # when opening this notification, mark it as read for this use
139 if not u_notification.read:
142 if not u_notification.read:
140 u_notification.mark_as_read()
143 u_notification.mark_as_read()
141 Session().commit()
144 Session().commit()
142
145
143 c.notification = notification
146 c.notification = notification
144
147
145 return self._get_template_context(c)
148 return self._get_template_context(c)
146
149
147 @LoginRequired()
150 @LoginRequired()
148 @NotAnonymous()
151 @NotAnonymous()
149 @CSRFRequired()
152 @CSRFRequired()
150 @view_config(
153 @view_config(
151 route_name='notifications_update', request_method='POST',
154 route_name='notifications_update', request_method='POST',
152 renderer='json_ext')
155 renderer='json_ext')
153 def notification_update(self):
156 def notification_update(self):
154 notification_id = self.request.matchdict['notification_id']
157 notification_id = self.request.matchdict['notification_id']
155 notification = Notification.get_or_404(notification_id)
158 notification = Notification.get_or_404(notification_id)
156
159
157 if not self._has_permissions(notification):
160 if not self._has_permissions(notification):
158 log.debug('User %s does not have permission to access notification',
161 log.debug('User %s does not have permission to access notification',
159 self._rhodecode_user)
162 self._rhodecode_user)
160 raise HTTPNotFound()
163 raise HTTPNotFound()
161
164
162 try:
165 try:
163 # updates notification read flag
166 # updates notification read flag
164 NotificationModel().mark_read(
167 NotificationModel().mark_read(
165 self._rhodecode_user.user_id, notification)
168 self._rhodecode_user.user_id, notification)
166 Session().commit()
169 Session().commit()
167 return 'ok'
170 return 'ok'
168 except Exception:
171 except Exception:
169 Session().rollback()
172 Session().rollback()
170 log.exception("Exception updating a notification item")
173 log.exception("Exception updating a notification item")
171
174
172 raise HTTPInternalServerError()
175 raise HTTPInternalServerError()
173
176
174 @LoginRequired()
177 @LoginRequired()
175 @NotAnonymous()
178 @NotAnonymous()
176 @CSRFRequired()
179 @CSRFRequired()
177 @view_config(
180 @view_config(
178 route_name='notifications_delete', request_method='POST',
181 route_name='notifications_delete', request_method='POST',
179 renderer='json_ext')
182 renderer='json_ext')
180 def notification_delete(self):
183 def notification_delete(self):
181 notification_id = self.request.matchdict['notification_id']
184 notification_id = self.request.matchdict['notification_id']
182 notification = Notification.get_or_404(notification_id)
185 notification = Notification.get_or_404(notification_id)
183 if not self._has_permissions(notification):
186 if not self._has_permissions(notification):
184 log.debug('User %s does not have permission to access notification',
187 log.debug('User %s does not have permission to access notification',
185 self._rhodecode_user)
188 self._rhodecode_user)
186 raise HTTPNotFound()
189 raise HTTPNotFound()
187
190
188 try:
191 try:
189 # deletes only notification2user
192 # deletes only notification2user
190 NotificationModel().delete(
193 NotificationModel().delete(
191 self._rhodecode_user.user_id, notification)
194 self._rhodecode_user.user_id, notification)
192 Session().commit()
195 Session().commit()
193 return 'ok'
196 return 'ok'
194 except Exception:
197 except Exception:
195 Session().rollback()
198 Session().rollback()
196 log.exception("Exception deleting a notification item")
199 log.exception("Exception deleting a notification item")
197
200
198 raise HTTPInternalServerError()
201 raise HTTPInternalServerError()
@@ -1,65 +1,67 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2017-2019 RhodeCode GmbH
3 # Copyright (C) 2017-2019 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import logging
21 import logging
22 from pyramid.view import view_config
22 from pyramid.view import view_config
23
23
24 from rhodecode.apps._base import RepoAppView
24 from rhodecode.apps._base import RepoAppView
25 from rhodecode.lib.helpers import SqlPage
25 from rhodecode.lib import helpers as h
26 from rhodecode.lib import helpers as h
26 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
27 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
27 from rhodecode.lib.utils2 import safe_int
28 from rhodecode.lib.utils2 import safe_int
28 from rhodecode.model.repo import RepoModel
29 from rhodecode.model.repo import RepoModel
29
30
30 log = logging.getLogger(__name__)
31 log = logging.getLogger(__name__)
31
32
32
33
33 class AuditLogsView(RepoAppView):
34 class AuditLogsView(RepoAppView):
34 def load_default_context(self):
35 def load_default_context(self):
35 c = self._get_local_tmpl_context()
36 c = self._get_local_tmpl_context()
36
37
38 return c
37 return c
39
38
40 @LoginRequired()
39 @LoginRequired()
41 @HasRepoPermissionAnyDecorator('repository.admin')
40 @HasRepoPermissionAnyDecorator('repository.admin')
42 @view_config(
41 @view_config(
43 route_name='edit_repo_audit_logs', request_method='GET',
42 route_name='edit_repo_audit_logs', request_method='GET',
44 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
43 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
45 def repo_audit_logs(self):
44 def repo_audit_logs(self):
46 _ = self.request.translate
45 _ = self.request.translate
47 c = self.load_default_context()
46 c = self.load_default_context()
48 c.db_repo = self.db_repo
47 c.db_repo = self.db_repo
49
48
50 c.active = 'audit'
49 c.active = 'audit'
51
50
52 p = safe_int(self.request.GET.get('page', 1), 1)
51 p = safe_int(self.request.GET.get('page', 1), 1)
53
52
54 filter_term = self.request.GET.get('filter')
53 filter_term = self.request.GET.get('filter')
55 user_log = RepoModel().get_repo_log(c.db_repo, filter_term)
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 if filter_term:
60 if filter_term:
59 kw['filter'] = filter_term
61 query_params['filter'] = filter_term
60 return self.request.current_route_path(_query=kw)
62 return self.request.current_route_path(_query=query_params)
61
63
62 c.audit_logs = h.Page(
64 c.audit_logs = SqlPage(
63 user_log, page=p, items_per_page=10, url=url_generator)
65 user_log, page=p, items_per_page=10, url_maker=url_generator)
64 c.filter_term = filter_term
66 c.filter_term = filter_term
65 return self._get_template_context(c)
67 return self._get_template_context(c)
@@ -1,365 +1,371 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2019 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21
21
22 import logging
22 import logging
23
23
24 from pyramid.httpexceptions import HTTPNotFound, HTTPFound
24 from pyramid.httpexceptions import HTTPNotFound, HTTPFound
25 from pyramid.view import view_config
25 from pyramid.view import view_config
26 from pyramid.renderers import render
26 from pyramid.renderers import render
27 from pyramid.response import Response
27 from pyramid.response import Response
28
28
29 from rhodecode.apps._base import RepoAppView
29 from rhodecode.apps._base import RepoAppView
30 import rhodecode.lib.helpers as h
30 import rhodecode.lib.helpers as h
31 from rhodecode.lib.auth import (
31 from rhodecode.lib.auth import (
32 LoginRequired, HasRepoPermissionAnyDecorator)
32 LoginRequired, HasRepoPermissionAnyDecorator)
33
33
34 from rhodecode.lib.ext_json import json
34 from rhodecode.lib.ext_json import json
35 from rhodecode.lib.graphmod import _colored, _dagwalker
35 from rhodecode.lib.graphmod import _colored, _dagwalker
36 from rhodecode.lib.helpers import RepoPage
36 from rhodecode.lib.helpers import RepoPage
37 from rhodecode.lib.utils2 import safe_int, safe_str, str2bool
37 from rhodecode.lib.utils2 import safe_int, safe_str, str2bool
38 from rhodecode.lib.vcs.exceptions import (
38 from rhodecode.lib.vcs.exceptions import (
39 RepositoryError, CommitDoesNotExistError,
39 RepositoryError, CommitDoesNotExistError,
40 CommitError, NodeDoesNotExistError, EmptyRepositoryError)
40 CommitError, NodeDoesNotExistError, EmptyRepositoryError)
41
41
42 log = logging.getLogger(__name__)
42 log = logging.getLogger(__name__)
43
43
44 DEFAULT_CHANGELOG_SIZE = 20
44 DEFAULT_CHANGELOG_SIZE = 20
45
45
46
46
47 class RepoChangelogView(RepoAppView):
47 class RepoChangelogView(RepoAppView):
48
48
49 def _get_commit_or_redirect(self, commit_id, redirect_after=True):
49 def _get_commit_or_redirect(self, commit_id, redirect_after=True):
50 """
50 """
51 This is a safe way to get commit. If an error occurs it redirects to
51 This is a safe way to get commit. If an error occurs it redirects to
52 tip with proper message
52 tip with proper message
53
53
54 :param commit_id: id of commit to fetch
54 :param commit_id: id of commit to fetch
55 :param redirect_after: toggle redirection
55 :param redirect_after: toggle redirection
56 """
56 """
57 _ = self.request.translate
57 _ = self.request.translate
58
58
59 try:
59 try:
60 return self.rhodecode_vcs_repo.get_commit(commit_id)
60 return self.rhodecode_vcs_repo.get_commit(commit_id)
61 except EmptyRepositoryError:
61 except EmptyRepositoryError:
62 if not redirect_after:
62 if not redirect_after:
63 return None
63 return None
64
64
65 h.flash(h.literal(
65 h.flash(h.literal(
66 _('There are no commits yet')), category='warning')
66 _('There are no commits yet')), category='warning')
67 raise HTTPFound(
67 raise HTTPFound(
68 h.route_path('repo_summary', repo_name=self.db_repo_name))
68 h.route_path('repo_summary', repo_name=self.db_repo_name))
69
69
70 except (CommitDoesNotExistError, LookupError):
70 except (CommitDoesNotExistError, LookupError):
71 msg = _('No such commit exists for this repository')
71 msg = _('No such commit exists for this repository')
72 h.flash(msg, category='error')
72 h.flash(msg, category='error')
73 raise HTTPNotFound()
73 raise HTTPNotFound()
74 except RepositoryError as e:
74 except RepositoryError as e:
75 h.flash(safe_str(h.escape(e)), category='error')
75 h.flash(safe_str(h.escape(e)), category='error')
76 raise HTTPNotFound()
76 raise HTTPNotFound()
77
77
78 def _graph(self, repo, commits, prev_data=None, next_data=None):
78 def _graph(self, repo, commits, prev_data=None, next_data=None):
79 """
79 """
80 Generates a DAG graph for repo
80 Generates a DAG graph for repo
81
81
82 :param repo: repo instance
82 :param repo: repo instance
83 :param commits: list of commits
83 :param commits: list of commits
84 """
84 """
85 if not commits:
85 if not commits:
86 return json.dumps([]), json.dumps([])
86 return json.dumps([]), json.dumps([])
87
87
88 def serialize(commit, parents=True):
88 def serialize(commit, parents=True):
89 data = dict(
89 data = dict(
90 raw_id=commit.raw_id,
90 raw_id=commit.raw_id,
91 idx=commit.idx,
91 idx=commit.idx,
92 branch=None,
92 branch=None,
93 )
93 )
94 if parents:
94 if parents:
95 data['parents'] = [
95 data['parents'] = [
96 serialize(x, parents=False) for x in commit.parents]
96 serialize(x, parents=False) for x in commit.parents]
97 return data
97 return data
98
98
99 prev_data = prev_data or []
99 prev_data = prev_data or []
100 next_data = next_data or []
100 next_data = next_data or []
101
101
102 current = [serialize(x) for x in commits]
102 current = [serialize(x) for x in commits]
103 commits = prev_data + current + next_data
103 commits = prev_data + current + next_data
104
104
105 dag = _dagwalker(repo, commits)
105 dag = _dagwalker(repo, commits)
106
106
107 data = [[commit_id, vtx, edges, branch]
107 data = [[commit_id, vtx, edges, branch]
108 for commit_id, vtx, edges, branch in _colored(dag)]
108 for commit_id, vtx, edges, branch in _colored(dag)]
109 return json.dumps(data), json.dumps(current)
109 return json.dumps(data), json.dumps(current)
110
110
111 def _check_if_valid_branch(self, branch_name, repo_name, f_path):
111 def _check_if_valid_branch(self, branch_name, repo_name, f_path):
112 if branch_name not in self.rhodecode_vcs_repo.branches_all:
112 if branch_name not in self.rhodecode_vcs_repo.branches_all:
113 h.flash('Branch {} is not found.'.format(h.escape(branch_name)),
113 h.flash('Branch {} is not found.'.format(h.escape(branch_name)),
114 category='warning')
114 category='warning')
115 redirect_url = h.route_path(
115 redirect_url = h.route_path(
116 'repo_commits_file', repo_name=repo_name,
116 'repo_commits_file', repo_name=repo_name,
117 commit_id=branch_name, f_path=f_path or '')
117 commit_id=branch_name, f_path=f_path or '')
118 raise HTTPFound(redirect_url)
118 raise HTTPFound(redirect_url)
119
119
120 def _load_changelog_data(
120 def _load_changelog_data(
121 self, c, collection, page, chunk_size, branch_name=None,
121 self, c, collection, page, chunk_size, branch_name=None,
122 dynamic=False, f_path=None, commit_id=None):
122 dynamic=False, f_path=None, commit_id=None):
123
123
124 def url_generator(**kw):
124 def url_generator(page_num):
125 query_params = {}
125 query_params = {
126 query_params.update(kw)
126 'page': page_num
127 }
128
129 if branch_name:
130 query_params.update({
131 'branch': branch_name
132 })
133
127 if f_path:
134 if f_path:
128 # changelog for file
135 # changelog for file
129 return h.route_path(
136 return h.route_path(
130 'repo_commits_file',
137 'repo_commits_file',
131 repo_name=c.rhodecode_db_repo.repo_name,
138 repo_name=c.rhodecode_db_repo.repo_name,
132 commit_id=commit_id, f_path=f_path,
139 commit_id=commit_id, f_path=f_path,
133 _query=query_params)
140 _query=query_params)
134 else:
141 else:
135 return h.route_path(
142 return h.route_path(
136 'repo_commits',
143 'repo_commits',
137 repo_name=c.rhodecode_db_repo.repo_name, _query=query_params)
144 repo_name=c.rhodecode_db_repo.repo_name, _query=query_params)
138
145
139 c.total_cs = len(collection)
146 c.total_cs = len(collection)
140 c.showing_commits = min(chunk_size, c.total_cs)
147 c.showing_commits = min(chunk_size, c.total_cs)
141 c.pagination = RepoPage(collection, page=page, item_count=c.total_cs,
148 c.pagination = RepoPage(collection, page=page, item_count=c.total_cs,
142 items_per_page=chunk_size, branch=branch_name,
149 items_per_page=chunk_size, url_maker=url_generator)
143 url=url_generator)
144
150
145 c.next_page = c.pagination.next_page
151 c.next_page = c.pagination.next_page
146 c.prev_page = c.pagination.previous_page
152 c.prev_page = c.pagination.previous_page
147
153
148 if dynamic:
154 if dynamic:
149 if self.request.GET.get('chunk') != 'next':
155 if self.request.GET.get('chunk') != 'next':
150 c.next_page = None
156 c.next_page = None
151 if self.request.GET.get('chunk') != 'prev':
157 if self.request.GET.get('chunk') != 'prev':
152 c.prev_page = None
158 c.prev_page = None
153
159
154 page_commit_ids = [x.raw_id for x in c.pagination]
160 page_commit_ids = [x.raw_id for x in c.pagination]
155 c.comments = c.rhodecode_db_repo.get_comments(page_commit_ids)
161 c.comments = c.rhodecode_db_repo.get_comments(page_commit_ids)
156 c.statuses = c.rhodecode_db_repo.statuses(page_commit_ids)
162 c.statuses = c.rhodecode_db_repo.statuses(page_commit_ids)
157
163
158 def load_default_context(self):
164 def load_default_context(self):
159 c = self._get_local_tmpl_context(include_app_defaults=True)
165 c = self._get_local_tmpl_context(include_app_defaults=True)
160
166
161 c.rhodecode_repo = self.rhodecode_vcs_repo
167 c.rhodecode_repo = self.rhodecode_vcs_repo
162
168
163 return c
169 return c
164
170
165 def _get_preload_attrs(self):
171 def _get_preload_attrs(self):
166 pre_load = ['author', 'branch', 'date', 'message', 'parents',
172 pre_load = ['author', 'branch', 'date', 'message', 'parents',
167 'obsolete', 'phase', 'hidden']
173 'obsolete', 'phase', 'hidden']
168 return pre_load
174 return pre_load
169
175
170 @LoginRequired()
176 @LoginRequired()
171 @HasRepoPermissionAnyDecorator(
177 @HasRepoPermissionAnyDecorator(
172 'repository.read', 'repository.write', 'repository.admin')
178 'repository.read', 'repository.write', 'repository.admin')
173 @view_config(
179 @view_config(
174 route_name='repo_commits', request_method='GET',
180 route_name='repo_commits', request_method='GET',
175 renderer='rhodecode:templates/commits/changelog.mako')
181 renderer='rhodecode:templates/commits/changelog.mako')
176 @view_config(
182 @view_config(
177 route_name='repo_commits_file', request_method='GET',
183 route_name='repo_commits_file', request_method='GET',
178 renderer='rhodecode:templates/commits/changelog.mako')
184 renderer='rhodecode:templates/commits/changelog.mako')
179 # old routes for backward compat
185 # old routes for backward compat
180 @view_config(
186 @view_config(
181 route_name='repo_changelog', request_method='GET',
187 route_name='repo_changelog', request_method='GET',
182 renderer='rhodecode:templates/commits/changelog.mako')
188 renderer='rhodecode:templates/commits/changelog.mako')
183 @view_config(
189 @view_config(
184 route_name='repo_changelog_file', request_method='GET',
190 route_name='repo_changelog_file', request_method='GET',
185 renderer='rhodecode:templates/commits/changelog.mako')
191 renderer='rhodecode:templates/commits/changelog.mako')
186 def repo_changelog(self):
192 def repo_changelog(self):
187 c = self.load_default_context()
193 c = self.load_default_context()
188
194
189 commit_id = self.request.matchdict.get('commit_id')
195 commit_id = self.request.matchdict.get('commit_id')
190 f_path = self._get_f_path(self.request.matchdict)
196 f_path = self._get_f_path(self.request.matchdict)
191 show_hidden = str2bool(self.request.GET.get('evolve'))
197 show_hidden = str2bool(self.request.GET.get('evolve'))
192
198
193 chunk_size = 20
199 chunk_size = 20
194
200
195 c.branch_name = branch_name = self.request.GET.get('branch') or ''
201 c.branch_name = branch_name = self.request.GET.get('branch') or ''
196 c.book_name = book_name = self.request.GET.get('bookmark') or ''
202 c.book_name = book_name = self.request.GET.get('bookmark') or ''
197 c.f_path = f_path
203 c.f_path = f_path
198 c.commit_id = commit_id
204 c.commit_id = commit_id
199 c.show_hidden = show_hidden
205 c.show_hidden = show_hidden
200
206
201 hist_limit = safe_int(self.request.GET.get('limit')) or None
207 hist_limit = safe_int(self.request.GET.get('limit')) or None
202
208
203 p = safe_int(self.request.GET.get('page', 1), 1)
209 p = safe_int(self.request.GET.get('page', 1), 1)
204
210
205 c.selected_name = branch_name or book_name
211 c.selected_name = branch_name or book_name
206 if not commit_id and branch_name:
212 if not commit_id and branch_name:
207 self._check_if_valid_branch(branch_name, self.db_repo_name, f_path)
213 self._check_if_valid_branch(branch_name, self.db_repo_name, f_path)
208
214
209 c.changelog_for_path = f_path
215 c.changelog_for_path = f_path
210 pre_load = self._get_preload_attrs()
216 pre_load = self._get_preload_attrs()
211
217
212 partial_xhr = self.request.environ.get('HTTP_X_PARTIAL_XHR')
218 partial_xhr = self.request.environ.get('HTTP_X_PARTIAL_XHR')
213 try:
219 try:
214 if f_path:
220 if f_path:
215 log.debug('generating changelog for path %s', f_path)
221 log.debug('generating changelog for path %s', f_path)
216 # get the history for the file !
222 # get the history for the file !
217 base_commit = self.rhodecode_vcs_repo.get_commit(commit_id)
223 base_commit = self.rhodecode_vcs_repo.get_commit(commit_id)
218
224
219 try:
225 try:
220 collection = base_commit.get_path_history(
226 collection = base_commit.get_path_history(
221 f_path, limit=hist_limit, pre_load=pre_load)
227 f_path, limit=hist_limit, pre_load=pre_load)
222 if collection and partial_xhr:
228 if collection and partial_xhr:
223 # for ajax call we remove first one since we're looking
229 # for ajax call we remove first one since we're looking
224 # at it right now in the context of a file commit
230 # at it right now in the context of a file commit
225 collection.pop(0)
231 collection.pop(0)
226 except (NodeDoesNotExistError, CommitError):
232 except (NodeDoesNotExistError, CommitError):
227 # this node is not present at tip!
233 # this node is not present at tip!
228 try:
234 try:
229 commit = self._get_commit_or_redirect(commit_id)
235 commit = self._get_commit_or_redirect(commit_id)
230 collection = commit.get_path_history(f_path)
236 collection = commit.get_path_history(f_path)
231 except RepositoryError as e:
237 except RepositoryError as e:
232 h.flash(safe_str(e), category='warning')
238 h.flash(safe_str(e), category='warning')
233 redirect_url = h.route_path(
239 redirect_url = h.route_path(
234 'repo_commits', repo_name=self.db_repo_name)
240 'repo_commits', repo_name=self.db_repo_name)
235 raise HTTPFound(redirect_url)
241 raise HTTPFound(redirect_url)
236 collection = list(reversed(collection))
242 collection = list(reversed(collection))
237 else:
243 else:
238 collection = self.rhodecode_vcs_repo.get_commits(
244 collection = self.rhodecode_vcs_repo.get_commits(
239 branch_name=branch_name, show_hidden=show_hidden,
245 branch_name=branch_name, show_hidden=show_hidden,
240 pre_load=pre_load, translate_tags=False)
246 pre_load=pre_load, translate_tags=False)
241
247
242 self._load_changelog_data(
248 self._load_changelog_data(
243 c, collection, p, chunk_size, c.branch_name,
249 c, collection, p, chunk_size, c.branch_name,
244 f_path=f_path, commit_id=commit_id)
250 f_path=f_path, commit_id=commit_id)
245
251
246 except EmptyRepositoryError as e:
252 except EmptyRepositoryError as e:
247 h.flash(safe_str(h.escape(e)), category='warning')
253 h.flash(safe_str(h.escape(e)), category='warning')
248 raise HTTPFound(
254 raise HTTPFound(
249 h.route_path('repo_summary', repo_name=self.db_repo_name))
255 h.route_path('repo_summary', repo_name=self.db_repo_name))
250 except HTTPFound:
256 except HTTPFound:
251 raise
257 raise
252 except (RepositoryError, CommitDoesNotExistError, Exception) as e:
258 except (RepositoryError, CommitDoesNotExistError, Exception) as e:
253 log.exception(safe_str(e))
259 log.exception(safe_str(e))
254 h.flash(safe_str(h.escape(e)), category='error')
260 h.flash(safe_str(h.escape(e)), category='error')
255 raise HTTPFound(
261 raise HTTPFound(
256 h.route_path('repo_commits', repo_name=self.db_repo_name))
262 h.route_path('repo_commits', repo_name=self.db_repo_name))
257
263
258 if partial_xhr or self.request.environ.get('HTTP_X_PJAX'):
264 if partial_xhr or self.request.environ.get('HTTP_X_PJAX'):
259 # case when loading dynamic file history in file view
265 # case when loading dynamic file history in file view
260 # loading from ajax, we don't want the first result, it's popped
266 # loading from ajax, we don't want the first result, it's popped
261 # in the code above
267 # in the code above
262 html = render(
268 html = render(
263 'rhodecode:templates/commits/changelog_file_history.mako',
269 'rhodecode:templates/commits/changelog_file_history.mako',
264 self._get_template_context(c), self.request)
270 self._get_template_context(c), self.request)
265 return Response(html)
271 return Response(html)
266
272
267 commit_ids = []
273 commit_ids = []
268 if not f_path:
274 if not f_path:
269 # only load graph data when not in file history mode
275 # only load graph data when not in file history mode
270 commit_ids = c.pagination
276 commit_ids = c.pagination
271
277
272 c.graph_data, c.graph_commits = self._graph(
278 c.graph_data, c.graph_commits = self._graph(
273 self.rhodecode_vcs_repo, commit_ids)
279 self.rhodecode_vcs_repo, commit_ids)
274
280
275 return self._get_template_context(c)
281 return self._get_template_context(c)
276
282
277 @LoginRequired()
283 @LoginRequired()
278 @HasRepoPermissionAnyDecorator(
284 @HasRepoPermissionAnyDecorator(
279 'repository.read', 'repository.write', 'repository.admin')
285 'repository.read', 'repository.write', 'repository.admin')
280 @view_config(
286 @view_config(
281 route_name='repo_commits_elements', request_method=('GET', 'POST'),
287 route_name='repo_commits_elements', request_method=('GET', 'POST'),
282 renderer='rhodecode:templates/commits/changelog_elements.mako',
288 renderer='rhodecode:templates/commits/changelog_elements.mako',
283 xhr=True)
289 xhr=True)
284 @view_config(
290 @view_config(
285 route_name='repo_commits_elements_file', request_method=('GET', 'POST'),
291 route_name='repo_commits_elements_file', request_method=('GET', 'POST'),
286 renderer='rhodecode:templates/commits/changelog_elements.mako',
292 renderer='rhodecode:templates/commits/changelog_elements.mako',
287 xhr=True)
293 xhr=True)
288 def repo_commits_elements(self):
294 def repo_commits_elements(self):
289 c = self.load_default_context()
295 c = self.load_default_context()
290 commit_id = self.request.matchdict.get('commit_id')
296 commit_id = self.request.matchdict.get('commit_id')
291 f_path = self._get_f_path(self.request.matchdict)
297 f_path = self._get_f_path(self.request.matchdict)
292 show_hidden = str2bool(self.request.GET.get('evolve'))
298 show_hidden = str2bool(self.request.GET.get('evolve'))
293
299
294 chunk_size = 20
300 chunk_size = 20
295 hist_limit = safe_int(self.request.GET.get('limit')) or None
301 hist_limit = safe_int(self.request.GET.get('limit')) or None
296
302
297 def wrap_for_error(err):
303 def wrap_for_error(err):
298 html = '<tr>' \
304 html = '<tr>' \
299 '<td colspan="9" class="alert alert-error">ERROR: {}</td>' \
305 '<td colspan="9" class="alert alert-error">ERROR: {}</td>' \
300 '</tr>'.format(err)
306 '</tr>'.format(err)
301 return Response(html)
307 return Response(html)
302
308
303 c.branch_name = branch_name = self.request.GET.get('branch') or ''
309 c.branch_name = branch_name = self.request.GET.get('branch') or ''
304 c.book_name = book_name = self.request.GET.get('bookmark') or ''
310 c.book_name = book_name = self.request.GET.get('bookmark') or ''
305 c.f_path = f_path
311 c.f_path = f_path
306 c.commit_id = commit_id
312 c.commit_id = commit_id
307 c.show_hidden = show_hidden
313 c.show_hidden = show_hidden
308
314
309 c.selected_name = branch_name or book_name
315 c.selected_name = branch_name or book_name
310 if branch_name and branch_name not in self.rhodecode_vcs_repo.branches_all:
316 if branch_name and branch_name not in self.rhodecode_vcs_repo.branches_all:
311 return wrap_for_error(
317 return wrap_for_error(
312 safe_str('Branch: {} is not valid'.format(branch_name)))
318 safe_str('Branch: {} is not valid'.format(branch_name)))
313
319
314 pre_load = self._get_preload_attrs()
320 pre_load = self._get_preload_attrs()
315
321
316 if f_path:
322 if f_path:
317 try:
323 try:
318 base_commit = self.rhodecode_vcs_repo.get_commit(commit_id)
324 base_commit = self.rhodecode_vcs_repo.get_commit(commit_id)
319 except (RepositoryError, CommitDoesNotExistError, Exception) as e:
325 except (RepositoryError, CommitDoesNotExistError, Exception) as e:
320 log.exception(safe_str(e))
326 log.exception(safe_str(e))
321 raise HTTPFound(
327 raise HTTPFound(
322 h.route_path('repo_commits', repo_name=self.db_repo_name))
328 h.route_path('repo_commits', repo_name=self.db_repo_name))
323
329
324 collection = base_commit.get_path_history(
330 collection = base_commit.get_path_history(
325 f_path, limit=hist_limit, pre_load=pre_load)
331 f_path, limit=hist_limit, pre_load=pre_load)
326 collection = list(reversed(collection))
332 collection = list(reversed(collection))
327 else:
333 else:
328 collection = self.rhodecode_vcs_repo.get_commits(
334 collection = self.rhodecode_vcs_repo.get_commits(
329 branch_name=branch_name, show_hidden=show_hidden, pre_load=pre_load,
335 branch_name=branch_name, show_hidden=show_hidden, pre_load=pre_load,
330 translate_tags=False)
336 translate_tags=False)
331
337
332 p = safe_int(self.request.GET.get('page', 1), 1)
338 p = safe_int(self.request.GET.get('page', 1), 1)
333 try:
339 try:
334 self._load_changelog_data(
340 self._load_changelog_data(
335 c, collection, p, chunk_size, dynamic=True,
341 c, collection, p, chunk_size, dynamic=True,
336 f_path=f_path, commit_id=commit_id)
342 f_path=f_path, commit_id=commit_id)
337 except EmptyRepositoryError as e:
343 except EmptyRepositoryError as e:
338 return wrap_for_error(safe_str(e))
344 return wrap_for_error(safe_str(e))
339 except (RepositoryError, CommitDoesNotExistError, Exception) as e:
345 except (RepositoryError, CommitDoesNotExistError, Exception) as e:
340 log.exception('Failed to fetch commits')
346 log.exception('Failed to fetch commits')
341 return wrap_for_error(safe_str(e))
347 return wrap_for_error(safe_str(e))
342
348
343 prev_data = None
349 prev_data = None
344 next_data = None
350 next_data = None
345
351
346 try:
352 try:
347 prev_graph = json.loads(self.request.POST.get('graph') or '{}')
353 prev_graph = json.loads(self.request.POST.get('graph') or '{}')
348 except json.JSONDecodeError:
354 except json.JSONDecodeError:
349 prev_graph = {}
355 prev_graph = {}
350
356
351 if self.request.GET.get('chunk') == 'prev':
357 if self.request.GET.get('chunk') == 'prev':
352 next_data = prev_graph
358 next_data = prev_graph
353 elif self.request.GET.get('chunk') == 'next':
359 elif self.request.GET.get('chunk') == 'next':
354 prev_data = prev_graph
360 prev_data = prev_graph
355
361
356 commit_ids = []
362 commit_ids = []
357 if not f_path:
363 if not f_path:
358 # only load graph data when not in file history mode
364 # only load graph data when not in file history mode
359 commit_ids = c.pagination
365 commit_ids = c.pagination
360
366
361 c.graph_data, c.graph_commits = self._graph(
367 c.graph_data, c.graph_commits = self._graph(
362 self.rhodecode_vcs_repo, commit_ids,
368 self.rhodecode_vcs_repo, commit_ids,
363 prev_data=prev_data, next_data=next_data)
369 prev_data=prev_data, next_data=next_data)
364
370
365 return self._get_template_context(c)
371 return self._get_template_context(c)
@@ -1,319 +1,319 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2011-2019 RhodeCode GmbH
3 # Copyright (C) 2011-2019 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import logging
21 import logging
22 import string
22 import string
23 import time
23 import time
24
24
25 import rhodecode
25 import rhodecode
26
26
27 from pyramid.view import view_config
27 from pyramid.view import view_config
28
28
29 from rhodecode.lib.view_utils import get_format_ref_id
29 from rhodecode.lib.view_utils import get_format_ref_id
30 from rhodecode.apps._base import RepoAppView
30 from rhodecode.apps._base import RepoAppView
31 from rhodecode.config.conf import (LANGUAGES_EXTENSIONS_MAP)
31 from rhodecode.config.conf import (LANGUAGES_EXTENSIONS_MAP)
32 from rhodecode.lib import helpers as h, rc_cache
32 from rhodecode.lib import helpers as h, rc_cache
33 from rhodecode.lib.utils2 import safe_str, safe_int
33 from rhodecode.lib.utils2 import safe_str, safe_int
34 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
34 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
35 from rhodecode.lib.ext_json import json
35 from rhodecode.lib.ext_json import json
36 from rhodecode.lib.vcs.backends.base import EmptyCommit
36 from rhodecode.lib.vcs.backends.base import EmptyCommit
37 from rhodecode.lib.vcs.exceptions import (
37 from rhodecode.lib.vcs.exceptions import (
38 CommitError, EmptyRepositoryError, CommitDoesNotExistError)
38 CommitError, EmptyRepositoryError, CommitDoesNotExistError)
39 from rhodecode.model.db import Statistics, CacheKey, User
39 from rhodecode.model.db import Statistics, CacheKey, User
40 from rhodecode.model.meta import Session
40 from rhodecode.model.meta import Session
41 from rhodecode.model.scm import ScmModel
41 from rhodecode.model.scm import ScmModel
42
42
43 log = logging.getLogger(__name__)
43 log = logging.getLogger(__name__)
44
44
45
45
46 class RepoSummaryView(RepoAppView):
46 class RepoSummaryView(RepoAppView):
47
47
48 def load_default_context(self):
48 def load_default_context(self):
49 c = self._get_local_tmpl_context(include_app_defaults=True)
49 c = self._get_local_tmpl_context(include_app_defaults=True)
50 c.rhodecode_repo = None
50 c.rhodecode_repo = None
51 if not c.repository_requirements_missing:
51 if not c.repository_requirements_missing:
52 c.rhodecode_repo = self.rhodecode_vcs_repo
52 c.rhodecode_repo = self.rhodecode_vcs_repo
53 return c
53 return c
54
54
55 def _load_commits_context(self, c):
55 def _load_commits_context(self, c):
56 p = safe_int(self.request.GET.get('page'), 1)
56 p = safe_int(self.request.GET.get('page'), 1)
57 size = safe_int(self.request.GET.get('size'), 10)
57 size = safe_int(self.request.GET.get('size'), 10)
58
58
59 def url_generator(**kw):
59 def url_generator(page_num):
60 query_params = {
60 query_params = {
61 'page': page_num,
61 'size': size
62 'size': size
62 }
63 }
63 query_params.update(kw)
64 return h.route_path(
64 return h.route_path(
65 'repo_summary_commits',
65 'repo_summary_commits',
66 repo_name=c.rhodecode_db_repo.repo_name, _query=query_params)
66 repo_name=c.rhodecode_db_repo.repo_name, _query=query_params)
67
67
68 pre_load = ['author', 'branch', 'date', 'message']
68 pre_load = ['author', 'branch', 'date', 'message']
69 try:
69 try:
70 collection = self.rhodecode_vcs_repo.get_commits(
70 collection = self.rhodecode_vcs_repo.get_commits(
71 pre_load=pre_load, translate_tags=False)
71 pre_load=pre_load, translate_tags=False)
72 except EmptyRepositoryError:
72 except EmptyRepositoryError:
73 collection = self.rhodecode_vcs_repo
73 collection = self.rhodecode_vcs_repo
74
74
75 c.repo_commits = h.RepoPage(
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 page_ids = [x.raw_id for x in c.repo_commits]
77 page_ids = [x.raw_id for x in c.repo_commits]
78 c.comments = self.db_repo.get_comments(page_ids)
78 c.comments = self.db_repo.get_comments(page_ids)
79 c.statuses = self.db_repo.statuses(page_ids)
79 c.statuses = self.db_repo.statuses(page_ids)
80
80
81 def _prepare_and_set_clone_url(self, c):
81 def _prepare_and_set_clone_url(self, c):
82 username = ''
82 username = ''
83 if self._rhodecode_user.username != User.DEFAULT_USER:
83 if self._rhodecode_user.username != User.DEFAULT_USER:
84 username = safe_str(self._rhodecode_user.username)
84 username = safe_str(self._rhodecode_user.username)
85
85
86 _def_clone_uri = _def_clone_uri_id = c.clone_uri_tmpl
86 _def_clone_uri = _def_clone_uri_id = c.clone_uri_tmpl
87 _def_clone_uri_ssh = c.clone_uri_ssh_tmpl
87 _def_clone_uri_ssh = c.clone_uri_ssh_tmpl
88
88
89 if '{repo}' in _def_clone_uri:
89 if '{repo}' in _def_clone_uri:
90 _def_clone_uri_id = _def_clone_uri.replace('{repo}', '_{repoid}')
90 _def_clone_uri_id = _def_clone_uri.replace('{repo}', '_{repoid}')
91 elif '{repoid}' in _def_clone_uri:
91 elif '{repoid}' in _def_clone_uri:
92 _def_clone_uri_id = _def_clone_uri.replace('_{repoid}', '{repo}')
92 _def_clone_uri_id = _def_clone_uri.replace('_{repoid}', '{repo}')
93
93
94 c.clone_repo_url = self.db_repo.clone_url(
94 c.clone_repo_url = self.db_repo.clone_url(
95 user=username, uri_tmpl=_def_clone_uri)
95 user=username, uri_tmpl=_def_clone_uri)
96 c.clone_repo_url_id = self.db_repo.clone_url(
96 c.clone_repo_url_id = self.db_repo.clone_url(
97 user=username, uri_tmpl=_def_clone_uri_id)
97 user=username, uri_tmpl=_def_clone_uri_id)
98 c.clone_repo_url_ssh = self.db_repo.clone_url(
98 c.clone_repo_url_ssh = self.db_repo.clone_url(
99 uri_tmpl=_def_clone_uri_ssh, ssh=True)
99 uri_tmpl=_def_clone_uri_ssh, ssh=True)
100
100
101 @LoginRequired()
101 @LoginRequired()
102 @HasRepoPermissionAnyDecorator(
102 @HasRepoPermissionAnyDecorator(
103 'repository.read', 'repository.write', 'repository.admin')
103 'repository.read', 'repository.write', 'repository.admin')
104 @view_config(
104 @view_config(
105 route_name='repo_summary_commits', request_method='GET',
105 route_name='repo_summary_commits', request_method='GET',
106 renderer='rhodecode:templates/summary/summary_commits.mako')
106 renderer='rhodecode:templates/summary/summary_commits.mako')
107 def summary_commits(self):
107 def summary_commits(self):
108 c = self.load_default_context()
108 c = self.load_default_context()
109 self._prepare_and_set_clone_url(c)
109 self._prepare_and_set_clone_url(c)
110 self._load_commits_context(c)
110 self._load_commits_context(c)
111 return self._get_template_context(c)
111 return self._get_template_context(c)
112
112
113 @LoginRequired()
113 @LoginRequired()
114 @HasRepoPermissionAnyDecorator(
114 @HasRepoPermissionAnyDecorator(
115 'repository.read', 'repository.write', 'repository.admin')
115 'repository.read', 'repository.write', 'repository.admin')
116 @view_config(
116 @view_config(
117 route_name='repo_summary', request_method='GET',
117 route_name='repo_summary', request_method='GET',
118 renderer='rhodecode:templates/summary/summary.mako')
118 renderer='rhodecode:templates/summary/summary.mako')
119 @view_config(
119 @view_config(
120 route_name='repo_summary_slash', request_method='GET',
120 route_name='repo_summary_slash', request_method='GET',
121 renderer='rhodecode:templates/summary/summary.mako')
121 renderer='rhodecode:templates/summary/summary.mako')
122 @view_config(
122 @view_config(
123 route_name='repo_summary_explicit', request_method='GET',
123 route_name='repo_summary_explicit', request_method='GET',
124 renderer='rhodecode:templates/summary/summary.mako')
124 renderer='rhodecode:templates/summary/summary.mako')
125 def summary(self):
125 def summary(self):
126 c = self.load_default_context()
126 c = self.load_default_context()
127
127
128 # Prepare the clone URL
128 # Prepare the clone URL
129 self._prepare_and_set_clone_url(c)
129 self._prepare_and_set_clone_url(c)
130
130
131 # update every 5 min
131 # update every 5 min
132 if self.db_repo.last_commit_cache_update_diff > 60 * 5:
132 if self.db_repo.last_commit_cache_update_diff > 60 * 5:
133 self.db_repo.update_commit_cache()
133 self.db_repo.update_commit_cache()
134
134
135 # If enabled, get statistics data
135 # If enabled, get statistics data
136
136
137 c.show_stats = bool(self.db_repo.enable_statistics)
137 c.show_stats = bool(self.db_repo.enable_statistics)
138
138
139 stats = Session().query(Statistics) \
139 stats = Session().query(Statistics) \
140 .filter(Statistics.repository == self.db_repo) \
140 .filter(Statistics.repository == self.db_repo) \
141 .scalar()
141 .scalar()
142
142
143 c.stats_percentage = 0
143 c.stats_percentage = 0
144
144
145 if stats and stats.languages:
145 if stats and stats.languages:
146 c.no_data = False is self.db_repo.enable_statistics
146 c.no_data = False is self.db_repo.enable_statistics
147 lang_stats_d = json.loads(stats.languages)
147 lang_stats_d = json.loads(stats.languages)
148
148
149 # Sort first by decreasing count and second by the file extension,
149 # Sort first by decreasing count and second by the file extension,
150 # so we have a consistent output.
150 # so we have a consistent output.
151 lang_stats_items = sorted(lang_stats_d.iteritems(),
151 lang_stats_items = sorted(lang_stats_d.iteritems(),
152 key=lambda k: (-k[1], k[0]))[:10]
152 key=lambda k: (-k[1], k[0]))[:10]
153 lang_stats = [(x, {"count": y,
153 lang_stats = [(x, {"count": y,
154 "desc": LANGUAGES_EXTENSIONS_MAP.get(x)})
154 "desc": LANGUAGES_EXTENSIONS_MAP.get(x)})
155 for x, y in lang_stats_items]
155 for x, y in lang_stats_items]
156
156
157 c.trending_languages = json.dumps(lang_stats)
157 c.trending_languages = json.dumps(lang_stats)
158 else:
158 else:
159 c.no_data = True
159 c.no_data = True
160 c.trending_languages = json.dumps({})
160 c.trending_languages = json.dumps({})
161
161
162 scm_model = ScmModel()
162 scm_model = ScmModel()
163 c.enable_downloads = self.db_repo.enable_downloads
163 c.enable_downloads = self.db_repo.enable_downloads
164 c.repository_followers = scm_model.get_followers(self.db_repo)
164 c.repository_followers = scm_model.get_followers(self.db_repo)
165 c.repository_forks = scm_model.get_forks(self.db_repo)
165 c.repository_forks = scm_model.get_forks(self.db_repo)
166
166
167 # first interaction with the VCS instance after here...
167 # first interaction with the VCS instance after here...
168 if c.repository_requirements_missing:
168 if c.repository_requirements_missing:
169 self.request.override_renderer = \
169 self.request.override_renderer = \
170 'rhodecode:templates/summary/missing_requirements.mako'
170 'rhodecode:templates/summary/missing_requirements.mako'
171 return self._get_template_context(c)
171 return self._get_template_context(c)
172
172
173 c.readme_data, c.readme_file = \
173 c.readme_data, c.readme_file = \
174 self._get_readme_data(self.db_repo, c.visual.default_renderer)
174 self._get_readme_data(self.db_repo, c.visual.default_renderer)
175
175
176 # loads the summary commits template context
176 # loads the summary commits template context
177 self._load_commits_context(c)
177 self._load_commits_context(c)
178
178
179 return self._get_template_context(c)
179 return self._get_template_context(c)
180
180
181 @LoginRequired()
181 @LoginRequired()
182 @HasRepoPermissionAnyDecorator(
182 @HasRepoPermissionAnyDecorator(
183 'repository.read', 'repository.write', 'repository.admin')
183 'repository.read', 'repository.write', 'repository.admin')
184 @view_config(
184 @view_config(
185 route_name='repo_stats', request_method='GET',
185 route_name='repo_stats', request_method='GET',
186 renderer='json_ext')
186 renderer='json_ext')
187 def repo_stats(self):
187 def repo_stats(self):
188 show_stats = bool(self.db_repo.enable_statistics)
188 show_stats = bool(self.db_repo.enable_statistics)
189 repo_id = self.db_repo.repo_id
189 repo_id = self.db_repo.repo_id
190
190
191 landing_commit = self.db_repo.get_landing_commit()
191 landing_commit = self.db_repo.get_landing_commit()
192 if isinstance(landing_commit, EmptyCommit):
192 if isinstance(landing_commit, EmptyCommit):
193 return {'size': 0, 'code_stats': {}}
193 return {'size': 0, 'code_stats': {}}
194
194
195 cache_seconds = safe_int(rhodecode.CONFIG.get('rc_cache.cache_repo.expiration_time'))
195 cache_seconds = safe_int(rhodecode.CONFIG.get('rc_cache.cache_repo.expiration_time'))
196 cache_on = cache_seconds > 0
196 cache_on = cache_seconds > 0
197
197
198 log.debug(
198 log.debug(
199 'Computing REPO STATS for repo_id %s commit_id `%s` '
199 'Computing REPO STATS for repo_id %s commit_id `%s` '
200 'with caching: %s[TTL: %ss]' % (
200 'with caching: %s[TTL: %ss]' % (
201 repo_id, landing_commit, cache_on, cache_seconds or 0))
201 repo_id, landing_commit, cache_on, cache_seconds or 0))
202
202
203 cache_namespace_uid = 'cache_repo.{}'.format(repo_id)
203 cache_namespace_uid = 'cache_repo.{}'.format(repo_id)
204 region = rc_cache.get_or_create_region('cache_repo', cache_namespace_uid)
204 region = rc_cache.get_or_create_region('cache_repo', cache_namespace_uid)
205
205
206 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid,
206 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid,
207 condition=cache_on)
207 condition=cache_on)
208 def compute_stats(repo_id, commit_id, _show_stats):
208 def compute_stats(repo_id, commit_id, _show_stats):
209 code_stats = {}
209 code_stats = {}
210 size = 0
210 size = 0
211 try:
211 try:
212 commit = self.db_repo.get_commit(commit_id)
212 commit = self.db_repo.get_commit(commit_id)
213
213
214 for node in commit.get_filenodes_generator():
214 for node in commit.get_filenodes_generator():
215 size += node.size
215 size += node.size
216 if not _show_stats:
216 if not _show_stats:
217 continue
217 continue
218 ext = string.lower(node.extension)
218 ext = string.lower(node.extension)
219 ext_info = LANGUAGES_EXTENSIONS_MAP.get(ext)
219 ext_info = LANGUAGES_EXTENSIONS_MAP.get(ext)
220 if ext_info:
220 if ext_info:
221 if ext in code_stats:
221 if ext in code_stats:
222 code_stats[ext]['count'] += 1
222 code_stats[ext]['count'] += 1
223 else:
223 else:
224 code_stats[ext] = {"count": 1, "desc": ext_info}
224 code_stats[ext] = {"count": 1, "desc": ext_info}
225 except (EmptyRepositoryError, CommitDoesNotExistError):
225 except (EmptyRepositoryError, CommitDoesNotExistError):
226 pass
226 pass
227 return {'size': h.format_byte_size_binary(size),
227 return {'size': h.format_byte_size_binary(size),
228 'code_stats': code_stats}
228 'code_stats': code_stats}
229
229
230 stats = compute_stats(self.db_repo.repo_id, landing_commit.raw_id, show_stats)
230 stats = compute_stats(self.db_repo.repo_id, landing_commit.raw_id, show_stats)
231 return stats
231 return stats
232
232
233 @LoginRequired()
233 @LoginRequired()
234 @HasRepoPermissionAnyDecorator(
234 @HasRepoPermissionAnyDecorator(
235 'repository.read', 'repository.write', 'repository.admin')
235 'repository.read', 'repository.write', 'repository.admin')
236 @view_config(
236 @view_config(
237 route_name='repo_refs_data', request_method='GET',
237 route_name='repo_refs_data', request_method='GET',
238 renderer='json_ext')
238 renderer='json_ext')
239 def repo_refs_data(self):
239 def repo_refs_data(self):
240 _ = self.request.translate
240 _ = self.request.translate
241 self.load_default_context()
241 self.load_default_context()
242
242
243 repo = self.rhodecode_vcs_repo
243 repo = self.rhodecode_vcs_repo
244 refs_to_create = [
244 refs_to_create = [
245 (_("Branch"), repo.branches, 'branch'),
245 (_("Branch"), repo.branches, 'branch'),
246 (_("Tag"), repo.tags, 'tag'),
246 (_("Tag"), repo.tags, 'tag'),
247 (_("Bookmark"), repo.bookmarks, 'book'),
247 (_("Bookmark"), repo.bookmarks, 'book'),
248 ]
248 ]
249 res = self._create_reference_data(repo, self.db_repo_name, refs_to_create)
249 res = self._create_reference_data(repo, self.db_repo_name, refs_to_create)
250 data = {
250 data = {
251 'more': False,
251 'more': False,
252 'results': res
252 'results': res
253 }
253 }
254 return data
254 return data
255
255
256 @LoginRequired()
256 @LoginRequired()
257 @HasRepoPermissionAnyDecorator(
257 @HasRepoPermissionAnyDecorator(
258 'repository.read', 'repository.write', 'repository.admin')
258 'repository.read', 'repository.write', 'repository.admin')
259 @view_config(
259 @view_config(
260 route_name='repo_refs_changelog_data', request_method='GET',
260 route_name='repo_refs_changelog_data', request_method='GET',
261 renderer='json_ext')
261 renderer='json_ext')
262 def repo_refs_changelog_data(self):
262 def repo_refs_changelog_data(self):
263 _ = self.request.translate
263 _ = self.request.translate
264 self.load_default_context()
264 self.load_default_context()
265
265
266 repo = self.rhodecode_vcs_repo
266 repo = self.rhodecode_vcs_repo
267
267
268 refs_to_create = [
268 refs_to_create = [
269 (_("Branches"), repo.branches, 'branch'),
269 (_("Branches"), repo.branches, 'branch'),
270 (_("Closed branches"), repo.branches_closed, 'branch_closed'),
270 (_("Closed branches"), repo.branches_closed, 'branch_closed'),
271 # TODO: enable when vcs can handle bookmarks filters
271 # TODO: enable when vcs can handle bookmarks filters
272 # (_("Bookmarks"), repo.bookmarks, "book"),
272 # (_("Bookmarks"), repo.bookmarks, "book"),
273 ]
273 ]
274 res = self._create_reference_data(
274 res = self._create_reference_data(
275 repo, self.db_repo_name, refs_to_create)
275 repo, self.db_repo_name, refs_to_create)
276 data = {
276 data = {
277 'more': False,
277 'more': False,
278 'results': res
278 'results': res
279 }
279 }
280 return data
280 return data
281
281
282 def _create_reference_data(self, repo, full_repo_name, refs_to_create):
282 def _create_reference_data(self, repo, full_repo_name, refs_to_create):
283 format_ref_id = get_format_ref_id(repo)
283 format_ref_id = get_format_ref_id(repo)
284
284
285 result = []
285 result = []
286 for title, refs, ref_type in refs_to_create:
286 for title, refs, ref_type in refs_to_create:
287 if refs:
287 if refs:
288 result.append({
288 result.append({
289 'text': title,
289 'text': title,
290 'children': self._create_reference_items(
290 'children': self._create_reference_items(
291 repo, full_repo_name, refs, ref_type,
291 repo, full_repo_name, refs, ref_type,
292 format_ref_id),
292 format_ref_id),
293 })
293 })
294 return result
294 return result
295
295
296 def _create_reference_items(self, repo, full_repo_name, refs, ref_type, format_ref_id):
296 def _create_reference_items(self, repo, full_repo_name, refs, ref_type, format_ref_id):
297 result = []
297 result = []
298 is_svn = h.is_svn(repo)
298 is_svn = h.is_svn(repo)
299 for ref_name, raw_id in refs.iteritems():
299 for ref_name, raw_id in refs.iteritems():
300 files_url = self._create_files_url(
300 files_url = self._create_files_url(
301 repo, full_repo_name, ref_name, raw_id, is_svn)
301 repo, full_repo_name, ref_name, raw_id, is_svn)
302 result.append({
302 result.append({
303 'text': ref_name,
303 'text': ref_name,
304 'id': format_ref_id(ref_name, raw_id),
304 'id': format_ref_id(ref_name, raw_id),
305 'raw_id': raw_id,
305 'raw_id': raw_id,
306 'type': ref_type,
306 'type': ref_type,
307 'files_url': files_url,
307 'files_url': files_url,
308 'idx': 0,
308 'idx': 0,
309 })
309 })
310 return result
310 return result
311
311
312 def _create_files_url(self, repo, full_repo_name, ref_name, raw_id, is_svn):
312 def _create_files_url(self, repo, full_repo_name, ref_name, raw_id, is_svn):
313 use_commit_id = '/' in ref_name or is_svn
313 use_commit_id = '/' in ref_name or is_svn
314 return h.route_path(
314 return h.route_path(
315 'repo_files',
315 'repo_files',
316 repo_name=full_repo_name,
316 repo_name=full_repo_name,
317 f_path=ref_name if is_svn else '',
317 f_path=ref_name if is_svn else '',
318 commit_id=raw_id if use_commit_id else ref_name,
318 commit_id=raw_id if use_commit_id else ref_name,
319 _query=dict(at=ref_name))
319 _query=dict(at=ref_name))
@@ -1,173 +1,180 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2011-2019 RhodeCode GmbH
3 # Copyright (C) 2011-2019 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import logging
21 import logging
22 import urllib
22 import urllib
23 from pyramid.view import view_config
23 from pyramid.view import view_config
24 from webhelpers2.html.tools import update_params
24 from webhelpers2.html.tools import update_params
25
25
26 from rhodecode.apps._base import BaseAppView, RepoAppView, RepoGroupAppView
26 from rhodecode.apps._base import BaseAppView, RepoAppView, RepoGroupAppView
27 from rhodecode.lib.auth import (
27 from rhodecode.lib.auth import (
28 LoginRequired, HasRepoPermissionAnyDecorator, HasRepoGroupPermissionAnyDecorator)
28 LoginRequired, HasRepoPermissionAnyDecorator, HasRepoGroupPermissionAnyDecorator)
29 from rhodecode.lib.helpers import Page
29 from rhodecode.lib.helpers import Page
30 from rhodecode.lib.utils2 import safe_str
30 from rhodecode.lib.utils2 import safe_str
31 from rhodecode.lib.index import searcher_from_config
31 from rhodecode.lib.index import searcher_from_config
32 from rhodecode.model import validation_schema
32 from rhodecode.model import validation_schema
33 from rhodecode.model.validation_schema.schemas import search_schema
33 from rhodecode.model.validation_schema.schemas import search_schema
34
34
35 log = logging.getLogger(__name__)
35 log = logging.getLogger(__name__)
36
36
37
37
38 def perform_search(request, tmpl_context, repo_name=None, repo_group_name=None):
38 def perform_search(request, tmpl_context, repo_name=None, repo_group_name=None):
39 searcher = searcher_from_config(request.registry.settings)
39 searcher = searcher_from_config(request.registry.settings)
40 formatted_results = []
40 formatted_results = []
41 execution_time = ''
41 execution_time = ''
42
42
43 schema = search_schema.SearchParamsSchema()
43 schema = search_schema.SearchParamsSchema()
44 search_tags = []
44 search_tags = []
45 search_params = {}
45 search_params = {}
46 errors = []
46 errors = []
47
47
48 try:
48 try:
49 search_params = schema.deserialize(
49 search_params = schema.deserialize(
50 dict(
50 dict(
51 search_query=request.GET.get('q'),
51 search_query=request.GET.get('q'),
52 search_type=request.GET.get('type'),
52 search_type=request.GET.get('type'),
53 search_sort=request.GET.get('sort'),
53 search_sort=request.GET.get('sort'),
54 search_max_lines=request.GET.get('max_lines'),
54 search_max_lines=request.GET.get('max_lines'),
55 page_limit=request.GET.get('page_limit'),
55 page_limit=request.GET.get('page_limit'),
56 requested_page=request.GET.get('page'),
56 requested_page=request.GET.get('page'),
57 )
57 )
58 )
58 )
59 except validation_schema.Invalid as e:
59 except validation_schema.Invalid as e:
60 errors = e.children
60 errors = e.children
61
61
62 def url_generator(**kw):
62 def url_generator(page_num):
63 q = urllib.quote(safe_str(search_query))
63 q = urllib.quote(safe_str(search_query))
64 return update_params(
64
65 "?q=%s&type=%s&max_lines=%s&sort=%s" % (
65 query_params = {
66 q, safe_str(search_type), search_max_lines, search_sort), **kw)
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 c = tmpl_context
76 c = tmpl_context
69 search_query = search_params.get('search_query')
77 search_query = search_params.get('search_query')
70 search_type = search_params.get('search_type')
78 search_type = search_params.get('search_type')
71 search_sort = search_params.get('search_sort')
79 search_sort = search_params.get('search_sort')
72 search_max_lines = search_params.get('search_max_lines')
80 search_max_lines = search_params.get('search_max_lines')
73 if search_params.get('search_query'):
81 if search_params.get('search_query'):
74 page_limit = search_params['page_limit']
82 page_limit = search_params['page_limit']
75 requested_page = search_params['requested_page']
83 requested_page = search_params['requested_page']
76
84
77 try:
85 try:
78 search_result = searcher.search(
86 search_result = searcher.search(
79 search_query, search_type, c.auth_user, repo_name, repo_group_name,
87 search_query, search_type, c.auth_user, repo_name, repo_group_name,
80 requested_page=requested_page, page_limit=page_limit, sort=search_sort)
88 requested_page=requested_page, page_limit=page_limit, sort=search_sort)
81
89
82 formatted_results = Page(
90 formatted_results = Page(
83 search_result['results'], page=requested_page,
91 search_result['results'], page=requested_page,
84 item_count=search_result['count'],
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 finally:
94 finally:
87 searcher.cleanup()
95 searcher.cleanup()
88
96
89 search_tags = searcher.extract_search_tags(search_query)
97 search_tags = searcher.extract_search_tags(search_query)
90
98
91 if not search_result['error']:
99 if not search_result['error']:
92 execution_time = '%s results (%.4f seconds)' % (
100 execution_time = '%s results (%.4f seconds)' % (
93 search_result['count'],
101 search_result['count'],
94 search_result['runtime'])
102 search_result['runtime'])
95 elif not errors:
103 elif not errors:
96 node = schema['search_query']
104 node = schema['search_query']
97 errors = [
105 errors = [
98 validation_schema.Invalid(node, search_result['error'])]
106 validation_schema.Invalid(node, search_result['error'])]
99
107
100 c.perm_user = c.auth_user
108 c.perm_user = c.auth_user
101 c.repo_name = repo_name
109 c.repo_name = repo_name
102 c.repo_group_name = repo_group_name
110 c.repo_group_name = repo_group_name
103 c.url_generator = url_generator
104 c.errors = errors
111 c.errors = errors
105 c.formatted_results = formatted_results
112 c.formatted_results = formatted_results
106 c.runtime = execution_time
113 c.runtime = execution_time
107 c.cur_query = search_query
114 c.cur_query = search_query
108 c.search_type = search_type
115 c.search_type = search_type
109 c.searcher = searcher
116 c.searcher = searcher
110 c.search_tags = search_tags
117 c.search_tags = search_tags
111
118
112 direction, sort_field = searcher.get_sort(search_type, search_sort)
119 direction, sort_field = searcher.get_sort(search_type, search_sort)
113 sort_definition = searcher.sort_def(search_type, direction, sort_field)
120 sort_definition = searcher.sort_def(search_type, direction, sort_field)
114 c.sort = ''
121 c.sort = ''
115 c.sort_tag = None
122 c.sort_tag = None
116 c.sort_tag_dir = direction
123 c.sort_tag_dir = direction
117 if sort_definition:
124 if sort_definition:
118 c.sort = '{}:{}'.format(direction, sort_field)
125 c.sort = '{}:{}'.format(direction, sort_field)
119 c.sort_tag = sort_field
126 c.sort_tag = sort_field
120
127
121
128
122 class SearchView(BaseAppView):
129 class SearchView(BaseAppView):
123 def load_default_context(self):
130 def load_default_context(self):
124 c = self._get_local_tmpl_context()
131 c = self._get_local_tmpl_context()
125 return c
132 return c
126
133
127 @LoginRequired()
134 @LoginRequired()
128 @view_config(
135 @view_config(
129 route_name='search', request_method='GET',
136 route_name='search', request_method='GET',
130 renderer='rhodecode:templates/search/search.mako')
137 renderer='rhodecode:templates/search/search.mako')
131 def search(self):
138 def search(self):
132 c = self.load_default_context()
139 c = self.load_default_context()
133 perform_search(self.request, c)
140 perform_search(self.request, c)
134 return self._get_template_context(c)
141 return self._get_template_context(c)
135
142
136
143
137 class SearchRepoView(RepoAppView):
144 class SearchRepoView(RepoAppView):
138 def load_default_context(self):
145 def load_default_context(self):
139 c = self._get_local_tmpl_context()
146 c = self._get_local_tmpl_context()
140 c.active = 'search'
147 c.active = 'search'
141 return c
148 return c
142
149
143 @LoginRequired()
150 @LoginRequired()
144 @HasRepoPermissionAnyDecorator(
151 @HasRepoPermissionAnyDecorator(
145 'repository.read', 'repository.write', 'repository.admin')
152 'repository.read', 'repository.write', 'repository.admin')
146 @view_config(
153 @view_config(
147 route_name='search_repo', request_method='GET',
154 route_name='search_repo', request_method='GET',
148 renderer='rhodecode:templates/search/search.mako')
155 renderer='rhodecode:templates/search/search.mako')
149 @view_config(
156 @view_config(
150 route_name='search_repo_alt', request_method='GET',
157 route_name='search_repo_alt', request_method='GET',
151 renderer='rhodecode:templates/search/search.mako')
158 renderer='rhodecode:templates/search/search.mako')
152 def search_repo(self):
159 def search_repo(self):
153 c = self.load_default_context()
160 c = self.load_default_context()
154 perform_search(self.request, c, repo_name=self.db_repo_name)
161 perform_search(self.request, c, repo_name=self.db_repo_name)
155 return self._get_template_context(c)
162 return self._get_template_context(c)
156
163
157
164
158 class SearchRepoGroupView(RepoGroupAppView):
165 class SearchRepoGroupView(RepoGroupAppView):
159 def load_default_context(self):
166 def load_default_context(self):
160 c = self._get_local_tmpl_context()
167 c = self._get_local_tmpl_context()
161 c.active = 'search'
168 c.active = 'search'
162 return c
169 return c
163
170
164 @LoginRequired()
171 @LoginRequired()
165 @HasRepoGroupPermissionAnyDecorator(
172 @HasRepoGroupPermissionAnyDecorator(
166 'group.read', 'group.write', 'group.admin')
173 'group.read', 'group.write', 'group.admin')
167 @view_config(
174 @view_config(
168 route_name='search_repo_group', request_method='GET',
175 route_name='search_repo_group', request_method='GET',
169 renderer='rhodecode:templates/search/search.mako')
176 renderer='rhodecode:templates/search/search.mako')
170 def search_repo_group(self):
177 def search_repo_group(self):
171 c = self.load_default_context()
178 c = self.load_default_context()
172 perform_search(self.request, c, repo_group_name=self.db_repo_group_name)
179 perform_search(self.request, c, repo_group_name=self.db_repo_group_name)
173 return self._get_template_context(c)
180 return self._get_template_context(c)
@@ -1,464 +1,469 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2012-2019 RhodeCode GmbH
3 # Copyright (C) 2012-2019 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import deform
21 import deform
22 import logging
22 import logging
23 import peppercorn
23 import peppercorn
24
24
25 from pyramid.httpexceptions import HTTPFound, HTTPForbidden, HTTPNotFound
25 from pyramid.httpexceptions import HTTPFound, HTTPForbidden, HTTPNotFound
26
26
27 from rhodecode.integrations import integration_type_registry
27 from rhodecode.integrations import integration_type_registry
28 from rhodecode.apps._base import BaseAppView
28 from rhodecode.apps._base import BaseAppView
29 from rhodecode.apps._base.navigation import navigation_list
29 from rhodecode.apps._base.navigation import navigation_list
30 from rhodecode.lib.paginate import PageURL
31 from rhodecode.lib.auth import (
30 from rhodecode.lib.auth import (
32 LoginRequired, CSRFRequired, HasPermissionAnyDecorator,
31 LoginRequired, CSRFRequired, HasPermissionAnyDecorator,
33 HasRepoPermissionAnyDecorator, HasRepoGroupPermissionAnyDecorator)
32 HasRepoPermissionAnyDecorator, HasRepoGroupPermissionAnyDecorator)
34 from rhodecode.lib.utils2 import safe_int
33 from rhodecode.lib.utils2 import safe_int
34 from rhodecode.lib.helpers import Page
35 from rhodecode.lib import helpers as h
35 from rhodecode.lib import helpers as h
36 from rhodecode.model.db import Repository, RepoGroup, Session, Integration
36 from rhodecode.model.db import Repository, RepoGroup, Session, Integration
37 from rhodecode.model.scm import ScmModel
37 from rhodecode.model.scm import ScmModel
38 from rhodecode.model.integration import IntegrationModel
38 from rhodecode.model.integration import IntegrationModel
39 from rhodecode.model.validation_schema.schemas.integration_schema import (
39 from rhodecode.model.validation_schema.schemas.integration_schema import (
40 make_integration_schema, IntegrationScopeType)
40 make_integration_schema, IntegrationScopeType)
41
41
42 log = logging.getLogger(__name__)
42 log = logging.getLogger(__name__)
43
43
44
44
45 class IntegrationSettingsViewBase(BaseAppView):
45 class IntegrationSettingsViewBase(BaseAppView):
46 """
46 """
47 Base Integration settings view used by both repo / global settings
47 Base Integration settings view used by both repo / global settings
48 """
48 """
49
49
50 def __init__(self, context, request):
50 def __init__(self, context, request):
51 super(IntegrationSettingsViewBase, self).__init__(context, request)
51 super(IntegrationSettingsViewBase, self).__init__(context, request)
52 self._load_view_context()
52 self._load_view_context()
53
53
54 def _load_view_context(self):
54 def _load_view_context(self):
55 """
55 """
56 This avoids boilerplate for repo/global+list/edit+views/templates
56 This avoids boilerplate for repo/global+list/edit+views/templates
57 by doing all possible contexts at the same time however it should
57 by doing all possible contexts at the same time however it should
58 be split up into separate functions once more "contexts" exist
58 be split up into separate functions once more "contexts" exist
59 """
59 """
60
60
61 self.IntegrationType = None
61 self.IntegrationType = None
62 self.repo = None
62 self.repo = None
63 self.repo_group = None
63 self.repo_group = None
64 self.integration = None
64 self.integration = None
65 self.integrations = {}
65 self.integrations = {}
66
66
67 request = self.request
67 request = self.request
68
68
69 if 'repo_name' in request.matchdict: # in repo settings context
69 if 'repo_name' in request.matchdict: # in repo settings context
70 repo_name = request.matchdict['repo_name']
70 repo_name = request.matchdict['repo_name']
71 self.repo = Repository.get_by_repo_name(repo_name)
71 self.repo = Repository.get_by_repo_name(repo_name)
72
72
73 if 'repo_group_name' in request.matchdict: # in group settings context
73 if 'repo_group_name' in request.matchdict: # in group settings context
74 repo_group_name = request.matchdict['repo_group_name']
74 repo_group_name = request.matchdict['repo_group_name']
75 self.repo_group = RepoGroup.get_by_group_name(repo_group_name)
75 self.repo_group = RepoGroup.get_by_group_name(repo_group_name)
76
76
77 if 'integration' in request.matchdict: # integration type context
77 if 'integration' in request.matchdict: # integration type context
78 integration_type = request.matchdict['integration']
78 integration_type = request.matchdict['integration']
79 if integration_type not in integration_type_registry:
79 if integration_type not in integration_type_registry:
80 raise HTTPNotFound()
80 raise HTTPNotFound()
81
81
82 self.IntegrationType = integration_type_registry[integration_type]
82 self.IntegrationType = integration_type_registry[integration_type]
83 if self.IntegrationType.is_dummy:
83 if self.IntegrationType.is_dummy:
84 raise HTTPNotFound()
84 raise HTTPNotFound()
85
85
86 if 'integration_id' in request.matchdict: # single integration context
86 if 'integration_id' in request.matchdict: # single integration context
87 integration_id = request.matchdict['integration_id']
87 integration_id = request.matchdict['integration_id']
88 self.integration = Integration.get(integration_id)
88 self.integration = Integration.get(integration_id)
89
89
90 # extra perms check just in case
90 # extra perms check just in case
91 if not self._has_perms_for_integration(self.integration):
91 if not self._has_perms_for_integration(self.integration):
92 raise HTTPForbidden()
92 raise HTTPForbidden()
93
93
94 self.settings = self.integration and self.integration.settings or {}
94 self.settings = self.integration and self.integration.settings or {}
95 self.admin_view = not (self.repo or self.repo_group)
95 self.admin_view = not (self.repo or self.repo_group)
96
96
97 def _has_perms_for_integration(self, integration):
97 def _has_perms_for_integration(self, integration):
98 perms = self.request.user.permissions
98 perms = self.request.user.permissions
99
99
100 if 'hg.admin' in perms['global']:
100 if 'hg.admin' in perms['global']:
101 return True
101 return True
102
102
103 if integration.repo:
103 if integration.repo:
104 return perms['repositories'].get(
104 return perms['repositories'].get(
105 integration.repo.repo_name) == 'repository.admin'
105 integration.repo.repo_name) == 'repository.admin'
106
106
107 if integration.repo_group:
107 if integration.repo_group:
108 return perms['repositories_groups'].get(
108 return perms['repositories_groups'].get(
109 integration.repo_group.group_name) == 'group.admin'
109 integration.repo_group.group_name) == 'group.admin'
110
110
111 return False
111 return False
112
112
113 def _get_local_tmpl_context(self, include_app_defaults=True):
113 def _get_local_tmpl_context(self, include_app_defaults=True):
114 _ = self.request.translate
114 _ = self.request.translate
115 c = super(IntegrationSettingsViewBase, self)._get_local_tmpl_context(
115 c = super(IntegrationSettingsViewBase, self)._get_local_tmpl_context(
116 include_app_defaults=include_app_defaults)
116 include_app_defaults=include_app_defaults)
117 c.active = 'integrations'
117 c.active = 'integrations'
118
118
119 return c
119 return c
120
120
121 def _form_schema(self):
121 def _form_schema(self):
122 schema = make_integration_schema(IntegrationType=self.IntegrationType,
122 schema = make_integration_schema(IntegrationType=self.IntegrationType,
123 settings=self.settings)
123 settings=self.settings)
124
124
125 # returns a clone, important if mutating the schema later
125 # returns a clone, important if mutating the schema later
126 return schema.bind(
126 return schema.bind(
127 permissions=self.request.user.permissions,
127 permissions=self.request.user.permissions,
128 no_scope=not self.admin_view)
128 no_scope=not self.admin_view)
129
129
130 def _form_defaults(self):
130 def _form_defaults(self):
131 _ = self.request.translate
131 _ = self.request.translate
132 defaults = {}
132 defaults = {}
133
133
134 if self.integration:
134 if self.integration:
135 defaults['settings'] = self.integration.settings or {}
135 defaults['settings'] = self.integration.settings or {}
136 defaults['options'] = {
136 defaults['options'] = {
137 'name': self.integration.name,
137 'name': self.integration.name,
138 'enabled': self.integration.enabled,
138 'enabled': self.integration.enabled,
139 'scope': {
139 'scope': {
140 'repo': self.integration.repo,
140 'repo': self.integration.repo,
141 'repo_group': self.integration.repo_group,
141 'repo_group': self.integration.repo_group,
142 'child_repos_only': self.integration.child_repos_only,
142 'child_repos_only': self.integration.child_repos_only,
143 },
143 },
144 }
144 }
145 else:
145 else:
146 if self.repo:
146 if self.repo:
147 scope = _('{repo_name} repository').format(
147 scope = _('{repo_name} repository').format(
148 repo_name=self.repo.repo_name)
148 repo_name=self.repo.repo_name)
149 elif self.repo_group:
149 elif self.repo_group:
150 scope = _('{repo_group_name} repo group').format(
150 scope = _('{repo_group_name} repo group').format(
151 repo_group_name=self.repo_group.group_name)
151 repo_group_name=self.repo_group.group_name)
152 else:
152 else:
153 scope = _('Global')
153 scope = _('Global')
154
154
155 defaults['options'] = {
155 defaults['options'] = {
156 'enabled': True,
156 'enabled': True,
157 'name': _('{name} integration').format(
157 'name': _('{name} integration').format(
158 name=self.IntegrationType.display_name),
158 name=self.IntegrationType.display_name),
159 }
159 }
160 defaults['options']['scope'] = {
160 defaults['options']['scope'] = {
161 'repo': self.repo,
161 'repo': self.repo,
162 'repo_group': self.repo_group,
162 'repo_group': self.repo_group,
163 }
163 }
164
164
165 return defaults
165 return defaults
166
166
167 def _delete_integration(self, integration):
167 def _delete_integration(self, integration):
168 _ = self.request.translate
168 _ = self.request.translate
169 Session().delete(integration)
169 Session().delete(integration)
170 Session().commit()
170 Session().commit()
171 h.flash(
171 h.flash(
172 _('Integration {integration_name} deleted successfully.').format(
172 _('Integration {integration_name} deleted successfully.').format(
173 integration_name=integration.name),
173 integration_name=integration.name),
174 category='success')
174 category='success')
175
175
176 if self.repo:
176 if self.repo:
177 redirect_to = self.request.route_path(
177 redirect_to = self.request.route_path(
178 'repo_integrations_home', repo_name=self.repo.repo_name)
178 'repo_integrations_home', repo_name=self.repo.repo_name)
179 elif self.repo_group:
179 elif self.repo_group:
180 redirect_to = self.request.route_path(
180 redirect_to = self.request.route_path(
181 'repo_group_integrations_home',
181 'repo_group_integrations_home',
182 repo_group_name=self.repo_group.group_name)
182 repo_group_name=self.repo_group.group_name)
183 else:
183 else:
184 redirect_to = self.request.route_path('global_integrations_home')
184 redirect_to = self.request.route_path('global_integrations_home')
185 raise HTTPFound(redirect_to)
185 raise HTTPFound(redirect_to)
186
186
187 def _integration_list(self):
187 def _integration_list(self):
188 """ List integrations """
188 """ List integrations """
189
189
190 c = self.load_default_context()
190 c = self.load_default_context()
191 if self.repo:
191 if self.repo:
192 scope = self.repo
192 scope = self.repo
193 elif self.repo_group:
193 elif self.repo_group:
194 scope = self.repo_group
194 scope = self.repo_group
195 else:
195 else:
196 scope = 'all'
196 scope = 'all'
197
197
198 integrations = []
198 integrations = []
199
199
200 for IntType, integration in IntegrationModel().get_integrations(
200 for IntType, integration in IntegrationModel().get_integrations(
201 scope=scope, IntegrationType=self.IntegrationType):
201 scope=scope, IntegrationType=self.IntegrationType):
202
202
203 # extra permissions check *just in case*
203 # extra permissions check *just in case*
204 if not self._has_perms_for_integration(integration):
204 if not self._has_perms_for_integration(integration):
205 continue
205 continue
206
206
207 integrations.append((IntType, integration))
207 integrations.append((IntType, integration))
208
208
209 sort_arg = self.request.GET.get('sort', 'name:asc')
209 sort_arg = self.request.GET.get('sort', 'name:asc')
210 sort_dir = 'asc'
210 sort_dir = 'asc'
211 if ':' in sort_arg:
211 if ':' in sort_arg:
212 sort_field, sort_dir = sort_arg.split(':')
212 sort_field, sort_dir = sort_arg.split(':')
213 else:
213 else:
214 sort_field = sort_arg, 'asc'
214 sort_field = sort_arg, 'asc'
215
215
216 assert sort_field in ('name', 'integration_type', 'enabled', 'scope')
216 assert sort_field in ('name', 'integration_type', 'enabled', 'scope')
217
217
218 integrations.sort(
218 integrations.sort(
219 key=lambda x: getattr(x[1], sort_field),
219 key=lambda x: getattr(x[1], sort_field),
220 reverse=(sort_dir == 'desc'))
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 page = safe_int(self.request.GET.get('page', 1), 1)
228 page = safe_int(self.request.GET.get('page', 1), 1)
224
229
225 integrations = h.Page(
230 integrations = Page(
226 integrations, page=page, items_per_page=10, url=page_url)
231 integrations, page=page, items_per_page=10, url_maker=url_generator)
227
232
228 c.rev_sort_dir = sort_dir != 'desc' and 'desc' or 'asc'
233 c.rev_sort_dir = sort_dir != 'desc' and 'desc' or 'asc'
229
234
230 c.current_IntegrationType = self.IntegrationType
235 c.current_IntegrationType = self.IntegrationType
231 c.integrations_list = integrations
236 c.integrations_list = integrations
232 c.available_integrations = integration_type_registry
237 c.available_integrations = integration_type_registry
233
238
234 return self._get_template_context(c)
239 return self._get_template_context(c)
235
240
236 def _settings_get(self, defaults=None, form=None):
241 def _settings_get(self, defaults=None, form=None):
237 """
242 """
238 View that displays the integration settings as a form.
243 View that displays the integration settings as a form.
239 """
244 """
240 c = self.load_default_context()
245 c = self.load_default_context()
241
246
242 defaults = defaults or self._form_defaults()
247 defaults = defaults or self._form_defaults()
243 schema = self._form_schema()
248 schema = self._form_schema()
244
249
245 if self.integration:
250 if self.integration:
246 buttons = ('submit', 'delete')
251 buttons = ('submit', 'delete')
247 else:
252 else:
248 buttons = ('submit',)
253 buttons = ('submit',)
249
254
250 form = form or deform.Form(schema, appstruct=defaults, buttons=buttons)
255 form = form or deform.Form(schema, appstruct=defaults, buttons=buttons)
251
256
252 c.form = form
257 c.form = form
253 c.current_IntegrationType = self.IntegrationType
258 c.current_IntegrationType = self.IntegrationType
254 c.integration = self.integration
259 c.integration = self.integration
255
260
256 return self._get_template_context(c)
261 return self._get_template_context(c)
257
262
258 def _settings_post(self):
263 def _settings_post(self):
259 """
264 """
260 View that validates and stores the integration settings.
265 View that validates and stores the integration settings.
261 """
266 """
262 _ = self.request.translate
267 _ = self.request.translate
263
268
264 controls = self.request.POST.items()
269 controls = self.request.POST.items()
265 pstruct = peppercorn.parse(controls)
270 pstruct = peppercorn.parse(controls)
266
271
267 if self.integration and pstruct.get('delete'):
272 if self.integration and pstruct.get('delete'):
268 return self._delete_integration(self.integration)
273 return self._delete_integration(self.integration)
269
274
270 schema = self._form_schema()
275 schema = self._form_schema()
271
276
272 skip_settings_validation = False
277 skip_settings_validation = False
273 if self.integration and 'enabled' not in pstruct.get('options', {}):
278 if self.integration and 'enabled' not in pstruct.get('options', {}):
274 skip_settings_validation = True
279 skip_settings_validation = True
275 schema['settings'].validator = None
280 schema['settings'].validator = None
276 for field in schema['settings'].children:
281 for field in schema['settings'].children:
277 field.validator = None
282 field.validator = None
278 field.missing = ''
283 field.missing = ''
279
284
280 if self.integration:
285 if self.integration:
281 buttons = ('submit', 'delete')
286 buttons = ('submit', 'delete')
282 else:
287 else:
283 buttons = ('submit',)
288 buttons = ('submit',)
284
289
285 form = deform.Form(schema, buttons=buttons)
290 form = deform.Form(schema, buttons=buttons)
286
291
287 if not self.admin_view:
292 if not self.admin_view:
288 # scope is read only field in these cases, and has to be added
293 # scope is read only field in these cases, and has to be added
289 options = pstruct.setdefault('options', {})
294 options = pstruct.setdefault('options', {})
290 if 'scope' not in options:
295 if 'scope' not in options:
291 options['scope'] = IntegrationScopeType().serialize(None, {
296 options['scope'] = IntegrationScopeType().serialize(None, {
292 'repo': self.repo,
297 'repo': self.repo,
293 'repo_group': self.repo_group,
298 'repo_group': self.repo_group,
294 })
299 })
295
300
296 try:
301 try:
297 valid_data = form.validate_pstruct(pstruct)
302 valid_data = form.validate_pstruct(pstruct)
298 except deform.ValidationFailure as e:
303 except deform.ValidationFailure as e:
299 h.flash(
304 h.flash(
300 _('Errors exist when saving integration settings. '
305 _('Errors exist when saving integration settings. '
301 'Please check the form inputs.'),
306 'Please check the form inputs.'),
302 category='error')
307 category='error')
303 return self._settings_get(form=e)
308 return self._settings_get(form=e)
304
309
305 if not self.integration:
310 if not self.integration:
306 self.integration = Integration()
311 self.integration = Integration()
307 self.integration.integration_type = self.IntegrationType.key
312 self.integration.integration_type = self.IntegrationType.key
308 Session().add(self.integration)
313 Session().add(self.integration)
309
314
310 scope = valid_data['options']['scope']
315 scope = valid_data['options']['scope']
311
316
312 IntegrationModel().update_integration(self.integration,
317 IntegrationModel().update_integration(self.integration,
313 name=valid_data['options']['name'],
318 name=valid_data['options']['name'],
314 enabled=valid_data['options']['enabled'],
319 enabled=valid_data['options']['enabled'],
315 settings=valid_data['settings'],
320 settings=valid_data['settings'],
316 repo=scope['repo'],
321 repo=scope['repo'],
317 repo_group=scope['repo_group'],
322 repo_group=scope['repo_group'],
318 child_repos_only=scope['child_repos_only'],
323 child_repos_only=scope['child_repos_only'],
319 )
324 )
320
325
321 self.integration.settings = valid_data['settings']
326 self.integration.settings = valid_data['settings']
322 Session().commit()
327 Session().commit()
323 # Display success message and redirect.
328 # Display success message and redirect.
324 h.flash(
329 h.flash(
325 _('Integration {integration_name} updated successfully.').format(
330 _('Integration {integration_name} updated successfully.').format(
326 integration_name=self.IntegrationType.display_name),
331 integration_name=self.IntegrationType.display_name),
327 category='success')
332 category='success')
328
333
329 # if integration scope changes, we must redirect to the right place
334 # if integration scope changes, we must redirect to the right place
330 # keeping in mind if the original view was for /repo/ or /_admin/
335 # keeping in mind if the original view was for /repo/ or /_admin/
331 admin_view = not (self.repo or self.repo_group)
336 admin_view = not (self.repo or self.repo_group)
332
337
333 if self.integration.repo and not admin_view:
338 if self.integration.repo and not admin_view:
334 redirect_to = self.request.route_path(
339 redirect_to = self.request.route_path(
335 'repo_integrations_edit',
340 'repo_integrations_edit',
336 repo_name=self.integration.repo.repo_name,
341 repo_name=self.integration.repo.repo_name,
337 integration=self.integration.integration_type,
342 integration=self.integration.integration_type,
338 integration_id=self.integration.integration_id)
343 integration_id=self.integration.integration_id)
339 elif self.integration.repo_group and not admin_view:
344 elif self.integration.repo_group and not admin_view:
340 redirect_to = self.request.route_path(
345 redirect_to = self.request.route_path(
341 'repo_group_integrations_edit',
346 'repo_group_integrations_edit',
342 repo_group_name=self.integration.repo_group.group_name,
347 repo_group_name=self.integration.repo_group.group_name,
343 integration=self.integration.integration_type,
348 integration=self.integration.integration_type,
344 integration_id=self.integration.integration_id)
349 integration_id=self.integration.integration_id)
345 else:
350 else:
346 redirect_to = self.request.route_path(
351 redirect_to = self.request.route_path(
347 'global_integrations_edit',
352 'global_integrations_edit',
348 integration=self.integration.integration_type,
353 integration=self.integration.integration_type,
349 integration_id=self.integration.integration_id)
354 integration_id=self.integration.integration_id)
350
355
351 return HTTPFound(redirect_to)
356 return HTTPFound(redirect_to)
352
357
353 def _new_integration(self):
358 def _new_integration(self):
354 c = self.load_default_context()
359 c = self.load_default_context()
355 c.available_integrations = integration_type_registry
360 c.available_integrations = integration_type_registry
356 return self._get_template_context(c)
361 return self._get_template_context(c)
357
362
358 def load_default_context(self):
363 def load_default_context(self):
359 raise NotImplementedError()
364 raise NotImplementedError()
360
365
361
366
362 class GlobalIntegrationsView(IntegrationSettingsViewBase):
367 class GlobalIntegrationsView(IntegrationSettingsViewBase):
363 def load_default_context(self):
368 def load_default_context(self):
364 c = self._get_local_tmpl_context()
369 c = self._get_local_tmpl_context()
365 c.repo = self.repo
370 c.repo = self.repo
366 c.repo_group = self.repo_group
371 c.repo_group = self.repo_group
367 c.navlist = navigation_list(self.request)
372 c.navlist = navigation_list(self.request)
368
373
369 return c
374 return c
370
375
371 @LoginRequired()
376 @LoginRequired()
372 @HasPermissionAnyDecorator('hg.admin')
377 @HasPermissionAnyDecorator('hg.admin')
373 def integration_list(self):
378 def integration_list(self):
374 return self._integration_list()
379 return self._integration_list()
375
380
376 @LoginRequired()
381 @LoginRequired()
377 @HasPermissionAnyDecorator('hg.admin')
382 @HasPermissionAnyDecorator('hg.admin')
378 def settings_get(self):
383 def settings_get(self):
379 return self._settings_get()
384 return self._settings_get()
380
385
381 @LoginRequired()
386 @LoginRequired()
382 @HasPermissionAnyDecorator('hg.admin')
387 @HasPermissionAnyDecorator('hg.admin')
383 @CSRFRequired()
388 @CSRFRequired()
384 def settings_post(self):
389 def settings_post(self):
385 return self._settings_post()
390 return self._settings_post()
386
391
387 @LoginRequired()
392 @LoginRequired()
388 @HasPermissionAnyDecorator('hg.admin')
393 @HasPermissionAnyDecorator('hg.admin')
389 def new_integration(self):
394 def new_integration(self):
390 return self._new_integration()
395 return self._new_integration()
391
396
392
397
393 class RepoIntegrationsView(IntegrationSettingsViewBase):
398 class RepoIntegrationsView(IntegrationSettingsViewBase):
394 def load_default_context(self):
399 def load_default_context(self):
395 c = self._get_local_tmpl_context()
400 c = self._get_local_tmpl_context()
396
401
397 c.repo = self.repo
402 c.repo = self.repo
398 c.repo_group = self.repo_group
403 c.repo_group = self.repo_group
399
404
400 self.db_repo = self.repo
405 self.db_repo = self.repo
401 c.rhodecode_db_repo = self.repo
406 c.rhodecode_db_repo = self.repo
402 c.repo_name = self.db_repo.repo_name
407 c.repo_name = self.db_repo.repo_name
403 c.repository_pull_requests = ScmModel().get_pull_requests(self.repo)
408 c.repository_pull_requests = ScmModel().get_pull_requests(self.repo)
404 c.repository_artifacts = ScmModel().get_artifacts(self.repo)
409 c.repository_artifacts = ScmModel().get_artifacts(self.repo)
405 c.repository_is_user_following = ScmModel().is_following_repo(
410 c.repository_is_user_following = ScmModel().is_following_repo(
406 c.repo_name, self._rhodecode_user.user_id)
411 c.repo_name, self._rhodecode_user.user_id)
407 c.has_origin_repo_read_perm = False
412 c.has_origin_repo_read_perm = False
408 if self.db_repo.fork:
413 if self.db_repo.fork:
409 c.has_origin_repo_read_perm = h.HasRepoPermissionAny(
414 c.has_origin_repo_read_perm = h.HasRepoPermissionAny(
410 'repository.write', 'repository.read', 'repository.admin')(
415 'repository.write', 'repository.read', 'repository.admin')(
411 self.db_repo.fork.repo_name, 'summary fork link')
416 self.db_repo.fork.repo_name, 'summary fork link')
412 return c
417 return c
413
418
414 @LoginRequired()
419 @LoginRequired()
415 @HasRepoPermissionAnyDecorator('repository.admin')
420 @HasRepoPermissionAnyDecorator('repository.admin')
416 def integration_list(self):
421 def integration_list(self):
417 return self._integration_list()
422 return self._integration_list()
418
423
419 @LoginRequired()
424 @LoginRequired()
420 @HasRepoPermissionAnyDecorator('repository.admin')
425 @HasRepoPermissionAnyDecorator('repository.admin')
421 def settings_get(self):
426 def settings_get(self):
422 return self._settings_get()
427 return self._settings_get()
423
428
424 @LoginRequired()
429 @LoginRequired()
425 @HasRepoPermissionAnyDecorator('repository.admin')
430 @HasRepoPermissionAnyDecorator('repository.admin')
426 @CSRFRequired()
431 @CSRFRequired()
427 def settings_post(self):
432 def settings_post(self):
428 return self._settings_post()
433 return self._settings_post()
429
434
430 @LoginRequired()
435 @LoginRequired()
431 @HasRepoPermissionAnyDecorator('repository.admin')
436 @HasRepoPermissionAnyDecorator('repository.admin')
432 def new_integration(self):
437 def new_integration(self):
433 return self._new_integration()
438 return self._new_integration()
434
439
435
440
436 class RepoGroupIntegrationsView(IntegrationSettingsViewBase):
441 class RepoGroupIntegrationsView(IntegrationSettingsViewBase):
437 def load_default_context(self):
442 def load_default_context(self):
438 c = self._get_local_tmpl_context()
443 c = self._get_local_tmpl_context()
439 c.repo = self.repo
444 c.repo = self.repo
440 c.repo_group = self.repo_group
445 c.repo_group = self.repo_group
441 c.navlist = navigation_list(self.request)
446 c.navlist = navigation_list(self.request)
442
447
443 return c
448 return c
444
449
445 @LoginRequired()
450 @LoginRequired()
446 @HasRepoGroupPermissionAnyDecorator('group.admin')
451 @HasRepoGroupPermissionAnyDecorator('group.admin')
447 def integration_list(self):
452 def integration_list(self):
448 return self._integration_list()
453 return self._integration_list()
449
454
450 @LoginRequired()
455 @LoginRequired()
451 @HasRepoGroupPermissionAnyDecorator('group.admin')
456 @HasRepoGroupPermissionAnyDecorator('group.admin')
452 def settings_get(self):
457 def settings_get(self):
453 return self._settings_get()
458 return self._settings_get()
454
459
455 @LoginRequired()
460 @LoginRequired()
456 @HasRepoGroupPermissionAnyDecorator('group.admin')
461 @HasRepoGroupPermissionAnyDecorator('group.admin')
457 @CSRFRequired()
462 @CSRFRequired()
458 def settings_post(self):
463 def settings_post(self):
459 return self._settings_post()
464 return self._settings_post()
460
465
461 @LoginRequired()
466 @LoginRequired()
462 @HasRepoGroupPermissionAnyDecorator('group.admin')
467 @HasRepoGroupPermissionAnyDecorator('group.admin')
463 def new_integration(self):
468 def new_integration(self):
464 return self._new_integration()
469 return self._new_integration()
@@ -1,2030 +1,1942 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2019 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 Helper functions
22 Helper functions
23
23
24 Consists of functions to typically be used within templates, but also
24 Consists of functions to typically be used within templates, but also
25 available to Controllers. This module is available to both as 'h'.
25 available to Controllers. This module is available to both as 'h'.
26 """
26 """
27
27
28 import os
28 import os
29 import random
29 import random
30 import hashlib
30 import hashlib
31 import StringIO
31 import StringIO
32 import textwrap
32 import textwrap
33 import urllib
33 import urllib
34 import math
34 import math
35 import logging
35 import logging
36 import re
36 import re
37 import time
37 import time
38 import string
38 import string
39 import hashlib
39 import hashlib
40 from collections import OrderedDict
40 from collections import OrderedDict
41
41
42 import pygments
42 import pygments
43 import itertools
43 import itertools
44 import fnmatch
44 import fnmatch
45 import bleach
45 import bleach
46
46
47 from pyramid import compat
47 from pyramid import compat
48 from datetime import datetime
48 from datetime import datetime
49 from functools import partial
49 from functools import partial
50 from pygments.formatters.html import HtmlFormatter
50 from pygments.formatters.html import HtmlFormatter
51 from pygments.lexers import (
51 from pygments.lexers import (
52 get_lexer_by_name, get_lexer_for_filename, get_lexer_for_mimetype)
52 get_lexer_by_name, get_lexer_for_filename, get_lexer_for_mimetype)
53
53
54 from pyramid.threadlocal import get_current_request
54 from pyramid.threadlocal import get_current_request
55
55
56 from webhelpers2.html import literal, HTML, escape
56 from webhelpers2.html import literal, HTML, escape
57 from webhelpers2.html._autolink import _auto_link_urls
57 from webhelpers2.html._autolink import _auto_link_urls
58 from webhelpers2.html.tools import (
58 from webhelpers2.html.tools import (
59 button_to, highlight, js_obfuscate, strip_links, strip_tags)
59 button_to, highlight, js_obfuscate, strip_links, strip_tags)
60
60
61 from webhelpers2.text import (
61 from webhelpers2.text import (
62 chop_at, collapse, convert_accented_entities,
62 chop_at, collapse, convert_accented_entities,
63 convert_misc_entities, lchop, plural, rchop, remove_formatting,
63 convert_misc_entities, lchop, plural, rchop, remove_formatting,
64 replace_whitespace, urlify, truncate, wrap_paragraphs)
64 replace_whitespace, urlify, truncate, wrap_paragraphs)
65 from webhelpers2.date import time_ago_in_words
65 from webhelpers2.date import time_ago_in_words
66
66
67 from webhelpers2.html.tags import (
67 from webhelpers2.html.tags import (
68 _input, NotGiven, _make_safe_id_component as safeid,
68 _input, NotGiven, _make_safe_id_component as safeid,
69 form as insecure_form,
69 form as insecure_form,
70 auto_discovery_link, checkbox, end_form, file,
70 auto_discovery_link, checkbox, end_form, file,
71 hidden, image, javascript_link, link_to, link_to_if, link_to_unless, ol,
71 hidden, image, javascript_link, link_to, link_to_if, link_to_unless, ol,
72 select as raw_select, stylesheet_link, submit, text, password, textarea,
72 select as raw_select, stylesheet_link, submit, text, password, textarea,
73 ul, radio, Options)
73 ul, radio, Options)
74
74
75 from webhelpers2.number import format_byte_size
75 from webhelpers2.number import format_byte_size
76
76
77 from rhodecode.lib.action_parser import action_parser
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 from rhodecode.lib.ext_json import json
79 from rhodecode.lib.ext_json import json
80 from rhodecode.lib.utils import repo_name_slug, get_custom_lexer
80 from rhodecode.lib.utils import repo_name_slug, get_custom_lexer
81 from rhodecode.lib.utils2 import (
81 from rhodecode.lib.utils2 import (
82 str2bool, safe_unicode, safe_str,
82 str2bool, safe_unicode, safe_str,
83 get_commit_safe, datetime_to_time, time_to_datetime, time_to_utcdatetime,
83 get_commit_safe, datetime_to_time, time_to_datetime, time_to_utcdatetime,
84 AttributeDict, safe_int, md5, md5_safe, get_host_info)
84 AttributeDict, safe_int, md5, md5_safe, get_host_info)
85 from rhodecode.lib.markup_renderer import MarkupRenderer, relative_links
85 from rhodecode.lib.markup_renderer import MarkupRenderer, relative_links
86 from rhodecode.lib.vcs.exceptions import CommitDoesNotExistError
86 from rhodecode.lib.vcs.exceptions import CommitDoesNotExistError
87 from rhodecode.lib.vcs.backends.base import BaseChangeset, EmptyCommit
87 from rhodecode.lib.vcs.backends.base import BaseChangeset, EmptyCommit
88 from rhodecode.lib.index.search_utils import get_matching_line_offsets
88 from rhodecode.lib.index.search_utils import get_matching_line_offsets
89 from rhodecode.config.conf import DATE_FORMAT, DATETIME_FORMAT
89 from rhodecode.config.conf import DATE_FORMAT, DATETIME_FORMAT
90 from rhodecode.model.changeset_status import ChangesetStatusModel
90 from rhodecode.model.changeset_status import ChangesetStatusModel
91 from rhodecode.model.db import Permission, User, Repository
91 from rhodecode.model.db import Permission, User, Repository
92 from rhodecode.model.repo_group import RepoGroupModel
92 from rhodecode.model.repo_group import RepoGroupModel
93 from rhodecode.model.settings import IssueTrackerSettingsModel
93 from rhodecode.model.settings import IssueTrackerSettingsModel
94
94
95
95
96 log = logging.getLogger(__name__)
96 log = logging.getLogger(__name__)
97
97
98
98
99 DEFAULT_USER = User.DEFAULT_USER
99 DEFAULT_USER = User.DEFAULT_USER
100 DEFAULT_USER_EMAIL = User.DEFAULT_USER_EMAIL
100 DEFAULT_USER_EMAIL = User.DEFAULT_USER_EMAIL
101
101
102
102
103 def asset(path, ver=None, **kwargs):
103 def asset(path, ver=None, **kwargs):
104 """
104 """
105 Helper to generate a static asset file path for rhodecode assets
105 Helper to generate a static asset file path for rhodecode assets
106
106
107 eg. h.asset('images/image.png', ver='3923')
107 eg. h.asset('images/image.png', ver='3923')
108
108
109 :param path: path of asset
109 :param path: path of asset
110 :param ver: optional version query param to append as ?ver=
110 :param ver: optional version query param to append as ?ver=
111 """
111 """
112 request = get_current_request()
112 request = get_current_request()
113 query = {}
113 query = {}
114 query.update(kwargs)
114 query.update(kwargs)
115 if ver:
115 if ver:
116 query = {'ver': ver}
116 query = {'ver': ver}
117 return request.static_path(
117 return request.static_path(
118 'rhodecode:public/{}'.format(path), _query=query)
118 'rhodecode:public/{}'.format(path), _query=query)
119
119
120
120
121 default_html_escape_table = {
121 default_html_escape_table = {
122 ord('&'): u'&amp;',
122 ord('&'): u'&amp;',
123 ord('<'): u'&lt;',
123 ord('<'): u'&lt;',
124 ord('>'): u'&gt;',
124 ord('>'): u'&gt;',
125 ord('"'): u'&quot;',
125 ord('"'): u'&quot;',
126 ord("'"): u'&#39;',
126 ord("'"): u'&#39;',
127 }
127 }
128
128
129
129
130 def html_escape(text, html_escape_table=default_html_escape_table):
130 def html_escape(text, html_escape_table=default_html_escape_table):
131 """Produce entities within text."""
131 """Produce entities within text."""
132 return text.translate(html_escape_table)
132 return text.translate(html_escape_table)
133
133
134
134
135 def chop_at_smart(s, sub, inclusive=False, suffix_if_chopped=None):
135 def chop_at_smart(s, sub, inclusive=False, suffix_if_chopped=None):
136 """
136 """
137 Truncate string ``s`` at the first occurrence of ``sub``.
137 Truncate string ``s`` at the first occurrence of ``sub``.
138
138
139 If ``inclusive`` is true, truncate just after ``sub`` rather than at it.
139 If ``inclusive`` is true, truncate just after ``sub`` rather than at it.
140 """
140 """
141 suffix_if_chopped = suffix_if_chopped or ''
141 suffix_if_chopped = suffix_if_chopped or ''
142 pos = s.find(sub)
142 pos = s.find(sub)
143 if pos == -1:
143 if pos == -1:
144 return s
144 return s
145
145
146 if inclusive:
146 if inclusive:
147 pos += len(sub)
147 pos += len(sub)
148
148
149 chopped = s[:pos]
149 chopped = s[:pos]
150 left = s[pos:].strip()
150 left = s[pos:].strip()
151
151
152 if left and suffix_if_chopped:
152 if left and suffix_if_chopped:
153 chopped += suffix_if_chopped
153 chopped += suffix_if_chopped
154
154
155 return chopped
155 return chopped
156
156
157
157
158 def shorter(text, size=20, prefix=False):
158 def shorter(text, size=20, prefix=False):
159 postfix = '...'
159 postfix = '...'
160 if len(text) > size:
160 if len(text) > size:
161 if prefix:
161 if prefix:
162 # shorten in front
162 # shorten in front
163 return postfix + text[-(size - len(postfix)):]
163 return postfix + text[-(size - len(postfix)):]
164 else:
164 else:
165 return text[:size - len(postfix)] + postfix
165 return text[:size - len(postfix)] + postfix
166 return text
166 return text
167
167
168
168
169 def reset(name, value=None, id=NotGiven, type="reset", **attrs):
169 def reset(name, value=None, id=NotGiven, type="reset", **attrs):
170 """
170 """
171 Reset button
171 Reset button
172 """
172 """
173 return _input(type, name, value, id, attrs)
173 return _input(type, name, value, id, attrs)
174
174
175
175
176 def select(name, selected_values, options, id=NotGiven, **attrs):
176 def select(name, selected_values, options, id=NotGiven, **attrs):
177
177
178 if isinstance(options, (list, tuple)):
178 if isinstance(options, (list, tuple)):
179 options_iter = options
179 options_iter = options
180 # Handle old value,label lists ... where value also can be value,label lists
180 # Handle old value,label lists ... where value also can be value,label lists
181 options = Options()
181 options = Options()
182 for opt in options_iter:
182 for opt in options_iter:
183 if isinstance(opt, tuple) and len(opt) == 2:
183 if isinstance(opt, tuple) and len(opt) == 2:
184 value, label = opt
184 value, label = opt
185 elif isinstance(opt, basestring):
185 elif isinstance(opt, basestring):
186 value = label = opt
186 value = label = opt
187 else:
187 else:
188 raise ValueError('invalid select option type %r' % type(opt))
188 raise ValueError('invalid select option type %r' % type(opt))
189
189
190 if isinstance(value, (list, tuple)):
190 if isinstance(value, (list, tuple)):
191 option_group = options.add_optgroup(label)
191 option_group = options.add_optgroup(label)
192 for opt2 in value:
192 for opt2 in value:
193 if isinstance(opt2, tuple) and len(opt2) == 2:
193 if isinstance(opt2, tuple) and len(opt2) == 2:
194 group_value, group_label = opt
194 group_value, group_label = opt
195 elif isinstance(opt2, basestring):
195 elif isinstance(opt2, basestring):
196 group_value = group_label = opt2
196 group_value = group_label = opt2
197 else:
197 else:
198 raise ValueError('invalid select option type %r' % type(opt2))
198 raise ValueError('invalid select option type %r' % type(opt2))
199
199
200 option_group.add_option(group_label, group_value)
200 option_group.add_option(group_label, group_value)
201 else:
201 else:
202 options.add_option(label, value)
202 options.add_option(label, value)
203
203
204 return raw_select(name, selected_values, options, id=id, **attrs)
204 return raw_select(name, selected_values, options, id=id, **attrs)
205
205
206
206
207 def branding(name, length=40):
207 def branding(name, length=40):
208 return truncate(name, length, indicator="")
208 return truncate(name, length, indicator="")
209
209
210
210
211 def FID(raw_id, path):
211 def FID(raw_id, path):
212 """
212 """
213 Creates a unique ID for filenode based on it's hash of path and commit
213 Creates a unique ID for filenode based on it's hash of path and commit
214 it's safe to use in urls
214 it's safe to use in urls
215
215
216 :param raw_id:
216 :param raw_id:
217 :param path:
217 :param path:
218 """
218 """
219
219
220 return 'c-%s-%s' % (short_id(raw_id), md5_safe(path)[:12])
220 return 'c-%s-%s' % (short_id(raw_id), md5_safe(path)[:12])
221
221
222
222
223 class _GetError(object):
223 class _GetError(object):
224 """Get error from form_errors, and represent it as span wrapped error
224 """Get error from form_errors, and represent it as span wrapped error
225 message
225 message
226
226
227 :param field_name: field to fetch errors for
227 :param field_name: field to fetch errors for
228 :param form_errors: form errors dict
228 :param form_errors: form errors dict
229 """
229 """
230
230
231 def __call__(self, field_name, form_errors):
231 def __call__(self, field_name, form_errors):
232 tmpl = """<span class="error_msg">%s</span>"""
232 tmpl = """<span class="error_msg">%s</span>"""
233 if form_errors and field_name in form_errors:
233 if form_errors and field_name in form_errors:
234 return literal(tmpl % form_errors.get(field_name))
234 return literal(tmpl % form_errors.get(field_name))
235
235
236
236
237 get_error = _GetError()
237 get_error = _GetError()
238
238
239
239
240 class _ToolTip(object):
240 class _ToolTip(object):
241
241
242 def __call__(self, tooltip_title, trim_at=50):
242 def __call__(self, tooltip_title, trim_at=50):
243 """
243 """
244 Special function just to wrap our text into nice formatted
244 Special function just to wrap our text into nice formatted
245 autowrapped text
245 autowrapped text
246
246
247 :param tooltip_title:
247 :param tooltip_title:
248 """
248 """
249 tooltip_title = escape(tooltip_title)
249 tooltip_title = escape(tooltip_title)
250 tooltip_title = tooltip_title.replace('<', '&lt;').replace('>', '&gt;')
250 tooltip_title = tooltip_title.replace('<', '&lt;').replace('>', '&gt;')
251 return tooltip_title
251 return tooltip_title
252
252
253
253
254 tooltip = _ToolTip()
254 tooltip = _ToolTip()
255
255
256 files_icon = u'<i class="file-breadcrumb-copy tooltip icon-clipboard clipboard-action" data-clipboard-text="{}" title="Copy the full path"></i>'
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 def files_breadcrumbs(repo_name, commit_id, file_path, at_ref=None, limit_items=False, linkify_last_item=False):
259 def files_breadcrumbs(repo_name, commit_id, file_path, at_ref=None, limit_items=False, linkify_last_item=False):
260 if isinstance(file_path, str):
260 if isinstance(file_path, str):
261 file_path = safe_unicode(file_path)
261 file_path = safe_unicode(file_path)
262
262
263 route_qry = {'at': at_ref} if at_ref else None
263 route_qry = {'at': at_ref} if at_ref else None
264
264
265 # first segment is a `..` link to repo files
265 # first segment is a `..` link to repo files
266 root_name = literal(u'<i class="icon-home"></i>')
266 root_name = literal(u'<i class="icon-home"></i>')
267 url_segments = [
267 url_segments = [
268 link_to(
268 link_to(
269 root_name,
269 root_name,
270 route_path(
270 route_path(
271 'repo_files',
271 'repo_files',
272 repo_name=repo_name,
272 repo_name=repo_name,
273 commit_id=commit_id,
273 commit_id=commit_id,
274 f_path='',
274 f_path='',
275 _query=route_qry),
275 _query=route_qry),
276 )]
276 )]
277
277
278 path_segments = file_path.split('/')
278 path_segments = file_path.split('/')
279 last_cnt = len(path_segments) - 1
279 last_cnt = len(path_segments) - 1
280 for cnt, segment in enumerate(path_segments):
280 for cnt, segment in enumerate(path_segments):
281 if not segment:
281 if not segment:
282 continue
282 continue
283 segment_html = escape(segment)
283 segment_html = escape(segment)
284
284
285 last_item = cnt == last_cnt
285 last_item = cnt == last_cnt
286
286
287 if last_item and linkify_last_item is False:
287 if last_item and linkify_last_item is False:
288 # plain version
288 # plain version
289 url_segments.append(segment_html)
289 url_segments.append(segment_html)
290 else:
290 else:
291 url_segments.append(
291 url_segments.append(
292 link_to(
292 link_to(
293 segment_html,
293 segment_html,
294 route_path(
294 route_path(
295 'repo_files',
295 'repo_files',
296 repo_name=repo_name,
296 repo_name=repo_name,
297 commit_id=commit_id,
297 commit_id=commit_id,
298 f_path='/'.join(path_segments[:cnt + 1]),
298 f_path='/'.join(path_segments[:cnt + 1]),
299 _query=route_qry),
299 _query=route_qry),
300 ))
300 ))
301
301
302 limited_url_segments = url_segments[:1] + ['...'] + url_segments[-5:]
302 limited_url_segments = url_segments[:1] + ['...'] + url_segments[-5:]
303 if limit_items and len(limited_url_segments) < len(url_segments):
303 if limit_items and len(limited_url_segments) < len(url_segments):
304 url_segments = limited_url_segments
304 url_segments = limited_url_segments
305
305
306 full_path = file_path
306 full_path = file_path
307 icon = files_icon.format(escape(full_path))
307 icon = files_icon.format(escape(full_path))
308 if file_path == '':
308 if file_path == '':
309 return root_name
309 return root_name
310 else:
310 else:
311 return literal(' / '.join(url_segments) + icon)
311 return literal(' / '.join(url_segments) + icon)
312
312
313
313
314 def files_url_data(request):
314 def files_url_data(request):
315 matchdict = request.matchdict
315 matchdict = request.matchdict
316
316
317 if 'f_path' not in matchdict:
317 if 'f_path' not in matchdict:
318 matchdict['f_path'] = ''
318 matchdict['f_path'] = ''
319
319
320 if 'commit_id' not in matchdict:
320 if 'commit_id' not in matchdict:
321 matchdict['commit_id'] = 'tip'
321 matchdict['commit_id'] = 'tip'
322
322
323 return json.dumps(matchdict)
323 return json.dumps(matchdict)
324
324
325
325
326 def code_highlight(code, lexer, formatter, use_hl_filter=False):
326 def code_highlight(code, lexer, formatter, use_hl_filter=False):
327 """
327 """
328 Lex ``code`` with ``lexer`` and format it with the formatter ``formatter``.
328 Lex ``code`` with ``lexer`` and format it with the formatter ``formatter``.
329
329
330 If ``outfile`` is given and a valid file object (an object
330 If ``outfile`` is given and a valid file object (an object
331 with a ``write`` method), the result will be written to it, otherwise
331 with a ``write`` method), the result will be written to it, otherwise
332 it is returned as a string.
332 it is returned as a string.
333 """
333 """
334 if use_hl_filter:
334 if use_hl_filter:
335 # add HL filter
335 # add HL filter
336 from rhodecode.lib.index import search_utils
336 from rhodecode.lib.index import search_utils
337 lexer.add_filter(search_utils.ElasticSearchHLFilter())
337 lexer.add_filter(search_utils.ElasticSearchHLFilter())
338 return pygments.format(pygments.lex(code, lexer), formatter)
338 return pygments.format(pygments.lex(code, lexer), formatter)
339
339
340
340
341 class CodeHtmlFormatter(HtmlFormatter):
341 class CodeHtmlFormatter(HtmlFormatter):
342 """
342 """
343 My code Html Formatter for source codes
343 My code Html Formatter for source codes
344 """
344 """
345
345
346 def wrap(self, source, outfile):
346 def wrap(self, source, outfile):
347 return self._wrap_div(self._wrap_pre(self._wrap_code(source)))
347 return self._wrap_div(self._wrap_pre(self._wrap_code(source)))
348
348
349 def _wrap_code(self, source):
349 def _wrap_code(self, source):
350 for cnt, it in enumerate(source):
350 for cnt, it in enumerate(source):
351 i, t = it
351 i, t = it
352 t = '<div id="L%s">%s</div>' % (cnt + 1, t)
352 t = '<div id="L%s">%s</div>' % (cnt + 1, t)
353 yield i, t
353 yield i, t
354
354
355 def _wrap_tablelinenos(self, inner):
355 def _wrap_tablelinenos(self, inner):
356 dummyoutfile = StringIO.StringIO()
356 dummyoutfile = StringIO.StringIO()
357 lncount = 0
357 lncount = 0
358 for t, line in inner:
358 for t, line in inner:
359 if t:
359 if t:
360 lncount += 1
360 lncount += 1
361 dummyoutfile.write(line)
361 dummyoutfile.write(line)
362
362
363 fl = self.linenostart
363 fl = self.linenostart
364 mw = len(str(lncount + fl - 1))
364 mw = len(str(lncount + fl - 1))
365 sp = self.linenospecial
365 sp = self.linenospecial
366 st = self.linenostep
366 st = self.linenostep
367 la = self.lineanchors
367 la = self.lineanchors
368 aln = self.anchorlinenos
368 aln = self.anchorlinenos
369 nocls = self.noclasses
369 nocls = self.noclasses
370 if sp:
370 if sp:
371 lines = []
371 lines = []
372
372
373 for i in range(fl, fl + lncount):
373 for i in range(fl, fl + lncount):
374 if i % st == 0:
374 if i % st == 0:
375 if i % sp == 0:
375 if i % sp == 0:
376 if aln:
376 if aln:
377 lines.append('<a href="#%s%d" class="special">%*d</a>' %
377 lines.append('<a href="#%s%d" class="special">%*d</a>' %
378 (la, i, mw, i))
378 (la, i, mw, i))
379 else:
379 else:
380 lines.append('<span class="special">%*d</span>' % (mw, i))
380 lines.append('<span class="special">%*d</span>' % (mw, i))
381 else:
381 else:
382 if aln:
382 if aln:
383 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
383 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
384 else:
384 else:
385 lines.append('%*d' % (mw, i))
385 lines.append('%*d' % (mw, i))
386 else:
386 else:
387 lines.append('')
387 lines.append('')
388 ls = '\n'.join(lines)
388 ls = '\n'.join(lines)
389 else:
389 else:
390 lines = []
390 lines = []
391 for i in range(fl, fl + lncount):
391 for i in range(fl, fl + lncount):
392 if i % st == 0:
392 if i % st == 0:
393 if aln:
393 if aln:
394 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
394 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
395 else:
395 else:
396 lines.append('%*d' % (mw, i))
396 lines.append('%*d' % (mw, i))
397 else:
397 else:
398 lines.append('')
398 lines.append('')
399 ls = '\n'.join(lines)
399 ls = '\n'.join(lines)
400
400
401 # in case you wonder about the seemingly redundant <div> here: since the
401 # in case you wonder about the seemingly redundant <div> here: since the
402 # content in the other cell also is wrapped in a div, some browsers in
402 # content in the other cell also is wrapped in a div, some browsers in
403 # some configurations seem to mess up the formatting...
403 # some configurations seem to mess up the formatting...
404 if nocls:
404 if nocls:
405 yield 0, ('<table class="%stable">' % self.cssclass +
405 yield 0, ('<table class="%stable">' % self.cssclass +
406 '<tr><td><div class="linenodiv" '
406 '<tr><td><div class="linenodiv" '
407 'style="background-color: #f0f0f0; padding-right: 10px">'
407 'style="background-color: #f0f0f0; padding-right: 10px">'
408 '<pre style="line-height: 125%">' +
408 '<pre style="line-height: 125%">' +
409 ls + '</pre></div></td><td id="hlcode" class="code">')
409 ls + '</pre></div></td><td id="hlcode" class="code">')
410 else:
410 else:
411 yield 0, ('<table class="%stable">' % self.cssclass +
411 yield 0, ('<table class="%stable">' % self.cssclass +
412 '<tr><td class="linenos"><div class="linenodiv"><pre>' +
412 '<tr><td class="linenos"><div class="linenodiv"><pre>' +
413 ls + '</pre></div></td><td id="hlcode" class="code">')
413 ls + '</pre></div></td><td id="hlcode" class="code">')
414 yield 0, dummyoutfile.getvalue()
414 yield 0, dummyoutfile.getvalue()
415 yield 0, '</td></tr></table>'
415 yield 0, '</td></tr></table>'
416
416
417
417
418 class SearchContentCodeHtmlFormatter(CodeHtmlFormatter):
418 class SearchContentCodeHtmlFormatter(CodeHtmlFormatter):
419 def __init__(self, **kw):
419 def __init__(self, **kw):
420 # only show these line numbers if set
420 # only show these line numbers if set
421 self.only_lines = kw.pop('only_line_numbers', [])
421 self.only_lines = kw.pop('only_line_numbers', [])
422 self.query_terms = kw.pop('query_terms', [])
422 self.query_terms = kw.pop('query_terms', [])
423 self.max_lines = kw.pop('max_lines', 5)
423 self.max_lines = kw.pop('max_lines', 5)
424 self.line_context = kw.pop('line_context', 3)
424 self.line_context = kw.pop('line_context', 3)
425 self.url = kw.pop('url', None)
425 self.url = kw.pop('url', None)
426
426
427 super(CodeHtmlFormatter, self).__init__(**kw)
427 super(CodeHtmlFormatter, self).__init__(**kw)
428
428
429 def _wrap_code(self, source):
429 def _wrap_code(self, source):
430 for cnt, it in enumerate(source):
430 for cnt, it in enumerate(source):
431 i, t = it
431 i, t = it
432 t = '<pre>%s</pre>' % t
432 t = '<pre>%s</pre>' % t
433 yield i, t
433 yield i, t
434
434
435 def _wrap_tablelinenos(self, inner):
435 def _wrap_tablelinenos(self, inner):
436 yield 0, '<table class="code-highlight %stable">' % self.cssclass
436 yield 0, '<table class="code-highlight %stable">' % self.cssclass
437
437
438 last_shown_line_number = 0
438 last_shown_line_number = 0
439 current_line_number = 1
439 current_line_number = 1
440
440
441 for t, line in inner:
441 for t, line in inner:
442 if not t:
442 if not t:
443 yield t, line
443 yield t, line
444 continue
444 continue
445
445
446 if current_line_number in self.only_lines:
446 if current_line_number in self.only_lines:
447 if last_shown_line_number + 1 != current_line_number:
447 if last_shown_line_number + 1 != current_line_number:
448 yield 0, '<tr>'
448 yield 0, '<tr>'
449 yield 0, '<td class="line">...</td>'
449 yield 0, '<td class="line">...</td>'
450 yield 0, '<td id="hlcode" class="code"></td>'
450 yield 0, '<td id="hlcode" class="code"></td>'
451 yield 0, '</tr>'
451 yield 0, '</tr>'
452
452
453 yield 0, '<tr>'
453 yield 0, '<tr>'
454 if self.url:
454 if self.url:
455 yield 0, '<td class="line"><a href="%s#L%i">%i</a></td>' % (
455 yield 0, '<td class="line"><a href="%s#L%i">%i</a></td>' % (
456 self.url, current_line_number, current_line_number)
456 self.url, current_line_number, current_line_number)
457 else:
457 else:
458 yield 0, '<td class="line"><a href="">%i</a></td>' % (
458 yield 0, '<td class="line"><a href="">%i</a></td>' % (
459 current_line_number)
459 current_line_number)
460 yield 0, '<td id="hlcode" class="code">' + line + '</td>'
460 yield 0, '<td id="hlcode" class="code">' + line + '</td>'
461 yield 0, '</tr>'
461 yield 0, '</tr>'
462
462
463 last_shown_line_number = current_line_number
463 last_shown_line_number = current_line_number
464
464
465 current_line_number += 1
465 current_line_number += 1
466
466
467 yield 0, '</table>'
467 yield 0, '</table>'
468
468
469
469
470 def hsv_to_rgb(h, s, v):
470 def hsv_to_rgb(h, s, v):
471 """ Convert hsv color values to rgb """
471 """ Convert hsv color values to rgb """
472
472
473 if s == 0.0:
473 if s == 0.0:
474 return v, v, v
474 return v, v, v
475 i = int(h * 6.0) # XXX assume int() truncates!
475 i = int(h * 6.0) # XXX assume int() truncates!
476 f = (h * 6.0) - i
476 f = (h * 6.0) - i
477 p = v * (1.0 - s)
477 p = v * (1.0 - s)
478 q = v * (1.0 - s * f)
478 q = v * (1.0 - s * f)
479 t = v * (1.0 - s * (1.0 - f))
479 t = v * (1.0 - s * (1.0 - f))
480 i = i % 6
480 i = i % 6
481 if i == 0:
481 if i == 0:
482 return v, t, p
482 return v, t, p
483 if i == 1:
483 if i == 1:
484 return q, v, p
484 return q, v, p
485 if i == 2:
485 if i == 2:
486 return p, v, t
486 return p, v, t
487 if i == 3:
487 if i == 3:
488 return p, q, v
488 return p, q, v
489 if i == 4:
489 if i == 4:
490 return t, p, v
490 return t, p, v
491 if i == 5:
491 if i == 5:
492 return v, p, q
492 return v, p, q
493
493
494
494
495 def unique_color_generator(n=10000, saturation=0.10, lightness=0.95):
495 def unique_color_generator(n=10000, saturation=0.10, lightness=0.95):
496 """
496 """
497 Generator for getting n of evenly distributed colors using
497 Generator for getting n of evenly distributed colors using
498 hsv color and golden ratio. It always return same order of colors
498 hsv color and golden ratio. It always return same order of colors
499
499
500 :param n: number of colors to generate
500 :param n: number of colors to generate
501 :param saturation: saturation of returned colors
501 :param saturation: saturation of returned colors
502 :param lightness: lightness of returned colors
502 :param lightness: lightness of returned colors
503 :returns: RGB tuple
503 :returns: RGB tuple
504 """
504 """
505
505
506 golden_ratio = 0.618033988749895
506 golden_ratio = 0.618033988749895
507 h = 0.22717784590367374
507 h = 0.22717784590367374
508
508
509 for _ in xrange(n):
509 for _ in xrange(n):
510 h += golden_ratio
510 h += golden_ratio
511 h %= 1
511 h %= 1
512 HSV_tuple = [h, saturation, lightness]
512 HSV_tuple = [h, saturation, lightness]
513 RGB_tuple = hsv_to_rgb(*HSV_tuple)
513 RGB_tuple = hsv_to_rgb(*HSV_tuple)
514 yield map(lambda x: str(int(x * 256)), RGB_tuple)
514 yield map(lambda x: str(int(x * 256)), RGB_tuple)
515
515
516
516
517 def color_hasher(n=10000, saturation=0.10, lightness=0.95):
517 def color_hasher(n=10000, saturation=0.10, lightness=0.95):
518 """
518 """
519 Returns a function which when called with an argument returns a unique
519 Returns a function which when called with an argument returns a unique
520 color for that argument, eg.
520 color for that argument, eg.
521
521
522 :param n: number of colors to generate
522 :param n: number of colors to generate
523 :param saturation: saturation of returned colors
523 :param saturation: saturation of returned colors
524 :param lightness: lightness of returned colors
524 :param lightness: lightness of returned colors
525 :returns: css RGB string
525 :returns: css RGB string
526
526
527 >>> color_hash = color_hasher()
527 >>> color_hash = color_hasher()
528 >>> color_hash('hello')
528 >>> color_hash('hello')
529 'rgb(34, 12, 59)'
529 'rgb(34, 12, 59)'
530 >>> color_hash('hello')
530 >>> color_hash('hello')
531 'rgb(34, 12, 59)'
531 'rgb(34, 12, 59)'
532 >>> color_hash('other')
532 >>> color_hash('other')
533 'rgb(90, 224, 159)'
533 'rgb(90, 224, 159)'
534 """
534 """
535
535
536 color_dict = {}
536 color_dict = {}
537 cgenerator = unique_color_generator(
537 cgenerator = unique_color_generator(
538 saturation=saturation, lightness=lightness)
538 saturation=saturation, lightness=lightness)
539
539
540 def get_color_string(thing):
540 def get_color_string(thing):
541 if thing in color_dict:
541 if thing in color_dict:
542 col = color_dict[thing]
542 col = color_dict[thing]
543 else:
543 else:
544 col = color_dict[thing] = cgenerator.next()
544 col = color_dict[thing] = cgenerator.next()
545 return "rgb(%s)" % (', '.join(col))
545 return "rgb(%s)" % (', '.join(col))
546
546
547 return get_color_string
547 return get_color_string
548
548
549
549
550 def get_lexer_safe(mimetype=None, filepath=None):
550 def get_lexer_safe(mimetype=None, filepath=None):
551 """
551 """
552 Tries to return a relevant pygments lexer using mimetype/filepath name,
552 Tries to return a relevant pygments lexer using mimetype/filepath name,
553 defaulting to plain text if none could be found
553 defaulting to plain text if none could be found
554 """
554 """
555 lexer = None
555 lexer = None
556 try:
556 try:
557 if mimetype:
557 if mimetype:
558 lexer = get_lexer_for_mimetype(mimetype)
558 lexer = get_lexer_for_mimetype(mimetype)
559 if not lexer:
559 if not lexer:
560 lexer = get_lexer_for_filename(filepath)
560 lexer = get_lexer_for_filename(filepath)
561 except pygments.util.ClassNotFound:
561 except pygments.util.ClassNotFound:
562 pass
562 pass
563
563
564 if not lexer:
564 if not lexer:
565 lexer = get_lexer_by_name('text')
565 lexer = get_lexer_by_name('text')
566
566
567 return lexer
567 return lexer
568
568
569
569
570 def get_lexer_for_filenode(filenode):
570 def get_lexer_for_filenode(filenode):
571 lexer = get_custom_lexer(filenode.extension) or filenode.lexer
571 lexer = get_custom_lexer(filenode.extension) or filenode.lexer
572 return lexer
572 return lexer
573
573
574
574
575 def pygmentize(filenode, **kwargs):
575 def pygmentize(filenode, **kwargs):
576 """
576 """
577 pygmentize function using pygments
577 pygmentize function using pygments
578
578
579 :param filenode:
579 :param filenode:
580 """
580 """
581 lexer = get_lexer_for_filenode(filenode)
581 lexer = get_lexer_for_filenode(filenode)
582 return literal(code_highlight(filenode.content, lexer,
582 return literal(code_highlight(filenode.content, lexer,
583 CodeHtmlFormatter(**kwargs)))
583 CodeHtmlFormatter(**kwargs)))
584
584
585
585
586 def is_following_repo(repo_name, user_id):
586 def is_following_repo(repo_name, user_id):
587 from rhodecode.model.scm import ScmModel
587 from rhodecode.model.scm import ScmModel
588 return ScmModel().is_following_repo(repo_name, user_id)
588 return ScmModel().is_following_repo(repo_name, user_id)
589
589
590
590
591 class _Message(object):
591 class _Message(object):
592 """A message returned by ``Flash.pop_messages()``.
592 """A message returned by ``Flash.pop_messages()``.
593
593
594 Converting the message to a string returns the message text. Instances
594 Converting the message to a string returns the message text. Instances
595 also have the following attributes:
595 also have the following attributes:
596
596
597 * ``message``: the message text.
597 * ``message``: the message text.
598 * ``category``: the category specified when the message was created.
598 * ``category``: the category specified when the message was created.
599 """
599 """
600
600
601 def __init__(self, category, message):
601 def __init__(self, category, message):
602 self.category = category
602 self.category = category
603 self.message = message
603 self.message = message
604
604
605 def __str__(self):
605 def __str__(self):
606 return self.message
606 return self.message
607
607
608 __unicode__ = __str__
608 __unicode__ = __str__
609
609
610 def __html__(self):
610 def __html__(self):
611 return escape(safe_unicode(self.message))
611 return escape(safe_unicode(self.message))
612
612
613
613
614 class Flash(object):
614 class Flash(object):
615 # List of allowed categories. If None, allow any category.
615 # List of allowed categories. If None, allow any category.
616 categories = ["warning", "notice", "error", "success"]
616 categories = ["warning", "notice", "error", "success"]
617
617
618 # Default category if none is specified.
618 # Default category if none is specified.
619 default_category = "notice"
619 default_category = "notice"
620
620
621 def __init__(self, session_key="flash", categories=None,
621 def __init__(self, session_key="flash", categories=None,
622 default_category=None):
622 default_category=None):
623 """
623 """
624 Instantiate a ``Flash`` object.
624 Instantiate a ``Flash`` object.
625
625
626 ``session_key`` is the key to save the messages under in the user's
626 ``session_key`` is the key to save the messages under in the user's
627 session.
627 session.
628
628
629 ``categories`` is an optional list which overrides the default list
629 ``categories`` is an optional list which overrides the default list
630 of categories.
630 of categories.
631
631
632 ``default_category`` overrides the default category used for messages
632 ``default_category`` overrides the default category used for messages
633 when none is specified.
633 when none is specified.
634 """
634 """
635 self.session_key = session_key
635 self.session_key = session_key
636 if categories is not None:
636 if categories is not None:
637 self.categories = categories
637 self.categories = categories
638 if default_category is not None:
638 if default_category is not None:
639 self.default_category = default_category
639 self.default_category = default_category
640 if self.categories and self.default_category not in self.categories:
640 if self.categories and self.default_category not in self.categories:
641 raise ValueError(
641 raise ValueError(
642 "unrecognized default category %r" % (self.default_category,))
642 "unrecognized default category %r" % (self.default_category,))
643
643
644 def pop_messages(self, session=None, request=None):
644 def pop_messages(self, session=None, request=None):
645 """
645 """
646 Return all accumulated messages and delete them from the session.
646 Return all accumulated messages and delete them from the session.
647
647
648 The return value is a list of ``Message`` objects.
648 The return value is a list of ``Message`` objects.
649 """
649 """
650 messages = []
650 messages = []
651
651
652 if not session:
652 if not session:
653 if not request:
653 if not request:
654 request = get_current_request()
654 request = get_current_request()
655 session = request.session
655 session = request.session
656
656
657 # Pop the 'old' pylons flash messages. They are tuples of the form
657 # Pop the 'old' pylons flash messages. They are tuples of the form
658 # (category, message)
658 # (category, message)
659 for cat, msg in session.pop(self.session_key, []):
659 for cat, msg in session.pop(self.session_key, []):
660 messages.append(_Message(cat, msg))
660 messages.append(_Message(cat, msg))
661
661
662 # Pop the 'new' pyramid flash messages for each category as list
662 # Pop the 'new' pyramid flash messages for each category as list
663 # of strings.
663 # of strings.
664 for cat in self.categories:
664 for cat in self.categories:
665 for msg in session.pop_flash(queue=cat):
665 for msg in session.pop_flash(queue=cat):
666 messages.append(_Message(cat, msg))
666 messages.append(_Message(cat, msg))
667 # Map messages from the default queue to the 'notice' category.
667 # Map messages from the default queue to the 'notice' category.
668 for msg in session.pop_flash():
668 for msg in session.pop_flash():
669 messages.append(_Message('notice', msg))
669 messages.append(_Message('notice', msg))
670
670
671 session.save()
671 session.save()
672 return messages
672 return messages
673
673
674 def json_alerts(self, session=None, request=None):
674 def json_alerts(self, session=None, request=None):
675 payloads = []
675 payloads = []
676 messages = flash.pop_messages(session=session, request=request)
676 messages = flash.pop_messages(session=session, request=request)
677 if messages:
677 if messages:
678 for message in messages:
678 for message in messages:
679 subdata = {}
679 subdata = {}
680 if hasattr(message.message, 'rsplit'):
680 if hasattr(message.message, 'rsplit'):
681 flash_data = message.message.rsplit('|DELIM|', 1)
681 flash_data = message.message.rsplit('|DELIM|', 1)
682 org_message = flash_data[0]
682 org_message = flash_data[0]
683 if len(flash_data) > 1:
683 if len(flash_data) > 1:
684 subdata = json.loads(flash_data[1])
684 subdata = json.loads(flash_data[1])
685 else:
685 else:
686 org_message = message.message
686 org_message = message.message
687 payloads.append({
687 payloads.append({
688 'message': {
688 'message': {
689 'message': u'{}'.format(org_message),
689 'message': u'{}'.format(org_message),
690 'level': message.category,
690 'level': message.category,
691 'force': True,
691 'force': True,
692 'subdata': subdata
692 'subdata': subdata
693 }
693 }
694 })
694 })
695 return json.dumps(payloads)
695 return json.dumps(payloads)
696
696
697 def __call__(self, message, category=None, ignore_duplicate=True,
697 def __call__(self, message, category=None, ignore_duplicate=True,
698 session=None, request=None):
698 session=None, request=None):
699
699
700 if not session:
700 if not session:
701 if not request:
701 if not request:
702 request = get_current_request()
702 request = get_current_request()
703 session = request.session
703 session = request.session
704
704
705 session.flash(
705 session.flash(
706 message, queue=category, allow_duplicate=not ignore_duplicate)
706 message, queue=category, allow_duplicate=not ignore_duplicate)
707
707
708
708
709 flash = Flash()
709 flash = Flash()
710
710
711 #==============================================================================
711 #==============================================================================
712 # SCM FILTERS available via h.
712 # SCM FILTERS available via h.
713 #==============================================================================
713 #==============================================================================
714 from rhodecode.lib.vcs.utils import author_name, author_email
714 from rhodecode.lib.vcs.utils import author_name, author_email
715 from rhodecode.lib.utils2 import credentials_filter, age, age_from_seconds
715 from rhodecode.lib.utils2 import credentials_filter, age, age_from_seconds
716 from rhodecode.model.db import User, ChangesetStatus
716 from rhodecode.model.db import User, ChangesetStatus
717
717
718 capitalize = lambda x: x.capitalize()
718 capitalize = lambda x: x.capitalize()
719 email = author_email
719 email = author_email
720 short_id = lambda x: x[:12]
720 short_id = lambda x: x[:12]
721 hide_credentials = lambda x: ''.join(credentials_filter(x))
721 hide_credentials = lambda x: ''.join(credentials_filter(x))
722
722
723
723
724 import pytz
724 import pytz
725 import tzlocal
725 import tzlocal
726 local_timezone = tzlocal.get_localzone()
726 local_timezone = tzlocal.get_localzone()
727
727
728
728
729 def age_component(datetime_iso, value=None, time_is_local=False):
729 def age_component(datetime_iso, value=None, time_is_local=False):
730 title = value or format_date(datetime_iso)
730 title = value or format_date(datetime_iso)
731 tzinfo = '+00:00'
731 tzinfo = '+00:00'
732
732
733 # detect if we have a timezone info, otherwise, add it
733 # detect if we have a timezone info, otherwise, add it
734 if time_is_local and isinstance(datetime_iso, datetime) and not datetime_iso.tzinfo:
734 if time_is_local and isinstance(datetime_iso, datetime) and not datetime_iso.tzinfo:
735 force_timezone = os.environ.get('RC_TIMEZONE', '')
735 force_timezone = os.environ.get('RC_TIMEZONE', '')
736 if force_timezone:
736 if force_timezone:
737 force_timezone = pytz.timezone(force_timezone)
737 force_timezone = pytz.timezone(force_timezone)
738 timezone = force_timezone or local_timezone
738 timezone = force_timezone or local_timezone
739 offset = timezone.localize(datetime_iso).strftime('%z')
739 offset = timezone.localize(datetime_iso).strftime('%z')
740 tzinfo = '{}:{}'.format(offset[:-2], offset[-2:])
740 tzinfo = '{}:{}'.format(offset[:-2], offset[-2:])
741
741
742 return literal(
742 return literal(
743 '<time class="timeago tooltip" '
743 '<time class="timeago tooltip" '
744 'title="{1}{2}" datetime="{0}{2}">{1}</time>'.format(
744 'title="{1}{2}" datetime="{0}{2}">{1}</time>'.format(
745 datetime_iso, title, tzinfo))
745 datetime_iso, title, tzinfo))
746
746
747
747
748 def _shorten_commit_id(commit_id, commit_len=None):
748 def _shorten_commit_id(commit_id, commit_len=None):
749 if commit_len is None:
749 if commit_len is None:
750 request = get_current_request()
750 request = get_current_request()
751 commit_len = request.call_context.visual.show_sha_length
751 commit_len = request.call_context.visual.show_sha_length
752 return commit_id[:commit_len]
752 return commit_id[:commit_len]
753
753
754
754
755 def show_id(commit, show_idx=None, commit_len=None):
755 def show_id(commit, show_idx=None, commit_len=None):
756 """
756 """
757 Configurable function that shows ID
757 Configurable function that shows ID
758 by default it's r123:fffeeefffeee
758 by default it's r123:fffeeefffeee
759
759
760 :param commit: commit instance
760 :param commit: commit instance
761 """
761 """
762 if show_idx is None:
762 if show_idx is None:
763 request = get_current_request()
763 request = get_current_request()
764 show_idx = request.call_context.visual.show_revision_number
764 show_idx = request.call_context.visual.show_revision_number
765
765
766 raw_id = _shorten_commit_id(commit.raw_id, commit_len=commit_len)
766 raw_id = _shorten_commit_id(commit.raw_id, commit_len=commit_len)
767 if show_idx:
767 if show_idx:
768 return 'r%s:%s' % (commit.idx, raw_id)
768 return 'r%s:%s' % (commit.idx, raw_id)
769 else:
769 else:
770 return '%s' % (raw_id, )
770 return '%s' % (raw_id, )
771
771
772
772
773 def format_date(date):
773 def format_date(date):
774 """
774 """
775 use a standardized formatting for dates used in RhodeCode
775 use a standardized formatting for dates used in RhodeCode
776
776
777 :param date: date/datetime object
777 :param date: date/datetime object
778 :return: formatted date
778 :return: formatted date
779 """
779 """
780
780
781 if date:
781 if date:
782 _fmt = "%a, %d %b %Y %H:%M:%S"
782 _fmt = "%a, %d %b %Y %H:%M:%S"
783 return safe_unicode(date.strftime(_fmt))
783 return safe_unicode(date.strftime(_fmt))
784
784
785 return u""
785 return u""
786
786
787
787
788 class _RepoChecker(object):
788 class _RepoChecker(object):
789
789
790 def __init__(self, backend_alias):
790 def __init__(self, backend_alias):
791 self._backend_alias = backend_alias
791 self._backend_alias = backend_alias
792
792
793 def __call__(self, repository):
793 def __call__(self, repository):
794 if hasattr(repository, 'alias'):
794 if hasattr(repository, 'alias'):
795 _type = repository.alias
795 _type = repository.alias
796 elif hasattr(repository, 'repo_type'):
796 elif hasattr(repository, 'repo_type'):
797 _type = repository.repo_type
797 _type = repository.repo_type
798 else:
798 else:
799 _type = repository
799 _type = repository
800 return _type == self._backend_alias
800 return _type == self._backend_alias
801
801
802
802
803 is_git = _RepoChecker('git')
803 is_git = _RepoChecker('git')
804 is_hg = _RepoChecker('hg')
804 is_hg = _RepoChecker('hg')
805 is_svn = _RepoChecker('svn')
805 is_svn = _RepoChecker('svn')
806
806
807
807
808 def get_repo_type_by_name(repo_name):
808 def get_repo_type_by_name(repo_name):
809 repo = Repository.get_by_repo_name(repo_name)
809 repo = Repository.get_by_repo_name(repo_name)
810 if repo:
810 if repo:
811 return repo.repo_type
811 return repo.repo_type
812
812
813
813
814 def is_svn_without_proxy(repository):
814 def is_svn_without_proxy(repository):
815 if is_svn(repository):
815 if is_svn(repository):
816 from rhodecode.model.settings import VcsSettingsModel
816 from rhodecode.model.settings import VcsSettingsModel
817 conf = VcsSettingsModel().get_ui_settings_as_config_obj()
817 conf = VcsSettingsModel().get_ui_settings_as_config_obj()
818 return not str2bool(conf.get('vcs_svn_proxy', 'http_requests_enabled'))
818 return not str2bool(conf.get('vcs_svn_proxy', 'http_requests_enabled'))
819 return False
819 return False
820
820
821
821
822 def discover_user(author):
822 def discover_user(author):
823 """
823 """
824 Tries to discover RhodeCode User based on the autho string. Author string
824 Tries to discover RhodeCode User based on the autho string. Author string
825 is typically `FirstName LastName <email@address.com>`
825 is typically `FirstName LastName <email@address.com>`
826 """
826 """
827
827
828 # if author is already an instance use it for extraction
828 # if author is already an instance use it for extraction
829 if isinstance(author, User):
829 if isinstance(author, User):
830 return author
830 return author
831
831
832 # Valid email in the attribute passed, see if they're in the system
832 # Valid email in the attribute passed, see if they're in the system
833 _email = author_email(author)
833 _email = author_email(author)
834 if _email != '':
834 if _email != '':
835 user = User.get_by_email(_email, case_insensitive=True, cache=True)
835 user = User.get_by_email(_email, case_insensitive=True, cache=True)
836 if user is not None:
836 if user is not None:
837 return user
837 return user
838
838
839 # Maybe it's a username, we try to extract it and fetch by username ?
839 # Maybe it's a username, we try to extract it and fetch by username ?
840 _author = author_name(author)
840 _author = author_name(author)
841 user = User.get_by_username(_author, case_insensitive=True, cache=True)
841 user = User.get_by_username(_author, case_insensitive=True, cache=True)
842 if user is not None:
842 if user is not None:
843 return user
843 return user
844
844
845 return None
845 return None
846
846
847
847
848 def email_or_none(author):
848 def email_or_none(author):
849 # extract email from the commit string
849 # extract email from the commit string
850 _email = author_email(author)
850 _email = author_email(author)
851
851
852 # If we have an email, use it, otherwise
852 # If we have an email, use it, otherwise
853 # see if it contains a username we can get an email from
853 # see if it contains a username we can get an email from
854 if _email != '':
854 if _email != '':
855 return _email
855 return _email
856 else:
856 else:
857 user = User.get_by_username(
857 user = User.get_by_username(
858 author_name(author), case_insensitive=True, cache=True)
858 author_name(author), case_insensitive=True, cache=True)
859
859
860 if user is not None:
860 if user is not None:
861 return user.email
861 return user.email
862
862
863 # No valid email, not a valid user in the system, none!
863 # No valid email, not a valid user in the system, none!
864 return None
864 return None
865
865
866
866
867 def link_to_user(author, length=0, **kwargs):
867 def link_to_user(author, length=0, **kwargs):
868 user = discover_user(author)
868 user = discover_user(author)
869 # user can be None, but if we have it already it means we can re-use it
869 # user can be None, but if we have it already it means we can re-use it
870 # in the person() function, so we save 1 intensive-query
870 # in the person() function, so we save 1 intensive-query
871 if user:
871 if user:
872 author = user
872 author = user
873
873
874 display_person = person(author, 'username_or_name_or_email')
874 display_person = person(author, 'username_or_name_or_email')
875 if length:
875 if length:
876 display_person = shorter(display_person, length)
876 display_person = shorter(display_person, length)
877
877
878 if user:
878 if user:
879 return link_to(
879 return link_to(
880 escape(display_person),
880 escape(display_person),
881 route_path('user_profile', username=user.username),
881 route_path('user_profile', username=user.username),
882 **kwargs)
882 **kwargs)
883 else:
883 else:
884 return escape(display_person)
884 return escape(display_person)
885
885
886
886
887 def link_to_group(users_group_name, **kwargs):
887 def link_to_group(users_group_name, **kwargs):
888 return link_to(
888 return link_to(
889 escape(users_group_name),
889 escape(users_group_name),
890 route_path('user_group_profile', user_group_name=users_group_name),
890 route_path('user_group_profile', user_group_name=users_group_name),
891 **kwargs)
891 **kwargs)
892
892
893
893
894 def person(author, show_attr="username_and_name"):
894 def person(author, show_attr="username_and_name"):
895 user = discover_user(author)
895 user = discover_user(author)
896 if user:
896 if user:
897 return getattr(user, show_attr)
897 return getattr(user, show_attr)
898 else:
898 else:
899 _author = author_name(author)
899 _author = author_name(author)
900 _email = email(author)
900 _email = email(author)
901 return _author or _email
901 return _author or _email
902
902
903
903
904 def author_string(email):
904 def author_string(email):
905 if email:
905 if email:
906 user = User.get_by_email(email, case_insensitive=True, cache=True)
906 user = User.get_by_email(email, case_insensitive=True, cache=True)
907 if user:
907 if user:
908 if user.first_name or user.last_name:
908 if user.first_name or user.last_name:
909 return '%s %s &lt;%s&gt;' % (
909 return '%s %s &lt;%s&gt;' % (
910 user.first_name, user.last_name, email)
910 user.first_name, user.last_name, email)
911 else:
911 else:
912 return email
912 return email
913 else:
913 else:
914 return email
914 return email
915 else:
915 else:
916 return None
916 return None
917
917
918
918
919 def person_by_id(id_, show_attr="username_and_name"):
919 def person_by_id(id_, show_attr="username_and_name"):
920 # attr to return from fetched user
920 # attr to return from fetched user
921 person_getter = lambda usr: getattr(usr, show_attr)
921 person_getter = lambda usr: getattr(usr, show_attr)
922
922
923 #maybe it's an ID ?
923 #maybe it's an ID ?
924 if str(id_).isdigit() or isinstance(id_, int):
924 if str(id_).isdigit() or isinstance(id_, int):
925 id_ = int(id_)
925 id_ = int(id_)
926 user = User.get(id_)
926 user = User.get(id_)
927 if user is not None:
927 if user is not None:
928 return person_getter(user)
928 return person_getter(user)
929 return id_
929 return id_
930
930
931
931
932 def gravatar_with_user(request, author, show_disabled=False, tooltip=False):
932 def gravatar_with_user(request, author, show_disabled=False, tooltip=False):
933 _render = request.get_partial_renderer('rhodecode:templates/base/base.mako')
933 _render = request.get_partial_renderer('rhodecode:templates/base/base.mako')
934 return _render('gravatar_with_user', author, show_disabled=show_disabled, tooltip=tooltip)
934 return _render('gravatar_with_user', author, show_disabled=show_disabled, tooltip=tooltip)
935
935
936
936
937 tags_paterns = OrderedDict((
937 tags_paterns = OrderedDict((
938 ('lang', (re.compile(r'\[(lang|language)\ \=\&gt;\ *([a-zA-Z\-\/\#\+\.]*)\]'),
938 ('lang', (re.compile(r'\[(lang|language)\ \=\&gt;\ *([a-zA-Z\-\/\#\+\.]*)\]'),
939 '<div class="metatag" tag="lang">\\2</div>')),
939 '<div class="metatag" tag="lang">\\2</div>')),
940
940
941 ('see', (re.compile(r'\[see\ \=\&gt;\ *([a-zA-Z0-9\/\=\?\&amp;\ \:\/\.\-]*)\]'),
941 ('see', (re.compile(r'\[see\ \=\&gt;\ *([a-zA-Z0-9\/\=\?\&amp;\ \:\/\.\-]*)\]'),
942 '<div class="metatag" tag="see">see: \\1 </div>')),
942 '<div class="metatag" tag="see">see: \\1 </div>')),
943
943
944 ('url', (re.compile(r'\[url\ \=\&gt;\ \[([a-zA-Z0-9\ \.\-\_]+)\]\((http://|https://|/)(.*?)\)\]'),
944 ('url', (re.compile(r'\[url\ \=\&gt;\ \[([a-zA-Z0-9\ \.\-\_]+)\]\((http://|https://|/)(.*?)\)\]'),
945 '<div class="metatag" tag="url"> <a href="\\2\\3">\\1</a> </div>')),
945 '<div class="metatag" tag="url"> <a href="\\2\\3">\\1</a> </div>')),
946
946
947 ('license', (re.compile(r'\[license\ \=\&gt;\ *([a-zA-Z0-9\/\=\?\&amp;\ \:\/\.\-]*)\]'),
947 ('license', (re.compile(r'\[license\ \=\&gt;\ *([a-zA-Z0-9\/\=\?\&amp;\ \:\/\.\-]*)\]'),
948 '<div class="metatag" tag="license"><a href="http:\/\/www.opensource.org/licenses/\\1">\\1</a></div>')),
948 '<div class="metatag" tag="license"><a href="http:\/\/www.opensource.org/licenses/\\1">\\1</a></div>')),
949
949
950 ('ref', (re.compile(r'\[(requires|recommends|conflicts|base)\ \=\&gt;\ *([a-zA-Z0-9\-\/]*)\]'),
950 ('ref', (re.compile(r'\[(requires|recommends|conflicts|base)\ \=\&gt;\ *([a-zA-Z0-9\-\/]*)\]'),
951 '<div class="metatag" tag="ref \\1">\\1: <a href="/\\2">\\2</a></div>')),
951 '<div class="metatag" tag="ref \\1">\\1: <a href="/\\2">\\2</a></div>')),
952
952
953 ('state', (re.compile(r'\[(stable|featured|stale|dead|dev|deprecated)\]'),
953 ('state', (re.compile(r'\[(stable|featured|stale|dead|dev|deprecated)\]'),
954 '<div class="metatag" tag="state \\1">\\1</div>')),
954 '<div class="metatag" tag="state \\1">\\1</div>')),
955
955
956 # label in grey
956 # label in grey
957 ('label', (re.compile(r'\[([a-z]+)\]'),
957 ('label', (re.compile(r'\[([a-z]+)\]'),
958 '<div class="metatag" tag="label">\\1</div>')),
958 '<div class="metatag" tag="label">\\1</div>')),
959
959
960 # generic catch all in grey
960 # generic catch all in grey
961 ('generic', (re.compile(r'\[([a-zA-Z0-9\.\-\_]+)\]'),
961 ('generic', (re.compile(r'\[([a-zA-Z0-9\.\-\_]+)\]'),
962 '<div class="metatag" tag="generic">\\1</div>')),
962 '<div class="metatag" tag="generic">\\1</div>')),
963 ))
963 ))
964
964
965
965
966 def extract_metatags(value):
966 def extract_metatags(value):
967 """
967 """
968 Extract supported meta-tags from given text value
968 Extract supported meta-tags from given text value
969 """
969 """
970 tags = []
970 tags = []
971 if not value:
971 if not value:
972 return tags, ''
972 return tags, ''
973
973
974 for key, val in tags_paterns.items():
974 for key, val in tags_paterns.items():
975 pat, replace_html = val
975 pat, replace_html = val
976 tags.extend([(key, x.group()) for x in pat.finditer(value)])
976 tags.extend([(key, x.group()) for x in pat.finditer(value)])
977 value = pat.sub('', value)
977 value = pat.sub('', value)
978
978
979 return tags, value
979 return tags, value
980
980
981
981
982 def style_metatag(tag_type, value):
982 def style_metatag(tag_type, value):
983 """
983 """
984 converts tags from value into html equivalent
984 converts tags from value into html equivalent
985 """
985 """
986 if not value:
986 if not value:
987 return ''
987 return ''
988
988
989 html_value = value
989 html_value = value
990 tag_data = tags_paterns.get(tag_type)
990 tag_data = tags_paterns.get(tag_type)
991 if tag_data:
991 if tag_data:
992 pat, replace_html = tag_data
992 pat, replace_html = tag_data
993 # convert to plain `unicode` instead of a markup tag to be used in
993 # convert to plain `unicode` instead of a markup tag to be used in
994 # regex expressions. safe_unicode doesn't work here
994 # regex expressions. safe_unicode doesn't work here
995 html_value = pat.sub(replace_html, unicode(value))
995 html_value = pat.sub(replace_html, unicode(value))
996
996
997 return html_value
997 return html_value
998
998
999
999
1000 def bool2icon(value, show_at_false=True):
1000 def bool2icon(value, show_at_false=True):
1001 """
1001 """
1002 Returns boolean value of a given value, represented as html element with
1002 Returns boolean value of a given value, represented as html element with
1003 classes that will represent icons
1003 classes that will represent icons
1004
1004
1005 :param value: given value to convert to html node
1005 :param value: given value to convert to html node
1006 """
1006 """
1007
1007
1008 if value: # does bool conversion
1008 if value: # does bool conversion
1009 return HTML.tag('i', class_="icon-true", title='True')
1009 return HTML.tag('i', class_="icon-true", title='True')
1010 else: # not true as bool
1010 else: # not true as bool
1011 if show_at_false:
1011 if show_at_false:
1012 return HTML.tag('i', class_="icon-false", title='False')
1012 return HTML.tag('i', class_="icon-false", title='False')
1013 return HTML.tag('i')
1013 return HTML.tag('i')
1014
1014
1015 #==============================================================================
1015 #==============================================================================
1016 # PERMS
1016 # PERMS
1017 #==============================================================================
1017 #==============================================================================
1018 from rhodecode.lib.auth import HasPermissionAny, HasPermissionAll, \
1018 from rhodecode.lib.auth import HasPermissionAny, HasPermissionAll, \
1019 HasRepoPermissionAny, HasRepoPermissionAll, HasRepoGroupPermissionAll, \
1019 HasRepoPermissionAny, HasRepoPermissionAll, HasRepoGroupPermissionAll, \
1020 HasRepoGroupPermissionAny, HasRepoPermissionAnyApi, get_csrf_token, \
1020 HasRepoGroupPermissionAny, HasRepoPermissionAnyApi, get_csrf_token, \
1021 csrf_token_key
1021 csrf_token_key
1022
1022
1023
1023
1024 #==============================================================================
1024 #==============================================================================
1025 # GRAVATAR URL
1025 # GRAVATAR URL
1026 #==============================================================================
1026 #==============================================================================
1027 class InitialsGravatar(object):
1027 class InitialsGravatar(object):
1028 def __init__(self, email_address, first_name, last_name, size=30,
1028 def __init__(self, email_address, first_name, last_name, size=30,
1029 background=None, text_color='#fff'):
1029 background=None, text_color='#fff'):
1030 self.size = size
1030 self.size = size
1031 self.first_name = first_name
1031 self.first_name = first_name
1032 self.last_name = last_name
1032 self.last_name = last_name
1033 self.email_address = email_address
1033 self.email_address = email_address
1034 self.background = background or self.str2color(email_address)
1034 self.background = background or self.str2color(email_address)
1035 self.text_color = text_color
1035 self.text_color = text_color
1036
1036
1037 def get_color_bank(self):
1037 def get_color_bank(self):
1038 """
1038 """
1039 returns a predefined list of colors that gravatars can use.
1039 returns a predefined list of colors that gravatars can use.
1040 Those are randomized distinct colors that guarantee readability and
1040 Those are randomized distinct colors that guarantee readability and
1041 uniqueness.
1041 uniqueness.
1042
1042
1043 generated with: http://phrogz.net/css/distinct-colors.html
1043 generated with: http://phrogz.net/css/distinct-colors.html
1044 """
1044 """
1045 return [
1045 return [
1046 '#bf3030', '#a67f53', '#00ff00', '#5989b3', '#392040', '#d90000',
1046 '#bf3030', '#a67f53', '#00ff00', '#5989b3', '#392040', '#d90000',
1047 '#402910', '#204020', '#79baf2', '#a700b3', '#bf6060', '#7f5320',
1047 '#402910', '#204020', '#79baf2', '#a700b3', '#bf6060', '#7f5320',
1048 '#008000', '#003059', '#ee00ff', '#ff0000', '#8c4b00', '#007300',
1048 '#008000', '#003059', '#ee00ff', '#ff0000', '#8c4b00', '#007300',
1049 '#005fb3', '#de73e6', '#ff4040', '#ffaa00', '#3df255', '#203140',
1049 '#005fb3', '#de73e6', '#ff4040', '#ffaa00', '#3df255', '#203140',
1050 '#47004d', '#591616', '#664400', '#59b365', '#0d2133', '#83008c',
1050 '#47004d', '#591616', '#664400', '#59b365', '#0d2133', '#83008c',
1051 '#592d2d', '#bf9f60', '#73e682', '#1d3f73', '#73006b', '#402020',
1051 '#592d2d', '#bf9f60', '#73e682', '#1d3f73', '#73006b', '#402020',
1052 '#b2862d', '#397341', '#597db3', '#e600d6', '#a60000', '#736039',
1052 '#b2862d', '#397341', '#597db3', '#e600d6', '#a60000', '#736039',
1053 '#00b318', '#79aaf2', '#330d30', '#ff8080', '#403010', '#16591f',
1053 '#00b318', '#79aaf2', '#330d30', '#ff8080', '#403010', '#16591f',
1054 '#002459', '#8c4688', '#e50000', '#ffbf40', '#00732e', '#102340',
1054 '#002459', '#8c4688', '#e50000', '#ffbf40', '#00732e', '#102340',
1055 '#bf60ac', '#8c4646', '#cc8800', '#00a642', '#1d3473', '#b32d98',
1055 '#bf60ac', '#8c4646', '#cc8800', '#00a642', '#1d3473', '#b32d98',
1056 '#660e00', '#ffd580', '#80ffb2', '#7391e6', '#733967', '#d97b6c',
1056 '#660e00', '#ffd580', '#80ffb2', '#7391e6', '#733967', '#d97b6c',
1057 '#8c5e00', '#59b389', '#3967e6', '#590047', '#73281d', '#665200',
1057 '#8c5e00', '#59b389', '#3967e6', '#590047', '#73281d', '#665200',
1058 '#00e67a', '#2d50b3', '#8c2377', '#734139', '#b2982d', '#16593a',
1058 '#00e67a', '#2d50b3', '#8c2377', '#734139', '#b2982d', '#16593a',
1059 '#001859', '#ff00aa', '#a65e53', '#ffcc00', '#0d3321', '#2d3959',
1059 '#001859', '#ff00aa', '#a65e53', '#ffcc00', '#0d3321', '#2d3959',
1060 '#731d56', '#401610', '#4c3d00', '#468c6c', '#002ca6', '#d936a3',
1060 '#731d56', '#401610', '#4c3d00', '#468c6c', '#002ca6', '#d936a3',
1061 '#d94c36', '#403920', '#36d9a3', '#0d1733', '#592d4a', '#993626',
1061 '#d94c36', '#403920', '#36d9a3', '#0d1733', '#592d4a', '#993626',
1062 '#cca300', '#00734d', '#46598c', '#8c005e', '#7f1100', '#8c7000',
1062 '#cca300', '#00734d', '#46598c', '#8c005e', '#7f1100', '#8c7000',
1063 '#00a66f', '#7382e6', '#b32d74', '#d9896c', '#ffe680', '#1d7362',
1063 '#00a66f', '#7382e6', '#b32d74', '#d9896c', '#ffe680', '#1d7362',
1064 '#364cd9', '#73003d', '#d93a00', '#998a4d', '#59b3a1', '#5965b3',
1064 '#364cd9', '#73003d', '#d93a00', '#998a4d', '#59b3a1', '#5965b3',
1065 '#e5007a', '#73341d', '#665f00', '#00b38f', '#0018b3', '#59163a',
1065 '#e5007a', '#73341d', '#665f00', '#00b38f', '#0018b3', '#59163a',
1066 '#b2502d', '#bfb960', '#00ffcc', '#23318c', '#a6537f', '#734939',
1066 '#b2502d', '#bfb960', '#00ffcc', '#23318c', '#a6537f', '#734939',
1067 '#b2a700', '#104036', '#3d3df2', '#402031', '#e56739', '#736f39',
1067 '#b2a700', '#104036', '#3d3df2', '#402031', '#e56739', '#736f39',
1068 '#79f2ea', '#000059', '#401029', '#4c1400', '#ffee00', '#005953',
1068 '#79f2ea', '#000059', '#401029', '#4c1400', '#ffee00', '#005953',
1069 '#101040', '#990052', '#402820', '#403d10', '#00ffee', '#0000d9',
1069 '#101040', '#990052', '#402820', '#403d10', '#00ffee', '#0000d9',
1070 '#ff80c4', '#a66953', '#eeff00', '#00ccbe', '#8080ff', '#e673a1',
1070 '#ff80c4', '#a66953', '#eeff00', '#00ccbe', '#8080ff', '#e673a1',
1071 '#a62c00', '#474d00', '#1a3331', '#46468c', '#733950', '#662900',
1071 '#a62c00', '#474d00', '#1a3331', '#46468c', '#733950', '#662900',
1072 '#858c23', '#238c85', '#0f0073', '#b20047', '#d9986c', '#becc00',
1072 '#858c23', '#238c85', '#0f0073', '#b20047', '#d9986c', '#becc00',
1073 '#396f73', '#281d73', '#ff0066', '#ff6600', '#dee673', '#59adb3',
1073 '#396f73', '#281d73', '#ff0066', '#ff6600', '#dee673', '#59adb3',
1074 '#6559b3', '#590024', '#b2622d', '#98b32d', '#36ced9', '#332d59',
1074 '#6559b3', '#590024', '#b2622d', '#98b32d', '#36ced9', '#332d59',
1075 '#40001a', '#733f1d', '#526600', '#005359', '#242040', '#bf6079',
1075 '#40001a', '#733f1d', '#526600', '#005359', '#242040', '#bf6079',
1076 '#735039', '#cef23d', '#007780', '#5630bf', '#66001b', '#b24700',
1076 '#735039', '#cef23d', '#007780', '#5630bf', '#66001b', '#b24700',
1077 '#acbf60', '#1d6273', '#25008c', '#731d34', '#a67453', '#50592d',
1077 '#acbf60', '#1d6273', '#25008c', '#731d34', '#a67453', '#50592d',
1078 '#00ccff', '#6600ff', '#ff0044', '#4c1f00', '#8a994d', '#79daf2',
1078 '#00ccff', '#6600ff', '#ff0044', '#4c1f00', '#8a994d', '#79daf2',
1079 '#a173e6', '#d93662', '#402310', '#aaff00', '#2d98b3', '#8c40ff',
1079 '#a173e6', '#d93662', '#402310', '#aaff00', '#2d98b3', '#8c40ff',
1080 '#592d39', '#ff8c40', '#354020', '#103640', '#1a0040', '#331a20',
1080 '#592d39', '#ff8c40', '#354020', '#103640', '#1a0040', '#331a20',
1081 '#331400', '#334d00', '#1d5673', '#583973', '#7f0022', '#4c3626',
1081 '#331400', '#334d00', '#1d5673', '#583973', '#7f0022', '#4c3626',
1082 '#88cc00', '#36a3d9', '#3d0073', '#d9364c', '#33241a', '#698c23',
1082 '#88cc00', '#36a3d9', '#3d0073', '#d9364c', '#33241a', '#698c23',
1083 '#5995b3', '#300059', '#e57382', '#7f3300', '#366600', '#00aaff',
1083 '#5995b3', '#300059', '#e57382', '#7f3300', '#366600', '#00aaff',
1084 '#3a1659', '#733941', '#663600', '#74b32d', '#003c59', '#7f53a6',
1084 '#3a1659', '#733941', '#663600', '#74b32d', '#003c59', '#7f53a6',
1085 '#73000f', '#ff8800', '#baf279', '#79caf2', '#291040', '#a6293a',
1085 '#73000f', '#ff8800', '#baf279', '#79caf2', '#291040', '#a6293a',
1086 '#b2742d', '#587339', '#0077b3', '#632699', '#400009', '#d9a66c',
1086 '#b2742d', '#587339', '#0077b3', '#632699', '#400009', '#d9a66c',
1087 '#294010', '#2d4a59', '#aa00ff', '#4c131b', '#b25f00', '#5ce600',
1087 '#294010', '#2d4a59', '#aa00ff', '#4c131b', '#b25f00', '#5ce600',
1088 '#267399', '#a336d9', '#990014', '#664e33', '#86bf60', '#0088ff',
1088 '#267399', '#a336d9', '#990014', '#664e33', '#86bf60', '#0088ff',
1089 '#7700b3', '#593a16', '#073300', '#1d4b73', '#ac60bf', '#e59539',
1089 '#7700b3', '#593a16', '#073300', '#1d4b73', '#ac60bf', '#e59539',
1090 '#4f8c46', '#368dd9', '#5c0073'
1090 '#4f8c46', '#368dd9', '#5c0073'
1091 ]
1091 ]
1092
1092
1093 def rgb_to_hex_color(self, rgb_tuple):
1093 def rgb_to_hex_color(self, rgb_tuple):
1094 """
1094 """
1095 Converts an rgb_tuple passed to an hex color.
1095 Converts an rgb_tuple passed to an hex color.
1096
1096
1097 :param rgb_tuple: tuple with 3 ints represents rgb color space
1097 :param rgb_tuple: tuple with 3 ints represents rgb color space
1098 """
1098 """
1099 return '#' + ("".join(map(chr, rgb_tuple)).encode('hex'))
1099 return '#' + ("".join(map(chr, rgb_tuple)).encode('hex'))
1100
1100
1101 def email_to_int_list(self, email_str):
1101 def email_to_int_list(self, email_str):
1102 """
1102 """
1103 Get every byte of the hex digest value of email and turn it to integer.
1103 Get every byte of the hex digest value of email and turn it to integer.
1104 It's going to be always between 0-255
1104 It's going to be always between 0-255
1105 """
1105 """
1106 digest = md5_safe(email_str.lower())
1106 digest = md5_safe(email_str.lower())
1107 return [int(digest[i * 2:i * 2 + 2], 16) for i in range(16)]
1107 return [int(digest[i * 2:i * 2 + 2], 16) for i in range(16)]
1108
1108
1109 def pick_color_bank_index(self, email_str, color_bank):
1109 def pick_color_bank_index(self, email_str, color_bank):
1110 return self.email_to_int_list(email_str)[0] % len(color_bank)
1110 return self.email_to_int_list(email_str)[0] % len(color_bank)
1111
1111
1112 def str2color(self, email_str):
1112 def str2color(self, email_str):
1113 """
1113 """
1114 Tries to map in a stable algorithm an email to color
1114 Tries to map in a stable algorithm an email to color
1115
1115
1116 :param email_str:
1116 :param email_str:
1117 """
1117 """
1118 color_bank = self.get_color_bank()
1118 color_bank = self.get_color_bank()
1119 # pick position (module it's length so we always find it in the
1119 # pick position (module it's length so we always find it in the
1120 # bank even if it's smaller than 256 values
1120 # bank even if it's smaller than 256 values
1121 pos = self.pick_color_bank_index(email_str, color_bank)
1121 pos = self.pick_color_bank_index(email_str, color_bank)
1122 return color_bank[pos]
1122 return color_bank[pos]
1123
1123
1124 def normalize_email(self, email_address):
1124 def normalize_email(self, email_address):
1125 import unicodedata
1125 import unicodedata
1126 # default host used to fill in the fake/missing email
1126 # default host used to fill in the fake/missing email
1127 default_host = u'localhost'
1127 default_host = u'localhost'
1128
1128
1129 if not email_address:
1129 if not email_address:
1130 email_address = u'%s@%s' % (User.DEFAULT_USER, default_host)
1130 email_address = u'%s@%s' % (User.DEFAULT_USER, default_host)
1131
1131
1132 email_address = safe_unicode(email_address)
1132 email_address = safe_unicode(email_address)
1133
1133
1134 if u'@' not in email_address:
1134 if u'@' not in email_address:
1135 email_address = u'%s@%s' % (email_address, default_host)
1135 email_address = u'%s@%s' % (email_address, default_host)
1136
1136
1137 if email_address.endswith(u'@'):
1137 if email_address.endswith(u'@'):
1138 email_address = u'%s%s' % (email_address, default_host)
1138 email_address = u'%s%s' % (email_address, default_host)
1139
1139
1140 email_address = unicodedata.normalize('NFKD', email_address)\
1140 email_address = unicodedata.normalize('NFKD', email_address)\
1141 .encode('ascii', 'ignore')
1141 .encode('ascii', 'ignore')
1142 return email_address
1142 return email_address
1143
1143
1144 def get_initials(self):
1144 def get_initials(self):
1145 """
1145 """
1146 Returns 2 letter initials calculated based on the input.
1146 Returns 2 letter initials calculated based on the input.
1147 The algorithm picks first given email address, and takes first letter
1147 The algorithm picks first given email address, and takes first letter
1148 of part before @, and then the first letter of server name. In case
1148 of part before @, and then the first letter of server name. In case
1149 the part before @ is in a format of `somestring.somestring2` it replaces
1149 the part before @ is in a format of `somestring.somestring2` it replaces
1150 the server letter with first letter of somestring2
1150 the server letter with first letter of somestring2
1151
1151
1152 In case function was initialized with both first and lastname, this
1152 In case function was initialized with both first and lastname, this
1153 overrides the extraction from email by first letter of the first and
1153 overrides the extraction from email by first letter of the first and
1154 last name. We add special logic to that functionality, In case Full name
1154 last name. We add special logic to that functionality, In case Full name
1155 is compound, like Guido Von Rossum, we use last part of the last name
1155 is compound, like Guido Von Rossum, we use last part of the last name
1156 (Von Rossum) picking `R`.
1156 (Von Rossum) picking `R`.
1157
1157
1158 Function also normalizes the non-ascii characters to they ascii
1158 Function also normalizes the non-ascii characters to they ascii
1159 representation, eg Δ„ => A
1159 representation, eg Δ„ => A
1160 """
1160 """
1161 import unicodedata
1161 import unicodedata
1162 # replace non-ascii to ascii
1162 # replace non-ascii to ascii
1163 first_name = unicodedata.normalize(
1163 first_name = unicodedata.normalize(
1164 'NFKD', safe_unicode(self.first_name)).encode('ascii', 'ignore')
1164 'NFKD', safe_unicode(self.first_name)).encode('ascii', 'ignore')
1165 last_name = unicodedata.normalize(
1165 last_name = unicodedata.normalize(
1166 'NFKD', safe_unicode(self.last_name)).encode('ascii', 'ignore')
1166 'NFKD', safe_unicode(self.last_name)).encode('ascii', 'ignore')
1167
1167
1168 # do NFKD encoding, and also make sure email has proper format
1168 # do NFKD encoding, and also make sure email has proper format
1169 email_address = self.normalize_email(self.email_address)
1169 email_address = self.normalize_email(self.email_address)
1170
1170
1171 # first push the email initials
1171 # first push the email initials
1172 prefix, server = email_address.split('@', 1)
1172 prefix, server = email_address.split('@', 1)
1173
1173
1174 # check if prefix is maybe a 'first_name.last_name' syntax
1174 # check if prefix is maybe a 'first_name.last_name' syntax
1175 _dot_split = prefix.rsplit('.', 1)
1175 _dot_split = prefix.rsplit('.', 1)
1176 if len(_dot_split) == 2 and _dot_split[1]:
1176 if len(_dot_split) == 2 and _dot_split[1]:
1177 initials = [_dot_split[0][0], _dot_split[1][0]]
1177 initials = [_dot_split[0][0], _dot_split[1][0]]
1178 else:
1178 else:
1179 initials = [prefix[0], server[0]]
1179 initials = [prefix[0], server[0]]
1180
1180
1181 # then try to replace either first_name or last_name
1181 # then try to replace either first_name or last_name
1182 fn_letter = (first_name or " ")[0].strip()
1182 fn_letter = (first_name or " ")[0].strip()
1183 ln_letter = (last_name.split(' ', 1)[-1] or " ")[0].strip()
1183 ln_letter = (last_name.split(' ', 1)[-1] or " ")[0].strip()
1184
1184
1185 if fn_letter:
1185 if fn_letter:
1186 initials[0] = fn_letter
1186 initials[0] = fn_letter
1187
1187
1188 if ln_letter:
1188 if ln_letter:
1189 initials[1] = ln_letter
1189 initials[1] = ln_letter
1190
1190
1191 return ''.join(initials).upper()
1191 return ''.join(initials).upper()
1192
1192
1193 def get_img_data_by_type(self, font_family, img_type):
1193 def get_img_data_by_type(self, font_family, img_type):
1194 default_user = """
1194 default_user = """
1195 <svg xmlns="http://www.w3.org/2000/svg"
1195 <svg xmlns="http://www.w3.org/2000/svg"
1196 version="1.1" x="0px" y="0px" width="{size}" height="{size}"
1196 version="1.1" x="0px" y="0px" width="{size}" height="{size}"
1197 viewBox="-15 -10 439.165 429.164"
1197 viewBox="-15 -10 439.165 429.164"
1198
1198
1199 xml:space="preserve"
1199 xml:space="preserve"
1200 style="background:{background};" >
1200 style="background:{background};" >
1201
1201
1202 <path d="M204.583,216.671c50.664,0,91.74-48.075,
1202 <path d="M204.583,216.671c50.664,0,91.74-48.075,
1203 91.74-107.378c0-82.237-41.074-107.377-91.74-107.377
1203 91.74-107.378c0-82.237-41.074-107.377-91.74-107.377
1204 c-50.668,0-91.74,25.14-91.74,107.377C112.844,
1204 c-50.668,0-91.74,25.14-91.74,107.377C112.844,
1205 168.596,153.916,216.671,
1205 168.596,153.916,216.671,
1206 204.583,216.671z" fill="{text_color}"/>
1206 204.583,216.671z" fill="{text_color}"/>
1207 <path d="M407.164,374.717L360.88,
1207 <path d="M407.164,374.717L360.88,
1208 270.454c-2.117-4.771-5.836-8.728-10.465-11.138l-71.83-37.392
1208 270.454c-2.117-4.771-5.836-8.728-10.465-11.138l-71.83-37.392
1209 c-1.584-0.823-3.502-0.663-4.926,0.415c-20.316,
1209 c-1.584-0.823-3.502-0.663-4.926,0.415c-20.316,
1210 15.366-44.203,23.488-69.076,23.488c-24.877,
1210 15.366-44.203,23.488-69.076,23.488c-24.877,
1211 0-48.762-8.122-69.078-23.488
1211 0-48.762-8.122-69.078-23.488
1212 c-1.428-1.078-3.346-1.238-4.93-0.415L58.75,
1212 c-1.428-1.078-3.346-1.238-4.93-0.415L58.75,
1213 259.316c-4.631,2.41-8.346,6.365-10.465,11.138L2.001,374.717
1213 259.316c-4.631,2.41-8.346,6.365-10.465,11.138L2.001,374.717
1214 c-3.191,7.188-2.537,15.412,1.75,22.005c4.285,
1214 c-3.191,7.188-2.537,15.412,1.75,22.005c4.285,
1215 6.592,11.537,10.526,19.4,10.526h362.861c7.863,0,15.117-3.936,
1215 6.592,11.537,10.526,19.4,10.526h362.861c7.863,0,15.117-3.936,
1216 19.402-10.527 C409.699,390.129,
1216 19.402-10.527 C409.699,390.129,
1217 410.355,381.902,407.164,374.717z" fill="{text_color}"/>
1217 410.355,381.902,407.164,374.717z" fill="{text_color}"/>
1218 </svg>""".format(
1218 </svg>""".format(
1219 size=self.size,
1219 size=self.size,
1220 background='#979797', # @grey4
1220 background='#979797', # @grey4
1221 text_color=self.text_color,
1221 text_color=self.text_color,
1222 font_family=font_family)
1222 font_family=font_family)
1223
1223
1224 return {
1224 return {
1225 "default_user": default_user
1225 "default_user": default_user
1226 }[img_type]
1226 }[img_type]
1227
1227
1228 def get_img_data(self, svg_type=None):
1228 def get_img_data(self, svg_type=None):
1229 """
1229 """
1230 generates the svg metadata for image
1230 generates the svg metadata for image
1231 """
1231 """
1232 fonts = [
1232 fonts = [
1233 '-apple-system',
1233 '-apple-system',
1234 'BlinkMacSystemFont',
1234 'BlinkMacSystemFont',
1235 'Segoe UI',
1235 'Segoe UI',
1236 'Roboto',
1236 'Roboto',
1237 'Oxygen-Sans',
1237 'Oxygen-Sans',
1238 'Ubuntu',
1238 'Ubuntu',
1239 'Cantarell',
1239 'Cantarell',
1240 'Helvetica Neue',
1240 'Helvetica Neue',
1241 'sans-serif'
1241 'sans-serif'
1242 ]
1242 ]
1243 font_family = ','.join(fonts)
1243 font_family = ','.join(fonts)
1244 if svg_type:
1244 if svg_type:
1245 return self.get_img_data_by_type(font_family, svg_type)
1245 return self.get_img_data_by_type(font_family, svg_type)
1246
1246
1247 initials = self.get_initials()
1247 initials = self.get_initials()
1248 img_data = """
1248 img_data = """
1249 <svg xmlns="http://www.w3.org/2000/svg" pointer-events="none"
1249 <svg xmlns="http://www.w3.org/2000/svg" pointer-events="none"
1250 width="{size}" height="{size}"
1250 width="{size}" height="{size}"
1251 style="width: 100%; height: 100%; background-color: {background}"
1251 style="width: 100%; height: 100%; background-color: {background}"
1252 viewBox="0 0 {size} {size}">
1252 viewBox="0 0 {size} {size}">
1253 <text text-anchor="middle" y="50%" x="50%" dy="0.35em"
1253 <text text-anchor="middle" y="50%" x="50%" dy="0.35em"
1254 pointer-events="auto" fill="{text_color}"
1254 pointer-events="auto" fill="{text_color}"
1255 font-family="{font_family}"
1255 font-family="{font_family}"
1256 style="font-weight: 400; font-size: {f_size}px;">{text}
1256 style="font-weight: 400; font-size: {f_size}px;">{text}
1257 </text>
1257 </text>
1258 </svg>""".format(
1258 </svg>""".format(
1259 size=self.size,
1259 size=self.size,
1260 f_size=self.size/2.05, # scale the text inside the box nicely
1260 f_size=self.size/2.05, # scale the text inside the box nicely
1261 background=self.background,
1261 background=self.background,
1262 text_color=self.text_color,
1262 text_color=self.text_color,
1263 text=initials.upper(),
1263 text=initials.upper(),
1264 font_family=font_family)
1264 font_family=font_family)
1265
1265
1266 return img_data
1266 return img_data
1267
1267
1268 def generate_svg(self, svg_type=None):
1268 def generate_svg(self, svg_type=None):
1269 img_data = self.get_img_data(svg_type)
1269 img_data = self.get_img_data(svg_type)
1270 return "data:image/svg+xml;base64,%s" % img_data.encode('base64')
1270 return "data:image/svg+xml;base64,%s" % img_data.encode('base64')
1271
1271
1272
1272
1273 def initials_gravatar(email_address, first_name, last_name, size=30):
1273 def initials_gravatar(email_address, first_name, last_name, size=30):
1274 svg_type = None
1274 svg_type = None
1275 if email_address == User.DEFAULT_USER_EMAIL:
1275 if email_address == User.DEFAULT_USER_EMAIL:
1276 svg_type = 'default_user'
1276 svg_type = 'default_user'
1277 klass = InitialsGravatar(email_address, first_name, last_name, size)
1277 klass = InitialsGravatar(email_address, first_name, last_name, size)
1278 return klass.generate_svg(svg_type=svg_type)
1278 return klass.generate_svg(svg_type=svg_type)
1279
1279
1280
1280
1281 def gravatar_url(email_address, size=30, request=None):
1281 def gravatar_url(email_address, size=30, request=None):
1282 request = get_current_request()
1282 request = get_current_request()
1283 _use_gravatar = request.call_context.visual.use_gravatar
1283 _use_gravatar = request.call_context.visual.use_gravatar
1284 _gravatar_url = request.call_context.visual.gravatar_url
1284 _gravatar_url = request.call_context.visual.gravatar_url
1285
1285
1286 _gravatar_url = _gravatar_url or User.DEFAULT_GRAVATAR_URL
1286 _gravatar_url = _gravatar_url or User.DEFAULT_GRAVATAR_URL
1287
1287
1288 email_address = email_address or User.DEFAULT_USER_EMAIL
1288 email_address = email_address or User.DEFAULT_USER_EMAIL
1289 if isinstance(email_address, unicode):
1289 if isinstance(email_address, unicode):
1290 # hashlib crashes on unicode items
1290 # hashlib crashes on unicode items
1291 email_address = safe_str(email_address)
1291 email_address = safe_str(email_address)
1292
1292
1293 # empty email or default user
1293 # empty email or default user
1294 if not email_address or email_address == User.DEFAULT_USER_EMAIL:
1294 if not email_address or email_address == User.DEFAULT_USER_EMAIL:
1295 return initials_gravatar(User.DEFAULT_USER_EMAIL, '', '', size=size)
1295 return initials_gravatar(User.DEFAULT_USER_EMAIL, '', '', size=size)
1296
1296
1297 if _use_gravatar:
1297 if _use_gravatar:
1298 # TODO: Disuse pyramid thread locals. Think about another solution to
1298 # TODO: Disuse pyramid thread locals. Think about another solution to
1299 # get the host and schema here.
1299 # get the host and schema here.
1300 request = get_current_request()
1300 request = get_current_request()
1301 tmpl = safe_str(_gravatar_url)
1301 tmpl = safe_str(_gravatar_url)
1302 tmpl = tmpl.replace('{email}', email_address)\
1302 tmpl = tmpl.replace('{email}', email_address)\
1303 .replace('{md5email}', md5_safe(email_address.lower())) \
1303 .replace('{md5email}', md5_safe(email_address.lower())) \
1304 .replace('{netloc}', request.host)\
1304 .replace('{netloc}', request.host)\
1305 .replace('{scheme}', request.scheme)\
1305 .replace('{scheme}', request.scheme)\
1306 .replace('{size}', safe_str(size))
1306 .replace('{size}', safe_str(size))
1307 return tmpl
1307 return tmpl
1308 else:
1308 else:
1309 return initials_gravatar(email_address, '', '', size=size)
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 def breadcrumb_repo_link(repo):
1312 def breadcrumb_repo_link(repo):
1401 """
1313 """
1402 Makes a breadcrumbs path link to repo
1314 Makes a breadcrumbs path link to repo
1403
1315
1404 ex::
1316 ex::
1405 group >> subgroup >> repo
1317 group >> subgroup >> repo
1406
1318
1407 :param repo: a Repository instance
1319 :param repo: a Repository instance
1408 """
1320 """
1409
1321
1410 path = [
1322 path = [
1411 link_to(group.name, route_path('repo_group_home', repo_group_name=group.group_name),
1323 link_to(group.name, route_path('repo_group_home', repo_group_name=group.group_name),
1412 title='last change:{}'.format(format_date(group.last_commit_change)))
1324 title='last change:{}'.format(format_date(group.last_commit_change)))
1413 for group in repo.groups_with_parents
1325 for group in repo.groups_with_parents
1414 ] + [
1326 ] + [
1415 link_to(repo.just_name, route_path('repo_summary', repo_name=repo.repo_name),
1327 link_to(repo.just_name, route_path('repo_summary', repo_name=repo.repo_name),
1416 title='last change:{}'.format(format_date(repo.last_commit_change)))
1328 title='last change:{}'.format(format_date(repo.last_commit_change)))
1417 ]
1329 ]
1418
1330
1419 return literal(' &raquo; '.join(path))
1331 return literal(' &raquo; '.join(path))
1420
1332
1421
1333
1422 def breadcrumb_repo_group_link(repo_group):
1334 def breadcrumb_repo_group_link(repo_group):
1423 """
1335 """
1424 Makes a breadcrumbs path link to repo
1336 Makes a breadcrumbs path link to repo
1425
1337
1426 ex::
1338 ex::
1427 group >> subgroup
1339 group >> subgroup
1428
1340
1429 :param repo_group: a Repository Group instance
1341 :param repo_group: a Repository Group instance
1430 """
1342 """
1431
1343
1432 path = [
1344 path = [
1433 link_to(group.name,
1345 link_to(group.name,
1434 route_path('repo_group_home', repo_group_name=group.group_name),
1346 route_path('repo_group_home', repo_group_name=group.group_name),
1435 title='last change:{}'.format(format_date(group.last_commit_change)))
1347 title='last change:{}'.format(format_date(group.last_commit_change)))
1436 for group in repo_group.parents
1348 for group in repo_group.parents
1437 ] + [
1349 ] + [
1438 link_to(repo_group.name,
1350 link_to(repo_group.name,
1439 route_path('repo_group_home', repo_group_name=repo_group.group_name),
1351 route_path('repo_group_home', repo_group_name=repo_group.group_name),
1440 title='last change:{}'.format(format_date(repo_group.last_commit_change)))
1352 title='last change:{}'.format(format_date(repo_group.last_commit_change)))
1441 ]
1353 ]
1442
1354
1443 return literal(' &raquo; '.join(path))
1355 return literal(' &raquo; '.join(path))
1444
1356
1445
1357
1446 def format_byte_size_binary(file_size):
1358 def format_byte_size_binary(file_size):
1447 """
1359 """
1448 Formats file/folder sizes to standard.
1360 Formats file/folder sizes to standard.
1449 """
1361 """
1450 if file_size is None:
1362 if file_size is None:
1451 file_size = 0
1363 file_size = 0
1452
1364
1453 formatted_size = format_byte_size(file_size, binary=True)
1365 formatted_size = format_byte_size(file_size, binary=True)
1454 return formatted_size
1366 return formatted_size
1455
1367
1456
1368
1457 def urlify_text(text_, safe=True, **href_attrs):
1369 def urlify_text(text_, safe=True, **href_attrs):
1458 """
1370 """
1459 Extract urls from text and make html links out of them
1371 Extract urls from text and make html links out of them
1460 """
1372 """
1461
1373
1462 url_pat = re.compile(r'''(http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@#.&+]'''
1374 url_pat = re.compile(r'''(http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@#.&+]'''
1463 '''|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+)''')
1375 '''|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+)''')
1464
1376
1465 def url_func(match_obj):
1377 def url_func(match_obj):
1466 url_full = match_obj.groups()[0]
1378 url_full = match_obj.groups()[0]
1467 a_options = dict(href_attrs)
1379 a_options = dict(href_attrs)
1468 a_options['href'] = url_full
1380 a_options['href'] = url_full
1469 a_text = url_full
1381 a_text = url_full
1470 return HTML.tag("a", a_text, **a_options)
1382 return HTML.tag("a", a_text, **a_options)
1471
1383
1472 _new_text = url_pat.sub(url_func, text_)
1384 _new_text = url_pat.sub(url_func, text_)
1473
1385
1474 if safe:
1386 if safe:
1475 return literal(_new_text)
1387 return literal(_new_text)
1476 return _new_text
1388 return _new_text
1477
1389
1478
1390
1479 def urlify_commits(text_, repo_name):
1391 def urlify_commits(text_, repo_name):
1480 """
1392 """
1481 Extract commit ids from text and make link from them
1393 Extract commit ids from text and make link from them
1482
1394
1483 :param text_:
1395 :param text_:
1484 :param repo_name: repo name to build the URL with
1396 :param repo_name: repo name to build the URL with
1485 """
1397 """
1486
1398
1487 url_pat = re.compile(r'(^|\s)([0-9a-fA-F]{12,40})($|\s)')
1399 url_pat = re.compile(r'(^|\s)([0-9a-fA-F]{12,40})($|\s)')
1488
1400
1489 def url_func(match_obj):
1401 def url_func(match_obj):
1490 commit_id = match_obj.groups()[1]
1402 commit_id = match_obj.groups()[1]
1491 pref = match_obj.groups()[0]
1403 pref = match_obj.groups()[0]
1492 suf = match_obj.groups()[2]
1404 suf = match_obj.groups()[2]
1493
1405
1494 tmpl = (
1406 tmpl = (
1495 '%(pref)s<a class="tooltip-hovercard %(cls)s" href="%(url)s" data-hovercard-alt="%(hovercard_alt)s" data-hovercard-url="%(hovercard_url)s">'
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 '%(commit_id)s</a>%(suf)s'
1408 '%(commit_id)s</a>%(suf)s'
1497 )
1409 )
1498 return tmpl % {
1410 return tmpl % {
1499 'pref': pref,
1411 'pref': pref,
1500 'cls': 'revision-link',
1412 'cls': 'revision-link',
1501 'url': route_url(
1413 'url': route_url(
1502 'repo_commit', repo_name=repo_name, commit_id=commit_id),
1414 'repo_commit', repo_name=repo_name, commit_id=commit_id),
1503 'commit_id': commit_id,
1415 'commit_id': commit_id,
1504 'suf': suf,
1416 'suf': suf,
1505 'hovercard_alt': 'Commit: {}'.format(commit_id),
1417 'hovercard_alt': 'Commit: {}'.format(commit_id),
1506 'hovercard_url': route_url(
1418 'hovercard_url': route_url(
1507 'hovercard_repo_commit', repo_name=repo_name, commit_id=commit_id)
1419 'hovercard_repo_commit', repo_name=repo_name, commit_id=commit_id)
1508 }
1420 }
1509
1421
1510 new_text = url_pat.sub(url_func, text_)
1422 new_text = url_pat.sub(url_func, text_)
1511
1423
1512 return new_text
1424 return new_text
1513
1425
1514
1426
1515 def _process_url_func(match_obj, repo_name, uid, entry,
1427 def _process_url_func(match_obj, repo_name, uid, entry,
1516 return_raw_data=False, link_format='html'):
1428 return_raw_data=False, link_format='html'):
1517 pref = ''
1429 pref = ''
1518 if match_obj.group().startswith(' '):
1430 if match_obj.group().startswith(' '):
1519 pref = ' '
1431 pref = ' '
1520
1432
1521 issue_id = ''.join(match_obj.groups())
1433 issue_id = ''.join(match_obj.groups())
1522
1434
1523 if link_format == 'html':
1435 if link_format == 'html':
1524 tmpl = (
1436 tmpl = (
1525 '%(pref)s<a class="tooltip %(cls)s" href="%(url)s" title="%(title)s">'
1437 '%(pref)s<a class="tooltip %(cls)s" href="%(url)s" title="%(title)s">'
1526 '%(issue-prefix)s%(id-repr)s'
1438 '%(issue-prefix)s%(id-repr)s'
1527 '</a>')
1439 '</a>')
1528 elif link_format == 'html+hovercard':
1440 elif link_format == 'html+hovercard':
1529 tmpl = (
1441 tmpl = (
1530 '%(pref)s<a class="tooltip-hovercard %(cls)s" href="%(url)s" data-hovercard-url="%(hovercard_url)s">'
1442 '%(pref)s<a class="tooltip-hovercard %(cls)s" href="%(url)s" data-hovercard-url="%(hovercard_url)s">'
1531 '%(issue-prefix)s%(id-repr)s'
1443 '%(issue-prefix)s%(id-repr)s'
1532 '</a>')
1444 '</a>')
1533 elif link_format in ['rst', 'rst+hovercard']:
1445 elif link_format in ['rst', 'rst+hovercard']:
1534 tmpl = '`%(issue-prefix)s%(id-repr)s <%(url)s>`_'
1446 tmpl = '`%(issue-prefix)s%(id-repr)s <%(url)s>`_'
1535 elif link_format in ['markdown', 'markdown+hovercard']:
1447 elif link_format in ['markdown', 'markdown+hovercard']:
1536 tmpl = '[%(pref)s%(issue-prefix)s%(id-repr)s](%(url)s)'
1448 tmpl = '[%(pref)s%(issue-prefix)s%(id-repr)s](%(url)s)'
1537 else:
1449 else:
1538 raise ValueError('Bad link_format:{}'.format(link_format))
1450 raise ValueError('Bad link_format:{}'.format(link_format))
1539
1451
1540 (repo_name_cleaned,
1452 (repo_name_cleaned,
1541 parent_group_name) = RepoGroupModel()._get_group_name_and_parent(repo_name)
1453 parent_group_name) = RepoGroupModel()._get_group_name_and_parent(repo_name)
1542
1454
1543 # variables replacement
1455 # variables replacement
1544 named_vars = {
1456 named_vars = {
1545 'id': issue_id,
1457 'id': issue_id,
1546 'repo': repo_name,
1458 'repo': repo_name,
1547 'repo_name': repo_name_cleaned,
1459 'repo_name': repo_name_cleaned,
1548 'group_name': parent_group_name,
1460 'group_name': parent_group_name,
1549 # set dummy keys so we always have them
1461 # set dummy keys so we always have them
1550 'hostname': '',
1462 'hostname': '',
1551 'netloc': '',
1463 'netloc': '',
1552 'scheme': ''
1464 'scheme': ''
1553 }
1465 }
1554
1466
1555 request = get_current_request()
1467 request = get_current_request()
1556 if request:
1468 if request:
1557 # exposes, hostname, netloc, scheme
1469 # exposes, hostname, netloc, scheme
1558 host_data = get_host_info(request)
1470 host_data = get_host_info(request)
1559 named_vars.update(host_data)
1471 named_vars.update(host_data)
1560
1472
1561 # named regex variables
1473 # named regex variables
1562 named_vars.update(match_obj.groupdict())
1474 named_vars.update(match_obj.groupdict())
1563 _url = string.Template(entry['url']).safe_substitute(**named_vars)
1475 _url = string.Template(entry['url']).safe_substitute(**named_vars)
1564 desc = string.Template(entry['desc']).safe_substitute(**named_vars)
1476 desc = string.Template(entry['desc']).safe_substitute(**named_vars)
1565 hovercard_url = string.Template(entry.get('hovercard_url', '')).safe_substitute(**named_vars)
1477 hovercard_url = string.Template(entry.get('hovercard_url', '')).safe_substitute(**named_vars)
1566
1478
1567 def quote_cleaner(input_str):
1479 def quote_cleaner(input_str):
1568 """Remove quotes as it's HTML"""
1480 """Remove quotes as it's HTML"""
1569 return input_str.replace('"', '')
1481 return input_str.replace('"', '')
1570
1482
1571 data = {
1483 data = {
1572 'pref': pref,
1484 'pref': pref,
1573 'cls': quote_cleaner('issue-tracker-link'),
1485 'cls': quote_cleaner('issue-tracker-link'),
1574 'url': quote_cleaner(_url),
1486 'url': quote_cleaner(_url),
1575 'id-repr': issue_id,
1487 'id-repr': issue_id,
1576 'issue-prefix': entry['pref'],
1488 'issue-prefix': entry['pref'],
1577 'serv': entry['url'],
1489 'serv': entry['url'],
1578 'title': desc,
1490 'title': desc,
1579 'hovercard_url': hovercard_url
1491 'hovercard_url': hovercard_url
1580 }
1492 }
1581
1493
1582 if return_raw_data:
1494 if return_raw_data:
1583 return {
1495 return {
1584 'id': issue_id,
1496 'id': issue_id,
1585 'url': _url
1497 'url': _url
1586 }
1498 }
1587 return tmpl % data
1499 return tmpl % data
1588
1500
1589
1501
1590 def get_active_pattern_entries(repo_name):
1502 def get_active_pattern_entries(repo_name):
1591 repo = None
1503 repo = None
1592 if repo_name:
1504 if repo_name:
1593 # Retrieving repo_name to avoid invalid repo_name to explode on
1505 # Retrieving repo_name to avoid invalid repo_name to explode on
1594 # IssueTrackerSettingsModel but still passing invalid name further down
1506 # IssueTrackerSettingsModel but still passing invalid name further down
1595 repo = Repository.get_by_repo_name(repo_name, cache=True)
1507 repo = Repository.get_by_repo_name(repo_name, cache=True)
1596
1508
1597 settings_model = IssueTrackerSettingsModel(repo=repo)
1509 settings_model = IssueTrackerSettingsModel(repo=repo)
1598 active_entries = settings_model.get_settings(cache=True)
1510 active_entries = settings_model.get_settings(cache=True)
1599 return active_entries
1511 return active_entries
1600
1512
1601
1513
1602 def process_patterns(text_string, repo_name, link_format='html', active_entries=None):
1514 def process_patterns(text_string, repo_name, link_format='html', active_entries=None):
1603
1515
1604 allowed_formats = ['html', 'rst', 'markdown',
1516 allowed_formats = ['html', 'rst', 'markdown',
1605 'html+hovercard', 'rst+hovercard', 'markdown+hovercard']
1517 'html+hovercard', 'rst+hovercard', 'markdown+hovercard']
1606 if link_format not in allowed_formats:
1518 if link_format not in allowed_formats:
1607 raise ValueError('Link format can be only one of:{} got {}'.format(
1519 raise ValueError('Link format can be only one of:{} got {}'.format(
1608 allowed_formats, link_format))
1520 allowed_formats, link_format))
1609
1521
1610 active_entries = active_entries or get_active_pattern_entries(repo_name)
1522 active_entries = active_entries or get_active_pattern_entries(repo_name)
1611 issues_data = []
1523 issues_data = []
1612 new_text = text_string
1524 new_text = text_string
1613
1525
1614 log.debug('Got %s entries to process', len(active_entries))
1526 log.debug('Got %s entries to process', len(active_entries))
1615 for uid, entry in active_entries.items():
1527 for uid, entry in active_entries.items():
1616 log.debug('found issue tracker entry with uid %s', uid)
1528 log.debug('found issue tracker entry with uid %s', uid)
1617
1529
1618 if not (entry['pat'] and entry['url']):
1530 if not (entry['pat'] and entry['url']):
1619 log.debug('skipping due to missing data')
1531 log.debug('skipping due to missing data')
1620 continue
1532 continue
1621
1533
1622 log.debug('issue tracker entry: uid: `%s` PAT:%s URL:%s PREFIX:%s',
1534 log.debug('issue tracker entry: uid: `%s` PAT:%s URL:%s PREFIX:%s',
1623 uid, entry['pat'], entry['url'], entry['pref'])
1535 uid, entry['pat'], entry['url'], entry['pref'])
1624
1536
1625 try:
1537 try:
1626 pattern = re.compile(r'%s' % entry['pat'])
1538 pattern = re.compile(r'%s' % entry['pat'])
1627 except re.error:
1539 except re.error:
1628 log.exception('issue tracker pattern: `%s` failed to compile', entry['pat'])
1540 log.exception('issue tracker pattern: `%s` failed to compile', entry['pat'])
1629 continue
1541 continue
1630
1542
1631 data_func = partial(
1543 data_func = partial(
1632 _process_url_func, repo_name=repo_name, entry=entry, uid=uid,
1544 _process_url_func, repo_name=repo_name, entry=entry, uid=uid,
1633 return_raw_data=True)
1545 return_raw_data=True)
1634
1546
1635 for match_obj in pattern.finditer(text_string):
1547 for match_obj in pattern.finditer(text_string):
1636 issues_data.append(data_func(match_obj))
1548 issues_data.append(data_func(match_obj))
1637
1549
1638 url_func = partial(
1550 url_func = partial(
1639 _process_url_func, repo_name=repo_name, entry=entry, uid=uid,
1551 _process_url_func, repo_name=repo_name, entry=entry, uid=uid,
1640 link_format=link_format)
1552 link_format=link_format)
1641
1553
1642 new_text = pattern.sub(url_func, new_text)
1554 new_text = pattern.sub(url_func, new_text)
1643 log.debug('processed prefix:uid `%s`', uid)
1555 log.debug('processed prefix:uid `%s`', uid)
1644
1556
1645 # finally use global replace, eg !123 -> pr-link, those will not catch
1557 # finally use global replace, eg !123 -> pr-link, those will not catch
1646 # if already similar pattern exists
1558 # if already similar pattern exists
1647 server_url = '${scheme}://${netloc}'
1559 server_url = '${scheme}://${netloc}'
1648 pr_entry = {
1560 pr_entry = {
1649 'pref': '!',
1561 'pref': '!',
1650 'url': server_url + '/_admin/pull-requests/${id}',
1562 'url': server_url + '/_admin/pull-requests/${id}',
1651 'desc': 'Pull Request !${id}',
1563 'desc': 'Pull Request !${id}',
1652 'hovercard_url': server_url + '/_hovercard/pull_request/${id}'
1564 'hovercard_url': server_url + '/_hovercard/pull_request/${id}'
1653 }
1565 }
1654 pr_url_func = partial(
1566 pr_url_func = partial(
1655 _process_url_func, repo_name=repo_name, entry=pr_entry, uid=None,
1567 _process_url_func, repo_name=repo_name, entry=pr_entry, uid=None,
1656 link_format=link_format+'+hovercard')
1568 link_format=link_format+'+hovercard')
1657 new_text = re.compile(r'(?:(?:^!)|(?: !))(\d+)').sub(pr_url_func, new_text)
1569 new_text = re.compile(r'(?:(?:^!)|(?: !))(\d+)').sub(pr_url_func, new_text)
1658 log.debug('processed !pr pattern')
1570 log.debug('processed !pr pattern')
1659
1571
1660 return new_text, issues_data
1572 return new_text, issues_data
1661
1573
1662
1574
1663 def urlify_commit_message(commit_text, repository=None, active_pattern_entries=None):
1575 def urlify_commit_message(commit_text, repository=None, active_pattern_entries=None):
1664 """
1576 """
1665 Parses given text message and makes proper links.
1577 Parses given text message and makes proper links.
1666 issues are linked to given issue-server, and rest is a commit link
1578 issues are linked to given issue-server, and rest is a commit link
1667 """
1579 """
1668 def escaper(_text):
1580 def escaper(_text):
1669 return _text.replace('<', '&lt;').replace('>', '&gt;')
1581 return _text.replace('<', '&lt;').replace('>', '&gt;')
1670
1582
1671 new_text = escaper(commit_text)
1583 new_text = escaper(commit_text)
1672
1584
1673 # extract http/https links and make them real urls
1585 # extract http/https links and make them real urls
1674 new_text = urlify_text(new_text, safe=False)
1586 new_text = urlify_text(new_text, safe=False)
1675
1587
1676 # urlify commits - extract commit ids and make link out of them, if we have
1588 # urlify commits - extract commit ids and make link out of them, if we have
1677 # the scope of repository present.
1589 # the scope of repository present.
1678 if repository:
1590 if repository:
1679 new_text = urlify_commits(new_text, repository)
1591 new_text = urlify_commits(new_text, repository)
1680
1592
1681 # process issue tracker patterns
1593 # process issue tracker patterns
1682 new_text, issues = process_patterns(new_text, repository or '',
1594 new_text, issues = process_patterns(new_text, repository or '',
1683 active_entries=active_pattern_entries)
1595 active_entries=active_pattern_entries)
1684
1596
1685 return literal(new_text)
1597 return literal(new_text)
1686
1598
1687
1599
1688 def render_binary(repo_name, file_obj):
1600 def render_binary(repo_name, file_obj):
1689 """
1601 """
1690 Choose how to render a binary file
1602 Choose how to render a binary file
1691 """
1603 """
1692
1604
1693 filename = file_obj.name
1605 filename = file_obj.name
1694
1606
1695 # images
1607 # images
1696 for ext in ['*.png', '*.jpg', '*.ico', '*.gif']:
1608 for ext in ['*.png', '*.jpg', '*.ico', '*.gif']:
1697 if fnmatch.fnmatch(filename, pat=ext):
1609 if fnmatch.fnmatch(filename, pat=ext):
1698 alt = escape(filename)
1610 alt = escape(filename)
1699 src = route_path(
1611 src = route_path(
1700 'repo_file_raw', repo_name=repo_name,
1612 'repo_file_raw', repo_name=repo_name,
1701 commit_id=file_obj.commit.raw_id,
1613 commit_id=file_obj.commit.raw_id,
1702 f_path=file_obj.path)
1614 f_path=file_obj.path)
1703 return literal(
1615 return literal(
1704 '<img class="rendered-binary" alt="{}" src="{}">'.format(alt, src))
1616 '<img class="rendered-binary" alt="{}" src="{}">'.format(alt, src))
1705
1617
1706
1618
1707 def renderer_from_filename(filename, exclude=None):
1619 def renderer_from_filename(filename, exclude=None):
1708 """
1620 """
1709 choose a renderer based on filename, this works only for text based files
1621 choose a renderer based on filename, this works only for text based files
1710 """
1622 """
1711
1623
1712 # ipython
1624 # ipython
1713 for ext in ['*.ipynb']:
1625 for ext in ['*.ipynb']:
1714 if fnmatch.fnmatch(filename, pat=ext):
1626 if fnmatch.fnmatch(filename, pat=ext):
1715 return 'jupyter'
1627 return 'jupyter'
1716
1628
1717 is_markup = MarkupRenderer.renderer_from_filename(filename, exclude=exclude)
1629 is_markup = MarkupRenderer.renderer_from_filename(filename, exclude=exclude)
1718 if is_markup:
1630 if is_markup:
1719 return is_markup
1631 return is_markup
1720 return None
1632 return None
1721
1633
1722
1634
1723 def render(source, renderer='rst', mentions=False, relative_urls=None,
1635 def render(source, renderer='rst', mentions=False, relative_urls=None,
1724 repo_name=None):
1636 repo_name=None):
1725
1637
1726 def maybe_convert_relative_links(html_source):
1638 def maybe_convert_relative_links(html_source):
1727 if relative_urls:
1639 if relative_urls:
1728 return relative_links(html_source, relative_urls)
1640 return relative_links(html_source, relative_urls)
1729 return html_source
1641 return html_source
1730
1642
1731 if renderer == 'plain':
1643 if renderer == 'plain':
1732 return literal(
1644 return literal(
1733 MarkupRenderer.plain(source, leading_newline=False))
1645 MarkupRenderer.plain(source, leading_newline=False))
1734
1646
1735 elif renderer == 'rst':
1647 elif renderer == 'rst':
1736 if repo_name:
1648 if repo_name:
1737 # process patterns on comments if we pass in repo name
1649 # process patterns on comments if we pass in repo name
1738 source, issues = process_patterns(
1650 source, issues = process_patterns(
1739 source, repo_name, link_format='rst')
1651 source, repo_name, link_format='rst')
1740
1652
1741 return literal(
1653 return literal(
1742 '<div class="rst-block">%s</div>' %
1654 '<div class="rst-block">%s</div>' %
1743 maybe_convert_relative_links(
1655 maybe_convert_relative_links(
1744 MarkupRenderer.rst(source, mentions=mentions)))
1656 MarkupRenderer.rst(source, mentions=mentions)))
1745
1657
1746 elif renderer == 'markdown':
1658 elif renderer == 'markdown':
1747 if repo_name:
1659 if repo_name:
1748 # process patterns on comments if we pass in repo name
1660 # process patterns on comments if we pass in repo name
1749 source, issues = process_patterns(
1661 source, issues = process_patterns(
1750 source, repo_name, link_format='markdown')
1662 source, repo_name, link_format='markdown')
1751
1663
1752 return literal(
1664 return literal(
1753 '<div class="markdown-block">%s</div>' %
1665 '<div class="markdown-block">%s</div>' %
1754 maybe_convert_relative_links(
1666 maybe_convert_relative_links(
1755 MarkupRenderer.markdown(source, flavored=True,
1667 MarkupRenderer.markdown(source, flavored=True,
1756 mentions=mentions)))
1668 mentions=mentions)))
1757
1669
1758 elif renderer == 'jupyter':
1670 elif renderer == 'jupyter':
1759 return literal(
1671 return literal(
1760 '<div class="ipynb">%s</div>' %
1672 '<div class="ipynb">%s</div>' %
1761 maybe_convert_relative_links(
1673 maybe_convert_relative_links(
1762 MarkupRenderer.jupyter(source)))
1674 MarkupRenderer.jupyter(source)))
1763
1675
1764 # None means just show the file-source
1676 # None means just show the file-source
1765 return None
1677 return None
1766
1678
1767
1679
1768 def commit_status(repo, commit_id):
1680 def commit_status(repo, commit_id):
1769 return ChangesetStatusModel().get_status(repo, commit_id)
1681 return ChangesetStatusModel().get_status(repo, commit_id)
1770
1682
1771
1683
1772 def commit_status_lbl(commit_status):
1684 def commit_status_lbl(commit_status):
1773 return dict(ChangesetStatus.STATUSES).get(commit_status)
1685 return dict(ChangesetStatus.STATUSES).get(commit_status)
1774
1686
1775
1687
1776 def commit_time(repo_name, commit_id):
1688 def commit_time(repo_name, commit_id):
1777 repo = Repository.get_by_repo_name(repo_name)
1689 repo = Repository.get_by_repo_name(repo_name)
1778 commit = repo.get_commit(commit_id=commit_id)
1690 commit = repo.get_commit(commit_id=commit_id)
1779 return commit.date
1691 return commit.date
1780
1692
1781
1693
1782 def get_permission_name(key):
1694 def get_permission_name(key):
1783 return dict(Permission.PERMS).get(key)
1695 return dict(Permission.PERMS).get(key)
1784
1696
1785
1697
1786 def journal_filter_help(request):
1698 def journal_filter_help(request):
1787 _ = request.translate
1699 _ = request.translate
1788 from rhodecode.lib.audit_logger import ACTIONS
1700 from rhodecode.lib.audit_logger import ACTIONS
1789 actions = '\n'.join(textwrap.wrap(', '.join(sorted(ACTIONS.keys())), 80))
1701 actions = '\n'.join(textwrap.wrap(', '.join(sorted(ACTIONS.keys())), 80))
1790
1702
1791 return _(
1703 return _(
1792 'Example filter terms:\n' +
1704 'Example filter terms:\n' +
1793 ' repository:vcs\n' +
1705 ' repository:vcs\n' +
1794 ' username:marcin\n' +
1706 ' username:marcin\n' +
1795 ' username:(NOT marcin)\n' +
1707 ' username:(NOT marcin)\n' +
1796 ' action:*push*\n' +
1708 ' action:*push*\n' +
1797 ' ip:127.0.0.1\n' +
1709 ' ip:127.0.0.1\n' +
1798 ' date:20120101\n' +
1710 ' date:20120101\n' +
1799 ' date:[20120101100000 TO 20120102]\n' +
1711 ' date:[20120101100000 TO 20120102]\n' +
1800 '\n' +
1712 '\n' +
1801 'Actions: {actions}\n' +
1713 'Actions: {actions}\n' +
1802 '\n' +
1714 '\n' +
1803 'Generate wildcards using \'*\' character:\n' +
1715 'Generate wildcards using \'*\' character:\n' +
1804 ' "repository:vcs*" - search everything starting with \'vcs\'\n' +
1716 ' "repository:vcs*" - search everything starting with \'vcs\'\n' +
1805 ' "repository:*vcs*" - search for repository containing \'vcs\'\n' +
1717 ' "repository:*vcs*" - search for repository containing \'vcs\'\n' +
1806 '\n' +
1718 '\n' +
1807 'Optional AND / OR operators in queries\n' +
1719 'Optional AND / OR operators in queries\n' +
1808 ' "repository:vcs OR repository:test"\n' +
1720 ' "repository:vcs OR repository:test"\n' +
1809 ' "username:test AND repository:test*"\n'
1721 ' "username:test AND repository:test*"\n'
1810 ).format(actions=actions)
1722 ).format(actions=actions)
1811
1723
1812
1724
1813 def not_mapped_error(repo_name):
1725 def not_mapped_error(repo_name):
1814 from rhodecode.translation import _
1726 from rhodecode.translation import _
1815 flash(_('%s repository is not mapped to db perhaps'
1727 flash(_('%s repository is not mapped to db perhaps'
1816 ' it was created or renamed from the filesystem'
1728 ' it was created or renamed from the filesystem'
1817 ' please run the application again'
1729 ' please run the application again'
1818 ' in order to rescan repositories') % repo_name, category='error')
1730 ' in order to rescan repositories') % repo_name, category='error')
1819
1731
1820
1732
1821 def ip_range(ip_addr):
1733 def ip_range(ip_addr):
1822 from rhodecode.model.db import UserIpMap
1734 from rhodecode.model.db import UserIpMap
1823 s, e = UserIpMap._get_ip_range(ip_addr)
1735 s, e = UserIpMap._get_ip_range(ip_addr)
1824 return '%s - %s' % (s, e)
1736 return '%s - %s' % (s, e)
1825
1737
1826
1738
1827 def form(url, method='post', needs_csrf_token=True, **attrs):
1739 def form(url, method='post', needs_csrf_token=True, **attrs):
1828 """Wrapper around webhelpers.tags.form to prevent CSRF attacks."""
1740 """Wrapper around webhelpers.tags.form to prevent CSRF attacks."""
1829 if method.lower() != 'get' and needs_csrf_token:
1741 if method.lower() != 'get' and needs_csrf_token:
1830 raise Exception(
1742 raise Exception(
1831 'Forms to POST/PUT/DELETE endpoints should have (in general) a ' +
1743 'Forms to POST/PUT/DELETE endpoints should have (in general) a ' +
1832 'CSRF token. If the endpoint does not require such token you can ' +
1744 'CSRF token. If the endpoint does not require such token you can ' +
1833 'explicitly set the parameter needs_csrf_token to false.')
1745 'explicitly set the parameter needs_csrf_token to false.')
1834
1746
1835 return insecure_form(url, method=method, **attrs)
1747 return insecure_form(url, method=method, **attrs)
1836
1748
1837
1749
1838 def secure_form(form_url, method="POST", multipart=False, **attrs):
1750 def secure_form(form_url, method="POST", multipart=False, **attrs):
1839 """Start a form tag that points the action to an url. This
1751 """Start a form tag that points the action to an url. This
1840 form tag will also include the hidden field containing
1752 form tag will also include the hidden field containing
1841 the auth token.
1753 the auth token.
1842
1754
1843 The url options should be given either as a string, or as a
1755 The url options should be given either as a string, or as a
1844 ``url()`` function. The method for the form defaults to POST.
1756 ``url()`` function. The method for the form defaults to POST.
1845
1757
1846 Options:
1758 Options:
1847
1759
1848 ``multipart``
1760 ``multipart``
1849 If set to True, the enctype is set to "multipart/form-data".
1761 If set to True, the enctype is set to "multipart/form-data".
1850 ``method``
1762 ``method``
1851 The method to use when submitting the form, usually either
1763 The method to use when submitting the form, usually either
1852 "GET" or "POST". If "PUT", "DELETE", or another verb is used, a
1764 "GET" or "POST". If "PUT", "DELETE", or another verb is used, a
1853 hidden input with name _method is added to simulate the verb
1765 hidden input with name _method is added to simulate the verb
1854 over POST.
1766 over POST.
1855
1767
1856 """
1768 """
1857
1769
1858 if 'request' in attrs:
1770 if 'request' in attrs:
1859 session = attrs['request'].session
1771 session = attrs['request'].session
1860 del attrs['request']
1772 del attrs['request']
1861 else:
1773 else:
1862 raise ValueError(
1774 raise ValueError(
1863 'Calling this form requires request= to be passed as argument')
1775 'Calling this form requires request= to be passed as argument')
1864
1776
1865 _form = insecure_form(form_url, method, multipart, **attrs)
1777 _form = insecure_form(form_url, method, multipart, **attrs)
1866 token = literal(
1778 token = literal(
1867 '<input type="hidden" name="{}" value="{}">'.format(
1779 '<input type="hidden" name="{}" value="{}">'.format(
1868 csrf_token_key, get_csrf_token(session)))
1780 csrf_token_key, get_csrf_token(session)))
1869
1781
1870 return literal("%s\n%s" % (_form, token))
1782 return literal("%s\n%s" % (_form, token))
1871
1783
1872
1784
1873 def dropdownmenu(name, selected, options, enable_filter=False, **attrs):
1785 def dropdownmenu(name, selected, options, enable_filter=False, **attrs):
1874 select_html = select(name, selected, options, **attrs)
1786 select_html = select(name, selected, options, **attrs)
1875
1787
1876 select2 = """
1788 select2 = """
1877 <script>
1789 <script>
1878 $(document).ready(function() {
1790 $(document).ready(function() {
1879 $('#%s').select2({
1791 $('#%s').select2({
1880 containerCssClass: 'drop-menu %s',
1792 containerCssClass: 'drop-menu %s',
1881 dropdownCssClass: 'drop-menu-dropdown',
1793 dropdownCssClass: 'drop-menu-dropdown',
1882 dropdownAutoWidth: true%s
1794 dropdownAutoWidth: true%s
1883 });
1795 });
1884 });
1796 });
1885 </script>
1797 </script>
1886 """
1798 """
1887
1799
1888 filter_option = """,
1800 filter_option = """,
1889 minimumResultsForSearch: -1
1801 minimumResultsForSearch: -1
1890 """
1802 """
1891 input_id = attrs.get('id') or name
1803 input_id = attrs.get('id') or name
1892 extra_classes = ' '.join(attrs.pop('extra_classes', []))
1804 extra_classes = ' '.join(attrs.pop('extra_classes', []))
1893 filter_enabled = "" if enable_filter else filter_option
1805 filter_enabled = "" if enable_filter else filter_option
1894 select_script = literal(select2 % (input_id, extra_classes, filter_enabled))
1806 select_script = literal(select2 % (input_id, extra_classes, filter_enabled))
1895
1807
1896 return literal(select_html+select_script)
1808 return literal(select_html+select_script)
1897
1809
1898
1810
1899 def get_visual_attr(tmpl_context_var, attr_name):
1811 def get_visual_attr(tmpl_context_var, attr_name):
1900 """
1812 """
1901 A safe way to get a variable from visual variable of template context
1813 A safe way to get a variable from visual variable of template context
1902
1814
1903 :param tmpl_context_var: instance of tmpl_context, usually present as `c`
1815 :param tmpl_context_var: instance of tmpl_context, usually present as `c`
1904 :param attr_name: name of the attribute we fetch from the c.visual
1816 :param attr_name: name of the attribute we fetch from the c.visual
1905 """
1817 """
1906 visual = getattr(tmpl_context_var, 'visual', None)
1818 visual = getattr(tmpl_context_var, 'visual', None)
1907 if not visual:
1819 if not visual:
1908 return
1820 return
1909 else:
1821 else:
1910 return getattr(visual, attr_name, None)
1822 return getattr(visual, attr_name, None)
1911
1823
1912
1824
1913 def get_last_path_part(file_node):
1825 def get_last_path_part(file_node):
1914 if not file_node.path:
1826 if not file_node.path:
1915 return u'/'
1827 return u'/'
1916
1828
1917 path = safe_unicode(file_node.path.split('/')[-1])
1829 path = safe_unicode(file_node.path.split('/')[-1])
1918 return u'../' + path
1830 return u'../' + path
1919
1831
1920
1832
1921 def route_url(*args, **kwargs):
1833 def route_url(*args, **kwargs):
1922 """
1834 """
1923 Wrapper around pyramids `route_url` (fully qualified url) function.
1835 Wrapper around pyramids `route_url` (fully qualified url) function.
1924 """
1836 """
1925 req = get_current_request()
1837 req = get_current_request()
1926 return req.route_url(*args, **kwargs)
1838 return req.route_url(*args, **kwargs)
1927
1839
1928
1840
1929 def route_path(*args, **kwargs):
1841 def route_path(*args, **kwargs):
1930 """
1842 """
1931 Wrapper around pyramids `route_path` function.
1843 Wrapper around pyramids `route_path` function.
1932 """
1844 """
1933 req = get_current_request()
1845 req = get_current_request()
1934 return req.route_path(*args, **kwargs)
1846 return req.route_path(*args, **kwargs)
1935
1847
1936
1848
1937 def route_path_or_none(*args, **kwargs):
1849 def route_path_or_none(*args, **kwargs):
1938 try:
1850 try:
1939 return route_path(*args, **kwargs)
1851 return route_path(*args, **kwargs)
1940 except KeyError:
1852 except KeyError:
1941 return None
1853 return None
1942
1854
1943
1855
1944 def current_route_path(request, **kw):
1856 def current_route_path(request, **kw):
1945 new_args = request.GET.mixed()
1857 new_args = request.GET.mixed()
1946 new_args.update(kw)
1858 new_args.update(kw)
1947 return request.current_route_path(_query=new_args)
1859 return request.current_route_path(_query=new_args)
1948
1860
1949
1861
1950 def curl_api_example(method, args):
1862 def curl_api_example(method, args):
1951 args_json = json.dumps(OrderedDict([
1863 args_json = json.dumps(OrderedDict([
1952 ('id', 1),
1864 ('id', 1),
1953 ('auth_token', 'SECRET'),
1865 ('auth_token', 'SECRET'),
1954 ('method', method),
1866 ('method', method),
1955 ('args', args)
1867 ('args', args)
1956 ]))
1868 ]))
1957
1869
1958 return "curl {api_url} -X POST -H 'content-type:text/plain' --data-binary '{args_json}'".format(
1870 return "curl {api_url} -X POST -H 'content-type:text/plain' --data-binary '{args_json}'".format(
1959 api_url=route_url('apiv2'),
1871 api_url=route_url('apiv2'),
1960 args_json=args_json
1872 args_json=args_json
1961 )
1873 )
1962
1874
1963
1875
1964 def api_call_example(method, args):
1876 def api_call_example(method, args):
1965 """
1877 """
1966 Generates an API call example via CURL
1878 Generates an API call example via CURL
1967 """
1879 """
1968 curl_call = curl_api_example(method, args)
1880 curl_call = curl_api_example(method, args)
1969
1881
1970 return literal(
1882 return literal(
1971 curl_call +
1883 curl_call +
1972 "<br/><br/>SECRET can be found in <a href=\"{token_url}\">auth-tokens</a> page, "
1884 "<br/><br/>SECRET can be found in <a href=\"{token_url}\">auth-tokens</a> page, "
1973 "and needs to be of `api calls` role."
1885 "and needs to be of `api calls` role."
1974 .format(token_url=route_url('my_account_auth_tokens')))
1886 .format(token_url=route_url('my_account_auth_tokens')))
1975
1887
1976
1888
1977 def notification_description(notification, request):
1889 def notification_description(notification, request):
1978 """
1890 """
1979 Generate notification human readable description based on notification type
1891 Generate notification human readable description based on notification type
1980 """
1892 """
1981 from rhodecode.model.notification import NotificationModel
1893 from rhodecode.model.notification import NotificationModel
1982 return NotificationModel().make_description(
1894 return NotificationModel().make_description(
1983 notification, translate=request.translate)
1895 notification, translate=request.translate)
1984
1896
1985
1897
1986 def go_import_header(request, db_repo=None):
1898 def go_import_header(request, db_repo=None):
1987 """
1899 """
1988 Creates a header for go-import functionality in Go Lang
1900 Creates a header for go-import functionality in Go Lang
1989 """
1901 """
1990
1902
1991 if not db_repo:
1903 if not db_repo:
1992 return
1904 return
1993 if 'go-get' not in request.GET:
1905 if 'go-get' not in request.GET:
1994 return
1906 return
1995
1907
1996 clone_url = db_repo.clone_url()
1908 clone_url = db_repo.clone_url()
1997 prefix = re.split(r'^https?:\/\/', clone_url)[-1]
1909 prefix = re.split(r'^https?:\/\/', clone_url)[-1]
1998 # we have a repo and go-get flag,
1910 # we have a repo and go-get flag,
1999 return literal('<meta name="go-import" content="{} {} {}">'.format(
1911 return literal('<meta name="go-import" content="{} {} {}">'.format(
2000 prefix, db_repo.repo_type, clone_url))
1912 prefix, db_repo.repo_type, clone_url))
2001
1913
2002
1914
2003 def reviewer_as_json(*args, **kwargs):
1915 def reviewer_as_json(*args, **kwargs):
2004 from rhodecode.apps.repository.utils import reviewer_as_json as _reviewer_as_json
1916 from rhodecode.apps.repository.utils import reviewer_as_json as _reviewer_as_json
2005 return _reviewer_as_json(*args, **kwargs)
1917 return _reviewer_as_json(*args, **kwargs)
2006
1918
2007
1919
2008 def get_repo_view_type(request):
1920 def get_repo_view_type(request):
2009 route_name = request.matched_route.name
1921 route_name = request.matched_route.name
2010 route_to_view_type = {
1922 route_to_view_type = {
2011 'repo_changelog': 'commits',
1923 'repo_changelog': 'commits',
2012 'repo_commits': 'commits',
1924 'repo_commits': 'commits',
2013 'repo_files': 'files',
1925 'repo_files': 'files',
2014 'repo_summary': 'summary',
1926 'repo_summary': 'summary',
2015 'repo_commit': 'commit'
1927 'repo_commit': 'commit'
2016 }
1928 }
2017
1929
2018 return route_to_view_type.get(route_name)
1930 return route_to_view_type.get(route_name)
2019
1931
2020
1932
2021 def is_active(menu_entry, selected):
1933 def is_active(menu_entry, selected):
2022 """
1934 """
2023 Returns active class for selecting menus in templates
1935 Returns active class for selecting menus in templates
2024 <li class=${h.is_active('settings', current_active)}></li>
1936 <li class=${h.is_active('settings', current_active)}></li>
2025 """
1937 """
2026 if not isinstance(menu_entry, list):
1938 if not isinstance(menu_entry, list):
2027 menu_entry = [menu_entry]
1939 menu_entry = [menu_entry]
2028
1940
2029 if selected in menu_entry:
1941 if selected in menu_entry:
2030 return "active"
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 # -*- coding: utf-8 -*-
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 # Copyright (C) 2010-2019 RhodeCode GmbH
880 # Copyright (C) 2010-2019 RhodeCode GmbH
4 #
881 #
5 # This program is free software: you can redistribute it and/or modify
882 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
883 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
884 # (only), as published by the Free Software Foundation.
8 #
885 #
9 # This program is distributed in the hope that it will be useful,
886 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
887 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
888 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
889 # GNU General Public License for more details.
13 #
890 #
14 # You should have received a copy of the GNU Affero General Public License
891 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
892 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
893 #
17 # This program is dual-licensed. If you wish to learn more about the
894 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
895 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
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
905 def __init__(self, pager, collection):
23 from webhelpers.paginate import PageURL
906 self.pager = pager
24 from webhelpers2.html import literal, HTML
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 Custom pager to match rendering style with paginator
991 Custom pager to match rendering style with paginator
30 """
992 """
31
993
32 def _get_pos(self, cur_page, max_page, items):
994 def __init__(self, collection, page=1, items_per_page=20, item_count=None,
33 edge = (items / 2) + 1
995 url_maker=None, **kwargs):
34 if (cur_page <= edge):
996 """
35 radius = max(items / 2, items - cur_page)
997 Special type of pager. We intercept collection to wrap it in our custom
36 elif (max_page - cur_page) < edge:
998 logic instead of using wrapper_class
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):
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
1001 super(Page, self).__init__(collection=collection, page=page,
52 A "re" (regular expressions) match object containing the
1002 items_per_page=items_per_page, item_count=item_count,
53 radius of linked pages around the current page in
1003 wrapper_class=None, url_maker=url_maker, **kwargs)
54 regexp_match.group(1) as a string
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
1006 class SqlPage(CustomPager):
63 # e.g. '1 .. 5 6 [7] 8 9 .. 12'
1007 """
64 # -> leftmost_page = 5
1008 Custom pager to match rendering style with paginator
65 # -> rightmost_page = 9
1009 """
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))
75
1010
76 # Insert dots if there are pages between the first page
1011 def __init__(self, collection, page=1, items_per_page=20, item_count=None,
77 # and the currently displayed page range
1012 url_maker=None, **kwargs):
78 if leftmost_page - self.first_page > 1:
1013 """
79 # Wrap in a SPAN tag if nolink_attr is set
1014 Special type of pager. We intercept collection to wrap it in our custom
80 text = '..'
1015 logic instead of using wrapper_class
81 if self.dotdot_attr:
1016 """
82 text = HTML.span(c=text, **self.dotdot_attr)
1017 collection = SqlalchemyOrmWrapper(self, collection)
83 nav_items.append(text)
84
1018
85 for thispage in xrange(leftmost_page, rightmost_page + 1):
1019 super(SqlPage, self).__init__(collection=collection, page=page,
86 # Hilight the current page number and do not use a link
1020 items_per_page=items_per_page, item_count=item_count,
87 if thispage == self.page:
1021 wrapper_class=None, url_maker=url_maker, **kwargs)
88 text = '%s' % (thispage,)
1022
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))
97
1023
98 # Insert dots if there are pages between the displayed
1024 class RepoCommitsWrapper(object):
99 # page numbers and the end of the page range
1025 """Wrapper class to access elements of a collection."""
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)
106
1026
107 # Create a link to the very last page (unless we are on the last
1027 def __init__(self, pager, collection):
108 # page or there would be no need to insert '..' spacers)
1028 self.pager = pager
109 if self.page != self.last_page and rightmost_page < self.last_page:
1029 self.collection = collection
110 nav_items.append(self._pagerlink(self.last_page, self.last_page))
111
1030
112 ## prerender links
1031 def __getitem__(self, range):
113 #_page_link = url.current()
1032 cur_page = self.pager.page
114 #nav_items.append(literal('<link rel="prerender" href="%s?page=%s">' % (_page_link, str(int(self.page)+1))))
1033 items_per_page = self.pager.items_per_page
115 #nav_items.append(literal('<link rel="prefetch" href="%s?page=%s">' % (_page_link, str(int(self.page)+1))))
1034 first_item = max(0, (len(self.collection) - (cur_page * items_per_page)))
116 return self.separator.join(nav_items)
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',
1038 def __len__(self):
119 show_if_single_page=False, separator=' ', onclick=None,
1039 return len(self.collection)
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):
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
1042 class RepoPage(CustomPager):
136 if self.page_count == 0 or (self.page_count == 1 and not show_if_single_page):
1043 """
137 return ''
1044 Create a "RepoPage" instance. special pager for paging repository
138
1045 """
139 from string import Template
140 # Replace ~...~ in token format by range of pages
141 result = re.sub(r'~(\d+)~', self._range, format)
142
1046
143 # Interpolate '%' variables
1047 def __init__(self, collection, page=1, items_per_page=20, item_count=None,
144 result = Template(result).safe_substitute({
1048 url_maker=None, **kwargs):
145 'first_page': self.first_page,
1049 """
146 'last_page': self.last_page,
1050 Special type of pager. We intercept collection to wrap it in our custom
147 'page': self.page,
1051 logic instead of using wrapper_class
148 'page_count': self.page_count,
1052 """
149 'items_per_page': self.items_per_page,
1053 collection = RepoCommitsWrapper(self, collection)
150 'first_item': self.first_item,
1054 super(RepoPage, self).__init__(collection=collection, page=page,
151 'last_item': self.last_item,
1055 items_per_page=items_per_page, item_count=item_count,
152 'item_count': self.item_count,
1056 wrapper_class=None, url_maker=url_maker, **kwargs)
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)
@@ -1,816 +1,822 b''
1 // navigation.less
1 // navigation.less
2 // For use in RhodeCode applications;
2 // For use in RhodeCode applications;
3 // see style guide documentation for guidelines.
3 // see style guide documentation for guidelines.
4
4
5 // TOP MAIN DARK NAVIGATION
5 // TOP MAIN DARK NAVIGATION
6
6
7 .header .main_nav.horizontal-list {
7 .header .main_nav.horizontal-list {
8 float: right;
8 float: right;
9 color: @grey4;
9 color: @grey4;
10 > li {
10 > li {
11 a {
11 a {
12 color: @grey4;
12 color: @grey4;
13 }
13 }
14 }
14 }
15 }
15 }
16
16
17 // HEADER NAVIGATION
17 // HEADER NAVIGATION
18
18
19 .horizontal-list {
19 .horizontal-list {
20 display: block;
20 display: block;
21 margin: 0;
21 margin: 0;
22 padding: 0;
22 padding: 0;
23 -webkit-padding-start: 0;
23 -webkit-padding-start: 0;
24 text-align: left;
24 text-align: left;
25 font-size: @navigation-fontsize;
25 font-size: @navigation-fontsize;
26 color: @grey6;
26 color: @grey6;
27 z-index:10;
27 z-index:10;
28
28
29 li {
29 li {
30 line-height: 1em;
30 line-height: 1em;
31 list-style-type: none;
31 list-style-type: none;
32 margin: 0 20px 0 0;
32 margin: 0 20px 0 0;
33
33
34 a {
34 a {
35 padding: 0 .5em;
35 padding: 0 .5em;
36
36
37 &.menu_link_notifications {
37 &.menu_link_notifications {
38 .pill(7px,@rcblue);
38 .pill(7px,@rcblue);
39 display: inline;
39 display: inline;
40 margin: 0 7px 0 .7em;
40 margin: 0 7px 0 .7em;
41 font-size: @basefontsize;
41 font-size: @basefontsize;
42 color: white;
42 color: white;
43
43
44 &.empty {
44 &.empty {
45 background-color: @grey4;
45 background-color: @grey4;
46 }
46 }
47
47
48 &:hover {
48 &:hover {
49 background-color: @rcdarkblue;
49 background-color: @rcdarkblue;
50 }
50 }
51 }
51 }
52 }
52 }
53 .pill_container {
53 .pill_container {
54 margin: 1.25em 0px 0px 0px;
54 margin: 1.25em 0px 0px 0px;
55 float: right;
55 float: right;
56 }
56 }
57
57
58 &#quick_login_li {
58 &#quick_login_li {
59 &:hover {
59 &:hover {
60 color: @grey5;
60 color: @grey5;
61 }
61 }
62
62
63 a.menu_link_notifications {
63 a.menu_link_notifications {
64 color: white;
64 color: white;
65 }
65 }
66
66
67 .user {
67 .user {
68 padding-bottom: 10px;
68 padding-bottom: 10px;
69 }
69 }
70 }
70 }
71
71
72 &:before { content: none; }
72 &:before { content: none; }
73
73
74 &:last-child {
74 &:last-child {
75 .menulabel {
75 .menulabel {
76 padding-right: 0;
76 padding-right: 0;
77 border-right: none;
77 border-right: none;
78
78
79 .show_more {
79 .show_more {
80 padding-right: 0;
80 padding-right: 0;
81 }
81 }
82 }
82 }
83
83
84 &> a {
84 &> a {
85 border-bottom: none;
85 border-bottom: none;
86 }
86 }
87 }
87 }
88
88
89 &.open {
89 &.open {
90
90
91 a {
91 a {
92 color: white;
92 color: white;
93 }
93 }
94 }
94 }
95
95
96 &:focus {
96 &:focus {
97 outline: none;
97 outline: none;
98 }
98 }
99
99
100 ul li {
100 ul li {
101 display: block;
101 display: block;
102
102
103 &:last-child> a {
103 &:last-child> a {
104 border-bottom: none;
104 border-bottom: none;
105 }
105 }
106
106
107 ul li:last-child a {
107 ul li:last-child a {
108 /* we don't expect more then 3 levels of submenu and the third
108 /* we don't expect more then 3 levels of submenu and the third
109 level can have different html structure */
109 level can have different html structure */
110 border-bottom: none;
110 border-bottom: none;
111 }
111 }
112 }
112 }
113 }
113 }
114
114
115 > li {
115 > li {
116 float: left;
116 float: left;
117 display: block;
117 display: block;
118 padding: 0;
118 padding: 0;
119
119
120 > a,
120 > a,
121 &.has_select2 a {
121 &.has_select2 a {
122 display: block;
122 display: block;
123 padding: 10px 0;
123 padding: 10px 0;
124 }
124 }
125
125
126 .menulabel {
126 .menulabel {
127 line-height: 1em;
127 line-height: 1em;
128 // for this specifically we do not use a variable
128 // for this specifically we do not use a variable
129 }
129 }
130
130
131 .menulink-counter {
131 .menulink-counter {
132 border: 1px solid @grey2;
132 border: 1px solid @grey2;
133 border-radius: @border-radius;
133 border-radius: @border-radius;
134 background: @grey7;
134 background: @grey7;
135 display: inline-block;
135 display: inline-block;
136 padding: 0px 4px;
136 padding: 0px 4px;
137 text-align: center;
137 text-align: center;
138 font-size: 12px;
138 font-size: 12px;
139 }
139 }
140
140
141 .pr_notifications {
141 .pr_notifications {
142 padding-left: .5em;
142 padding-left: .5em;
143 }
143 }
144
144
145 .pr_notifications + .menulabel {
145 .pr_notifications + .menulabel {
146 display:inline;
146 display:inline;
147 padding-left: 0;
147 padding-left: 0;
148 }
148 }
149
149
150 &:hover,
150 &:hover,
151 &.open,
151 &.open,
152 &.active {
152 &.active {
153 a {
153 a {
154 color: @rcblue;
154 color: @rcblue;
155 }
155 }
156 }
156 }
157 }
157 }
158
158
159 pre {
159 pre {
160 margin: 0;
160 margin: 0;
161 padding: 0;
161 padding: 0;
162 }
162 }
163
163
164 .select2-container,
164 .select2-container,
165 .menulink.childs {
165 .menulink.childs {
166 position: relative;
166 position: relative;
167 }
167 }
168
168
169 .menulink {
169 .menulink {
170 &.disabled {
170 &.disabled {
171 color: @grey3;
171 color: @grey3;
172 cursor: default;
172 cursor: default;
173 opacity: 0.5;
173 opacity: 0.5;
174 }
174 }
175 }
175 }
176
176
177 #quick_login {
177 #quick_login {
178
178
179 li a {
179 li a {
180 padding: .5em 0;
180 padding: .5em 0;
181 border-bottom: none;
181 border-bottom: none;
182 color: @grey2;
182 color: @grey2;
183
183
184 &:hover { color: @rcblue; }
184 &:hover { color: @rcblue; }
185 }
185 }
186 }
186 }
187
187
188 #quick_login_link {
188 #quick_login_link {
189 display: inline-block;
189 display: inline-block;
190
190
191 .gravatar {
191 .gravatar {
192 border: 1px solid @grey5;
192 border: 1px solid @grey5;
193 }
193 }
194
194
195 .gravatar-login {
195 .gravatar-login {
196 height: 20px;
196 height: 20px;
197 width: 20px;
197 width: 20px;
198 margin: -8px 0;
198 margin: -8px 0;
199 padding: 0;
199 padding: 0;
200 }
200 }
201
201
202 &:hover .user {
202 &:hover .user {
203 color: @grey6;
203 color: @grey6;
204 }
204 }
205 }
205 }
206 }
206 }
207 .header .horizontal-list {
207 .header .horizontal-list {
208
208
209 li {
209 li {
210
210
211 &#quick_login_li {
211 &#quick_login_li {
212 padding-left: .5em;
212 padding-left: .5em;
213 margin-right: 0px;
213 margin-right: 0px;
214
214
215 &:hover #quick_login_link {
215 &:hover #quick_login_link {
216 color: inherit;
216 color: inherit;
217 }
217 }
218
218
219 .menu_link_user {
219 .menu_link_user {
220 padding: 0 2px;
220 padding: 0 2px;
221 }
221 }
222 }
222 }
223 list-style-type: none;
223 list-style-type: none;
224 }
224 }
225
225
226 > li {
226 > li {
227
227
228 a {
228 a {
229 padding: 18px 0 12px 0;
229 padding: 18px 0 12px 0;
230 color: @nav-grey;
230 color: @nav-grey;
231
231
232 &.menu_link_notifications {
232 &.menu_link_notifications {
233 padding: 1px 8px;
233 padding: 1px 8px;
234 }
234 }
235 }
235 }
236
236
237 &:hover,
237 &:hover,
238 &.open,
238 &.open,
239 &.active {
239 &.active {
240 .pill_container a {
240 .pill_container a {
241 // don't select text for the pill container, it has it' own
241 // don't select text for the pill container, it has it' own
242 // hover behaviour
242 // hover behaviour
243 color: @nav-grey;
243 color: @nav-grey;
244 }
244 }
245 }
245 }
246
246
247 &:hover,
247 &:hover,
248 &.open,
248 &.open,
249 &.active {
249 &.active {
250 a {
250 a {
251 color: @grey6;
251 color: @grey6;
252 }
252 }
253 }
253 }
254
254
255 .select2-dropdown-open a {
255 .select2-dropdown-open a {
256 color: @grey6;
256 color: @grey6;
257 }
257 }
258
258
259 .repo-switcher {
259 .repo-switcher {
260 padding-left: 0;
260 padding-left: 0;
261
261
262 .menulabel {
262 .menulabel {
263 padding-left: 0;
263 padding-left: 0;
264 }
264 }
265 }
265 }
266 }
266 }
267
267
268 li ul li {
268 li ul li {
269 background-color:@grey2;
269 background-color:@grey2;
270
270
271 a {
271 a {
272 padding: .5em 0;
272 padding: .5em 0;
273 border-bottom: @border-thickness solid @border-default-color;
273 border-bottom: @border-thickness solid @border-default-color;
274 color: @grey6;
274 color: @grey6;
275 }
275 }
276
276
277 &:last-child a, &.last a{
277 &:last-child a, &.last a{
278 border-bottom: none;
278 border-bottom: none;
279 }
279 }
280
280
281 &:hover {
281 &:hover {
282 background-color: @grey3;
282 background-color: @grey3;
283 }
283 }
284 }
284 }
285
285
286 .submenu {
286 .submenu {
287 margin-top: 5px;
287 margin-top: 5px;
288 }
288 }
289 }
289 }
290
290
291 // SUBMENUS
291 // SUBMENUS
292 .navigation .submenu {
292 .navigation .submenu {
293 display: none;
293 display: none;
294 }
294 }
295
295
296 .navigation li.open {
296 .navigation li.open {
297 .submenu {
297 .submenu {
298 display: block;
298 display: block;
299 }
299 }
300 }
300 }
301
301
302 .navigation li:last-child .submenu {
302 .navigation li:last-child .submenu {
303 right: auto;
303 right: auto;
304 left: 0;
304 left: 0;
305 border: 1px solid @grey5;
305 border: 1px solid @grey5;
306 background: @white;
306 background: @white;
307 box-shadow: @dropdown-shadow;
307 box-shadow: @dropdown-shadow;
308 }
308 }
309
309
310 .submenu {
310 .submenu {
311 position: absolute;
311 position: absolute;
312 top: 100%;
312 top: 100%;
313 left: 0;
313 left: 0;
314 min-width: 180px;
314 min-width: 180px;
315 margin: 2px 0 0;
315 margin: 2px 0 0;
316 padding: 0;
316 padding: 0;
317 text-align: left;
317 text-align: left;
318 font-family: @text-light;
318 font-family: @text-light;
319 border-radius: @border-radius;
319 border-radius: @border-radius;
320 z-index: 20;
320 z-index: 20;
321
321
322 li {
322 li {
323 display: block;
323 display: block;
324 margin: 0;
324 margin: 0;
325 padding: 0 .5em;
325 padding: 0 .5em;
326 line-height: 1em;
326 line-height: 1em;
327 color: @grey3;
327 color: @grey3;
328 background-color: @white;
328 background-color: @white;
329 list-style-type: none;
329 list-style-type: none;
330
330
331 a {
331 a {
332 display: block;
332 display: block;
333 width: 100%;
333 width: 100%;
334 padding: .5em 0;
334 padding: .5em 0;
335 border-right: none;
335 border-right: none;
336 border-bottom: @border-thickness solid white;
336 border-bottom: @border-thickness solid white;
337 color: @grey3;
337 color: @grey3;
338 }
338 }
339
339
340 ul {
340 ul {
341 display: none;
341 display: none;
342 position: absolute;
342 position: absolute;
343 top: 0;
343 top: 0;
344 right: 100%;
344 right: 100%;
345 padding: 0;
345 padding: 0;
346 z-index: 30;
346 z-index: 30;
347 }
347 }
348 &:hover {
348 &:hover {
349 background-color: @grey7;
349 background-color: @grey7;
350 -webkit-transition: background .3s;
350 -webkit-transition: background .3s;
351 -moz-transition: background .3s;
351 -moz-transition: background .3s;
352 -o-transition: background .3s;
352 -o-transition: background .3s;
353 transition: background .3s;
353 transition: background .3s;
354
354
355 ul {
355 ul {
356 display: block;
356 display: block;
357 }
357 }
358 }
358 }
359 }
359 }
360
360
361 }
361 }
362
362
363
363
364
364
365
365
366 // repo dropdown
366 // repo dropdown
367 .quick_repo_menu {
367 .quick_repo_menu {
368 width: 15px;
368 width: 15px;
369 text-align: center;
369 text-align: center;
370 position: relative;
370 position: relative;
371 cursor: pointer;
371 cursor: pointer;
372
372
373 div {
373 div {
374 overflow: visible !important;
374 overflow: visible !important;
375 }
375 }
376
376
377 &.sorting {
377 &.sorting {
378 cursor: auto;
378 cursor: auto;
379 }
379 }
380
380
381 &:hover {
381 &:hover {
382 .menu_items_container {
382 .menu_items_container {
383 position: absolute;
383 position: absolute;
384 display: block;
384 display: block;
385 }
385 }
386 .menu_items {
386 .menu_items {
387 display: block;
387 display: block;
388 }
388 }
389 }
389 }
390
390
391 i {
391 i {
392 margin: 0;
392 margin: 0;
393 color: @grey4;
393 color: @grey4;
394 }
394 }
395
395
396 .menu_items_container {
396 .menu_items_container {
397 position: absolute;
397 position: absolute;
398 top: 0;
398 top: 0;
399 left: 100%;
399 left: 100%;
400 margin: 0;
400 margin: 0;
401 padding: 0;
401 padding: 0;
402 list-style: none;
402 list-style: none;
403 background-color: @grey6;
403 background-color: @grey6;
404 z-index: 999;
404 z-index: 999;
405 text-align: left;
405 text-align: left;
406
406
407 a {
407 a {
408 color: @grey2;
408 color: @grey2;
409 }
409 }
410
410
411 ul.menu_items {
411 ul.menu_items {
412 margin: 0;
412 margin: 0;
413 padding: 0;
413 padding: 0;
414 }
414 }
415
415
416 li {
416 li {
417 margin: 0;
417 margin: 0;
418 padding: 0;
418 padding: 0;
419 line-height: 1em;
419 line-height: 1em;
420 list-style-type: none;
420 list-style-type: none;
421
421
422 a {
422 a {
423 display: block;
423 display: block;
424 height: 16px;
424 height: 16px;
425 padding: 8px; //must add up to td height (28px)
425 padding: 8px; //must add up to td height (28px)
426 width: 120px; // set width
426 width: 120px; // set width
427
427
428 &:hover {
428 &:hover {
429 background-color: @grey5;
429 background-color: @grey5;
430 -webkit-transition: background .3s;
430 -webkit-transition: background .3s;
431 -moz-transition: background .3s;
431 -moz-transition: background .3s;
432 -o-transition: background .3s;
432 -o-transition: background .3s;
433 transition: background .3s;
433 transition: background .3s;
434 }
434 }
435 }
435 }
436 }
436 }
437 }
437 }
438 }
438 }
439
439
440
440
441 // new objects main action
441 // new objects main action
442 .action-menu {
442 .action-menu {
443 left: auto;
443 left: auto;
444 right: 0;
444 right: 0;
445 padding: 12px;
445 padding: 12px;
446 z-index: 999;
446 z-index: 999;
447 overflow: hidden;
447 overflow: hidden;
448 background-color: #fff;
448 background-color: #fff;
449 border: 1px solid @grey5;
449 border: 1px solid @grey5;
450 color: @grey2;
450 color: @grey2;
451 box-shadow: @dropdown-shadow;
451 box-shadow: @dropdown-shadow;
452
452
453 .submenu-title {
453 .submenu-title {
454 font-weight: bold;
454 font-weight: bold;
455 }
455 }
456
456
457 .submenu-title:not(:first-of-type) {
457 .submenu-title:not(:first-of-type) {
458 padding-top: 10px;
458 padding-top: 10px;
459 }
459 }
460
460
461 &.submenu {
461 &.submenu {
462 min-width: 200px;
462 min-width: 200px;
463
463
464 ol {
464 ol {
465 padding:0;
465 padding:0;
466 }
466 }
467
467
468 li {
468 li {
469 display: block;
469 display: block;
470 margin: 0;
470 margin: 0;
471 padding: .2em .5em;
471 padding: .2em .5em;
472 line-height: 1em;
472 line-height: 1em;
473
473
474 background-color: #fff;
474 background-color: #fff;
475 list-style-type: none;
475 list-style-type: none;
476
476
477 a {
477 a {
478 padding: 4px;
478 padding: 4px;
479 color: @grey4 !important;
479 color: @grey4 !important;
480 border-bottom: none;
480 border-bottom: none;
481 }
481 }
482 }
482 }
483 li:not(.submenu-title) a:hover{
483 li:not(.submenu-title) a:hover{
484 color: @grey2 !important;
484 color: @grey2 !important;
485 }
485 }
486 }
486 }
487 }
487 }
488
488
489
489
490 // Header Repository Switcher
490 // Header Repository Switcher
491 // Select2 Dropdown
491 // Select2 Dropdown
492 #select2-drop.select2-drop.repo-switcher-dropdown {
492 #select2-drop.select2-drop.repo-switcher-dropdown {
493 width: auto !important;
493 width: auto !important;
494 margin-top: 5px;
494 margin-top: 5px;
495 padding: 1em 0;
495 padding: 1em 0;
496 text-align: left;
496 text-align: left;
497 .border-radius-bottom(@border-radius);
497 .border-radius-bottom(@border-radius);
498 border-color: transparent;
498 border-color: transparent;
499 color: @grey6;
499 color: @grey6;
500 background-color: @grey2;
500 background-color: @grey2;
501
501
502 input {
502 input {
503 min-width: 90%;
503 min-width: 90%;
504 }
504 }
505
505
506 ul.select2-result-sub {
506 ul.select2-result-sub {
507
507
508 li {
508 li {
509 line-height: 1em;
509 line-height: 1em;
510
510
511 &:hover,
511 &:hover,
512 &.select2-highlighted {
512 &.select2-highlighted {
513 background-color: @grey3;
513 background-color: @grey3;
514 }
514 }
515 }
515 }
516
516
517 &:before { content: none; }
517 &:before { content: none; }
518 }
518 }
519
519
520 ul.select2-results {
520 ul.select2-results {
521 min-width: 200px;
521 min-width: 200px;
522 margin: 0;
522 margin: 0;
523 padding: 0;
523 padding: 0;
524 list-style-type: none;
524 list-style-type: none;
525 overflow-x: visible;
525 overflow-x: visible;
526 overflow-y: scroll;
526 overflow-y: scroll;
527
527
528 li {
528 li {
529 padding: 0 8px;
529 padding: 0 8px;
530 line-height: 1em;
530 line-height: 1em;
531 color: @grey6;
531 color: @grey6;
532
532
533 &>.select2-result-label {
533 &>.select2-result-label {
534 padding: 8px 0;
534 padding: 8px 0;
535 border-bottom: @border-thickness solid @grey3;
535 border-bottom: @border-thickness solid @grey3;
536 white-space: nowrap;
536 white-space: nowrap;
537 color: @grey5;
537 color: @grey5;
538 cursor: pointer;
538 cursor: pointer;
539 }
539 }
540
540
541 &.select2-result-with-children {
541 &.select2-result-with-children {
542 margin: 0;
542 margin: 0;
543 padding: 0;
543 padding: 0;
544 }
544 }
545
545
546 &.select2-result-unselectable > .select2-result-label {
546 &.select2-result-unselectable > .select2-result-label {
547 margin: 0 8px;
547 margin: 0 8px;
548 }
548 }
549
549
550 }
550 }
551 }
551 }
552
552
553 ul.select2-result-sub {
553 ul.select2-result-sub {
554 margin: 0;
554 margin: 0;
555 padding: 0;
555 padding: 0;
556
556
557 li {
557 li {
558 display: block;
558 display: block;
559 margin: 0;
559 margin: 0;
560 border-right: none;
560 border-right: none;
561 line-height: 1em;
561 line-height: 1em;
562 font-family: @text-light;
562 font-family: @text-light;
563 color: @grey2;
563 color: @grey2;
564 list-style-type: none;
564 list-style-type: none;
565
565
566 &:hover {
566 &:hover {
567 background-color: @grey3;
567 background-color: @grey3;
568 }
568 }
569 }
569 }
570 }
570 }
571 }
571 }
572
572
573
573
574 #context-bar {
574 #context-bar {
575 display: block;
575 display: block;
576 margin: 0 auto 20px 0;
576 margin: 0 auto 20px 0;
577 padding: 0 @header-padding;
577 padding: 0 @header-padding;
578 background-color: @grey7;
578 background-color: @grey7;
579 border-bottom: 1px solid @grey5;
579 border-bottom: 1px solid @grey5;
580
580
581 .clear {
581 .clear {
582 clear: both;
582 clear: both;
583 }
583 }
584 }
584 }
585
585
586 ul#context-pages {
586 ul#context-pages {
587 li {
587 li {
588 list-style-type: none;
588 list-style-type: none;
589
589
590 a {
590 a {
591 color: @grey2;
591 color: @grey2;
592
592
593 &:hover {
593 &:hover {
594 color: @grey1;
594 color: @grey1;
595 }
595 }
596 }
596 }
597
597
598 &.active {
598 &.active {
599 // special case, non-variable color
599 // special case, non-variable color
600 border-bottom: 2px solid @rcblue;
600 border-bottom: 2px solid @rcblue;
601
601
602 a {
602 a {
603 color: @rcblue;
603 color: @rcblue;
604 }
604 }
605 }
605 }
606 }
606 }
607 }
607 }
608
608
609 // PAGINATION
609 // PAGINATION
610
610
611 .pagination {
611 .pagination {
612 border: @border-thickness solid @grey5;
612 border: @border-thickness solid @grey5;
613 color: @grey2;
613 color: @grey2;
614 box-shadow: @button-shadow;
614 box-shadow: @button-shadow;
615
615
616 .current {
616 .current {
617 color: @grey4;
617 color: @grey4;
618 }
618 }
619 }
619 }
620
620
621 .dataTables_processing {
621 .dataTables_processing {
622 text-align: center;
622 text-align: center;
623 font-size: 1.1em;
623 font-size: 1.1em;
624 position: relative;
624 position: relative;
625 top: 95px;
625 top: 95px;
626 }
626 }
627
627
628 .dataTables_paginate, .pagination-wh {
628 .dataTables_paginate,
629 text-align: left;
629 .pagination-wh {
630 text-align: center;
630 display: inline-block;
631 display: inline-block;
631 border-left: 1px solid @grey5;
632 border-left: 1px solid @grey5;
632 float: none;
633 float: none;
633 overflow: hidden;
634 overflow: hidden;
634 box-shadow: @button-shadow;
635 box-shadow: @button-shadow;
635
636
636 .paginate_button, .pager_curpage,
637 .paginate_button, .pager_curpage,
637 .pager_link, .pg-previous, .pg-next, .pager_dotdot {
638 .pager_link, .pg-previous, .pg-next, .pager_dotdot {
638 display: inline-block;
639 display: inline-block;
639 padding: @menupadding/4 @menupadding;
640 padding: @menupadding/4 @menupadding;
640 border: 1px solid @grey5;
641 border: 1px solid @grey5;
641 border-left: 0;
642 margin-left: -1px;
642 color: @grey2;
643 color: @grey2;
643 cursor: pointer;
644 cursor: pointer;
644 float: left;
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 &:hover {
652 &:hover {
647 color: @rcdarkblue;
653 color: @rcdarkblue;
648 }
654 }
649 }
655 }
650
656
651 .paginate_button.disabled,
657 .paginate_button.disabled,
652 .disabled {
658 .disabled {
653 color: @grey3;
659 color: @grey3;
654 cursor: default;
660 cursor: default;
655 opacity: 0.5;
661 opacity: 0.5;
656 }
662 }
657
663
658 .paginate_button.current, .pager_curpage {
664 .paginate_button.current, .pager_curpage {
659 background: @rcblue;
665 background: @rcblue;
660 border-color: @rcblue;
666 border-color: @rcblue;
661 color: @white;
667 color: @white;
662 }
668 }
663
669
664 .ellipsis {
670 .ellipsis {
665 display: inline-block;
671 display: inline-block;
666 text-align: left;
672 text-align: left;
667 padding: @menupadding/4 @menupadding;
673 padding: @menupadding/4 @menupadding;
668 border: 1px solid @grey5;
674 border: 1px solid @grey5;
669 border-left: 0;
675 border-left: 0;
670 float: left;
676 float: left;
671 }
677 }
672 }
678 }
673
679
674 // SIDEBAR
680 // SIDEBAR
675
681
676 .sidebar {
682 .sidebar {
677 .block-left;
683 .block-left;
678 clear: left;
684 clear: left;
679 max-width: @sidebar-width;
685 max-width: @sidebar-width;
680 margin-right: @sidebarpadding;
686 margin-right: @sidebarpadding;
681 padding-right: @sidebarpadding;
687 padding-right: @sidebarpadding;
682 font-family: @text-regular;
688 font-family: @text-regular;
683 color: @grey1;
689 color: @grey1;
684
690
685 .nav-pills {
691 .nav-pills {
686 margin: 0;
692 margin: 0;
687 }
693 }
688
694
689 .nav {
695 .nav {
690 list-style: none;
696 list-style: none;
691 padding: 0;
697 padding: 0;
692
698
693 li {
699 li {
694 padding-bottom: @menupadding;
700 padding-bottom: @menupadding;
695 line-height: 1em;
701 line-height: 1em;
696 color: @grey4;
702 color: @grey4;
697 list-style-type: none;
703 list-style-type: none;
698
704
699 &.active a {
705 &.active a {
700 color: @grey2;
706 color: @grey2;
701 }
707 }
702
708
703 a {
709 a {
704 color: @grey4;
710 color: @grey4;
705 }
711 }
706 }
712 }
707
713
708 }
714 }
709 }
715 }
710
716
711 .main_filter_help_box {
717 .main_filter_help_box {
712 padding: 7px 7px;
718 padding: 7px 7px;
713 display: inline-block;
719 display: inline-block;
714 vertical-align: top;
720 vertical-align: top;
715 background: inherit;
721 background: inherit;
716 position: absolute;
722 position: absolute;
717 right: 0;
723 right: 0;
718 top: 9px;
724 top: 9px;
719 }
725 }
720
726
721 .main_filter_input_box {
727 .main_filter_input_box {
722 display: inline-block;
728 display: inline-block;
723
729
724 .searchItems {
730 .searchItems {
725 display:flex;
731 display:flex;
726 background: @black;
732 background: @black;
727 padding: 0px;
733 padding: 0px;
728 border-radius: 3px;
734 border-radius: 3px;
729 border: 1px solid @black;
735 border: 1px solid @black;
730
736
731 a {
737 a {
732 border: none !important;
738 border: none !important;
733 }
739 }
734 }
740 }
735
741
736 .searchTag {
742 .searchTag {
737 line-height: 28px;
743 line-height: 28px;
738 padding: 0 5px;
744 padding: 0 5px;
739
745
740 .tag {
746 .tag {
741 color: @grey5;
747 color: @grey5;
742 border-color: @grey2;
748 border-color: @grey2;
743 background: @grey1;
749 background: @grey1;
744 }
750 }
745 }
751 }
746
752
747 .searchTagFilter {
753 .searchTagFilter {
748 background-color: @black !important;
754 background-color: @black !important;
749 margin-right: 0;
755 margin-right: 0;
750 }
756 }
751
757
752 .searchTagHelp {
758 .searchTagHelp {
753 background-color: @grey1 !important;
759 background-color: @grey1 !important;
754 margin: 0;
760 margin: 0;
755 }
761 }
756 .searchTagHelp:hover {
762 .searchTagHelp:hover {
757 background-color: @grey1 !important;
763 background-color: @grey1 !important;
758 }
764 }
759 .searchTagInput {
765 .searchTagInput {
760 background-color: @grey1 !important;
766 background-color: @grey1 !important;
761 margin-right: 0;
767 margin-right: 0;
762 }
768 }
763 }
769 }
764
770
765 .main_filter_box {
771 .main_filter_box {
766 margin: 9px 0 0 0;
772 margin: 9px 0 0 0;
767 }
773 }
768
774
769 #main_filter_help {
775 #main_filter_help {
770 background: @grey1;
776 background: @grey1;
771 border: 1px solid black;
777 border: 1px solid black;
772 position: absolute;
778 position: absolute;
773 white-space: pre;
779 white-space: pre;
774 z-index: 9999;
780 z-index: 9999;
775 color: @nav-grey;
781 color: @nav-grey;
776 padding: 0 10px;
782 padding: 0 10px;
777 }
783 }
778
784
779 input {
785 input {
780
786
781 &.main_filter_input {
787 &.main_filter_input {
782 padding: 5px 10px;
788 padding: 5px 10px;
783 min-width: 340px;
789 min-width: 340px;
784 color: @grey7;
790 color: @grey7;
785 background: @black;
791 background: @black;
786 min-height: 18px;
792 min-height: 18px;
787 border: 0;
793 border: 0;
788
794
789 &:active {
795 &:active {
790 color: @grey2 !important;
796 color: @grey2 !important;
791 background: white !important;
797 background: white !important;
792 }
798 }
793 &:focus {
799 &:focus {
794 color: @grey2 !important;
800 color: @grey2 !important;
795 background: white !important;
801 background: white !important;
796 }
802 }
797 }
803 }
798 }
804 }
799
805
800
806
801
807
802 .main_filter_input::placeholder {
808 .main_filter_input::placeholder {
803 color: @nav-grey;
809 color: @nav-grey;
804 opacity: 1;
810 opacity: 1;
805 }
811 }
806
812
807 .notice-box {
813 .notice-box {
808 display:block !important;
814 display:block !important;
809 padding: 9px 0 !important;
815 padding: 9px 0 !important;
810 }
816 }
811
817
812 .menulabel-notice {
818 .menulabel-notice {
813 border: 1px solid @color5;
819 border: 1px solid @color5;
814 padding:7px 10px;
820 padding:7px 10px;
815 color: @color5;
821 color: @color5;
816 }
822 }
@@ -1,69 +1,69 b''
1 <%namespace name="base" file="/base/base.mako"/>
1 <%namespace name="base" file="/base/base.mako"/>
2
2
3 %if c.audit_logs:
3 %if c.audit_logs:
4 <table class="rctable admin_log">
4 <table class="rctable admin_log">
5 <tr>
5 <tr>
6 <th>${_('Uid')}</th>
6 <th>${_('Uid')}</th>
7 <th>${_('Username')}</th>
7 <th>${_('Username')}</th>
8 <th>${_('Action')}</th>
8 <th>${_('Action')}</th>
9 <th>${_('Action Data')}</th>
9 <th>${_('Action Data')}</th>
10 <th>${_('Repository')}</th>
10 <th>${_('Repository')}</th>
11 <th>${_('Date')}</th>
11 <th>${_('Date')}</th>
12 <th>${_('IP')}</th>
12 <th>${_('IP')}</th>
13 </tr>
13 </tr>
14
14
15 %for cnt,l in enumerate(c.audit_logs):
15 %for cnt,l in enumerate(c.audit_logs):
16 <tr class="parity${cnt%2}">
16 <tr class="parity${cnt%2}">
17 <td class="td-col">
17 <td class="td-col">
18 <a href="${h.route_path('admin_audit_log_entry', audit_log_id=l.entry_id)}">${l.entry_id}</a>
18 <a href="${h.route_path('admin_audit_log_entry', audit_log_id=l.entry_id)}">${l.entry_id}</a>
19 </td>
19 </td>
20 <td class="td-user">
20 <td class="td-user">
21 %if l.user is not None:
21 %if l.user is not None:
22 ${base.gravatar_with_user(l.user.email)}
22 ${base.gravatar_with_user(l.user.email)}
23 %else:
23 %else:
24 ${l.username}
24 ${l.username}
25 %endif
25 %endif
26 </td>
26 </td>
27 <td class="td-journalaction">
27 <td class="td-journalaction">
28 % if l.version == l.VERSION_1:
28 % if l.version == l.VERSION_1:
29 ${h.action_parser(request, l)[0]()}
29 ${h.action_parser(request, l)[0]()}
30 % else:
30 % else:
31 ${h.literal(l.action)}
31 ${h.literal(l.action)}
32 % endif
32 % endif
33
33
34 <div class="journal_action_params">
34 <div class="journal_action_params">
35 % if l.version == l.VERSION_1:
35 % if l.version == l.VERSION_1:
36 ${h.literal(h.action_parser(request, l)[1]())}
36 ${h.literal(h.action_parser(request, l)[1]())}
37 % endif
37 % endif
38 </div>
38 </div>
39 </td>
39 </td>
40 <td>
40 <td>
41 % if l.version == l.VERSION_2:
41 % if l.version == l.VERSION_2:
42 <a href="#" onclick="$('#entry-'+${l.user_log_id}).toggle();return false">${_('toggle')}</a>
42 <a href="#" onclick="$('#entry-'+${l.user_log_id}).toggle();return false">${_('toggle')}</a>
43 <div id="entry-${l.user_log_id}" style="display: none">
43 <div id="entry-${l.user_log_id}" style="display: none">
44 <pre>${h.json.dumps(l.action_data, indent=4, sort_keys=True)}</pre>
44 <pre>${h.json.dumps(l.action_data, indent=4, sort_keys=True)}</pre>
45 </div>
45 </div>
46 % else:
46 % else:
47 <pre title="${_('data not available for v1 entries type')}">-</pre>
47 <pre title="${_('data not available for v1 entries type')}">-</pre>
48 % endif
48 % endif
49 </td>
49 </td>
50 <td class="td-componentname">
50 <td class="td-componentname">
51 %if l.repository is not None:
51 %if l.repository is not None:
52 ${h.link_to(l.repository.repo_name, h.route_path('repo_summary',repo_name=l.repository.repo_name))}
52 ${h.link_to(l.repository.repo_name, h.route_path('repo_summary',repo_name=l.repository.repo_name))}
53 %else:
53 %else:
54 ${l.repository_name}
54 ${l.repository_name}
55 %endif
55 %endif
56 </td>
56 </td>
57
57
58 <td class="td-time">${h.format_date(l.action_date)}</td>
58 <td class="td-time">${h.format_date(l.action_date)}</td>
59 <td class="td-ip">${l.user_ip}</td>
59 <td class="td-ip">${l.user_ip}</td>
60 </tr>
60 </tr>
61 %endfor
61 %endfor
62 </table>
62 </table>
63
63
64 <div class="pagination-wh pagination-left">
64 <div class="pagination-wh pagination-left">
65 ${c.audit_logs.pager('$link_previous ~2~ $link_next')}
65 ${c.audit_logs.render()}
66 </div>
66 </div>
67 %else:
67 %else:
68 ${_('No actions yet')}
68 ${_('No actions yet')}
69 %endif No newline at end of file
69 %endif
@@ -1,220 +1,220 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <%inherit file="base.mako"/>
2 <%inherit file="base.mako"/>
3
3
4 <%def name="breadcrumbs_links()">
4 <%def name="breadcrumbs_links()">
5 %if c.repo:
5 %if c.repo:
6 ${h.link_to('Settings',h.route_path('edit_repo', repo_name=c.repo.repo_name))}
6 ${h.link_to('Settings',h.route_path('edit_repo', repo_name=c.repo.repo_name))}
7 %elif c.repo_group:
7 %elif c.repo_group:
8 ${h.link_to(_('Admin'),h.route_path('admin_home'))}
8 ${h.link_to(_('Admin'),h.route_path('admin_home'))}
9 &raquo;
9 &raquo;
10 ${h.link_to(_('Repository Groups'),h.route_path('repo_groups'))}
10 ${h.link_to(_('Repository Groups'),h.route_path('repo_groups'))}
11 &raquo;
11 &raquo;
12 ${h.link_to(c.repo_group.group_name,h.route_path('edit_repo_group', repo_group_name=c.repo_group.group_name))}
12 ${h.link_to(c.repo_group.group_name,h.route_path('edit_repo_group', repo_group_name=c.repo_group.group_name))}
13 %else:
13 %else:
14 ${h.link_to(_('Admin'),h.route_path('admin_home'))}
14 ${h.link_to(_('Admin'),h.route_path('admin_home'))}
15 &raquo;
15 &raquo;
16 ${h.link_to(_('Settings'),h.route_path('admin_settings'))}
16 ${h.link_to(_('Settings'),h.route_path('admin_settings'))}
17 %endif
17 %endif
18 %if c.current_IntegrationType:
18 %if c.current_IntegrationType:
19 &raquo;
19 &raquo;
20 %if c.repo:
20 %if c.repo:
21 ${h.link_to(_('Integrations'),
21 ${h.link_to(_('Integrations'),
22 request.route_path(route_name='repo_integrations_home',
22 request.route_path(route_name='repo_integrations_home',
23 repo_name=c.repo.repo_name))}
23 repo_name=c.repo.repo_name))}
24 %elif c.repo_group:
24 %elif c.repo_group:
25 ${h.link_to(_('Integrations'),
25 ${h.link_to(_('Integrations'),
26 request.route_path(route_name='repo_group_integrations_home',
26 request.route_path(route_name='repo_group_integrations_home',
27 repo_group_name=c.repo_group.group_name))}
27 repo_group_name=c.repo_group.group_name))}
28 %else:
28 %else:
29 ${h.link_to(_('Integrations'),
29 ${h.link_to(_('Integrations'),
30 request.route_path(route_name='global_integrations_home'))}
30 request.route_path(route_name='global_integrations_home'))}
31 %endif
31 %endif
32 &raquo;
32 &raquo;
33 ${c.current_IntegrationType.display_name}
33 ${c.current_IntegrationType.display_name}
34 %else:
34 %else:
35 &raquo;
35 &raquo;
36 ${_('Integrations')}
36 ${_('Integrations')}
37 %endif
37 %endif
38 </%def>
38 </%def>
39
39
40 <div class="panel panel-default">
40 <div class="panel panel-default">
41 <div class="panel-heading">
41 <div class="panel-heading">
42 <h3 class="panel-title">
42 <h3 class="panel-title">
43 %if c.repo:
43 %if c.repo:
44 ${_('Current Integrations for Repository: {repo_name}').format(repo_name=c.repo.repo_name)}
44 ${_('Current Integrations for Repository: {repo_name}').format(repo_name=c.repo.repo_name)}
45 %elif c.repo_group:
45 %elif c.repo_group:
46 ${_('Repository Group Integrations: {}').format(c.repo_group.group_name)}</h3>
46 ${_('Repository Group Integrations: {}').format(c.repo_group.group_name)}</h3>
47 %else:
47 %else:
48 ${_('Current Integrations')}
48 ${_('Current Integrations')}
49 %endif
49 %endif
50 </h3>
50 </h3>
51 </div>
51 </div>
52 <div class="panel-body">
52 <div class="panel-body">
53
53
54 <%
54 <%
55 integration_type = c.current_IntegrationType and c.current_IntegrationType.display_name or ''
55 integration_type = c.current_IntegrationType and c.current_IntegrationType.display_name or ''
56
56
57 if c.repo:
57 if c.repo:
58 create_url = h.route_path('repo_integrations_new', repo_name=c.repo.repo_name)
58 create_url = h.route_path('repo_integrations_new', repo_name=c.repo.repo_name)
59 elif c.repo_group:
59 elif c.repo_group:
60 create_url = h.route_path('repo_group_integrations_new', repo_group_name=c.repo_group.group_name)
60 create_url = h.route_path('repo_group_integrations_new', repo_group_name=c.repo_group.group_name)
61 else:
61 else:
62 create_url = h.route_path('global_integrations_new')
62 create_url = h.route_path('global_integrations_new')
63 %>
63 %>
64 <p class="pull-right">
64 <p class="pull-right">
65 <a href="${create_url}" class="btn btn-small btn-success">${_(u'Create new integration')}</a>
65 <a href="${create_url}" class="btn btn-small btn-success">${_(u'Create new integration')}</a>
66 </p>
66 </p>
67
67
68 <table class="rctable integrations">
68 <table class="rctable integrations">
69 <thead>
69 <thead>
70 <tr>
70 <tr>
71 <th><a href="?sort=enabled:${c.rev_sort_dir}">${_('Enabled')}</a></th>
71 <th><a href="?sort=enabled:${c.rev_sort_dir}">${_('Enabled')}</a></th>
72 <th><a href="?sort=name:${c.rev_sort_dir}">${_('Name')}</a></th>
72 <th><a href="?sort=name:${c.rev_sort_dir}">${_('Name')}</a></th>
73 <th colspan="2"><a href="?sort=integration_type:${c.rev_sort_dir}">${_('Type')}</a></th>
73 <th colspan="2"><a href="?sort=integration_type:${c.rev_sort_dir}">${_('Type')}</a></th>
74 <th><a href="?sort=scope:${c.rev_sort_dir}">${_('Scope')}</a></th>
74 <th><a href="?sort=scope:${c.rev_sort_dir}">${_('Scope')}</a></th>
75 <th>${_('Actions')}</th>
75 <th>${_('Actions')}</th>
76 <th></th>
76 <th></th>
77 </tr>
77 </tr>
78 </thead>
78 </thead>
79 <tbody>
79 <tbody>
80 %if not c.integrations_list:
80 %if not c.integrations_list:
81 <tr>
81 <tr>
82 <td colspan="7">
82 <td colspan="7">
83
83
84 %if c.repo:
84 %if c.repo:
85 ${_('No {type} integrations for repo {repo} exist yet.').format(type=integration_type, repo=c.repo.repo_name)}
85 ${_('No {type} integrations for repo {repo} exist yet.').format(type=integration_type, repo=c.repo.repo_name)}
86 %elif c.repo_group:
86 %elif c.repo_group:
87 ${_('No {type} integrations for repogroup {repogroup} exist yet.').format(type=integration_type, repogroup=c.repo_group.group_name)}
87 ${_('No {type} integrations for repogroup {repogroup} exist yet.').format(type=integration_type, repogroup=c.repo_group.group_name)}
88 %else:
88 %else:
89 ${_('No {type} integrations exist yet.').format(type=integration_type)}
89 ${_('No {type} integrations exist yet.').format(type=integration_type)}
90 %endif
90 %endif
91
91
92 %if c.current_IntegrationType:
92 %if c.current_IntegrationType:
93 <%
93 <%
94 if c.repo:
94 if c.repo:
95 create_url = h.route_path('repo_integrations_create', repo_name=c.repo.repo_name, integration=c.current_IntegrationType.key)
95 create_url = h.route_path('repo_integrations_create', repo_name=c.repo.repo_name, integration=c.current_IntegrationType.key)
96 elif c.repo_group:
96 elif c.repo_group:
97 create_url = h.route_path('repo_group_integrations_create', repo_group_name=c.repo_group.group_name, integration=c.current_IntegrationType.key)
97 create_url = h.route_path('repo_group_integrations_create', repo_group_name=c.repo_group.group_name, integration=c.current_IntegrationType.key)
98 else:
98 else:
99 create_url = h.route_path('global_integrations_create', integration=c.current_IntegrationType.key)
99 create_url = h.route_path('global_integrations_create', integration=c.current_IntegrationType.key)
100 %>
100 %>
101 %endif
101 %endif
102
102
103 <a href="${create_url}">${_(u'Create one')}</a>
103 <a href="${create_url}">${_(u'Create one')}</a>
104 </td>
104 </td>
105 </tr>
105 </tr>
106 %endif
106 %endif
107 %for IntegrationType, integration in c.integrations_list:
107 %for IntegrationType, integration in c.integrations_list:
108 <tr id="integration_${integration.integration_id}">
108 <tr id="integration_${integration.integration_id}">
109 <td class="td-enabled">
109 <td class="td-enabled">
110 <div class="pull-left">
110 <div class="pull-left">
111 ${h.bool2icon(integration.enabled)}
111 ${h.bool2icon(integration.enabled)}
112 </div>
112 </div>
113 </td>
113 </td>
114 <td class="td-description">
114 <td class="td-description">
115 ${integration.name}
115 ${integration.name}
116 </td>
116 </td>
117 <td class="td-icon">
117 <td class="td-icon">
118 %if integration.integration_type in c.available_integrations:
118 %if integration.integration_type in c.available_integrations:
119 <div class="integration-icon">
119 <div class="integration-icon">
120 ${c.available_integrations[integration.integration_type].icon()|n}
120 ${c.available_integrations[integration.integration_type].icon()|n}
121 </div>
121 </div>
122 %else:
122 %else:
123 ?
123 ?
124 %endif
124 %endif
125 </td>
125 </td>
126 <td class="td-type">
126 <td class="td-type">
127 ${integration.integration_type}
127 ${integration.integration_type}
128 </td>
128 </td>
129 <td class="td-scope">
129 <td class="td-scope">
130 %if integration.repo:
130 %if integration.repo:
131 <a href="${h.route_path('repo_summary', repo_name=integration.repo.repo_name)}">
131 <a href="${h.route_path('repo_summary', repo_name=integration.repo.repo_name)}">
132 ${_('repo')}:${integration.repo.repo_name}
132 ${_('repo')}:${integration.repo.repo_name}
133 </a>
133 </a>
134 %elif integration.repo_group:
134 %elif integration.repo_group:
135 <a href="${h.route_path('repo_group_home', repo_group_name=integration.repo_group.group_name)}">
135 <a href="${h.route_path('repo_group_home', repo_group_name=integration.repo_group.group_name)}">
136 ${_('repogroup')}:${integration.repo_group.group_name}
136 ${_('repogroup')}:${integration.repo_group.group_name}
137 %if integration.child_repos_only:
137 %if integration.child_repos_only:
138 ${_('child repos only')}
138 ${_('child repos only')}
139 %else:
139 %else:
140 ${_('cascade to all')}
140 ${_('cascade to all')}
141 %endif
141 %endif
142 </a>
142 </a>
143 %else:
143 %else:
144 %if integration.child_repos_only:
144 %if integration.child_repos_only:
145 ${_('top level repos only')}
145 ${_('top level repos only')}
146 %else:
146 %else:
147 ${_('global')}
147 ${_('global')}
148 %endif
148 %endif
149 </td>
149 </td>
150 %endif
150 %endif
151 <td class="td-action">
151 <td class="td-action">
152 %if not IntegrationType:
152 %if not IntegrationType:
153 ${_('unknown integration')}
153 ${_('unknown integration')}
154 %else:
154 %else:
155 <%
155 <%
156 if c.repo:
156 if c.repo:
157 edit_url = request.route_path('repo_integrations_edit',
157 edit_url = request.route_path('repo_integrations_edit',
158 repo_name=c.repo.repo_name,
158 repo_name=c.repo.repo_name,
159 integration=integration.integration_type,
159 integration=integration.integration_type,
160 integration_id=integration.integration_id)
160 integration_id=integration.integration_id)
161 elif c.repo_group:
161 elif c.repo_group:
162 edit_url = request.route_path('repo_group_integrations_edit',
162 edit_url = request.route_path('repo_group_integrations_edit',
163 repo_group_name=c.repo_group.group_name,
163 repo_group_name=c.repo_group.group_name,
164 integration=integration.integration_type,
164 integration=integration.integration_type,
165 integration_id=integration.integration_id)
165 integration_id=integration.integration_id)
166 else:
166 else:
167 edit_url = request.route_path('global_integrations_edit',
167 edit_url = request.route_path('global_integrations_edit',
168 integration=integration.integration_type,
168 integration=integration.integration_type,
169 integration_id=integration.integration_id)
169 integration_id=integration.integration_id)
170 %>
170 %>
171 <div class="grid_edit">
171 <div class="grid_edit">
172 <a href="${edit_url}">${_('Edit')}</a>
172 <a href="${edit_url}">${_('Edit')}</a>
173 </div>
173 </div>
174 <div class="grid_delete">
174 <div class="grid_delete">
175 <a href="${edit_url}"
175 <a href="${edit_url}"
176 class="btn btn-link btn-danger delete_integration_entry"
176 class="btn btn-link btn-danger delete_integration_entry"
177 data-desc="${integration.name}"
177 data-desc="${integration.name}"
178 data-uid="${integration.integration_id}">
178 data-uid="${integration.integration_id}">
179 ${_('Delete')}
179 ${_('Delete')}
180 </a>
180 </a>
181 </div>
181 </div>
182 %endif
182 %endif
183 </td>
183 </td>
184 </tr>
184 </tr>
185 %endfor
185 %endfor
186 <tr id="last-row"></tr>
186 <tr id="last-row"></tr>
187 </tbody>
187 </tbody>
188 </table>
188 </table>
189 <div class="integrations-paginator">
189 <div class="integrations-paginator">
190 <div class="pagination-wh pagination-left">
190 <div class="pagination-wh pagination-left">
191 ${c.integrations_list.pager('$link_previous ~2~ $link_next')}
191 ${c.integrations_list.render()}
192 </div>
192 </div>
193 </div>
193 </div>
194 </div>
194 </div>
195 </div>
195 </div>
196 <script type="text/javascript">
196 <script type="text/javascript">
197 var delete_integration = function(entry) {
197 var delete_integration = function(entry) {
198 if (confirm("Confirm to remove this integration: "+$(entry).data('desc'))) {
198 if (confirm("Confirm to remove this integration: "+$(entry).data('desc'))) {
199 var request = $.ajax({
199 var request = $.ajax({
200 type: "POST",
200 type: "POST",
201 url: $(entry).attr('href'),
201 url: $(entry).attr('href'),
202 data: {
202 data: {
203 'delete': 'delete',
203 'delete': 'delete',
204 'csrf_token': CSRF_TOKEN
204 'csrf_token': CSRF_TOKEN
205 },
205 },
206 success: function(){
206 success: function(){
207 location.reload();
207 location.reload();
208 },
208 },
209 error: function(data, textStatus, errorThrown){
209 error: function(data, textStatus, errorThrown){
210 alert("Error while deleting entry.\nError code {0} ({1}). URL: {2}".format(data.status,data.statusText,$(entry)[0].url));
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 $('.delete_integration_entry').on('click', function(e){
216 $('.delete_integration_entry').on('click', function(e){
217 e.preventDefault();
217 e.preventDefault();
218 delete_integration(this);
218 delete_integration(this);
219 });
219 });
220 </script> No newline at end of file
220 </script>
@@ -1,46 +1,46 b''
1 <%namespace name="base" file="/base/base.mako"/>
1 <%namespace name="base" file="/base/base.mako"/>
2
2
3 <div class="panel panel-default">
3 <div class="panel panel-default">
4 <div class="panel-heading">
4 <div class="panel-heading">
5 <h3 class="panel-title">${_('My notifications')}</h3>
5 <h3 class="panel-title">${_('My notifications')}</h3>
6 </div>
6 </div>
7
7
8 <div class="panel-body">
8 <div class="panel-body">
9 %if c.notifications:
9 %if c.notifications:
10
10
11 <div class="notification-list notification-table">
11 <div class="notification-list notification-table">
12 %for notification in c.notifications:
12 %for notification in c.notifications:
13 <div id="notification_${notification.notification.notification_id}" class="container ${'unread' if not notification.read else '' }">
13 <div id="notification_${notification.notification.notification_id}" class="container ${'unread' if not notification.read else '' }">
14 <div class="notification-header">
14 <div class="notification-header">
15 <div class="desc ${'unread' if not notification.read else '' }">
15 <div class="desc ${'unread' if not notification.read else '' }">
16 ${base.gravatar(notification.notification.created_by_user.email, 16)}
16 ${base.gravatar(notification.notification.created_by_user.email, 16)}
17 <a href="${h.route_path('notifications_show', notification_id=notification.notification.notification_id)}">
17 <a href="${h.route_path('notifications_show', notification_id=notification.notification.notification_id)}">
18 ${h.notification_description(notification.notification, request)}
18 ${h.notification_description(notification.notification, request)}
19 </a>
19 </a>
20 </div>
20 </div>
21 <div class="delete-notifications">
21 <div class="delete-notifications">
22 <span onclick="deleteNotification(${notification.notification.notification_id})" class="delete-notification tooltip" title="${_('Delete')}"><i class="icon-delete"></i></span>
22 <span onclick="deleteNotification(${notification.notification.notification_id})" class="delete-notification tooltip" title="${_('Delete')}"><i class="icon-delete"></i></span>
23 </div>
23 </div>
24 <div class="read-notifications">
24 <div class="read-notifications">
25 %if not notification.read:
25 %if not notification.read:
26 <span onclick="readNotification(${notification.notification.notification_id})" class="read-notification tooltip" title="${_('Mark as read')}"><i class="icon-ok"></i></span>
26 <span onclick="readNotification(${notification.notification.notification_id})" class="read-notification tooltip" title="${_('Mark as read')}"><i class="icon-ok"></i></span>
27 %endif
27 %endif
28 </div>
28 </div>
29 </div>
29 </div>
30 <div class="notification-subject"></div>
30 <div class="notification-subject"></div>
31 </div>
31 </div>
32 %endfor
32 %endfor
33 </div>
33 </div>
34
34
35 <div class="notification-paginator">
35 <div class="notification-paginator">
36 <div class="pagination-wh pagination-left">
36 <div class="pagination-wh pagination-left">
37 ${c.notifications.pager('$link_previous ~2~ $link_next')}
37 ${c.notifications.render()}
38 </div>
38 </div>
39 </div>
39 </div>
40
40
41 %else:
41 %else:
42 <div class="table">${_('No notifications here yet')}</div>
42 <div class="table">${_('No notifications here yet')}</div>
43 %endif
43 %endif
44
44
45 </div>
45 </div>
46 </div>
46 </div>
@@ -1,326 +1,326 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2
2
3 <%inherit file="/base/base.mako"/>
3 <%inherit file="/base/base.mako"/>
4
4
5 <%def name="title()">
5 <%def name="title()">
6 ${_('%s Changelog') % c.repo_name}
6 ${_('%s Changelog') % c.repo_name}
7 %if c.changelog_for_path:
7 %if c.changelog_for_path:
8 /${c.changelog_for_path}
8 /${c.changelog_for_path}
9 %endif
9 %endif
10 %if c.rhodecode_name:
10 %if c.rhodecode_name:
11 &middot; ${h.branding(c.rhodecode_name)}
11 &middot; ${h.branding(c.rhodecode_name)}
12 %endif
12 %endif
13 </%def>
13 </%def>
14
14
15 <%def name="breadcrumbs_links()">
15 <%def name="breadcrumbs_links()">
16 %if c.changelog_for_path:
16 %if c.changelog_for_path:
17 /${c.changelog_for_path}
17 /${c.changelog_for_path}
18 %endif
18 %endif
19 </%def>
19 </%def>
20
20
21 <%def name="menu_bar_nav()">
21 <%def name="menu_bar_nav()">
22 ${self.menu_items(active='repositories')}
22 ${self.menu_items(active='repositories')}
23 </%def>
23 </%def>
24
24
25 <%def name="menu_bar_subnav()">
25 <%def name="menu_bar_subnav()">
26 ${self.repo_menu(active='commits')}
26 ${self.repo_menu(active='commits')}
27 </%def>
27 </%def>
28
28
29 <%def name="main()">
29 <%def name="main()">
30
30
31 <div class="box">
31 <div class="box">
32
32
33 <div class="title">
33 <div class="title">
34 <div id="filter_changelog">
34 <div id="filter_changelog">
35 ${h.hidden('branch_filter')}
35 ${h.hidden('branch_filter')}
36 %if c.selected_name:
36 %if c.selected_name:
37 <div class="btn btn-default" id="clear_filter" >
37 <div class="btn btn-default" id="clear_filter" >
38 ${_('Clear filter')}
38 ${_('Clear filter')}
39 </div>
39 </div>
40 %endif
40 %endif
41 </div>
41 </div>
42 <div class="pull-left obsolete-toggle">
42 <div class="pull-left obsolete-toggle">
43 % if h.is_hg(c.rhodecode_repo):
43 % if h.is_hg(c.rhodecode_repo):
44 % if c.show_hidden:
44 % if c.show_hidden:
45 <a class="action-link" href="${h.current_route_path(request, evolve=0)}">${_('Hide obsolete/hidden')}</a>
45 <a class="action-link" href="${h.current_route_path(request, evolve=0)}">${_('Hide obsolete/hidden')}</a>
46 % else:
46 % else:
47 <a class="action-link" href="${h.current_route_path(request, evolve=1)}">${_('Show obsolete/hidden')}</a>
47 <a class="action-link" href="${h.current_route_path(request, evolve=1)}">${_('Show obsolete/hidden')}</a>
48 % endif
48 % endif
49 % else:
49 % else:
50 <span class="action-link disabled">${_('Show hidden')}</span>
50 <span class="action-link disabled">${_('Show hidden')}</span>
51 % endif
51 % endif
52 </div>
52 </div>
53 <ul class="links">
53 <ul class="links">
54 <li>
54 <li>
55
55
56 %if c.rhodecode_db_repo.fork:
56 %if c.rhodecode_db_repo.fork:
57 <span>
57 <span>
58 <a id="compare_fork_button"
58 <a id="compare_fork_button"
59 title="${h.tooltip(_('Compare fork with %s' % c.rhodecode_db_repo.fork.repo_name))}"
59 title="${h.tooltip(_('Compare fork with %s' % c.rhodecode_db_repo.fork.repo_name))}"
60 class="btn btn-small"
60 class="btn btn-small"
61 href="${h.route_path('repo_compare',
61 href="${h.route_path('repo_compare',
62 repo_name=c.rhodecode_db_repo.fork.repo_name,
62 repo_name=c.rhodecode_db_repo.fork.repo_name,
63 source_ref_type=c.rhodecode_db_repo.landing_rev[0],
63 source_ref_type=c.rhodecode_db_repo.landing_rev[0],
64 source_ref=c.rhodecode_db_repo.landing_rev[1],
64 source_ref=c.rhodecode_db_repo.landing_rev[1],
65 target_ref_type='branch' if request.GET.get('branch') else c.rhodecode_db_repo.landing_rev[0],
65 target_ref_type='branch' if request.GET.get('branch') else c.rhodecode_db_repo.landing_rev[0],
66 target_ref=request.GET.get('branch') or c.rhodecode_db_repo.landing_rev[1],
66 target_ref=request.GET.get('branch') or c.rhodecode_db_repo.landing_rev[1],
67 _query=dict(merge=1, target_repo=c.repo_name))}"
67 _query=dict(merge=1, target_repo=c.repo_name))}"
68 >
68 >
69 ${_('Compare fork with Parent (%s)' % c.rhodecode_db_repo.fork.repo_name)}
69 ${_('Compare fork with Parent (%s)' % c.rhodecode_db_repo.fork.repo_name)}
70 </a>
70 </a>
71 </span>
71 </span>
72 %endif
72 %endif
73
73
74 ## pr open link
74 ## pr open link
75 %if h.is_hg(c.rhodecode_repo) or h.is_git(c.rhodecode_repo):
75 %if h.is_hg(c.rhodecode_repo) or h.is_git(c.rhodecode_repo):
76 <span>
76 <span>
77 <a id="open_new_pull_request" class="btn btn-small btn-success" href="${h.route_path('pullrequest_new',repo_name=c.repo_name)}">
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 ${_('Open new pull request')}
78 ${_('Open new pull request')}
79 </a>
79 </a>
80 </span>
80 </span>
81 %endif
81 %endif
82
82
83 </li>
83 </li>
84 </ul>
84 </ul>
85 </div>
85 </div>
86
86
87 % if c.pagination:
87 % if c.pagination:
88 <script type="text/javascript" src="${h.asset('js/src/plugins/jquery.commits-graph.js')}"></script>
88 <script type="text/javascript" src="${h.asset('js/src/plugins/jquery.commits-graph.js')}"></script>
89
89
90 <div class="graph-header">
90 <div class="graph-header">
91 ${self.breadcrumbs('breadcrumbs_light')}
91 ${self.breadcrumbs('breadcrumbs_light')}
92 </div>
92 </div>
93
93
94 <div id="graph">
94 <div id="graph">
95 <div class="graph-col-wrapper">
95 <div class="graph-col-wrapper">
96 <div id="graph_nodes">
96 <div id="graph_nodes">
97 <div id="graph_canvas"></div>
97 <div id="graph_canvas"></div>
98 </div>
98 </div>
99 <div id="graph_content" class="graph_full_width">
99 <div id="graph_content" class="graph_full_width">
100
100
101 <div class="table">
101 <div class="table">
102 <table id="changesets" class="rctable">
102 <table id="changesets" class="rctable">
103 <tr>
103 <tr>
104 ## checkbox
104 ## checkbox
105 <th colspan="4">
105 <th colspan="4">
106 ## clear selection
106 ## clear selection
107 <div title="${_('Clear selection')}" class="btn btn-sm" id="rev_range_clear" style="display:none">
107 <div title="${_('Clear selection')}" class="btn btn-sm" id="rev_range_clear" style="display:none">
108 <i class="icon-cancel-circled2"></i>
108 <i class="icon-cancel-circled2"></i>
109 </div>
109 </div>
110 <div class="btn btn-sm disabled" disabled="disabled" id="rev_range_more" style="display:none;">${_('Select second commit')}</div>
110 <div class="btn btn-sm disabled" disabled="disabled" id="rev_range_more" style="display:none;">${_('Select second commit')}</div>
111 <a href="#" class="btn btn-success btn-sm" id="rev_range_container" style="display:none;"></a>
111 <a href="#" class="btn btn-success btn-sm" id="rev_range_container" style="display:none;"></a>
112 </th>
112 </th>
113
113
114 ## commit message expand arrow
114 ## commit message expand arrow
115 <th></th>
115 <th></th>
116 <th>${_('Commit Message')}</th>
116 <th>${_('Commit Message')}</th>
117
117
118 <th>${_('Age')}</th>
118 <th>${_('Age')}</th>
119 <th>${_('Author')}</th>
119 <th>${_('Author')}</th>
120
120
121 <th>${_('Refs')}</th>
121 <th>${_('Refs')}</th>
122 ## comments
122 ## comments
123 <th></th>
123 <th></th>
124 </tr>
124 </tr>
125
125
126 <tbody class="commits-range">
126 <tbody class="commits-range">
127 <%include file='changelog_elements.mako'/>
127 <%include file='changelog_elements.mako'/>
128 </tbody>
128 </tbody>
129 </table>
129 </table>
130 </div>
130 </div>
131 </div>
131 </div>
132 <div class="pagination-wh pagination-left">
132 <div class="pagination-wh pagination-left">
133 ${c.pagination.pager('$link_previous ~2~ $link_next')}
133 ${c.pagination.render()}
134 </div>
134 </div>
135 <div id="commit-counter" data-total=${c.total_cs} class="pull-right">
135 <div id="commit-counter" data-total=${c.total_cs} class="pull-right">
136 ${_ungettext('showing %d out of %d commit', 'showing %d out of %d commits', c.showing_commits) % (c.showing_commits, c.total_cs)}
136 ${_ungettext('showing %d out of %d commit', 'showing %d out of %d commits', c.showing_commits) % (c.showing_commits, c.total_cs)}
137 </div>
137 </div>
138 </div>
138 </div>
139
139
140 <script type="text/javascript">
140 <script type="text/javascript">
141 var cache = {};
141 var cache = {};
142 $(function(){
142 $(function(){
143
143
144 // Create links to commit ranges when range checkboxes are selected
144 // Create links to commit ranges when range checkboxes are selected
145 var $commitCheckboxes = $('.commit-range');
145 var $commitCheckboxes = $('.commit-range');
146 // cache elements
146 // cache elements
147 var $commitRangeMore = $('#rev_range_more');
147 var $commitRangeMore = $('#rev_range_more');
148 var $commitRangeContainer = $('#rev_range_container');
148 var $commitRangeContainer = $('#rev_range_container');
149 var $commitRangeClear = $('#rev_range_clear');
149 var $commitRangeClear = $('#rev_range_clear');
150
150
151 var checkboxRangeSelector = function(e){
151 var checkboxRangeSelector = function(e){
152 var selectedCheckboxes = [];
152 var selectedCheckboxes = [];
153 for (pos in $commitCheckboxes){
153 for (pos in $commitCheckboxes){
154 if($commitCheckboxes[pos].checked){
154 if($commitCheckboxes[pos].checked){
155 selectedCheckboxes.push($commitCheckboxes[pos]);
155 selectedCheckboxes.push($commitCheckboxes[pos]);
156 }
156 }
157 }
157 }
158 var open_new_pull_request = $('#open_new_pull_request');
158 var open_new_pull_request = $('#open_new_pull_request');
159
159
160 if (open_new_pull_request) {
160 if (open_new_pull_request) {
161 var selected_changes = selectedCheckboxes.length;
161 var selected_changes = selectedCheckboxes.length;
162 open_new_pull_request.hide();
162 open_new_pull_request.hide();
163 if (selected_changes == 1) {
163 if (selected_changes == 1) {
164 open_new_pull_request.html(_gettext('Open new pull request for selected commit'));
164 open_new_pull_request.html(_gettext('Open new pull request for selected commit'));
165 } else {
165 } else {
166 open_new_pull_request.html(_gettext('Open new pull request'));
166 open_new_pull_request.html(_gettext('Open new pull request'));
167 }
167 }
168 open_new_pull_request.show();
168 open_new_pull_request.show();
169 }
169 }
170
170
171 if (selectedCheckboxes.length > 0) {
171 if (selectedCheckboxes.length > 0) {
172 $('#compare_fork_button').hide();
172 $('#compare_fork_button').hide();
173 var commitStart = $(selectedCheckboxes[selectedCheckboxes.length-1]).data();
173 var commitStart = $(selectedCheckboxes[selectedCheckboxes.length-1]).data();
174
174
175 var revStart = commitStart.commitId;
175 var revStart = commitStart.commitId;
176
176
177 var commitEnd = $(selectedCheckboxes[0]).data();
177 var commitEnd = $(selectedCheckboxes[0]).data();
178 var revEnd = commitEnd.commitId;
178 var revEnd = commitEnd.commitId;
179
179
180 var lbl_start = '{0}'.format(commitStart.commitIdx, commitStart.shortId);
180 var lbl_start = '{0}'.format(commitStart.commitIdx, commitStart.shortId);
181 var lbl_end = '{0}'.format(commitEnd.commitIdx, commitEnd.shortId);
181 var lbl_end = '{0}'.format(commitEnd.commitIdx, commitEnd.shortId);
182
182
183 var url = pyroutes.url('repo_commit', {'repo_name': '${c.repo_name}', 'commit_id': revStart+'...'+revEnd});
183 var url = pyroutes.url('repo_commit', {'repo_name': '${c.repo_name}', 'commit_id': revStart+'...'+revEnd});
184 var link = _gettext('Show commit range {0} ... {1}').format(lbl_start, lbl_end);
184 var link = _gettext('Show commit range {0} ... {1}').format(lbl_start, lbl_end);
185
185
186 if (selectedCheckboxes.length > 1) {
186 if (selectedCheckboxes.length > 1) {
187 $commitRangeClear.show();
187 $commitRangeClear.show();
188 $commitRangeMore.hide();
188 $commitRangeMore.hide();
189
189
190 $commitRangeContainer
190 $commitRangeContainer
191 .attr('href',url)
191 .attr('href',url)
192 .html(link)
192 .html(link)
193 .show();
193 .show();
194
194
195
195
196 } else {
196 } else {
197 $commitRangeContainer.hide();
197 $commitRangeContainer.hide();
198 $commitRangeClear.show();
198 $commitRangeClear.show();
199 $commitRangeMore.show();
199 $commitRangeMore.show();
200 }
200 }
201
201
202 // pull-request link
202 // pull-request link
203 if (selectedCheckboxes.length == 1){
203 if (selectedCheckboxes.length == 1){
204 var _url = pyroutes.url('pullrequest_new', {'repo_name': '${c.repo_name}', 'commit': revEnd});
204 var _url = pyroutes.url('pullrequest_new', {'repo_name': '${c.repo_name}', 'commit': revEnd});
205 open_new_pull_request.attr('href', _url);
205 open_new_pull_request.attr('href', _url);
206 } else {
206 } else {
207 var _url = pyroutes.url('pullrequest_new', {'repo_name': '${c.repo_name}'});
207 var _url = pyroutes.url('pullrequest_new', {'repo_name': '${c.repo_name}'});
208 open_new_pull_request.attr('href', _url);
208 open_new_pull_request.attr('href', _url);
209 }
209 }
210
210
211 } else {
211 } else {
212 $commitRangeContainer.hide();
212 $commitRangeContainer.hide();
213 $commitRangeClear.hide();
213 $commitRangeClear.hide();
214 $commitRangeMore.hide();
214 $commitRangeMore.hide();
215
215
216 %if c.branch_name:
216 %if c.branch_name:
217 var _url = pyroutes.url('pullrequest_new', {'repo_name': '${c.repo_name}', 'branch':'${c.branch_name}'});
217 var _url = pyroutes.url('pullrequest_new', {'repo_name': '${c.repo_name}', 'branch':'${c.branch_name}'});
218 open_new_pull_request.attr('href', _url);
218 open_new_pull_request.attr('href', _url);
219 %else:
219 %else:
220 var _url = pyroutes.url('pullrequest_new', {'repo_name': '${c.repo_name}'});
220 var _url = pyroutes.url('pullrequest_new', {'repo_name': '${c.repo_name}'});
221 open_new_pull_request.attr('href', _url);
221 open_new_pull_request.attr('href', _url);
222 %endif
222 %endif
223 $('#compare_fork_button').show();
223 $('#compare_fork_button').show();
224 }
224 }
225 };
225 };
226
226
227 $commitCheckboxes.on('click', checkboxRangeSelector);
227 $commitCheckboxes.on('click', checkboxRangeSelector);
228
228
229 $commitRangeClear.on('click',function(e) {
229 $commitRangeClear.on('click',function(e) {
230 $commitCheckboxes.attr('checked', false);
230 $commitCheckboxes.attr('checked', false);
231 checkboxRangeSelector();
231 checkboxRangeSelector();
232 e.preventDefault();
232 e.preventDefault();
233 });
233 });
234
234
235 // make sure the buttons are consistent when navigate back and forth
235 // make sure the buttons are consistent when navigate back and forth
236 checkboxRangeSelector();
236 checkboxRangeSelector();
237
237
238 var msgs = $('.message');
238 var msgs = $('.message');
239 // get first element height
239 // get first element height
240 var el = $('#graph_content .container')[0];
240 var el = $('#graph_content .container')[0];
241 var row_h = el.clientHeight;
241 var row_h = el.clientHeight;
242 for (var i=0; i < msgs.length; i++) {
242 for (var i=0; i < msgs.length; i++) {
243 var m = msgs[i];
243 var m = msgs[i];
244
244
245 var h = m.clientHeight;
245 var h = m.clientHeight;
246 var pad = $(m).css('padding');
246 var pad = $(m).css('padding');
247 if (h > row_h) {
247 if (h > row_h) {
248 var offset = row_h - (h+12);
248 var offset = row_h - (h+12);
249 $(m.nextElementSibling).css('display','block');
249 $(m.nextElementSibling).css('display','block');
250 $(m.nextElementSibling).css('margin-top',offset+'px');
250 $(m.nextElementSibling).css('margin-top',offset+'px');
251 }
251 }
252 }
252 }
253
253
254 $("#clear_filter").on("click", function() {
254 $("#clear_filter").on("click", function() {
255 var filter = {'repo_name': '${c.repo_name}'};
255 var filter = {'repo_name': '${c.repo_name}'};
256 window.location = pyroutes.url('repo_commits', filter);
256 window.location = pyroutes.url('repo_commits', filter);
257 });
257 });
258
258
259 $("#branch_filter").select2({
259 $("#branch_filter").select2({
260 'dropdownAutoWidth': true,
260 'dropdownAutoWidth': true,
261 'width': 'resolve',
261 'width': 'resolve',
262 'placeholder': "${c.selected_name or _('Branch filter')}",
262 'placeholder': "${c.selected_name or _('Branch filter')}",
263 containerCssClass: "drop-menu",
263 containerCssClass: "drop-menu",
264 dropdownCssClass: "drop-menu-dropdown",
264 dropdownCssClass: "drop-menu-dropdown",
265 query: function(query){
265 query: function(query){
266 var key = 'cache';
266 var key = 'cache';
267 var cached = cache[key] ;
267 var cached = cache[key] ;
268 if(cached) {
268 if(cached) {
269 var data = {results: []};
269 var data = {results: []};
270 //filter results
270 //filter results
271 $.each(cached.results, function(){
271 $.each(cached.results, function(){
272 var section = this.text;
272 var section = this.text;
273 var children = [];
273 var children = [];
274 $.each(this.children, function(){
274 $.each(this.children, function(){
275 if(query.term.length == 0 || this.text.toUpperCase().indexOf(query.term.toUpperCase()) >= 0 ){
275 if(query.term.length == 0 || this.text.toUpperCase().indexOf(query.term.toUpperCase()) >= 0 ){
276 children.push({'id': this.id, 'text': this.text, 'type': this.type})
276 children.push({'id': this.id, 'text': this.text, 'type': this.type})
277 }
277 }
278 });
278 });
279 data.results.push({'text': section, 'children': children});
279 data.results.push({'text': section, 'children': children});
280 query.callback({results: data.results});
280 query.callback({results: data.results});
281 });
281 });
282 }else{
282 }else{
283 $.ajax({
283 $.ajax({
284 url: pyroutes.url('repo_refs_changelog_data', {'repo_name': '${c.repo_name}'}),
284 url: pyroutes.url('repo_refs_changelog_data', {'repo_name': '${c.repo_name}'}),
285 data: {},
285 data: {},
286 dataType: 'json',
286 dataType: 'json',
287 type: 'GET',
287 type: 'GET',
288 success: function(data) {
288 success: function(data) {
289 cache[key] = data;
289 cache[key] = data;
290 query.callback({results: data.results});
290 query.callback({results: data.results});
291 }
291 }
292 })
292 })
293 }
293 }
294 }
294 }
295 });
295 });
296 $('#branch_filter').on('change', function(e){
296 $('#branch_filter').on('change', function(e){
297 var data = $('#branch_filter').select2('data');
297 var data = $('#branch_filter').select2('data');
298 //type: branch_closed
298 //type: branch_closed
299 var selected = data.text;
299 var selected = data.text;
300 var filter = {'repo_name': '${c.repo_name}'};
300 var filter = {'repo_name': '${c.repo_name}'};
301 if(data.type == 'branch' || data.type == 'branch_closed'){
301 if(data.type == 'branch' || data.type == 'branch_closed'){
302 filter["branch"] = selected;
302 filter["branch"] = selected;
303 if (data.type == 'branch_closed') {
303 if (data.type == 'branch_closed') {
304 filter["evolve"] = '1';
304 filter["evolve"] = '1';
305 }
305 }
306 }
306 }
307 else if (data.type == 'book'){
307 else if (data.type == 'book'){
308 filter["bookmark"] = selected;
308 filter["bookmark"] = selected;
309 }
309 }
310 window.location = pyroutes.url('repo_commits', filter);
310 window.location = pyroutes.url('repo_commits', filter);
311 });
311 });
312
312
313 commitsController = new CommitsController();
313 commitsController = new CommitsController();
314 % if not c.changelog_for_path:
314 % if not c.changelog_for_path:
315 commitsController.reloadGraph();
315 commitsController.reloadGraph();
316 % endif
316 % endif
317
317
318 });
318 });
319
319
320 </script>
320 </script>
321 </div>
321 </div>
322 % else:
322 % else:
323 ${_('There are no changes yet')}
323 ${_('There are no changes yet')}
324 % endif
324 % endif
325 </div>
325 </div>
326 </%def>
326 </%def>
@@ -1,58 +1,52 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <%inherit file="/base/base.mako"/>
2 <%inherit file="/base/base.mako"/>
3 <%def name="title()">
3 <%def name="title()">
4 ${_('Journal')}
4 ${_('Journal')}
5 %if c.rhodecode_name:
5 %if c.rhodecode_name:
6 &middot; ${h.branding(c.rhodecode_name)}
6 &middot; ${h.branding(c.rhodecode_name)}
7 %endif
7 %endif
8 </%def>
8 </%def>
9
9
10 <%def name="breadcrumbs_links()">
10 <%def name="breadcrumbs_links()">
11 ${h.form(None, id_="filter_form", method="get")}
11 ${h.form(None, id_="filter_form", method="get")}
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...')}"/>
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 <input type='submit' value="${_('filter')}" class="btn" />
13 <input type='submit' value="${_('filter')}" class="btn" />
14 ${_('Journal')} - ${_ungettext('%s entry', '%s entries', c.journal_pager.item_count) % (c.journal_pager.item_count)}
14 ${_('Journal')} - ${_ungettext('%s entry', '%s entries', c.journal_pager.item_count) % (c.journal_pager.item_count)}
15 ${h.end_form()}
15 ${h.end_form()}
16 <p class="filterexample" style="position: inherit" onclick="$('#search-help').toggle()">${_('Example Queries')}</p>
16 <p class="filterexample" style="position: inherit" onclick="$('#search-help').toggle()">${_('Example Queries')}</p>
17 <pre id="search-help" style="display: none">${h.tooltip(h.journal_filter_help(request))}</pre>
17 <pre id="search-help" style="display: none">${h.tooltip(h.journal_filter_help(request))}</pre>
18 </%def>
18 </%def>
19
19
20 <%def name="menu_bar_nav()">
20 <%def name="menu_bar_nav()">
21 ${self.menu_items(active='journal')}
21 ${self.menu_items(active='journal')}
22 </%def>
22 </%def>
23
23
24 <%def name="head_extra()">
24 <%def name="head_extra()">
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" />
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 <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" />
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 </%def>
27 </%def>
28
28
29 <%def name="main()">
29 <%def name="main()">
30
30
31 <div class="box">
31 <div class="box">
32 <!-- box / title -->
32 <!-- box / title -->
33 <div class="title journal">
33 <div class="title journal">
34 ${self.breadcrumbs()}
34 ${self.breadcrumbs()}
35 <ul class="links icon-only-links block-right">
35 <ul class="links icon-only-links block-right">
36 <li>
36 <li>
37 <span><a id="refresh" href="${h.route_path('journal')}"><i class="icon-refresh"></i></a></span>
37 <span><a id="refresh" href="${h.route_path('journal')}"><i class="icon-refresh"></i></a></span>
38 </li>
38 </li>
39 <li>
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 </li>
43 </li>
42 </ul>
44 </ul>
43 </div>
45 </div>
44 <div id="journal">${c.journal_data|n}</div>
46 <div id="journal">${c.journal_data|n}</div>
45 </div>
47 </div>
46
48
47 <script type="text/javascript">
49 <script type="text/javascript">
48
50 $('#j_filter').autoGrowInput();
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
57 </script>
51 </script>
58 </%def>
52 </%def>
@@ -1,54 +1,47 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <%namespace name="base" file="/base/base.mako"/>
2 <%namespace name="base" file="/base/base.mako"/>
3
3
4 %if c.journal_day_aggreagate:
4 %if c.journal_day_aggreagate:
5 %for day,items in c.journal_day_aggreagate:
5 %for day,items in c.journal_day_aggreagate:
6 <div class="journal_day">${day}</div>
6 <div class="journal_day">${day}</div>
7 % for user,entries in items:
7 % for user,entries in items:
8 <div class="journal_container">
8 <div class="journal_container">
9 ${base.gravatar(user.email if user else '', 30)}
9 ${base.gravatar(user.email if user else '', 30)}
10 %if user:
10 %if user:
11 <div class="journal_user user">${h.link_to_user(user.username)}</div>
11 <div class="journal_user user">${h.link_to_user(user.username)}</div>
12 %else:
12 %else:
13 <div class="journal_user user deleted">${entries[0].username}</div>
13 <div class="journal_user user deleted">${entries[0].username}</div>
14 %endif
14 %endif
15 <div class="journal_action_container">
15 <div class="journal_action_container">
16 % for entry in entries:
16 % for entry in entries:
17 <div class="journal_icon"> ${h.action_parser(request, entry)[2]()}</div>
17 <div class="journal_icon"> ${h.action_parser(request, entry)[2]()}</div>
18 <div class="journal_action">${h.action_parser(request, entry)[0]()}</div>
18 <div class="journal_action">${h.action_parser(request, entry)[0]()}</div>
19 <div class="journal_repo">
19 <div class="journal_repo">
20 <span class="journal_repo_name">
20 <span class="journal_repo_name">
21 %if entry.repository is not None:
21 %if entry.repository is not None:
22 ${h.link_to(entry.repository.repo_name,
22 ${h.link_to(entry.repository.repo_name,
23 h.route_path('repo_summary',repo_name=entry.repository.repo_name))}
23 h.route_path('repo_summary',repo_name=entry.repository.repo_name))}
24 %else:
24 %else:
25 ${entry.repository_name}
25 ${entry.repository_name}
26 %endif
26 %endif
27 </span>
27 </span>
28 </div>
28 </div>
29 <div class="journal_action_params">${h.literal(h.action_parser(request, entry)[1]())}</div>
29 <div class="journal_action_params">${h.literal(h.action_parser(request, entry)[1]())}</div>
30 <div class="date">
30 <div class="date">
31 ${h.age_component(entry.action_date, time_is_local=True)}
31 ${h.age_component(entry.action_date, time_is_local=True)}
32 </div>
32 </div>
33 %endfor
33 %endfor
34 </div>
34 </div>
35 </div>
35 </div>
36 %endfor
36 %endfor
37 %endfor
37 %endfor
38
38
39 <div class="pagination-wh pagination-left" >
39 <div class="pagination-wh pagination-left">
40 ${c.journal_pager.pager('$link_previous ~2~ $link_next')}
40 ${c.journal_pager.render()}
41 </div>
41 </div>
42 <script type="text/javascript">
42
43 $(document).pjax('#journal .pager_link','#journal');
44 $(document).on('pjax:success',function(){
45 show_more_event();
46 timeagoActivate();
47 tooltipActivate();
48 });
49 </script>
50 %else:
43 %else:
51 <div>
44 <div>
52 ${_('No entries yet')}
45 ${_('No entries yet')}
53 </div>
46 </div>
54 %endif
47 %endif
@@ -1,241 +1,241 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <%inherit file="/base/base.mako"/>
2 <%inherit file="/base/base.mako"/>
3
3
4 <%def name="title()">
4 <%def name="title()">
5 %if c.repo_name:
5 %if c.repo_name:
6 ${_('Search inside repository {repo_name}').format(repo_name=c.repo_name)}
6 ${_('Search inside repository {repo_name}').format(repo_name=c.repo_name)}
7 %elif c.repo_group_name:
7 %elif c.repo_group_name:
8 ${_('Search inside repository group {repo_group_name}').format(repo_group_name=c.repo_group_name)}
8 ${_('Search inside repository group {repo_group_name}').format(repo_group_name=c.repo_group_name)}
9 %else:
9 %else:
10 ${_('Search inside all accessible repositories')}
10 ${_('Search inside all accessible repositories')}
11 %endif
11 %endif
12 %if c.rhodecode_name:
12 %if c.rhodecode_name:
13 &middot; ${h.branding(c.rhodecode_name)}
13 &middot; ${h.branding(c.rhodecode_name)}
14 %endif
14 %endif
15 </%def>
15 </%def>
16
16
17 <%def name="breadcrumbs_links()">
17 <%def name="breadcrumbs_links()">
18 %if c.repo_name:
18 %if c.repo_name:
19 ${_('Search inside repository {repo_name}').format(repo_name=c.repo_name)}
19 ${_('Search inside repository {repo_name}').format(repo_name=c.repo_name)}
20 %elif c.repo_group_name:
20 %elif c.repo_group_name:
21 ${_('Search inside repository group {repo_group_name}').format(repo_group_name=c.repo_group_name)}
21 ${_('Search inside repository group {repo_group_name}').format(repo_group_name=c.repo_group_name)}
22 %else:
22 %else:
23 ${_('Search inside all accessible repositories')}
23 ${_('Search inside all accessible repositories')}
24 %endif
24 %endif
25
25
26 </%def>
26 </%def>
27
27
28 <%def name="menu_bar_nav()">
28 <%def name="menu_bar_nav()">
29 %if c.repo_name:
29 %if c.repo_name:
30 ${self.menu_items(active='search')}
30 ${self.menu_items(active='search')}
31 %elif c.repo_group_name:
31 %elif c.repo_group_name:
32 ${self.menu_items(active='search')}
32 ${self.menu_items(active='search')}
33 %else:
33 %else:
34 ${self.menu_items(active='search')}
34 ${self.menu_items(active='search')}
35 %endif
35 %endif
36 </%def>
36 </%def>
37
37
38 <%def name="menu_bar_subnav()">
38 <%def name="menu_bar_subnav()">
39 %if c.repo_name:
39 %if c.repo_name:
40 <% active_entry = {'content':'files', 'path':'files', 'commit':'commits'}.get(c.search_type, 'summary')%>
40 <% active_entry = {'content':'files', 'path':'files', 'commit':'commits'}.get(c.search_type, 'summary')%>
41 ${self.repo_menu(active=active_entry)}
41 ${self.repo_menu(active=active_entry)}
42 %elif c.repo_group_name:
42 %elif c.repo_group_name:
43 ${self.repo_group_menu(active='home')}
43 ${self.repo_group_menu(active='home')}
44 %endif
44 %endif
45 </%def>
45 </%def>
46
46
47 <%def name="repo_icon(db_repo)">
47 <%def name="repo_icon(db_repo)">
48 %if h.is_hg(db_repo):
48 %if h.is_hg(db_repo):
49 <i class="icon-hg"></i>
49 <i class="icon-hg"></i>
50 %endif
50 %endif
51 %if h.is_git(db_repo):
51 %if h.is_git(db_repo):
52 <i class="icon-git"></i>
52 <i class="icon-git"></i>
53 %endif
53 %endif
54 %if h.is_svn(db_repo):
54 %if h.is_svn(db_repo):
55 <i class="icon-svn"></i>
55 <i class="icon-svn"></i>
56 %endif
56 %endif
57 </%def>
57 </%def>
58
58
59 <%def name="repo_group_icon()">
59 <%def name="repo_group_icon()">
60 <i class="icon-repo-group"></i>
60 <i class="icon-repo-group"></i>
61 </%def>
61 </%def>
62
62
63
63
64 <%def name="field_sort(field_name)">
64 <%def name="field_sort(field_name)">
65
65
66 <%
66 <%
67 if c.sort.startswith('asc:'):
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 elif c.sort.startswith('desc:'):
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 </%def>
74 </%def>
75
75
76
76
77 <%def name="main()">
77 <%def name="main()">
78 <div class="box">
78 <div class="box">
79 %if c.repo_name:
79 %if c.repo_name:
80 <!-- box / title -->
80 <!-- box / title -->
81 ${h.form(h.route_path('search_repo',repo_name=c.repo_name),method='get')}
81 ${h.form(h.route_path('search_repo',repo_name=c.repo_name),method='get')}
82 %elif c.repo_group_name:
82 %elif c.repo_group_name:
83 <!-- box / title -->
83 <!-- box / title -->
84 ${h.form(h.route_path('search_repo_group',repo_group_name=c.repo_group_name),method='get')}
84 ${h.form(h.route_path('search_repo_group',repo_group_name=c.repo_group_name),method='get')}
85 %else:
85 %else:
86 <!-- box / title -->
86 <!-- box / title -->
87 <div class="title">
87 <div class="title">
88 ${self.breadcrumbs()}
88 ${self.breadcrumbs()}
89 <ul class="links">&nbsp;</ul>
89 <ul class="links">&nbsp;</ul>
90 </div>
90 </div>
91 <!-- end box / title -->
91 <!-- end box / title -->
92 ${h.form(h.route_path('search'), method='get')}
92 ${h.form(h.route_path('search'), method='get')}
93 %endif
93 %endif
94 <div class="form search-form">
94 <div class="form search-form">
95 <div class="fields">
95 <div class="fields">
96
96
97 ${h.text('q', c.cur_query, placeholder="Enter query...")}
97 ${h.text('q', c.cur_query, placeholder="Enter query...")}
98
98
99 ${h.select('type',c.search_type,[('content',_('Files')), ('path',_('File path')),('commit',_('Commits'))],id='id_search_type')}
99 ${h.select('type',c.search_type,[('content',_('Files')), ('path',_('File path')),('commit',_('Commits'))],id='id_search_type')}
100 ${h.hidden('max_lines', '10')}
100 ${h.hidden('max_lines', '10')}
101
101
102 <input type="submit" value="${_('Search')}" class="btn"/>
102 <input type="submit" value="${_('Search')}" class="btn"/>
103 <br/>
103 <br/>
104
104
105 <div class="search-tags">
105 <div class="search-tags">
106 <span class="tag tag8">
106 <span class="tag tag8">
107 %if c.repo_name:
107 %if c.repo_name:
108 <a href="${h.route_path('search', _query={'q': c.cur_query, 'type': request.GET.get('type', 'content')})}">${_('Global Search')}</a>
108 <a href="${h.route_path('search', _query={'q': c.cur_query, 'type': request.GET.get('type', 'content')})}">${_('Global Search')}</a>
109 %elif c.repo_group_name:
109 %elif c.repo_group_name:
110 <a href="${h.route_path('search', _query={'q': c.cur_query, 'type': request.GET.get('type', 'content')})}">${_('Global Search')}</a>
110 <a href="${h.route_path('search', _query={'q': c.cur_query, 'type': request.GET.get('type', 'content')})}">${_('Global Search')}</a>
111 % else:
111 % else:
112 ${_('Global Search')}
112 ${_('Global Search')}
113 %endif
113 %endif
114 </span>
114 </span>
115
115
116 %if c.repo_name:
116 %if c.repo_name:
117 Β»
117 Β»
118 <span class="tag tag8">
118 <span class="tag tag8">
119 ${repo_icon(c.rhodecode_db_repo)}
119 ${repo_icon(c.rhodecode_db_repo)}
120 ${c.repo_name}
120 ${c.repo_name}
121 </span>
121 </span>
122
122
123 %elif c.repo_group_name:
123 %elif c.repo_group_name:
124 Β»
124 Β»
125 <span class="tag tag8">
125 <span class="tag tag8">
126 ${repo_group_icon()}
126 ${repo_group_icon()}
127 ${c.repo_group_name}
127 ${c.repo_group_name}
128 </span>
128 </span>
129 %endif
129 %endif
130
130
131 % if c.sort_tag:
131 % if c.sort_tag:
132 <span class="tag tag8">
132 <span class="tag tag8">
133 % if c.sort_tag_dir == 'asc':
133 % if c.sort_tag_dir == 'asc':
134 <i class="icon-angle-down"></i>
134 <i class="icon-angle-down"></i>
135 % elif c.sort_tag_dir == 'desc':
135 % elif c.sort_tag_dir == 'desc':
136 <i class="icon-angle-up"></i>
136 <i class="icon-angle-up"></i>
137 % endif
137 % endif
138 ${_('sort')}:${c.sort_tag}
138 ${_('sort')}:${c.sort_tag}
139 </span>
139 </span>
140 % endif
140 % endif
141
141
142 % for search_tag in c.search_tags:
142 % for search_tag in c.search_tags:
143 <br/><span class="tag disabled" style="margin-top: 3px">${search_tag}</span>
143 <br/><span class="tag disabled" style="margin-top: 3px">${search_tag}</span>
144 % endfor
144 % endfor
145
145
146 </div>
146 </div>
147
147
148 <div class="search-feedback-items">
148 <div class="search-feedback-items">
149 % for error in c.errors:
149 % for error in c.errors:
150 <span class="error-message">
150 <span class="error-message">
151 % for k,v in error.asdict().items():
151 % for k,v in error.asdict().items():
152 ${k} - ${v}
152 ${k} - ${v}
153 % endfor
153 % endfor
154 </span>
154 </span>
155 % endfor
155 % endfor
156 <div class="field">
156 <div class="field">
157 <p class="filterexample" style="position: inherit" onclick="$('#search-help').toggle()">${_('Query Language examples')}</p>
157 <p class="filterexample" style="position: inherit" onclick="$('#search-help').toggle()">${_('Query Language examples')}</p>
158 <pre id="search-help" style="display: none">\
158 <pre id="search-help" style="display: none">\
159
159
160 % if c.searcher.name == 'whoosh':
160 % if c.searcher.name == 'whoosh':
161 Example filter terms for `Whoosh` search:
161 Example filter terms for `Whoosh` search:
162 query lang: <a href="${c.searcher.query_lang_doc}">Whoosh Query Language</a>
162 query lang: <a href="${c.searcher.query_lang_doc}">Whoosh Query Language</a>
163 Whoosh has limited query capabilities. For advanced search use ElasticSearch 6 from RhodeCode EE edition.
163 Whoosh has limited query capabilities. For advanced search use ElasticSearch 6 from RhodeCode EE edition.
164
164
165 Generate wildcards using '*' character:
165 Generate wildcards using '*' character:
166 "repo_name:vcs*" - search everything starting with 'vcs'
166 "repo_name:vcs*" - search everything starting with 'vcs'
167 "repo_name:*vcs*" - search for repository containing 'vcs'
167 "repo_name:*vcs*" - search for repository containing 'vcs'
168
168
169 Optional AND / OR operators in queries
169 Optional AND / OR operators in queries
170 "repo_name:vcs OR repo_name:test"
170 "repo_name:vcs OR repo_name:test"
171 "owner:test AND repo_name:test*" AND extension:py
171 "owner:test AND repo_name:test*" AND extension:py
172
172
173 Move advanced search is available via ElasticSearch6 backend in EE edition.
173 Move advanced search is available via ElasticSearch6 backend in EE edition.
174 % elif c.searcher.name == 'elasticsearch' and c.searcher.es_version == '2':
174 % elif c.searcher.name == 'elasticsearch' and c.searcher.es_version == '2':
175 Example filter terms for `ElasticSearch-${c.searcher.es_version}`search:
175 Example filter terms for `ElasticSearch-${c.searcher.es_version}`search:
176 ElasticSearch-2 has limited query capabilities. For advanced search use ElasticSearch 6 from RhodeCode EE edition.
176 ElasticSearch-2 has limited query capabilities. For advanced search use ElasticSearch 6 from RhodeCode EE edition.
177
177
178 search type: content (File Content)
178 search type: content (File Content)
179 indexed fields: content
179 indexed fields: content
180
180
181 # search for `fix` string in all files
181 # search for `fix` string in all files
182 fix
182 fix
183
183
184 search type: commit (Commit message)
184 search type: commit (Commit message)
185 indexed fields: message
185 indexed fields: message
186
186
187 search type: path (File name)
187 search type: path (File name)
188 indexed fields: path
188 indexed fields: path
189
189
190 % else:
190 % else:
191 Example filter terms for `ElasticSearch-${c.searcher.es_version}`search:
191 Example filter terms for `ElasticSearch-${c.searcher.es_version}`search:
192 query lang: <a href="${c.searcher.query_lang_doc}">ES 6 Query Language</a>
192 query lang: <a href="${c.searcher.query_lang_doc}">ES 6 Query Language</a>
193 The reserved characters needed espace by `\`: + - = && || > < ! ( ) { } [ ] ^ " ~ * ? : \ /
193 The reserved characters needed espace by `\`: + - = && || > < ! ( ) { } [ ] ^ " ~ * ? : \ /
194 % for handler in c.searcher.get_handlers().values():
194 % for handler in c.searcher.get_handlers().values():
195
195
196 search type: ${handler.search_type_label}
196 search type: ${handler.search_type_label}
197 *indexed fields*: ${', '.join( [('\n ' if x[0]%4==0 else '')+x[1] for x in enumerate(handler.es_6_field_names)])}
197 *indexed fields*: ${', '.join( [('\n ' if x[0]%4==0 else '')+x[1] for x in enumerate(handler.es_6_field_names)])}
198 % for entry in handler.es_6_example_queries:
198 % for entry in handler.es_6_example_queries:
199 ${entry.rstrip()}
199 ${entry.rstrip()}
200 % endfor
200 % endfor
201 % endfor
201 % endfor
202
202
203 % endif
203 % endif
204 </pre>
204 </pre>
205 </div>
205 </div>
206
206
207 <div class="field">${c.runtime}</div>
207 <div class="field">${c.runtime}</div>
208 </div>
208 </div>
209 </div>
209 </div>
210 </div>
210 </div>
211
211
212 ${h.end_form()}
212 ${h.end_form()}
213 <div class="search">
213 <div class="search">
214 % if c.search_type == 'content':
214 % if c.search_type == 'content':
215 <%include file='search_content.mako'/>
215 <%include file='search_content.mako'/>
216 % elif c.search_type == 'path':
216 % elif c.search_type == 'path':
217 <%include file='search_path.mako'/>
217 <%include file='search_path.mako'/>
218 % elif c.search_type == 'commit':
218 % elif c.search_type == 'commit':
219 <%include file='search_commit.mako'/>
219 <%include file='search_commit.mako'/>
220 % elif c.search_type == 'repository':
220 % elif c.search_type == 'repository':
221 <%include file='search_repository.mako'/>
221 <%include file='search_repository.mako'/>
222 % endif
222 % endif
223 </div>
223 </div>
224 </div>
224 </div>
225 <script>
225 <script>
226 $(document).ready(function(){
226 $(document).ready(function(){
227 $("#id_search_type").select2({
227 $("#id_search_type").select2({
228 'containerCssClass': "drop-menu",
228 'containerCssClass': "drop-menu",
229 'dropdownCssClass': "drop-menu-dropdown",
229 'dropdownCssClass': "drop-menu-dropdown",
230 'dropdownAutoWidth': true,
230 'dropdownAutoWidth': true,
231 'minimumResultsForSearch': -1
231 'minimumResultsForSearch': -1
232 });
232 });
233
233
234 $('#q').autoGrowInput({maxWidth: 920});
234 $('#q').autoGrowInput({maxWidth: 920});
235
235
236 setTimeout(function() {
236 setTimeout(function() {
237 $('#q').keyup()
237 $('#q').keyup()
238 }, 1);
238 }, 1);
239 })
239 })
240 </script>
240 </script>
241 </%def>
241 </%def>
@@ -1,98 +1,98 b''
1 <%namespace name="base" file="/base/base.mako"/>
1 <%namespace name="base" file="/base/base.mako"/>
2 <%namespace name="search" file="/search/search.mako"/>
2 <%namespace name="search" file="/search/search.mako"/>
3
3
4 % if c.formatted_results:
4 % if c.formatted_results:
5
5
6 <table class="rctable search-results">
6 <table class="rctable search-results">
7 <tr>
7 <tr>
8 <th>${_('Repository')}</th>
8 <th>${_('Repository')}</th>
9 <th>${_('Commit')}</th>
9 <th>${_('Commit')}</th>
10 <th></th>
10 <th></th>
11 <th>
11 <th>
12 <a href="${search.field_sort('message')}">${_('Commit message')}</a>
12 <a href="${search.field_sort('message')}">${_('Commit message')}</a>
13 </th>
13 </th>
14 <th>
14 <th>
15 <a href="${search.field_sort('date')}">${_('Commit date')}</a>
15 <a href="${search.field_sort('date')}">${_('Commit date')}</a>
16 </th>
16 </th>
17 <th>
17 <th>
18 <a href="${search.field_sort('author_email')}">${_('Author')}</a>
18 <a href="${search.field_sort('author_email')}">${_('Author')}</a>
19 </th>
19 </th>
20 </tr>
20 </tr>
21 %for entry in c.formatted_results:
21 %for entry in c.formatted_results:
22 ## search results are additionally filtered, and this check is just a safe gate
22 ## search results are additionally filtered, and this check is just a safe gate
23 % if c.rhodecode_user.is_admin or h.HasRepoPermissionAny('repository.write','repository.read','repository.admin')(entry['repository'], 'search results commit check'):
23 % if c.rhodecode_user.is_admin or h.HasRepoPermissionAny('repository.write','repository.read','repository.admin')(entry['repository'], 'search results commit check'):
24 <tr class="body">
24 <tr class="body">
25 <td class="td-componentname">
25 <td class="td-componentname">
26 <% repo_type = entry.get('repo_type') or h.get_repo_type_by_name(entry.get('repository')) %>
26 <% repo_type = entry.get('repo_type') or h.get_repo_type_by_name(entry.get('repository')) %>
27 ${search.repo_icon(repo_type)}
27 ${search.repo_icon(repo_type)}
28 ${h.link_to(entry['repository'], h.route_path('repo_summary',repo_name=entry['repository']))}
28 ${h.link_to(entry['repository'], h.route_path('repo_summary',repo_name=entry['repository']))}
29 </td>
29 </td>
30 <td class="td-hash">
30 <td class="td-hash">
31 ${h.link_to(h._shorten_commit_id(entry['commit_id']),
31 ${h.link_to(h._shorten_commit_id(entry['commit_id']),
32 h.route_path('repo_commit',repo_name=entry['repository'],commit_id=entry['commit_id']))}
32 h.route_path('repo_commit',repo_name=entry['repository'],commit_id=entry['commit_id']))}
33 </td>
33 </td>
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')}">
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 <div>
35 <div>
36 <i class="icon-expand-linked"></i>&nbsp;
36 <i class="icon-expand-linked"></i>&nbsp;
37 </div>
37 </div>
38 </td>
38 </td>
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">
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 %if entry.get('message_hl'):
40 %if entry.get('message_hl'):
41 ${h.literal(entry['message_hl'])}
41 ${h.literal(entry['message_hl'])}
42 %else:
42 %else:
43 ${h.urlify_commit_message(entry['message'], entry['repository'])}
43 ${h.urlify_commit_message(entry['message'], entry['repository'])}
44 %endif
44 %endif
45 </td>
45 </td>
46 <td class="td-time">
46 <td class="td-time">
47 ${h.age_component(h.time_to_utcdatetime(entry['date']))}
47 ${h.age_component(h.time_to_utcdatetime(entry['date']))}
48 </td>
48 </td>
49
49
50 <td class="td-user author">
50 <td class="td-user author">
51 <%
51 <%
52 ## es6 stores this as object
52 ## es6 stores this as object
53 author = entry['author']
53 author = entry['author']
54 if isinstance(author, dict):
54 if isinstance(author, dict):
55 author = author['email']
55 author = author['email']
56 %>
56 %>
57 ${base.gravatar_with_user(author, tooltip=True)}
57 ${base.gravatar_with_user(author, tooltip=True)}
58 </td>
58 </td>
59 </tr>
59 </tr>
60 % endif
60 % endif
61 %endfor
61 %endfor
62 </table>
62 </table>
63
63
64 %if c.cur_query:
64 %if c.cur_query:
65 <div class="pagination-wh pagination-left">
65 <div class="pagination-wh pagination-left">
66 ${c.formatted_results.pager('$link_previous ~2~ $link_next')}
66 ${c.formatted_results.render()}
67 </div>
67 </div>
68 %endif
68 %endif
69
69
70 <script>
70 <script>
71 $('.expand_commit').on('click',function(e){
71 $('.expand_commit').on('click',function(e){
72 var target_expand = $(this);
72 var target_expand = $(this);
73 var cid = target_expand.data('commit-id');
73 var cid = target_expand.data('commit-id');
74
74
75 if (target_expand.hasClass('open')){
75 if (target_expand.hasClass('open')){
76 $('#c-'+cid).css({'height': '1.5em', 'white-space': 'nowrap', 'text-overflow': 'ellipsis', 'overflow':'hidden'});
76 $('#c-'+cid).css({'height': '1.5em', 'white-space': 'nowrap', 'text-overflow': 'ellipsis', 'overflow':'hidden'});
77 $('#t-'+cid).css({'height': 'auto', 'line-height': '.9em', 'text-overflow': 'ellipsis', 'overflow':'hidden'});
77 $('#t-'+cid).css({'height': 'auto', 'line-height': '.9em', 'text-overflow': 'ellipsis', 'overflow':'hidden'});
78 target_expand.removeClass('open');
78 target_expand.removeClass('open');
79 }
79 }
80 else {
80 else {
81 $('#c-'+cid).css({'height': 'auto', 'white-space': 'normal', 'text-overflow': 'initial', 'overflow':'visible'});
81 $('#c-'+cid).css({'height': 'auto', 'white-space': 'normal', 'text-overflow': 'initial', 'overflow':'visible'});
82 $('#t-'+cid).css({'height': 'auto', 'max-height': 'none', 'text-overflow': 'initial', 'overflow':'visible'});
82 $('#t-'+cid).css({'height': 'auto', 'max-height': 'none', 'text-overflow': 'initial', 'overflow':'visible'});
83 target_expand.addClass('open');
83 target_expand.addClass('open');
84 }
84 }
85 });
85 });
86
86
87 $(".message.td-description").mark(
87 $(".message.td-description").mark(
88 "${c.searcher.query_to_mark(c.cur_query, 'message')}",
88 "${c.searcher.query_to_mark(c.cur_query, 'message')}",
89 {
89 {
90 "className": 'match',
90 "className": 'match',
91 "accuracy": "complementary",
91 "accuracy": "complementary",
92 "ignorePunctuation": ":._(){}[]!'+=".split("")
92 "ignorePunctuation": ":._(){}[]!'+=".split("")
93 }
93 }
94 );
94 );
95
95
96 </script>
96 </script>
97
97
98 % endif
98 % endif
@@ -1,165 +1,165 b''
1 <%namespace name="search" file="/search/search.mako"/>
1 <%namespace name="search" file="/search/search.mako"/>
2
2
3 <%def name="highlight_text_file(has_matched_content, file_content, lexer, html_formatter, matching_lines, shown_matching_lines, url, use_hl_filter)">
3 <%def name="highlight_text_file(has_matched_content, file_content, lexer, html_formatter, matching_lines, shown_matching_lines, url, use_hl_filter)">
4 % if has_matched_content:
4 % if has_matched_content:
5 ${h.code_highlight(file_content, lexer, html_formatter, use_hl_filter=use_hl_filter)|n}
5 ${h.code_highlight(file_content, lexer, html_formatter, use_hl_filter=use_hl_filter)|n}
6 % else:
6 % else:
7 ${_('No content matched')} <br/>
7 ${_('No content matched')} <br/>
8 % endif
8 % endif
9
9
10 %if len(matching_lines) > shown_matching_lines:
10 %if len(matching_lines) > shown_matching_lines:
11 <a href="${url}">
11 <a href="${url}">
12 ${len(matching_lines) - shown_matching_lines} ${_('more matches in this file')}
12 ${len(matching_lines) - shown_matching_lines} ${_('more matches in this file')}
13 </a>
13 </a>
14 %endif
14 %endif
15 </%def>
15 </%def>
16
16
17 <div class="search-results">
17 <div class="search-results">
18 <% query_mark = c.searcher.query_to_mark(c.cur_query, 'content') %>
18 <% query_mark = c.searcher.query_to_mark(c.cur_query, 'content') %>
19
19
20 %for entry in c.formatted_results:
20 %for entry in c.formatted_results:
21
21
22 <%
22 <%
23 file_content = entry['content_highlight'] or entry['content']
23 file_content = entry['content_highlight'] or entry['content']
24 mimetype = entry.get('mimetype')
24 mimetype = entry.get('mimetype')
25 filepath = entry.get('path')
25 filepath = entry.get('path')
26 max_lines = h.safe_int(request.GET.get('max_lines', '10'))
26 max_lines = h.safe_int(request.GET.get('max_lines', '10'))
27 line_context = h.safe_int(request.GET.get('line_contenxt', '3'))
27 line_context = h.safe_int(request.GET.get('line_contenxt', '3'))
28
28
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})
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 terms = c.cur_query
30 terms = c.cur_query
31
31
32 if c.searcher.is_es_6:
32 if c.searcher.is_es_6:
33 # use empty terms so we default to markers usage
33 # use empty terms so we default to markers usage
34 total_lines, matching_lines = h.get_matching_line_offsets(file_content, terms=None)
34 total_lines, matching_lines = h.get_matching_line_offsets(file_content, terms=None)
35 else:
35 else:
36 total_lines, matching_lines = h.get_matching_line_offsets(file_content, terms)
36 total_lines, matching_lines = h.get_matching_line_offsets(file_content, terms)
37
37
38 shown_matching_lines = 0
38 shown_matching_lines = 0
39 lines_of_interest = set()
39 lines_of_interest = set()
40 for line_number in matching_lines:
40 for line_number in matching_lines:
41 if len(lines_of_interest) < max_lines:
41 if len(lines_of_interest) < max_lines:
42 lines_of_interest |= set(range(
42 lines_of_interest |= set(range(
43 max(line_number - line_context, 0),
43 max(line_number - line_context, 0),
44 min(line_number + line_context, total_lines + 1)))
44 min(line_number + line_context, total_lines + 1)))
45 shown_matching_lines += 1
45 shown_matching_lines += 1
46 lexer = h.get_lexer_safe(mimetype=mimetype, filepath=filepath)
46 lexer = h.get_lexer_safe(mimetype=mimetype, filepath=filepath)
47
47
48 html_formatter = h.SearchContentCodeHtmlFormatter(
48 html_formatter = h.SearchContentCodeHtmlFormatter(
49 linenos=True,
49 linenos=True,
50 cssclass="code-highlight",
50 cssclass="code-highlight",
51 url=match_file_url,
51 url=match_file_url,
52 query_terms=terms,
52 query_terms=terms,
53 only_line_numbers=lines_of_interest
53 only_line_numbers=lines_of_interest
54 )
54 )
55
55
56 has_matched_content = len(lines_of_interest) >= 1
56 has_matched_content = len(lines_of_interest) >= 1
57
57
58 %>
58 %>
59 ## search results are additionally filtered, and this check is just a safe gate
59 ## search results are additionally filtered, and this check is just a safe gate
60 % if c.rhodecode_user.is_admin or h.HasRepoPermissionAny('repository.write','repository.read','repository.admin')(entry['repository'], 'search results content check'):
60 % if c.rhodecode_user.is_admin or h.HasRepoPermissionAny('repository.write','repository.read','repository.admin')(entry['repository'], 'search results content check'):
61 <div class="codeblock">
61 <div class="codeblock">
62 <h1>
62 <h1>
63 <% repo_type = entry.get('repo_type') or h.get_repo_type_by_name(entry.get('repository')) %>
63 <% repo_type = entry.get('repo_type') or h.get_repo_type_by_name(entry.get('repository')) %>
64 ${search.repo_icon(repo_type)}
64 ${search.repo_icon(repo_type)}
65 ${h.link_to(entry['repository'], h.route_path('repo_summary', repo_name=entry['repository']))}
65 ${h.link_to(entry['repository'], h.route_path('repo_summary', repo_name=entry['repository']))}
66 </h1>
66 </h1>
67
67
68 <div class="codeblock-header">
68 <div class="codeblock-header">
69
69
70 <div class="file-filename">
70 <div class="file-filename">
71 <i class="icon-file"></i> ${entry['f_path'].split('/')[-1]}
71 <i class="icon-file"></i> ${entry['f_path'].split('/')[-1]}
72 </div>
72 </div>
73
73
74 <div class="file-stats">
74 <div class="file-stats">
75 <div class="stats-info">
75 <div class="stats-info">
76 <span class="stats-first-item">
76 <span class="stats-first-item">
77 ${entry.get('lines', 0.)} ${_ungettext('line', 'lines', entry.get('lines', 0.))}
77 ${entry.get('lines', 0.)} ${_ungettext('line', 'lines', entry.get('lines', 0.))}
78 (${len(matching_lines)} ${_ungettext('matched', 'matched', len(matching_lines))})
78 (${len(matching_lines)} ${_ungettext('matched', 'matched', len(matching_lines))})
79 </span>
79 </span>
80 <span>
80 <span>
81 % if entry.get('size'):
81 % if entry.get('size'):
82 | ${h.format_byte_size_binary(entry['size'])}
82 | ${h.format_byte_size_binary(entry['size'])}
83 % endif
83 % endif
84 </span>
84 </span>
85 <span>
85 <span>
86 % if entry.get('mimetype'):
86 % if entry.get('mimetype'):
87 | ${entry.get('mimetype', "unknown mimetype")}
87 | ${entry.get('mimetype', "unknown mimetype")}
88 % endif
88 % endif
89 </span>
89 </span>
90 </div>
90 </div>
91 </div>
91 </div>
92 </div>
92 </div>
93
93
94 <div class="path clear-fix">
94 <div class="path clear-fix">
95 <div class="pull-left">
95 <div class="pull-left">
96 ${h.files_breadcrumbs(entry['repository'],entry.get('commit_id', 'tip'),entry['f_path'], linkify_last_item=True)}
96 ${h.files_breadcrumbs(entry['repository'],entry.get('commit_id', 'tip'),entry['f_path'], linkify_last_item=True)}
97 </div>
97 </div>
98
98
99 <div class="pull-right stats">
99 <div class="pull-right stats">
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',''))}">
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 ## ${_('Show Full History')}
101 ## ${_('Show Full History')}
102 ## </a>
102 ## </a>
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','')))}
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 ## | ${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','')))}
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 <div class="search-tags">
105 <div class="search-tags">
106
106
107 <% repo_group = entry.get('repository_group')%>
107 <% repo_group = entry.get('repository_group')%>
108 ## hiden if in repo group view
108 ## hiden if in repo group view
109 % if repo_group and not c.repo_group_name:
109 % if repo_group and not c.repo_group_name:
110 <span class="tag tag8">
110 <span class="tag tag8">
111 ${search.repo_group_icon()}
111 ${search.repo_group_icon()}
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>
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 </span>
113 </span>
114 % endif
114 % endif
115 ## hiden if in repo view
115 ## hiden if in repo view
116 % if not c.repo_name:
116 % if not c.repo_name:
117 <span class="tag tag8">
117 <span class="tag tag8">
118 ${search.repo_icon(repo_type)}
118 ${search.repo_icon(repo_type)}
119 <a href="${h.route_path('search_repo', repo_name=entry.get('repo_name'), _query={'q': c.cur_query})}">${_('Narrow to this repository')}</a>
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 </span>
120 </span>
121 % endif
121 % endif
122 </div>
122 </div>
123
123
124 </div>
124 </div>
125 <div class="clear-fix"></div>
125 <div class="clear-fix"></div>
126 </div>
126 </div>
127
127
128
128
129 <div class="code-body search-code-body clear-fix">
129 <div class="code-body search-code-body clear-fix">
130 ${highlight_text_file(
130 ${highlight_text_file(
131 has_matched_content=has_matched_content,
131 has_matched_content=has_matched_content,
132 file_content=file_content,
132 file_content=file_content,
133 lexer=lexer,
133 lexer=lexer,
134 html_formatter=html_formatter,
134 html_formatter=html_formatter,
135 matching_lines=matching_lines,
135 matching_lines=matching_lines,
136 shown_matching_lines=shown_matching_lines,
136 shown_matching_lines=shown_matching_lines,
137 url=match_file_url,
137 url=match_file_url,
138 use_hl_filter=c.searcher.is_es_6
138 use_hl_filter=c.searcher.is_es_6
139 )}
139 )}
140 </div>
140 </div>
141
141
142 </div>
142 </div>
143 % endif
143 % endif
144 %endfor
144 %endfor
145 </div>
145 </div>
146 %if c.cur_query and c.formatted_results:
146 %if c.cur_query and c.formatted_results:
147 <div class="pagination-wh pagination-left" >
147 <div class="pagination-wh pagination-left" >
148 ${c.formatted_results.pager('$link_previous ~2~ $link_next')}
148 ${c.formatted_results.render()}
149 </div>
149 </div>
150 %endif
150 %endif
151
151
152 %if c.cur_query:
152 %if c.cur_query:
153 <script type="text/javascript">
153 <script type="text/javascript">
154 $(function(){
154 $(function(){
155 $(".search-code-body").mark(
155 $(".search-code-body").mark(
156 "${query_mark}",
156 "${query_mark}",
157 {
157 {
158 "className": 'match',
158 "className": 'match',
159 "accuracy": "complementary",
159 "accuracy": "complementary",
160 "ignorePunctuation": ":._(){}[]!'+=".split("")
160 "ignorePunctuation": ":._(){}[]!'+=".split("")
161 }
161 }
162 );
162 );
163 })
163 })
164 </script>
164 </script>
165 %endif
165 %endif
@@ -1,53 +1,53 b''
1 <%namespace name="search" file="/search/search.mako"/>
1 <%namespace name="search" file="/search/search.mako"/>
2
2
3 % if c.formatted_results:
3 % if c.formatted_results:
4
4
5 <table class="rctable search-results">
5 <table class="rctable search-results">
6 <tr>
6 <tr>
7 <th>${_('Repository')}</th>
7 <th>${_('Repository')}</th>
8 <th>
8 <th>
9 <a href="${search.field_sort('file')}">${_('File')}</a>
9 <a href="${search.field_sort('file')}">${_('File')}</a>
10 </th>
10 </th>
11 <th>
11 <th>
12 <a href="${search.field_sort('size')}">${_('Size')}</a>
12 <a href="${search.field_sort('size')}">${_('Size')}</a>
13 </th>
13 </th>
14 <th>
14 <th>
15 <a href="${search.field_sort('lines')}">${_('Lines')}</a>
15 <a href="${search.field_sort('lines')}">${_('Lines')}</a>
16 </th>
16 </th>
17 </tr>
17 </tr>
18 %for entry in c.formatted_results:
18 %for entry in c.formatted_results:
19 ## search results are additionally filtered, and this check is just a safe gate
19 ## search results are additionally filtered, and this check is just a safe gate
20 % if c.rhodecode_user.is_admin or h.HasRepoPermissionAny('repository.write','repository.read','repository.admin')(entry['repository'], 'search results path check'):
20 % if c.rhodecode_user.is_admin or h.HasRepoPermissionAny('repository.write','repository.read','repository.admin')(entry['repository'], 'search results path check'):
21 <tr class="body">
21 <tr class="body">
22 <td class="td-componentname">
22 <td class="td-componentname">
23 <% repo_type = entry.get('repo_type') or h.get_repo_type_by_name(entry.get('repository')) %>
23 <% repo_type = entry.get('repo_type') or h.get_repo_type_by_name(entry.get('repository')) %>
24 ${search.repo_icon(repo_type)}
24 ${search.repo_icon(repo_type)}
25 ${h.link_to(entry['repository'], h.route_path('repo_summary',repo_name=entry['repository']))}
25 ${h.link_to(entry['repository'], h.route_path('repo_summary',repo_name=entry['repository']))}
26 </td>
26 </td>
27 <td class="td-componentname">
27 <td class="td-componentname">
28 <i class="icon-file"></i>
28 <i class="icon-file"></i>
29 ${h.link_to(h.literal(entry['f_path']),
29 ${h.link_to(h.literal(entry['f_path']),
30 h.route_path('repo_files',repo_name=entry['repository'],commit_id='tip',f_path=entry['f_path']))}
30 h.route_path('repo_files',repo_name=entry['repository'],commit_id='tip',f_path=entry['f_path']))}
31 </td>
31 </td>
32 <td>
32 <td>
33 %if entry.get('size'):
33 %if entry.get('size'):
34 ${h.format_byte_size_binary(entry['size'])}
34 ${h.format_byte_size_binary(entry['size'])}
35 %endif
35 %endif
36 </td>
36 </td>
37 <td>
37 <td>
38 %if entry.get('lines'):
38 %if entry.get('lines'):
39 ${entry.get('lines', 0.)}
39 ${entry.get('lines', 0.)}
40 %endif
40 %endif
41 </td>
41 </td>
42 </tr>
42 </tr>
43 % endif
43 % endif
44 %endfor
44 %endfor
45 </table>
45 </table>
46
46
47 %if c.cur_query:
47 %if c.cur_query:
48 <div class="pagination-wh pagination-left">
48 <div class="pagination-wh pagination-left">
49 ${c.formatted_results.pager('$link_previous ~2~ $link_next')}
49 ${c.formatted_results.render()}
50 </div>
50 </div>
51 %endif
51 %endif
52
52
53 % endif
53 % endif
@@ -1,167 +1,167 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <%namespace name="base" file="/base/base.mako"/>
2 <%namespace name="base" file="/base/base.mako"/>
3 %if c.repo_commits:
3 %if c.repo_commits:
4 <table class="rctable repo_summary table_disp">
4 <table class="rctable repo_summary table_disp">
5 <tr>
5 <tr>
6
6
7 <th class="status"></th>
7 <th class="status"></th>
8 <th>${_('Commit')}</th>
8 <th>${_('Commit')}</th>
9 <th>${_('Commit message')}</th>
9 <th>${_('Commit message')}</th>
10 <th>${_('Age')}</th>
10 <th>${_('Age')}</th>
11 <th>${_('Author')}</th>
11 <th>${_('Author')}</th>
12 <th colspan="2">${_('Refs')}</th>
12 <th colspan="2">${_('Refs')}</th>
13 </tr>
13 </tr>
14
14
15 ## to speed up lookups cache some functions before the loop
15 ## to speed up lookups cache some functions before the loop
16 <%
16 <%
17 active_patterns = h.get_active_pattern_entries(c.repo_name)
17 active_patterns = h.get_active_pattern_entries(c.repo_name)
18 urlify_commit_message = h.partial(h.urlify_commit_message, active_pattern_entries=active_patterns)
18 urlify_commit_message = h.partial(h.urlify_commit_message, active_pattern_entries=active_patterns)
19 %>
19 %>
20 %for cnt,cs in enumerate(c.repo_commits):
20 %for cnt,cs in enumerate(c.repo_commits):
21 <tr class="parity${cnt%2}">
21 <tr class="parity${cnt%2}">
22
22
23 <td class="td-status">
23 <td class="td-status">
24 %if c.statuses.get(cs.raw_id):
24 %if c.statuses.get(cs.raw_id):
25 <div class="changeset-status-ico shortlog">
25 <div class="changeset-status-ico shortlog">
26 %if c.statuses.get(cs.raw_id)[2]:
26 %if c.statuses.get(cs.raw_id)[2]:
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])}">
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 <i class="icon-circle review-status-${c.statuses.get(cs.raw_id)[0]}"></i>
28 <i class="icon-circle review-status-${c.statuses.get(cs.raw_id)[0]}"></i>
29 </a>
29 </a>
30 %else:
30 %else:
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)}">
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 <i class="icon-circle review-status-${c.statuses.get(cs.raw_id)[0]}"></i>
32 <i class="icon-circle review-status-${c.statuses.get(cs.raw_id)[0]}"></i>
33 </a>
33 </a>
34 %endif
34 %endif
35 </div>
35 </div>
36 %else:
36 %else:
37 <i class="icon-circle review-status-not_reviewed" title="${_('Commit status: Not Reviewed')}"></i>
37 <i class="icon-circle review-status-not_reviewed" title="${_('Commit status: Not Reviewed')}"></i>
38 %endif
38 %endif
39 </td>
39 </td>
40 <td class="td-hash">
40 <td class="td-hash">
41 <code>
41 <code>
42 <a href="${h.route_path('repo_commit', repo_name=c.repo_name, commit_id=cs.raw_id)}">${h.show_id(cs)}</a>
42 <a href="${h.route_path('repo_commit', repo_name=c.repo_name, commit_id=cs.raw_id)}">${h.show_id(cs)}</a>
43 <i class="tooltip icon-clipboard clipboard-action" data-clipboard-text="${cs.raw_id}" title="${_('Copy the full commit id')}"></i>
43 <i class="tooltip icon-clipboard clipboard-action" data-clipboard-text="${cs.raw_id}" title="${_('Copy the full commit id')}"></i>
44 </code>
44 </code>
45 </td>
45 </td>
46
46
47 <td class="td-description mid">
47 <td class="td-description mid">
48 <div class="log-container truncate-wrap">
48 <div class="log-container truncate-wrap">
49 <div class="message truncate" id="c-${cs.raw_id}">${urlify_commit_message(cs.message, c.repo_name)}</div>
49 <div class="message truncate" id="c-${cs.raw_id}">${urlify_commit_message(cs.message, c.repo_name)}</div>
50 </div>
50 </div>
51 </td>
51 </td>
52
52
53 <td class="td-time">
53 <td class="td-time">
54 ${h.age_component(cs.date)}
54 ${h.age_component(cs.date)}
55 </td>
55 </td>
56 <td class="td-user author">
56 <td class="td-user author">
57 ${base.gravatar_with_user(cs.author, tooltip=True)}
57 ${base.gravatar_with_user(cs.author, tooltip=True)}
58 </td>
58 </td>
59
59
60 <td class="td-tags">
60 <td class="td-tags">
61 <div class="autoexpand">
61 <div class="autoexpand">
62 %if h.is_hg(c.rhodecode_repo):
62 %if h.is_hg(c.rhodecode_repo):
63 %for book in cs.bookmarks:
63 %for book in cs.bookmarks:
64 <span class="booktag tag" title="${h.tooltip(_('Bookmark %s') % book)}">
64 <span class="booktag tag" title="${h.tooltip(_('Bookmark %s') % book)}">
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>
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 </span>
66 </span>
67 %endfor
67 %endfor
68 %endif
68 %endif
69 ## tags
69 ## tags
70 %for tag in cs.tags:
70 %for tag in cs.tags:
71 <span class="tagtag tag" title="${h.tooltip(_('Tag %s') % tag)}">
71 <span class="tagtag tag" title="${h.tooltip(_('Tag %s') % tag)}">
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>
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 </span>
73 </span>
74 %endfor
74 %endfor
75
75
76 ## branch
76 ## branch
77 %if cs.branch:
77 %if cs.branch:
78 <span class="branchtag tag" title="${h.tooltip(_('Branch %s') % cs.branch)}">
78 <span class="branchtag tag" title="${h.tooltip(_('Branch %s') % cs.branch)}">
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>
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 </span>
80 </span>
81 %endif
81 %endif
82 </div>
82 </div>
83 </td>
83 </td>
84 <td class="td-comments">
84 <td class="td-comments">
85 <% cs_comments = c.comments.get(cs.raw_id,[]) %>
85 <% cs_comments = c.comments.get(cs.raw_id,[]) %>
86 % if cs_comments:
86 % if cs_comments:
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)}">
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 <i class="icon-comment"></i> ${len(cs_comments)}
88 <i class="icon-comment"></i> ${len(cs_comments)}
89 </a>
89 </a>
90 % else:
90 % else:
91 <i class="icon-comment"></i> ${len(cs_comments)}
91 <i class="icon-comment"></i> ${len(cs_comments)}
92 % endif
92 % endif
93 </td>
93 </td>
94 </tr>
94 </tr>
95 %endfor
95 %endfor
96
96
97 </table>
97 </table>
98
98
99 <script type="text/javascript">
99 <script type="text/javascript">
100 $(document).pjax('#shortlog_data .pager_link','#shortlog_data', {timeout: 5000, scrollTo: false, push: false});
100 $(document).pjax('#shortlog_data .pager_link','#shortlog_data', {timeout: 5000, scrollTo: false, push: false});
101 $(document).on('pjax:success', function(){ timeagoActivate(); tooltipActivate();});
101 $(document).on('pjax:success', function(){ timeagoActivate(); tooltipActivate();});
102 $(document).on('pjax:timeout', function(event) {
102 $(document).on('pjax:timeout', function(event) {
103 // Prevent default timeout redirection behavior
103 // Prevent default timeout redirection behavior
104 event.preventDefault()
104 event.preventDefault()
105 })
105 })
106
106
107 </script>
107 </script>
108
108
109 <div class="pagination-wh pagination-left">
109 <div class="pagination-wh pagination-left">
110 ${c.repo_commits.pager('$link_previous ~2~ $link_next')}
110 ${c.repo_commits.render()}
111 </div>
111 </div>
112 %else:
112 %else:
113
113
114 %if h.HasRepoPermissionAny('repository.write','repository.admin')(c.repo_name):
114 %if h.HasRepoPermissionAny('repository.write','repository.admin')(c.repo_name):
115 <div class="quick_start">
115 <div class="quick_start">
116 <div class="fieldset">
116 <div class="fieldset">
117 <p><b>${_('Add or upload files directly via RhodeCode:')}</b></p>
117 <p><b>${_('Add or upload files directly via RhodeCode:')}</b></p>
118 <div class="pull-left">
118 <div class="pull-left">
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>
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 </div>
120 </div>
121 <div class="pull-left">
121 <div class="pull-left">
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>
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 </div>
123 </div>
124 %endif
124 %endif
125 </div>
125 </div>
126
126
127 <div class="fieldset">
127 <div class="fieldset">
128 <p><b>${_('Push new repo:')}</b></p>
128 <p><b>${_('Push new repo:')}</b></p>
129 <pre>
129 <pre>
130 %if h.is_git(c.rhodecode_repo):
130 %if h.is_git(c.rhodecode_repo):
131 git clone ${c.clone_repo_url}
131 git clone ${c.clone_repo_url}
132 git add README # add first file
132 git add README # add first file
133 git commit -m "Initial commit" # commit with message
133 git commit -m "Initial commit" # commit with message
134 git remote add origin ${c.clone_repo_url}
134 git remote add origin ${c.clone_repo_url}
135 git push -u origin master # push changes back to default master branch
135 git push -u origin master # push changes back to default master branch
136 %elif h.is_hg(c.rhodecode_repo):
136 %elif h.is_hg(c.rhodecode_repo):
137 hg clone ${c.clone_repo_url}
137 hg clone ${c.clone_repo_url}
138 hg add README # add first file
138 hg add README # add first file
139 hg commit -m "Initial commit" # commit with message
139 hg commit -m "Initial commit" # commit with message
140 hg push ${c.clone_repo_url}
140 hg push ${c.clone_repo_url}
141 %elif h.is_svn(c.rhodecode_repo):
141 %elif h.is_svn(c.rhodecode_repo):
142 svn co ${c.clone_repo_url}
142 svn co ${c.clone_repo_url}
143 svn add README # add first file
143 svn add README # add first file
144 svn commit -m "Initial commit"
144 svn commit -m "Initial commit"
145 svn commit # send changes back to the server
145 svn commit # send changes back to the server
146 %endif
146 %endif
147 </pre>
147 </pre>
148 </div>
148 </div>
149
149
150 <div class="fieldset">
150 <div class="fieldset">
151 <p><b>${_('Existing repository?')}</b></p>
151 <p><b>${_('Existing repository?')}</b></p>
152 <pre>
152 <pre>
153 %if h.is_git(c.rhodecode_repo):
153 %if h.is_git(c.rhodecode_repo):
154 git remote add origin ${c.clone_repo_url}
154 git remote add origin ${c.clone_repo_url}
155 git push -u origin master
155 git push -u origin master
156 %elif h.is_hg(c.rhodecode_repo):
156 %elif h.is_hg(c.rhodecode_repo):
157 hg push ${c.clone_repo_url}
157 hg push ${c.clone_repo_url}
158 %elif h.is_svn(c.rhodecode_repo):
158 %elif h.is_svn(c.rhodecode_repo):
159 svn co ${c.clone_repo_url}
159 svn co ${c.clone_repo_url}
160 %endif
160 %endif
161 </pre>
161 </pre>
162
162
163 </div>
163 </div>
164
164
165
165
166 </div>
166 </div>
167 %endif
167 %endif
General Comments 0
You need to be logged in to leave comments. Login now