##// END OF EJS Templates
implements #212 moved default encoding variable into rhodecode-config. It's now possible to change...
marcink -
r2016:6020e388 beta
parent child Browse files
Show More
@@ -1,288 +1,294 b''
1 1 ################################################################################
2 2 ################################################################################
3 3 # RhodeCode - Pylons environment configuration #
4 4 # #
5 5 # The %(here)s variable will be replaced with the parent directory of this file#
6 6 ################################################################################
7 7
8 8 [DEFAULT]
9 9 debug = true
10 10 pdebug = false
11 11 ################################################################################
12 12 ## Uncomment and replace with the address which should receive ##
13 13 ## any error reports after application crash ##
14 14 ## Additionally those settings will be used by RhodeCode mailing system ##
15 15 ################################################################################
16 16 #email_to = admin@localhost
17 17 #error_email_from = paste_error@localhost
18 18 #app_email_from = rhodecode-noreply@localhost
19 19 #error_message =
20 20 #email_prefix = [RhodeCode]
21 21
22 22 #smtp_server = mail.server.com
23 23 #smtp_username =
24 24 #smtp_password =
25 25 #smtp_port =
26 26 #smtp_use_tls = false
27 27 #smtp_use_ssl = true
28 28 # Specify available auth parameters here (e.g. LOGIN PLAIN CRAM-MD5, etc.)
29 29 #smtp_auth =
30 30
31 31 [server:main]
32 32 ##nr of threads to spawn
33 33 threadpool_workers = 5
34 34
35 35 ##max request before thread respawn
36 36 threadpool_max_requests = 10
37 37
38 38 ##option to use threads of process
39 39 use_threadpool = true
40 40
41 41 use = egg:Paste#http
42 42 host = 0.0.0.0
43 43 port = 5000
44 44
45 45 [app:main]
46 46 use = egg:rhodecode
47 47 full_stack = true
48 48 static_files = true
49 lang=en
49 lang = en
50 50 cache_dir = %(here)s/data
51 51 index_dir = %(here)s/data/index
52 app_instance_uuid = develop
52 app_instance_uuid = rc-develop
53 53 cut_off_limit = 256000
54 54 force_https = false
55 55 commit_parse_limit = 25
56 56 use_gravatar = true
57 57 container_auth_enabled = false
58 58 proxypass_auth_enabled = false
59 default_encoding = utf8
59 60
60 61 ## overwrite schema of clone url
61 62 ## available vars:
62 63 ## scheme - http/https
63 64 ## user - current user
64 65 ## pass - password
65 66 ## netloc - network location
66 67 ## path - usually repo_name
67 68
68 69 #clone_uri = {scheme}://{user}{pass}{netloc}{path}
69 70
70 71 ## issue tracking mapping for commits messages
71 72 ## comment out issue_pat, issue_server, issue_prefix to enable
72 73
73 74 ## pattern to get the issues from commit messages
74 75 ## default one used here is #<numbers> with a regex passive group for `#`
75 76 ## {id} will be all groups matched from this pattern
76 77
77 78 issue_pat = (?:\s*#)(\d+)
78 79
79 80 ## server url to the issue, each {id} will be replaced with match
80 81 ## fetched from the regex and {repo} is replaced with repository name
81 82
82 83 issue_server_link = https://myissueserver.com/{repo}/issue/{id}
83 84
84 85 ## prefix to add to link to indicate it's an url
85 86 ## #314 will be replaced by <issue_prefix><id>
86 87
87 88 issue_prefix = #
88 89
90 ## instance-id prefix
91 ## a prefix key for this instance used for cache invalidation when running
92 ## multiple instances of rhodecode, make sure it's globally unique for
93 ## all running rhodecode instances. Leave empty if you don't use it
94 instance_id =
89 95
90 96 ####################################
91 97 ### CELERY CONFIG ####
92 98 ####################################
93 99 use_celery = false
94 100 broker.host = localhost
95 101 broker.vhost = rabbitmqhost
96 102 broker.port = 5672
97 103 broker.user = rabbitmq
98 104 broker.password = qweqwe
99 105
100 106 celery.imports = rhodecode.lib.celerylib.tasks
101 107
102 108 celery.result.backend = amqp
103 109 celery.result.dburi = amqp://
104 110 celery.result.serialier = json
105 111
106 112 #celery.send.task.error.emails = true
107 113 #celery.amqp.task.result.expires = 18000
108 114
109 115 celeryd.concurrency = 2
110 116 #celeryd.log.file = celeryd.log
111 117 celeryd.log.level = debug
112 118 celeryd.max.tasks.per.child = 1
113 119
114 120 #tasks will never be sent to the queue, but executed locally instead.
115 121 celery.always.eager = false
116 122
117 123 ####################################
118 124 ### BEAKER CACHE ####
119 125 ####################################
120 126 beaker.cache.data_dir=%(here)s/data/cache/data
121 127 beaker.cache.lock_dir=%(here)s/data/cache/lock
122 128
123 129 beaker.cache.regions=super_short_term,short_term,long_term,sql_cache_short,sql_cache_med,sql_cache_long
124 130
125 131 beaker.cache.super_short_term.type=memory
126 132 beaker.cache.super_short_term.expire=10
127 133 beaker.cache.super_short_term.key_length = 256
128 134
129 135 beaker.cache.short_term.type=memory
130 136 beaker.cache.short_term.expire=60
131 137 beaker.cache.short_term.key_length = 256
132 138
133 139 beaker.cache.long_term.type=memory
134 140 beaker.cache.long_term.expire=36000
135 141 beaker.cache.long_term.key_length = 256
136 142
137 143 beaker.cache.sql_cache_short.type=memory
138 144 beaker.cache.sql_cache_short.expire=10
139 145 beaker.cache.sql_cache_short.key_length = 256
140 146
141 147 beaker.cache.sql_cache_med.type=memory
142 148 beaker.cache.sql_cache_med.expire=360
143 149 beaker.cache.sql_cache_med.key_length = 256
144 150
145 151 beaker.cache.sql_cache_long.type=file
146 152 beaker.cache.sql_cache_long.expire=3600
147 153 beaker.cache.sql_cache_long.key_length = 256
148 154
149 155 ####################################
150 156 ### BEAKER SESSION ####
151 157 ####################################
152 158 ## Type of storage used for the session, current types are
153 159 ## dbm, file, memcached, database, and memory.
154 160 ## The storage uses the Container API
155 161 ## that is also used by the cache system.
156 162
157 163 ## db session example
158 164
159 165 #beaker.session.type = ext:database
160 166 #beaker.session.sa.url = postgresql://postgres:qwe@localhost/rhodecode
161 167 #beaker.session.table_name = db_session
162 168
163 169 ## encrypted cookie session, good for many instances
164 170 #beaker.session.type = cookie
165 171
166 172 beaker.session.type = file
167 173 beaker.session.key = rhodecode
168 174 #beaker.session.encrypt_key = g654dcno0-9873jhgfreyu
169 175 #beaker.session.validate_key = 9712sds2212c--zxc123
170 176 beaker.session.timeout = 36000
171 177 beaker.session.httponly = true
172 178
173 179 ## uncomment for https secure cookie
174 180 beaker.session.secure = false
175 181
176 182 ##auto save the session to not to use .save()
177 183 beaker.session.auto = False
178 184
179 185 ##true exire at browser close
180 186 #beaker.session.cookie_expires = 3600
181 187
182
188
183 189 ################################################################################
184 190 ## WARNING: *THE LINE BELOW MUST BE UNCOMMENTED ON A PRODUCTION ENVIRONMENT* ##
185 191 ## Debug mode will enable the interactive debugging tool, allowing ANYONE to ##
186 192 ## execute malicious code after an exception is raised. ##
187 193 ################################################################################
188 194 #set debug = false
189 195
190 196 ##################################
191 197 ### LOGVIEW CONFIG ###
192 198 ##################################
193 199 logview.sqlalchemy = #faa
194 200 logview.pylons.templating = #bfb
195 201 logview.pylons.util = #eee
196 202
197 203 #########################################################
198 204 ### DB CONFIGS - EACH DB WILL HAVE IT'S OWN CONFIG ###
199 205 #########################################################
200 206 #sqlalchemy.db1.url = sqlite:///%(here)s/rhodecode.db
201 207 sqlalchemy.db1.url = postgresql://postgres:qwe@localhost/rhodecode
202 208 sqlalchemy.db1.echo = false
203 209 sqlalchemy.db1.pool_recycle = 3600
204 210 sqlalchemy.convert_unicode = true
205 211
206 212 ################################
207 213 ### LOGGING CONFIGURATION ####
208 214 ################################
209 215 [loggers]
210 216 keys = root, routes, rhodecode, sqlalchemy, beaker, templates
211 217
212 218 [handlers]
213 219 keys = console, console_sql
214 220
215 221 [formatters]
216 222 keys = generic, color_formatter, color_formatter_sql
217 223
218 224 #############
219 225 ## LOGGERS ##
220 226 #############
221 227 [logger_root]
222 228 level = NOTSET
223 229 handlers = console
224 230
225 231 [logger_routes]
226 232 level = DEBUG
227 233 handlers =
228 234 qualname = routes.middleware
229 235 # "level = DEBUG" logs the route matched and routing variables.
230 236 propagate = 1
231 237
232 238 [logger_beaker]
233 239 level = DEBUG
234 240 handlers =
235 241 qualname = beaker.container
236 242 propagate = 1
237 243
238 244 [logger_templates]
239 245 level = INFO
240 246 handlers =
241 247 qualname = pylons.templating
242 248 propagate = 1
243 249
244 250 [logger_rhodecode]
245 251 level = DEBUG
246 252 handlers =
247 253 qualname = rhodecode
248 254 propagate = 1
249 255
250 256 [logger_sqlalchemy]
251 257 level = INFO
252 258 handlers = console_sql
253 259 qualname = sqlalchemy.engine
254 260 propagate = 0
255 261
256 262 ##############
257 263 ## HANDLERS ##
258 264 ##############
259 265
260 266 [handler_console]
261 267 class = StreamHandler
262 268 args = (sys.stderr,)
263 269 level = DEBUG
264 270 formatter = color_formatter
265 271
266 272 [handler_console_sql]
267 273 class = StreamHandler
268 274 args = (sys.stderr,)
269 275 level = DEBUG
270 276 formatter = color_formatter_sql
271 277
272 278 ################
273 279 ## FORMATTERS ##
274 280 ################
275 281
276 282 [formatter_generic]
277 283 format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
278 284 datefmt = %Y-%m-%d %H:%M:%S
279 285
280 286 [formatter_color_formatter]
281 287 class=rhodecode.lib.colored_formatter.ColorFormatter
282 288 format= %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
283 289 datefmt = %Y-%m-%d %H:%M:%S
284 290
285 291 [formatter_color_formatter_sql]
286 292 class=rhodecode.lib.colored_formatter.ColorFormatterSql
287 293 format= %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
288 294 datefmt = %Y-%m-%d %H:%M:%S
@@ -1,288 +1,295 b''
1 1 ################################################################################
2 2 ################################################################################
3 3 # RhodeCode - Pylons environment configuration #
4 4 # #
5 5 # The %(here)s variable will be replaced with the parent directory of this file#
6 6 ################################################################################
7 7
8 8 [DEFAULT]
9 9 debug = true
10 10 pdebug = false
11 11 ################################################################################
12 12 ## Uncomment and replace with the address which should receive ##
13 13 ## any error reports after application crash ##
14 14 ## Additionally those settings will be used by RhodeCode mailing system ##
15 15 ################################################################################
16 16 #email_to = admin@localhost
17 17 #error_email_from = paste_error@localhost
18 18 #app_email_from = rhodecode-noreply@localhost
19 19 #error_message =
20 20 #email_prefix = [RhodeCode]
21 21
22 22 #smtp_server = mail.server.com
23 23 #smtp_username =
24 24 #smtp_password =
25 25 #smtp_port =
26 26 #smtp_use_tls = false
27 27 #smtp_use_ssl = true
28 28 # Specify available auth parameters here (e.g. LOGIN PLAIN CRAM-MD5, etc.)
29 29 #smtp_auth =
30 30
31 31 [server:main]
32 32 ##nr of threads to spawn
33 33 threadpool_workers = 5
34 34
35 35 ##max request before thread respawn
36 36 threadpool_max_requests = 10
37 37
38 38 ##option to use threads of process
39 39 use_threadpool = true
40 40
41 41 use = egg:Paste#http
42 42 host = 127.0.0.1
43 43 port = 8001
44 44
45 45 [app:main]
46 46 use = egg:rhodecode
47 47 full_stack = true
48 48 static_files = true
49 lang=en
49 lang = en
50 50 cache_dir = %(here)s/data
51 51 index_dir = %(here)s/data/index
52 app_instance_uuid = prod1234
52 app_instance_uuid = rc-production
53 53 cut_off_limit = 256000
54 force_https = false
54 force_https = false
55 55 commit_parse_limit = 50
56 56 use_gravatar = true
57 57 container_auth_enabled = false
58 58 proxypass_auth_enabled = false
59 default_encoding = utf8
59 60
60 61 ## overwrite schema of clone url
61 62 ## available vars:
62 63 ## scheme - http/https
63 64 ## user - current user
64 65 ## pass - password
65 66 ## netloc - network location
66 67 ## path - usually repo_name
67 68
68 69 #clone_uri = {scheme}://{user}{pass}{netloc}{path}
69 70
70 71 ## issue tracking mapping for commits messages
71 72 ## comment out issue_pat, issue_server, issue_prefix to enable
72 73
73 74 ## pattern to get the issues from commit messages
74 75 ## default one used here is #<numbers> with a regex passive group for `#`
75 76 ## {id} will be all groups matched from this pattern
76 77
77 78 issue_pat = (?:\s*#)(\d+)
78 79
79 80 ## server url to the issue, each {id} will be replaced with match
80 81 ## fetched from the regex and {repo} is replaced with repository name
81 82
82 83 issue_server_link = https://myissueserver.com/{repo}/issue/{id}
83 84
84 85 ## prefix to add to link to indicate it's an url
85 86 ## #314 will be replaced by <issue_prefix><id>
86 87
87 88 issue_prefix = #
88 89
90 ## instance-id prefix
91 ## a prefix key for this instance used for cache invalidation when running
92 ## multiple instances of rhodecode, make sure it's globally unique for
93 ## all running rhodecode instances. Leave empty if you don't use it
94 instance_id =
89 95
90 96 ####################################
91 97 ### CELERY CONFIG ####
92 98 ####################################
93 99 use_celery = false
94 100 broker.host = localhost
95 101 broker.vhost = rabbitmqhost
96 102 broker.port = 5672
97 103 broker.user = rabbitmq
98 104 broker.password = qweqwe
99 105
100 106 celery.imports = rhodecode.lib.celerylib.tasks
101 107
102 108 celery.result.backend = amqp
103 109 celery.result.dburi = amqp://
104 110 celery.result.serialier = json
105 111
106 112 #celery.send.task.error.emails = true
107 113 #celery.amqp.task.result.expires = 18000
108 114
109 115 celeryd.concurrency = 2
110 116 #celeryd.log.file = celeryd.log
111 117 celeryd.log.level = debug
112 118 celeryd.max.tasks.per.child = 1
113 119
114 120 #tasks will never be sent to the queue, but executed locally instead.
115 121 celery.always.eager = false
116 122
117 123 ####################################
118 124 ### BEAKER CACHE ####
119 125 ####################################
120 126 beaker.cache.data_dir=%(here)s/data/cache/data
121 127 beaker.cache.lock_dir=%(here)s/data/cache/lock
122 128
123 129 beaker.cache.regions=super_short_term,short_term,long_term,sql_cache_short,sql_cache_med,sql_cache_long
124 130
125 131 beaker.cache.super_short_term.type=memory
126 132 beaker.cache.super_short_term.expire=10
127 133 beaker.cache.super_short_term.key_length = 256
128 134
129 135 beaker.cache.short_term.type=memory
130 136 beaker.cache.short_term.expire=60
131 137 beaker.cache.short_term.key_length = 256
132 138
133 139 beaker.cache.long_term.type=memory
134 140 beaker.cache.long_term.expire=36000
135 141 beaker.cache.long_term.key_length = 256
136 142
137 143 beaker.cache.sql_cache_short.type=memory
138 144 beaker.cache.sql_cache_short.expire=10
139 145 beaker.cache.sql_cache_short.key_length = 256
140 146
141 147 beaker.cache.sql_cache_med.type=memory
142 148 beaker.cache.sql_cache_med.expire=360
143 149 beaker.cache.sql_cache_med.key_length = 256
144 150
145 151 beaker.cache.sql_cache_long.type=file
146 152 beaker.cache.sql_cache_long.expire=3600
147 153 beaker.cache.sql_cache_long.key_length = 256
148 154
149 155 ####################################
150 156 ### BEAKER SESSION ####
151 157 ####################################
152 158 ## Type of storage used for the session, current types are
153 159 ## dbm, file, memcached, database, and memory.
154 160 ## The storage uses the Container API
155 161 ## that is also used by the cache system.
156 162
157 163 ## db session example
158 164
159 165 #beaker.session.type = ext:database
160 166 #beaker.session.sa.url = postgresql://postgres:qwe@localhost/rhodecode
161 167 #beaker.session.table_name = db_session
162 168
163 169 ## encrypted cookie session, good for many instances
164 170 #beaker.session.type = cookie
165 171
166 172 beaker.session.type = file
167 173 beaker.session.key = rhodecode
174 # secure cookie requires AES python libraries
168 175 #beaker.session.encrypt_key = g654dcno0-9873jhgfreyu
169 176 #beaker.session.validate_key = 9712sds2212c--zxc123
170 177 beaker.session.timeout = 36000
171 178 beaker.session.httponly = true
172 179
173 180 ## uncomment for https secure cookie
174 181 beaker.session.secure = false
175 182
176 183 ##auto save the session to not to use .save()
177 184 beaker.session.auto = False
178 185
179 186 ##true exire at browser close
180 187 #beaker.session.cookie_expires = 3600
181 188
182 189
183 190 ################################################################################
184 191 ## WARNING: *THE LINE BELOW MUST BE UNCOMMENTED ON A PRODUCTION ENVIRONMENT* ##
185 192 ## Debug mode will enable the interactive debugging tool, allowing ANYONE to ##
186 193 ## execute malicious code after an exception is raised. ##
187 194 ################################################################################
188 195 set debug = false
189 196
190 197 ##################################
191 198 ### LOGVIEW CONFIG ###
192 199 ##################################
193 200 logview.sqlalchemy = #faa
194 201 logview.pylons.templating = #bfb
195 202 logview.pylons.util = #eee
196 203
197 204 #########################################################
198 205 ### DB CONFIGS - EACH DB WILL HAVE IT'S OWN CONFIG ###
199 206 #########################################################
200 207 #sqlalchemy.db1.url = sqlite:///%(here)s/rhodecode.db
201 208 sqlalchemy.db1.url = postgresql://postgres:qwe@localhost/rhodecode
202 209 sqlalchemy.db1.echo = false
203 210 sqlalchemy.db1.pool_recycle = 3600
204 211 sqlalchemy.convert_unicode = true
205 212
206 213 ################################
207 214 ### LOGGING CONFIGURATION ####
208 215 ################################
209 216 [loggers]
210 217 keys = root, routes, rhodecode, sqlalchemy, beaker, templates
211 218
212 219 [handlers]
213 220 keys = console, console_sql
214 221
215 222 [formatters]
216 223 keys = generic, color_formatter, color_formatter_sql
217 224
218 225 #############
219 226 ## LOGGERS ##
220 227 #############
221 228 [logger_root]
222 229 level = NOTSET
223 230 handlers = console
224 231
225 232 [logger_routes]
226 233 level = DEBUG
227 234 handlers =
228 235 qualname = routes.middleware
229 236 # "level = DEBUG" logs the route matched and routing variables.
230 237 propagate = 1
231 238
232 239 [logger_beaker]
233 240 level = DEBUG
234 241 handlers =
235 242 qualname = beaker.container
236 243 propagate = 1
237 244
238 245 [logger_templates]
239 246 level = INFO
240 247 handlers =
241 248 qualname = pylons.templating
242 249 propagate = 1
243 250
244 251 [logger_rhodecode]
245 252 level = DEBUG
246 253 handlers =
247 254 qualname = rhodecode
248 255 propagate = 1
249 256
250 257 [logger_sqlalchemy]
251 258 level = INFO
252 259 handlers = console_sql
253 260 qualname = sqlalchemy.engine
254 261 propagate = 0
255 262
256 263 ##############
257 264 ## HANDLERS ##
258 265 ##############
259 266
260 267 [handler_console]
261 268 class = StreamHandler
262 269 args = (sys.stderr,)
263 270 level = INFO
264 271 formatter = generic
265 272
266 273 [handler_console_sql]
267 274 class = StreamHandler
268 275 args = (sys.stderr,)
269 276 level = WARN
270 277 formatter = generic
271 278
272 279 ################
273 280 ## FORMATTERS ##
274 281 ################
275 282
276 283 [formatter_generic]
277 284 format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
278 285 datefmt = %Y-%m-%d %H:%M:%S
279 286
280 287 [formatter_color_formatter]
281 288 class=rhodecode.lib.colored_formatter.ColorFormatter
282 289 format= %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
283 290 datefmt = %Y-%m-%d %H:%M:%S
284 291
285 292 [formatter_color_formatter_sql]
286 293 class=rhodecode.lib.colored_formatter.ColorFormatterSql
287 294 format= %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
288 295 datefmt = %Y-%m-%d %H:%M:%S
@@ -1,299 +1,305 b''
1 1 ################################################################################
2 2 ################################################################################
3 3 # RhodeCode - Pylons environment configuration #
4 4 # #
5 5 # The %(here)s variable will be replaced with the parent directory of this file#
6 6 ################################################################################
7 7
8 8 [DEFAULT]
9 9 debug = true
10 10 pdebug = false
11 11 ################################################################################
12 12 ## Uncomment and replace with the address which should receive ##
13 13 ## any error reports after application crash ##
14 14 ## Additionally those settings will be used by RhodeCode mailing system ##
15 15 ################################################################################
16 16 #email_to = admin@localhost
17 17 #error_email_from = paste_error@localhost
18 18 #app_email_from = rhodecode-noreply@localhost
19 19 #error_message =
20 20 #email_prefix = [RhodeCode]
21 21
22 22 #smtp_server = mail.server.com
23 23 #smtp_username =
24 24 #smtp_password =
25 25 #smtp_port =
26 26 #smtp_use_tls = false
27 27 #smtp_use_ssl = true
28 28 # Specify available auth parameters here (e.g. LOGIN PLAIN CRAM-MD5, etc.)
29 29 #smtp_auth =
30 30
31 31 [server:main]
32 32 ##nr of threads to spawn
33 33 threadpool_workers = 5
34 34
35 35 ##max request before thread respawn
36 36 threadpool_max_requests = 10
37 37
38 38 ##option to use threads of process
39 39 use_threadpool = true
40 40
41 41 use = egg:Paste#http
42 42 host = 127.0.0.1
43 43 port = 5000
44 44
45 45 [app:main]
46 46 use = egg:rhodecode
47 47 full_stack = true
48 48 static_files = true
49 lang=en
49 lang = en
50 50 cache_dir = %(here)s/data
51 51 index_dir = %(here)s/data/index
52 52 app_instance_uuid = ${app_instance_uuid}
53 53 cut_off_limit = 256000
54 force_https = false
54 force_https = false
55 55 commit_parse_limit = 50
56 56 use_gravatar = true
57 57 container_auth_enabled = false
58 58 proxypass_auth_enabled = false
59 default_encoding = utf8
59 60
60 61 ## overwrite schema of clone url
61 62 ## available vars:
62 63 ## scheme - http/https
63 64 ## user - current user
64 65 ## pass - password
65 66 ## netloc - network location
66 67 ## path - usually repo_name
67 68
68 # clone_uri = {scheme}://{user}{pass}{netloc}{path}
69 #clone_uri = {scheme}://{user}{pass}{netloc}{path}
69 70
70 71 ## issue tracking mapping for commits messages
71 72 ## comment out issue_pat, issue_server, issue_prefix to enable
72 73
73 74 ## pattern to get the issues from commit messages
74 75 ## default one used here is #<numbers> with a regex passive group for `#`
75 76 ## {id} will be all groups matched from this pattern
76 77
77 78 issue_pat = (?:\s*#)(\d+)
78 79
79 80 ## server url to the issue, each {id} will be replaced with match
80 81 ## fetched from the regex and {repo} is replaced with repository name
81 82
82 83 issue_server_link = https://myissueserver.com/{repo}/issue/{id}
83 84
84 85 ## prefix to add to link to indicate it's an url
85 86 ## #314 will be replaced by <issue_prefix><id>
86 87
87 88 issue_prefix = #
88 89
90 ## instance-id prefix
91 ## a prefix key for this instance used for cache invalidation when running
92 ## multiple instances of rhodecode, make sure it's globally unique for
93 ## all running rhodecode instances. Leave empty if you don't use it
94 instance_id =
89 95
90 96 ####################################
91 97 ### CELERY CONFIG ####
92 98 ####################################
93 99 use_celery = false
94 100 broker.host = localhost
95 101 broker.vhost = rabbitmqhost
96 102 broker.port = 5672
97 103 broker.user = rabbitmq
98 104 broker.password = qweqwe
99 105
100 106 celery.imports = rhodecode.lib.celerylib.tasks
101 107
102 108 celery.result.backend = amqp
103 109 celery.result.dburi = amqp://
104 110 celery.result.serialier = json
105 111
106 112 #celery.send.task.error.emails = true
107 113 #celery.amqp.task.result.expires = 18000
108 114
109 115 celeryd.concurrency = 2
110 116 #celeryd.log.file = celeryd.log
111 117 celeryd.log.level = debug
112 118 celeryd.max.tasks.per.child = 1
113 119
114 120 #tasks will never be sent to the queue, but executed locally instead.
115 121 celery.always.eager = false
116 122
117 123 ####################################
118 124 ### BEAKER CACHE ####
119 125 ####################################
120 126 beaker.cache.data_dir=%(here)s/data/cache/data
121 127 beaker.cache.lock_dir=%(here)s/data/cache/lock
122 128
123 129 beaker.cache.regions=super_short_term,short_term,long_term,sql_cache_short,sql_cache_med,sql_cache_long
124 130
125 131 beaker.cache.super_short_term.type=memory
126 132 beaker.cache.super_short_term.expire=10
127 133 beaker.cache.super_short_term.key_length = 256
128 134
129 135 beaker.cache.short_term.type=memory
130 136 beaker.cache.short_term.expire=60
131 137 beaker.cache.short_term.key_length = 256
132 138
133 139 beaker.cache.long_term.type=memory
134 140 beaker.cache.long_term.expire=36000
135 141 beaker.cache.long_term.key_length = 256
136 142
137 143 beaker.cache.sql_cache_short.type=memory
138 144 beaker.cache.sql_cache_short.expire=10
139 145 beaker.cache.sql_cache_short.key_length = 256
140 146
141 147 beaker.cache.sql_cache_med.type=memory
142 148 beaker.cache.sql_cache_med.expire=360
143 149 beaker.cache.sql_cache_med.key_length = 256
144 150
145 151 beaker.cache.sql_cache_long.type=file
146 152 beaker.cache.sql_cache_long.expire=3600
147 153 beaker.cache.sql_cache_long.key_length = 256
148 154
149 155 ####################################
150 156 ### BEAKER SESSION ####
151 157 ####################################
152 158 ## Type of storage used for the session, current types are
153 159 ## dbm, file, memcached, database, and memory.
154 160 ## The storage uses the Container API
155 161 ## that is also used by the cache system.
156 162
157 163 ## db session example
158 164
159 165 #beaker.session.type = ext:database
160 166 #beaker.session.sa.url = postgresql://postgres:qwe@localhost/rhodecode
161 167 #beaker.session.table_name = db_session
162 168
163 169 ## encrypted cookie session, good for many instances
164 170 #beaker.session.type = cookie
165 171
166 172 beaker.session.type = file
167 173 beaker.session.key = rhodecode
168 174 # secure cookie requires AES python libraries
169 175 #beaker.session.encrypt_key = ${app_instance_secret}
170 176 #beaker.session.validate_key = ${app_instance_secret}
171 177 beaker.session.timeout = 36000
172 178 beaker.session.httponly = true
173 179
174 180 ## uncomment for https secure cookie
175 181 beaker.session.secure = false
176 182
177 183 ##auto save the session to not to use .save()
178 184 beaker.session.auto = False
179 185
180 186 ##true exire at browser close
181 187 #beaker.session.cookie_expires = 3600
182 188
183 189
184 190 ################################################################################
185 191 ## WARNING: *THE LINE BELOW MUST BE UNCOMMENTED ON A PRODUCTION ENVIRONMENT* ##
186 192 ## Debug mode will enable the interactive debugging tool, allowing ANYONE to ##
187 193 ## execute malicious code after an exception is raised. ##
188 194 ################################################################################
189 195 set debug = false
190 196
191 197 ##################################
192 198 ### LOGVIEW CONFIG ###
193 199 ##################################
194 200 logview.sqlalchemy = #faa
195 201 logview.pylons.templating = #bfb
196 202 logview.pylons.util = #eee
197 203
198 204 #########################################################
199 205 ### DB CONFIGS - EACH DB WILL HAVE IT'S OWN CONFIG ###
200 206 #########################################################
201 207
202 208 # SQLITE [default]
203 209 sqlalchemy.db1.url = sqlite:///%(here)s/rhodecode.db
204 210
205 211 # POSTGRESQL
206 212 # sqlalchemy.db1.url = postgresql://user:pass@localhost/rhodecode
207 213
208 214 # MySQL
209 215 # sqlalchemy.db1.url = mysql://user:pass@localhost/rhodecode
210 216
211 217 # see sqlalchemy docs for others
212 218
213 219 sqlalchemy.db1.echo = false
214 220 sqlalchemy.db1.pool_recycle = 3600
215 221 sqlalchemy.convert_unicode = true
216 222
217 223 ################################
218 224 ### LOGGING CONFIGURATION ####
219 225 ################################
220 226 [loggers]
221 227 keys = root, routes, rhodecode, sqlalchemy, beaker, templates
222 228
223 229 [handlers]
224 230 keys = console, console_sql
225 231
226 232 [formatters]
227 233 keys = generic, color_formatter, color_formatter_sql
228 234
229 235 #############
230 236 ## LOGGERS ##
231 237 #############
232 238 [logger_root]
233 239 level = NOTSET
234 240 handlers = console
235 241
236 242 [logger_routes]
237 243 level = DEBUG
238 244 handlers =
239 245 qualname = routes.middleware
240 246 # "level = DEBUG" logs the route matched and routing variables.
241 247 propagate = 1
242 248
243 249 [logger_beaker]
244 250 level = DEBUG
245 251 handlers =
246 252 qualname = beaker.container
247 253 propagate = 1
248 254
249 255 [logger_templates]
250 256 level = INFO
251 257 handlers =
252 258 qualname = pylons.templating
253 259 propagate = 1
254 260
255 261 [logger_rhodecode]
256 262 level = DEBUG
257 263 handlers =
258 264 qualname = rhodecode
259 265 propagate = 1
260 266
261 267 [logger_sqlalchemy]
262 268 level = INFO
263 269 handlers = console_sql
264 270 qualname = sqlalchemy.engine
265 271 propagate = 0
266 272
267 273 ##############
268 274 ## HANDLERS ##
269 275 ##############
270 276
271 277 [handler_console]
272 278 class = StreamHandler
273 279 args = (sys.stderr,)
274 280 level = INFO
275 formatter = color_formatter
281 formatter = generic
276 282
277 283 [handler_console_sql]
278 284 class = StreamHandler
279 285 args = (sys.stderr,)
280 286 level = WARN
281 formatter = color_formatter_sql
287 formatter = generic
282 288
283 289 ################
284 290 ## FORMATTERS ##
285 291 ################
286 292
287 293 [formatter_generic]
288 294 format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
289 295 datefmt = %Y-%m-%d %H:%M:%S
290 296
291 297 [formatter_color_formatter]
292 298 class=rhodecode.lib.colored_formatter.ColorFormatter
293 299 format= %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
294 300 datefmt = %Y-%m-%d %H:%M:%S
295 301
296 302 [formatter_color_formatter_sql]
297 303 class=rhodecode.lib.colored_formatter.ColorFormatterSql
298 304 format= %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
299 datefmt = %Y-%m-%d %H:%M:%S No newline at end of file
305 datefmt = %Y-%m-%d %H:%M:%S
@@ -1,85 +1,87 b''
1 1 """Pylons environment configuration"""
2 2
3 3 import os
4 4 import logging
5 5
6 6 from mako.lookup import TemplateLookup
7 7 from pylons.configuration import PylonsConfig
8 8 from pylons.error import handle_mako_error
9 9
10 10 import rhodecode
11 11 import rhodecode.lib.app_globals as app_globals
12 12 import rhodecode.lib.helpers
13 13
14 14 from rhodecode.config.routing import make_map
15 15 # don't remove this import it does magic for celery
16 16 from rhodecode.lib import celerypylons, str2bool
17 17 from rhodecode.lib import engine_from_config
18 18 from rhodecode.lib.auth import set_available_permissions
19 19 from rhodecode.lib.utils import repo2db_mapper, make_ui, set_rhodecode_config
20 20 from rhodecode.model import init_model
21 21 from rhodecode.model.scm import ScmModel
22 22
23 23 log = logging.getLogger(__name__)
24 24
25 25
26 26 def load_environment(global_conf, app_conf, initial=False):
27 27 """Configure the Pylons environment via the ``pylons.config``
28 28 object
29 29 """
30 30 config = PylonsConfig()
31 31
32 32 # Pylons paths
33 33 root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
34 34 paths = dict(root=root,
35 35 controllers=os.path.join(root, 'controllers'),
36 36 static_files=os.path.join(root, 'public'),
37 37 templates=[os.path.join(root, 'templates')])
38 38
39 39 # Initialize config with the basic options
40 40 config.init_app(global_conf, app_conf, package='rhodecode', paths=paths)
41 41
42 42 # store some globals into rhodecode
43 43 rhodecode.CELERY_ON = str2bool(config['app_conf'].get('use_celery'))
44 rhodecode.CONFIG = config
45 44
46 45 config['routes.map'] = make_map(config)
47 46 config['pylons.app_globals'] = app_globals.Globals(config)
48 47 config['pylons.h'] = rhodecode.lib.helpers
49
48 rhodecode.CONFIG = config
50 49 # Setup cache object as early as possible
51 50 import pylons
52 51 pylons.cache._push_object(config['pylons.app_globals'].cache)
53 52
54 53 # Create the Mako TemplateLookup, with the default auto-escaping
55 54 config['pylons.app_globals'].mako_lookup = TemplateLookup(
56 55 directories=paths['templates'],
57 56 error_handler=handle_mako_error,
58 57 module_directory=os.path.join(app_conf['cache_dir'], 'templates'),
59 58 input_encoding='utf-8', default_filters=['escape'],
60 59 imports=['from webhelpers.html import escape'])
61 60
62 #sets the c attribute access when don't existing attribute are accessed
61 # sets the c attribute access when don't existing attribute are accessed
63 62 config['pylons.strict_tmpl_context'] = True
64 63 test = os.path.split(config['__file__'])[-1] == 'test.ini'
65 64 if test:
66 65 from rhodecode.lib.utils import create_test_env, create_test_index
67 66 from rhodecode.tests import TESTS_TMP_PATH
68 67 create_test_env(TESTS_TMP_PATH, config)
69 68 create_test_index(TESTS_TMP_PATH, config, True)
70 69
71 #MULTIPLE DB configs
70 # MULTIPLE DB configs
72 71 # Setup the SQLAlchemy database engine
73 72 sa_engine_db1 = engine_from_config(config, 'sqlalchemy.db1.')
74 73
75 74 init_model(sa_engine_db1)
76 75
77 76 repos_path = make_ui('db').configitems('paths')[0][1]
78 77 repo2db_mapper(ScmModel().repo_scan(repos_path))
79 78 set_available_permissions(config)
80 79 config['base_path'] = repos_path
81 80 set_rhodecode_config(config)
82 81 # CONFIGURATION OPTIONS HERE (note: all config options will override
83 82 # any Pylons config options)
84 83
84 # store config reference into our module to skip import magic of
85 # pylons
86 rhodecode.CONFIG.update(config)
85 87 return config
@@ -1,454 +1,464 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.lib.__init__
4 4 ~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 Some simple helper functions
7 7
8 8 :created_on: Jan 5, 2011
9 9 :author: marcink
10 10 :copyright: (C) 2011-2012 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software: you can redistribute it and/or modify
14 14 # it under the terms of the GNU General Public License as published by
15 15 # the Free Software Foundation, either version 3 of the License, or
16 16 # (at your option) any later version.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 25
26 26 import os
27 27 import re
28 28 from rhodecode.lib.vcs.utils.lazy import LazyProperty
29 29
30 30
31 31 def __get_lem():
32 32 from pygments import lexers
33 33 from string import lower
34 34 from collections import defaultdict
35 35
36 36 d = defaultdict(lambda: [])
37 37
38 38 def __clean(s):
39 39 s = s.lstrip('*')
40 40 s = s.lstrip('.')
41 41
42 42 if s.find('[') != -1:
43 43 exts = []
44 44 start, stop = s.find('['), s.find(']')
45 45
46 46 for suffix in s[start + 1:stop]:
47 47 exts.append(s[:s.find('[')] + suffix)
48 48 return map(lower, exts)
49 49 else:
50 50 return map(lower, [s])
51 51
52 52 for lx, t in sorted(lexers.LEXERS.items()):
53 53 m = map(__clean, t[-2])
54 54 if m:
55 55 m = reduce(lambda x, y: x + y, m)
56 56 for ext in m:
57 57 desc = lx.replace('Lexer', '')
58 58 d[ext].append(desc)
59 59
60 60 return dict(d)
61 61
62 62 # language map is also used by whoosh indexer, which for those specified
63 63 # extensions will index it's content
64 64 LANGUAGES_EXTENSIONS_MAP = __get_lem()
65 65
66 66 # Additional mappings that are not present in the pygments lexers
67 67 # NOTE: that this will overide any mappings in LANGUAGES_EXTENSIONS_MAP
68 68 ADDITIONAL_MAPPINGS = {'xaml': 'XAML'}
69 69
70 70 LANGUAGES_EXTENSIONS_MAP.update(ADDITIONAL_MAPPINGS)
71 71
72 72 # list of readme files to search in file tree and display in summary
73 73 # attached weights defines the search order lower is first
74 74 ALL_READMES = [
75 75 ('readme', 0), ('README', 0), ('Readme', 0),
76 76 ('doc/readme', 1), ('doc/README', 1), ('doc/Readme', 1),
77 77 ('Docs/readme', 2), ('Docs/README', 2), ('Docs/Readme', 2),
78 78 ('DOCS/readme', 2), ('DOCS/README', 2), ('DOCS/Readme', 2),
79 79 ('docs/readme', 2), ('docs/README', 2), ('docs/Readme', 2),
80 80 ]
81 81
82 82 # extension together with weights to search lower is first
83 83 RST_EXTS = [
84 84 ('', 0), ('.rst', 1), ('.rest', 1),
85 85 ('.RST', 2), ('.REST', 2),
86 86 ('.txt', 3), ('.TXT', 3)
87 87 ]
88 88
89 89 MARKDOWN_EXTS = [
90 90 ('.md', 1), ('.MD', 1),
91 91 ('.mkdn', 2), ('.MKDN', 2),
92 92 ('.mdown', 3), ('.MDOWN', 3),
93 93 ('.markdown', 4), ('.MARKDOWN', 4)
94 94 ]
95 95
96 96 PLAIN_EXTS = [('.text', 2), ('.TEXT', 2)]
97 97
98 98 ALL_EXTS = MARKDOWN_EXTS + RST_EXTS + PLAIN_EXTS
99 99
100 100
101 101 def str2bool(_str):
102 102 """
103 103 returs True/False value from given string, it tries to translate the
104 104 string into boolean
105 105
106 106 :param _str: string value to translate into boolean
107 107 :rtype: boolean
108 108 :returns: boolean from given string
109 109 """
110 110 if _str is None:
111 111 return False
112 112 if _str in (True, False):
113 113 return _str
114 114 _str = str(_str).strip().lower()
115 115 return _str in ('t', 'true', 'y', 'yes', 'on', '1')
116 116
117 117
118 118 def convert_line_endings(line, mode):
119 119 """
120 120 Converts a given line "line end" accordingly to given mode
121 121
122 122 Available modes are::
123 123 0 - Unix
124 124 1 - Mac
125 125 2 - DOS
126 126
127 127 :param line: given line to convert
128 128 :param mode: mode to convert to
129 129 :rtype: str
130 130 :return: converted line according to mode
131 131 """
132 132 from string import replace
133 133
134 134 if mode == 0:
135 135 line = replace(line, '\r\n', '\n')
136 136 line = replace(line, '\r', '\n')
137 137 elif mode == 1:
138 138 line = replace(line, '\r\n', '\r')
139 139 line = replace(line, '\n', '\r')
140 140 elif mode == 2:
141 141 line = re.sub("\r(?!\n)|(?<!\r)\n", "\r\n", line)
142 142 return line
143 143
144 144
145 145 def detect_mode(line, default):
146 146 """
147 147 Detects line break for given line, if line break couldn't be found
148 148 given default value is returned
149 149
150 150 :param line: str line
151 151 :param default: default
152 152 :rtype: int
153 153 :return: value of line end on of 0 - Unix, 1 - Mac, 2 - DOS
154 154 """
155 155 if line.endswith('\r\n'):
156 156 return 2
157 157 elif line.endswith('\n'):
158 158 return 0
159 159 elif line.endswith('\r'):
160 160 return 1
161 161 else:
162 162 return default
163 163
164 164
165 165 def generate_api_key(username, salt=None):
166 166 """
167 167 Generates unique API key for given username, if salt is not given
168 168 it'll be generated from some random string
169 169
170 170 :param username: username as string
171 171 :param salt: salt to hash generate KEY
172 172 :rtype: str
173 173 :returns: sha1 hash from username+salt
174 174 """
175 175 from tempfile import _RandomNameSequence
176 176 import hashlib
177 177
178 178 if salt is None:
179 179 salt = _RandomNameSequence().next()
180 180
181 181 return hashlib.sha1(username + salt).hexdigest()
182 182
183 183
184 def safe_unicode(str_, from_encoding='utf8'):
184 def safe_unicode(str_, from_encoding=None):
185 185 """
186 186 safe unicode function. Does few trick to turn str_ into unicode
187 187
188 188 In case of UnicodeDecode error we try to return it with encoding detected
189 189 by chardet library if it fails fallback to unicode with errors replaced
190 190
191 191 :param str_: string to decode
192 192 :rtype: unicode
193 193 :returns: unicode object
194 194 """
195 195 if isinstance(str_, unicode):
196 196 return str_
197 197
198 if not from_encoding:
199 import rhodecode
200 DEFAULT_ENCODING = rhodecode.CONFIG.get('default_encoding','utf8')
201 from_encoding = DEFAULT_ENCODING
202
198 203 try:
199 204 return unicode(str_)
200 205 except UnicodeDecodeError:
201 206 pass
202 207
203 208 try:
204 209 return unicode(str_, from_encoding)
205 210 except UnicodeDecodeError:
206 211 pass
207 212
208 213 try:
209 214 import chardet
210 215 encoding = chardet.detect(str_)['encoding']
211 216 if encoding is None:
212 217 raise Exception()
213 218 return str_.decode(encoding)
214 219 except (ImportError, UnicodeDecodeError, Exception):
215 220 return unicode(str_, from_encoding, 'replace')
216 221
217 222
218 def safe_str(unicode_, to_encoding='utf8'):
223 def safe_str(unicode_, to_encoding=None):
219 224 """
220 225 safe str function. Does few trick to turn unicode_ into string
221 226
222 227 In case of UnicodeEncodeError we try to return it with encoding detected
223 228 by chardet library if it fails fallback to string with errors replaced
224 229
225 230 :param unicode_: unicode to encode
226 231 :rtype: str
227 232 :returns: str object
228 233 """
229 234
230 235 if not isinstance(unicode_, basestring):
231 236 return str(unicode_)
232 237
233 238 if isinstance(unicode_, str):
234 239 return unicode_
235 240
241 if not to_encoding:
242 import rhodecode
243 DEFAULT_ENCODING = rhodecode.CONFIG.get('default_encoding','utf8')
244 to_encoding = DEFAULT_ENCODING
245
236 246 try:
237 247 return unicode_.encode(to_encoding)
238 248 except UnicodeEncodeError:
239 249 pass
240 250
241 251 try:
242 252 import chardet
243 253 encoding = chardet.detect(unicode_)['encoding']
244 254 print encoding
245 255 if encoding is None:
246 256 raise UnicodeEncodeError()
247 257
248 258 return unicode_.encode(encoding)
249 259 except (ImportError, UnicodeEncodeError):
250 260 return unicode_.encode(to_encoding, 'replace')
251 261
252 262 return safe_str
253 263
254 264
255 265 def engine_from_config(configuration, prefix='sqlalchemy.', **kwargs):
256 266 """
257 267 Custom engine_from_config functions that makes sure we use NullPool for
258 268 file based sqlite databases. This prevents errors on sqlite. This only
259 269 applies to sqlalchemy versions < 0.7.0
260 270
261 271 """
262 272 import sqlalchemy
263 273 from sqlalchemy import engine_from_config as efc
264 274 import logging
265 275
266 276 if int(sqlalchemy.__version__.split('.')[1]) < 7:
267 277
268 278 # This solution should work for sqlalchemy < 0.7.0, and should use
269 279 # proxy=TimerProxy() for execution time profiling
270 280
271 281 from sqlalchemy.pool import NullPool
272 282 url = configuration[prefix + 'url']
273 283
274 284 if url.startswith('sqlite'):
275 285 kwargs.update({'poolclass': NullPool})
276 286 return efc(configuration, prefix, **kwargs)
277 287 else:
278 288 import time
279 289 from sqlalchemy import event
280 290 from sqlalchemy.engine import Engine
281 291
282 292 log = logging.getLogger('sqlalchemy.engine')
283 293 BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE = xrange(30, 38)
284 294 engine = efc(configuration, prefix, **kwargs)
285 295
286 296 def color_sql(sql):
287 297 COLOR_SEQ = "\033[1;%dm"
288 298 COLOR_SQL = YELLOW
289 299 normal = '\x1b[0m'
290 300 return ''.join([COLOR_SEQ % COLOR_SQL, sql, normal])
291 301
292 302 if configuration['debug']:
293 303 #attach events only for debug configuration
294 304
295 305 def before_cursor_execute(conn, cursor, statement,
296 306 parameters, context, executemany):
297 307 context._query_start_time = time.time()
298 308 log.info(color_sql(">>>>> STARTING QUERY >>>>>"))
299 309
300 310
301 311 def after_cursor_execute(conn, cursor, statement,
302 312 parameters, context, executemany):
303 313 total = time.time() - context._query_start_time
304 314 log.info(color_sql("<<<<< TOTAL TIME: %f <<<<<" % total))
305 315
306 316 event.listen(engine, "before_cursor_execute",
307 317 before_cursor_execute)
308 318 event.listen(engine, "after_cursor_execute",
309 319 after_cursor_execute)
310 320
311 321 return engine
312 322
313 323
314 324 def age(curdate):
315 325 """
316 326 turns a datetime into an age string.
317 327
318 328 :param curdate: datetime object
319 329 :rtype: unicode
320 330 :returns: unicode words describing age
321 331 """
322 332
323 333 from datetime import datetime
324 334 from webhelpers.date import time_ago_in_words
325 335
326 336 _ = lambda s: s
327 337
328 338 if not curdate:
329 339 return ''
330 340
331 341 agescales = [(_(u"year"), 3600 * 24 * 365),
332 342 (_(u"month"), 3600 * 24 * 30),
333 343 (_(u"day"), 3600 * 24),
334 344 (_(u"hour"), 3600),
335 345 (_(u"minute"), 60),
336 346 (_(u"second"), 1), ]
337 347
338 348 age = datetime.now() - curdate
339 349 age_seconds = (age.days * agescales[2][1]) + age.seconds
340 350 pos = 1
341 351 for scale in agescales:
342 352 if scale[1] <= age_seconds:
343 353 if pos == 6:
344 354 pos = 5
345 355 return '%s %s' % (time_ago_in_words(curdate,
346 356 agescales[pos][0]), _('ago'))
347 357 pos += 1
348 358
349 359 return _(u'just now')
350 360
351 361
352 362 def uri_filter(uri):
353 363 """
354 364 Removes user:password from given url string
355 365
356 366 :param uri:
357 367 :rtype: unicode
358 368 :returns: filtered list of strings
359 369 """
360 370 if not uri:
361 371 return ''
362 372
363 373 proto = ''
364 374
365 375 for pat in ('https://', 'http://'):
366 376 if uri.startswith(pat):
367 377 uri = uri[len(pat):]
368 378 proto = pat
369 379 break
370 380
371 381 # remove passwords and username
372 382 uri = uri[uri.find('@') + 1:]
373 383
374 384 # get the port
375 385 cred_pos = uri.find(':')
376 386 if cred_pos == -1:
377 387 host, port = uri, None
378 388 else:
379 389 host, port = uri[:cred_pos], uri[cred_pos + 1:]
380 390
381 391 return filter(None, [proto, host, port])
382 392
383 393
384 394 def credentials_filter(uri):
385 395 """
386 396 Returns a url with removed credentials
387 397
388 398 :param uri:
389 399 """
390 400
391 401 uri = uri_filter(uri)
392 402 #check if we have port
393 403 if len(uri) > 2 and uri[2]:
394 404 uri[2] = ':' + uri[2]
395 405
396 406 return ''.join(uri)
397 407
398 408
399 409 def get_changeset_safe(repo, rev):
400 410 """
401 411 Safe version of get_changeset if this changeset doesn't exists for a
402 412 repo it returns a Dummy one instead
403 413
404 414 :param repo:
405 415 :param rev:
406 416 """
407 417 from rhodecode.lib.vcs.backends.base import BaseRepository
408 418 from rhodecode.lib.vcs.exceptions import RepositoryError
409 419 if not isinstance(repo, BaseRepository):
410 420 raise Exception('You must pass an Repository '
411 421 'object as first argument got %s', type(repo))
412 422
413 423 try:
414 424 cs = repo.get_changeset(rev)
415 425 except RepositoryError:
416 426 from rhodecode.lib.utils import EmptyChangeset
417 427 cs = EmptyChangeset(requested_revision=rev)
418 428 return cs
419 429
420 430
421 431 def get_current_revision(quiet=False):
422 432 """
423 433 Returns tuple of (number, id) from repository containing this package
424 434 or None if repository could not be found.
425 435
426 436 :param quiet: prints error for fetching revision if True
427 437 """
428 438
429 439 try:
430 440 from rhodecode.lib.vcs import get_repo
431 441 from rhodecode.lib.vcs.utils.helpers import get_scm
432 442 repopath = os.path.join(os.path.dirname(__file__), '..', '..')
433 443 scm = get_scm(repopath)[0]
434 444 repo = get_repo(path=repopath, alias=scm)
435 445 tip = repo.get_changeset()
436 446 return (tip.revision, tip.short_id)
437 447 except Exception, err:
438 448 if not quiet:
439 449 print ("Cannot retrieve rhodecode's revision. Original error "
440 450 "was: %s" % err)
441 451 return None
442 452
443 453
444 454 def extract_mentioned_users(s):
445 455 """
446 456 Returns unique usernames from given string s that have @mention
447 457
448 458 :param s: string to get mentions
449 459 """
450 460 usrs = {}
451 461 for username in re.findall(r'(?:^@|\s@)(\w+)', s):
452 462 usrs[username] = username
453 463
454 464 return sorted(usrs.keys())
@@ -1,180 +1,181 b''
1 1 """The base Controller API
2 2
3 3 Provides the BaseController class for subclassing.
4 4 """
5 5 import logging
6 6 import time
7 7 import traceback
8 8
9 9 from paste.auth.basic import AuthBasicAuthenticator
10 10
11 11 from pylons import config, tmpl_context as c, request, session, url
12 12 from pylons.controllers import WSGIController
13 13 from pylons.controllers.util import redirect
14 14 from pylons.templating import render_mako as render
15 15
16 16 from rhodecode import __version__, BACKENDS
17 17
18 18 from rhodecode.lib import str2bool
19 19 from rhodecode.lib.auth import AuthUser, get_container_username, authfunc,\
20 20 HasPermissionAnyMiddleware
21 21 from rhodecode.lib.utils import get_repo_slug, invalidate_cache
22 22 from rhodecode.model import meta
23 23
24 24 from rhodecode.model.db import Repository
25 25 from rhodecode.model.notification import NotificationModel
26 26 from rhodecode.model.scm import ScmModel
27 27
28 28 log = logging.getLogger(__name__)
29 29
30 30
31 31 class BaseVCSController(object):
32 32
33 33 def __init__(self, application, config):
34 34 self.application = application
35 35 self.config = config
36 36 # base path of repo locations
37 37 self.basepath = self.config['base_path']
38 38 #authenticate this mercurial request using authfunc
39 39 self.authenticate = AuthBasicAuthenticator('', authfunc)
40 40 self.ipaddr = '0.0.0.0'
41 41
42 42 def _handle_request(self, environ, start_response):
43 43 raise NotImplementedError()
44 44
45 45 def _get_by_id(self, repo_name):
46 46 """
47 47 Get's a special pattern _<ID> from clone url and tries to replace it
48 48 with a repository_name for support of _<ID> non changable urls
49 49
50 50 :param repo_name:
51 51 """
52 52 try:
53 53 data = repo_name.split('/')
54 54 if len(data) >= 2:
55 55 by_id = data[1].split('_')
56 56 if len(by_id) == 2 and by_id[1].isdigit():
57 57 _repo_name = Repository.get(by_id[1]).repo_name
58 58 data[1] = _repo_name
59 59 except:
60 60 log.debug('Failed to extract repo_name from id %s' % (
61 61 traceback.format_exc()
62 62 )
63 63 )
64 64
65 65 return '/'.join(data)
66 66
67 67 def _invalidate_cache(self, repo_name):
68 68 """
69 69 Set's cache for this repository for invalidation on next access
70 70
71 71 :param repo_name: full repo name, also a cache key
72 72 """
73 73 invalidate_cache('get_repo_cached_%s' % repo_name)
74 74
75 75 def _check_permission(self, action, user, repo_name):
76 76 """
77 77 Checks permissions using action (push/pull) user and repository
78 78 name
79 79
80 80 :param action: push or pull action
81 81 :param user: user instance
82 82 :param repo_name: repository name
83 83 """
84 84 if action == 'push':
85 85 if not HasPermissionAnyMiddleware('repository.write',
86 86 'repository.admin')(user,
87 87 repo_name):
88 88 return False
89 89
90 90 else:
91 91 #any other action need at least read permission
92 92 if not HasPermissionAnyMiddleware('repository.read',
93 93 'repository.write',
94 94 'repository.admin')(user,
95 95 repo_name):
96 96 return False
97 97
98 98 return True
99 99
100 100 def __call__(self, environ, start_response):
101 101 start = time.time()
102 102 try:
103 103 return self._handle_request(environ, start_response)
104 104 finally:
105 105 log = logging.getLogger('rhodecode.' + self.__class__.__name__)
106 106 log.debug('Request time: %.3fs' % (time.time() - start))
107 107 meta.Session.remove()
108 108
109 109
110 110 class BaseController(WSGIController):
111 111
112 112 def __before__(self):
113 113 c.rhodecode_version = __version__
114 c.rhodecode_instanceid = config.get('instance_id')
114 115 c.rhodecode_name = config.get('rhodecode_title')
115 116 c.use_gravatar = str2bool(config.get('use_gravatar'))
116 117 c.ga_code = config.get('rhodecode_ga_code')
117 118 c.repo_name = get_repo_slug(request)
118 119 c.backends = BACKENDS.keys()
119 120 c.unread_notifications = NotificationModel()\
120 121 .get_unread_cnt_for_user(c.rhodecode_user.user_id)
121 122 self.cut_off_limit = int(config.get('cut_off_limit'))
122 123
123 124 self.sa = meta.Session
124 125 self.scm_model = ScmModel(self.sa)
125 126
126 127 def __call__(self, environ, start_response):
127 128 """Invoke the Controller"""
128 129 # WSGIController.__call__ dispatches to the Controller method
129 130 # the request is routed to. This routing information is
130 131 # available in environ['pylons.routes_dict']
131 132 start = time.time()
132 133 try:
133 134 # make sure that we update permissions each time we call controller
134 135 api_key = request.GET.get('api_key')
135 136 cookie_store = session.get('rhodecode_user') or {}
136 137 user_id = cookie_store.get('user_id', None)
137 138 username = get_container_username(environ, config)
138 139
139 140 auth_user = AuthUser(user_id, api_key, username)
140 141 request.user = auth_user
141 142 self.rhodecode_user = c.rhodecode_user = auth_user
142 143 if not self.rhodecode_user.is_authenticated and \
143 144 self.rhodecode_user.user_id is not None:
144 145 self.rhodecode_user\
145 146 .set_authenticated(cookie_store.get('is_authenticated'))
146 147
147 148 session['rhodecode_user'] = self.rhodecode_user.get_cookie_store()
148 149 session.save()
149 150 return WSGIController.__call__(self, environ, start_response)
150 151 finally:
151 152 log.debug('Request time: %.3fs' % (time.time() - start))
152 153 meta.Session.remove()
153 154
154 155
155 156 class BaseRepoController(BaseController):
156 157 """
157 158 Base class for controllers responsible for loading all needed data for
158 159 repository loaded items are
159 160
160 161 c.rhodecode_repo: instance of scm repository
161 162 c.rhodecode_db_repo: instance of db
162 163 c.repository_followers: number of followers
163 164 c.repository_forks: number of forks
164 165 """
165 166
166 167 def __before__(self):
167 168 super(BaseRepoController, self).__before__()
168 169 if c.repo_name:
169 170
170 171 c.rhodecode_db_repo = Repository.get_by_repo_name(c.repo_name)
171 172 c.rhodecode_repo = c.rhodecode_db_repo.scm_instance
172 173
173 174 if c.rhodecode_repo is None:
174 175 log.error('%s this repository is present in database but it '
175 176 'cannot be created as an scm instance', c.repo_name)
176 177
177 178 redirect(url('home'))
178 179
179 180 c.repository_followers = self.scm_model.get_followers(c.repo_name)
180 181 c.repository_forks = self.scm_model.get_forks(c.repo_name)
@@ -1,133 +1,139 b''
1 1 """
2 2 This module provides some useful tools for ``vcs`` like annotate/diff html
3 3 output. It also includes some internal helpers.
4 4 """
5 5 import sys
6 6 import time
7 7 import datetime
8 8
9 9
10 10 def makedate():
11 11 lt = time.localtime()
12 12 if lt[8] == 1 and time.daylight:
13 13 tz = time.altzone
14 14 else:
15 15 tz = time.timezone
16 16 return time.mktime(lt), tz
17 17
18 18
19 19 def date_fromtimestamp(unixts, tzoffset=0):
20 20 """
21 21 Makes a local datetime object out of unix timestamp
22 22
23 23 :param unixts:
24 24 :param tzoffset:
25 25 """
26 26
27 27 return datetime.datetime.fromtimestamp(float(unixts))
28 28
29 29
30 def safe_unicode(str_, from_encoding='utf8'):
30 def safe_unicode(str_, from_encoding=None):
31 31 """
32 32 safe unicode function. Does few trick to turn str_ into unicode
33 33
34 34 In case of UnicodeDecode error we try to return it with encoding detected
35 35 by chardet library if it fails fallback to unicode with errors replaced
36 36
37 37 :param str_: string to decode
38 38 :rtype: unicode
39 39 :returns: unicode object
40 40 """
41 41 if isinstance(str_, unicode):
42 42 return str_
43
43 if not from_encoding:
44 import rhodecode
45 DEFAULT_ENCODING = rhodecode.CONFIG.get('default_encoding','utf8')
46 from_encoding = DEFAULT_ENCODING
44 47 try:
45 48 return unicode(str_)
46 49 except UnicodeDecodeError:
47 50 pass
48 51
49 52 try:
50 53 return unicode(str_, from_encoding)
51 54 except UnicodeDecodeError:
52 55 pass
53 56
54 57 try:
55 58 import chardet
56 59 encoding = chardet.detect(str_)['encoding']
57 60 if encoding is None:
58 61 raise Exception()
59 62 return str_.decode(encoding)
60 63 except (ImportError, UnicodeDecodeError, Exception):
61 64 return unicode(str_, from_encoding, 'replace')
62 65
63 66
64 def safe_str(unicode_, to_encoding='utf8'):
67 def safe_str(unicode_, to_encoding=None):
65 68 """
66 69 safe str function. Does few trick to turn unicode_ into string
67 70
68 71 In case of UnicodeEncodeError we try to return it with encoding detected
69 72 by chardet library if it fails fallback to string with errors replaced
70 73
71 74 :param unicode_: unicode to encode
72 75 :rtype: str
73 76 :returns: str object
74 77 """
75 78
76 79 if isinstance(unicode_, str):
77 80 return unicode_
78
81 if not to_encoding:
82 import rhodecode
83 DEFAULT_ENCODING = rhodecode.CONFIG.get('default_encoding','utf8')
84 to_encoding = DEFAULT_ENCODING
79 85 try:
80 86 return unicode_.encode(to_encoding)
81 87 except UnicodeEncodeError:
82 88 pass
83 89
84 90 try:
85 91 import chardet
86 92 encoding = chardet.detect(unicode_)['encoding']
87 93 print encoding
88 94 if encoding is None:
89 95 raise UnicodeEncodeError()
90 96
91 97 return unicode_.encode(encoding)
92 98 except (ImportError, UnicodeEncodeError):
93 99 return unicode_.encode(to_encoding, 'replace')
94 100
95 101 return safe_str
96 102
97 103
98 104 def author_email(author):
99 105 """
100 106 returns email address of given author.
101 107 If any of <,> sign are found, it fallbacks to regex findall()
102 108 and returns first found result or empty string
103 109
104 110 Regex taken from http://www.regular-expressions.info/email.html
105 111 """
106 112 import re
107 113 r = author.find('>')
108 114 l = author.find('<')
109 115
110 116 if l == -1 or r == -1:
111 117 # fallback to regex match of email out of a string
112 118 email_re = re.compile(r"""[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!"""
113 119 r"""#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z"""
114 120 r"""0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]"""
115 121 r"""*[a-z0-9])?""", re.IGNORECASE)
116 122 m = re.findall(email_re, author)
117 123 return m[0] if m else ''
118 124
119 125 return author[l + 1:r].strip()
120 126
121 127
122 128 def author_name(author):
123 129 """
124 130 get name of author, or else username.
125 131 It'll try to find an email in the author string and just cut it off
126 132 to get the username
127 133 """
128 134
129 135 if not '@' in author:
130 136 return author
131 137 else:
132 138 return author.replace(author_email(author), '').replace('<', '')\
133 139 .replace('>', '').strip()
@@ -1,1198 +1,1203 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.model.db
4 4 ~~~~~~~~~~~~~~~~~~
5 5
6 6 Database Models for RhodeCode
7 7
8 8 :created_on: Apr 08, 2010
9 9 :author: marcink
10 10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software: you can redistribute it and/or modify
14 14 # it under the terms of the GNU General Public License as published by
15 15 # the Free Software Foundation, either version 3 of the License, or
16 16 # (at your option) any later version.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 25
26 26 import os
27 27 import logging
28 28 import datetime
29 29 import traceback
30 30 from collections import defaultdict
31 31
32 32 from sqlalchemy import *
33 33 from sqlalchemy.ext.hybrid import hybrid_property
34 34 from sqlalchemy.orm import relationship, joinedload, class_mapper, validates
35 35 from beaker.cache import cache_region, region_invalidate
36 36
37 37 from rhodecode.lib.vcs import get_backend
38 38 from rhodecode.lib.vcs.utils.helpers import get_scm
39 39 from rhodecode.lib.vcs.exceptions import VCSError
40 40 from rhodecode.lib.vcs.utils.lazy import LazyProperty
41 41
42 42 from rhodecode.lib import str2bool, safe_str, get_changeset_safe, safe_unicode
43 43 from rhodecode.lib.compat import json
44 44 from rhodecode.lib.caching_query import FromCache
45 45
46 46 from rhodecode.model.meta import Base, Session
47 47
48 48
49 49 log = logging.getLogger(__name__)
50 50
51 51 #==============================================================================
52 52 # BASE CLASSES
53 53 #==============================================================================
54 54
55 55
56 56 class ModelSerializer(json.JSONEncoder):
57 57 """
58 58 Simple Serializer for JSON,
59 59
60 60 usage::
61 61
62 62 to make object customized for serialization implement a __json__
63 63 method that will return a dict for serialization into json
64 64
65 65 example::
66 66
67 67 class Task(object):
68 68
69 69 def __init__(self, name, value):
70 70 self.name = name
71 71 self.value = value
72 72
73 73 def __json__(self):
74 74 return dict(name=self.name,
75 75 value=self.value)
76 76
77 77 """
78 78
79 79 def default(self, obj):
80 80
81 81 if hasattr(obj, '__json__'):
82 82 return obj.__json__()
83 83 else:
84 84 return json.JSONEncoder.default(self, obj)
85 85
86 86
87 87 class BaseModel(object):
88 88 """
89 89 Base Model for all classess
90 90 """
91 91
92 92 @classmethod
93 93 def _get_keys(cls):
94 94 """return column names for this model """
95 95 return class_mapper(cls).c.keys()
96 96
97 97 def get_dict(self):
98 98 """
99 99 return dict with keys and values corresponding
100 100 to this model data """
101 101
102 102 d = {}
103 103 for k in self._get_keys():
104 104 d[k] = getattr(self, k)
105 105
106 106 # also use __json__() if present to get additional fields
107 107 for k, val in getattr(self, '__json__', lambda: {})().iteritems():
108 108 d[k] = val
109 109 return d
110 110
111 111 def get_appstruct(self):
112 112 """return list with keys and values tupples corresponding
113 113 to this model data """
114 114
115 115 l = []
116 116 for k in self._get_keys():
117 117 l.append((k, getattr(self, k),))
118 118 return l
119 119
120 120 def populate_obj(self, populate_dict):
121 121 """populate model with data from given populate_dict"""
122 122
123 123 for k in self._get_keys():
124 124 if k in populate_dict:
125 125 setattr(self, k, populate_dict[k])
126 126
127 127 @classmethod
128 128 def query(cls):
129 129 return Session.query(cls)
130 130
131 131 @classmethod
132 132 def get(cls, id_):
133 133 if id_:
134 134 return cls.query().get(id_)
135 135
136 136 @classmethod
137 137 def getAll(cls):
138 138 return cls.query().all()
139 139
140 140 @classmethod
141 141 def delete(cls, id_):
142 142 obj = cls.query().get(id_)
143 143 Session.delete(obj)
144 144
145 145
146 146 class RhodeCodeSetting(Base, BaseModel):
147 147 __tablename__ = 'rhodecode_settings'
148 148 __table_args__ = (
149 149 UniqueConstraint('app_settings_name'),
150 150 {'extend_existing': True}
151 151 )
152 152 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
153 153 app_settings_name = Column("app_settings_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
154 154 _app_settings_value = Column("app_settings_value", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
155 155
156 156 def __init__(self, k='', v=''):
157 157 self.app_settings_name = k
158 158 self.app_settings_value = v
159 159
160 160 @validates('_app_settings_value')
161 161 def validate_settings_value(self, key, val):
162 162 assert type(val) == unicode
163 163 return val
164 164
165 165 @hybrid_property
166 166 def app_settings_value(self):
167 167 v = self._app_settings_value
168 168 if self.app_settings_name == 'ldap_active':
169 169 v = str2bool(v)
170 170 return v
171 171
172 172 @app_settings_value.setter
173 173 def app_settings_value(self, val):
174 174 """
175 175 Setter that will always make sure we use unicode in app_settings_value
176 176
177 177 :param val:
178 178 """
179 179 self._app_settings_value = safe_unicode(val)
180 180
181 181 def __repr__(self):
182 182 return "<%s('%s:%s')>" % (
183 183 self.__class__.__name__,
184 184 self.app_settings_name, self.app_settings_value
185 185 )
186 186
187 187 @classmethod
188 188 def get_by_name(cls, ldap_key):
189 189 return cls.query()\
190 190 .filter(cls.app_settings_name == ldap_key).scalar()
191 191
192 192 @classmethod
193 193 def get_app_settings(cls, cache=False):
194 194
195 195 ret = cls.query()
196 196
197 197 if cache:
198 198 ret = ret.options(FromCache("sql_cache_short", "get_hg_settings"))
199 199
200 200 if not ret:
201 201 raise Exception('Could not get application settings !')
202 202 settings = {}
203 203 for each in ret:
204 204 settings['rhodecode_' + each.app_settings_name] = \
205 205 each.app_settings_value
206 206
207 207 return settings
208 208
209 209 @classmethod
210 210 def get_ldap_settings(cls, cache=False):
211 211 ret = cls.query()\
212 212 .filter(cls.app_settings_name.startswith('ldap_')).all()
213 213 fd = {}
214 214 for row in ret:
215 215 fd.update({row.app_settings_name:row.app_settings_value})
216 216
217 217 return fd
218 218
219 219
220 220 class RhodeCodeUi(Base, BaseModel):
221 221 __tablename__ = 'rhodecode_ui'
222 222 __table_args__ = (
223 223 UniqueConstraint('ui_key'),
224 224 {'extend_existing': True}
225 225 )
226 226
227 227 HOOK_UPDATE = 'changegroup.update'
228 228 HOOK_REPO_SIZE = 'changegroup.repo_size'
229 229 HOOK_PUSH = 'pretxnchangegroup.push_logger'
230 230 HOOK_PULL = 'preoutgoing.pull_logger'
231 231
232 232 ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
233 233 ui_section = Column("ui_section", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
234 234 ui_key = Column("ui_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
235 235 ui_value = Column("ui_value", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
236 236 ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True)
237 237
238 238 @classmethod
239 239 def get_by_key(cls, key):
240 240 return cls.query().filter(cls.ui_key == key)
241 241
242 242 @classmethod
243 243 def get_builtin_hooks(cls):
244 244 q = cls.query()
245 245 q = q.filter(cls.ui_key.in_([cls.HOOK_UPDATE,
246 246 cls.HOOK_REPO_SIZE,
247 247 cls.HOOK_PUSH, cls.HOOK_PULL]))
248 248 return q.all()
249 249
250 250 @classmethod
251 251 def get_custom_hooks(cls):
252 252 q = cls.query()
253 253 q = q.filter(~cls.ui_key.in_([cls.HOOK_UPDATE,
254 254 cls.HOOK_REPO_SIZE,
255 255 cls.HOOK_PUSH, cls.HOOK_PULL]))
256 256 q = q.filter(cls.ui_section == 'hooks')
257 257 return q.all()
258 258
259 259 @classmethod
260 260 def create_or_update_hook(cls, key, val):
261 261 new_ui = cls.get_by_key(key).scalar() or cls()
262 262 new_ui.ui_section = 'hooks'
263 263 new_ui.ui_active = True
264 264 new_ui.ui_key = key
265 265 new_ui.ui_value = val
266 266
267 267 Session.add(new_ui)
268 268
269 269
270 270 class User(Base, BaseModel):
271 271 __tablename__ = 'users'
272 272 __table_args__ = (
273 273 UniqueConstraint('username'), UniqueConstraint('email'),
274 274 {'extend_existing': True}
275 275 )
276 276 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
277 277 username = Column("username", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
278 278 password = Column("password", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
279 279 active = Column("active", Boolean(), nullable=True, unique=None, default=None)
280 280 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
281 281 name = Column("name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
282 282 lastname = Column("lastname", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
283 283 _email = Column("email", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
284 284 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
285 285 ldap_dn = Column("ldap_dn", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
286 286 api_key = Column("api_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
287 287
288 288 user_log = relationship('UserLog', cascade='all')
289 289 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
290 290
291 291 repositories = relationship('Repository')
292 292 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
293 293 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
294 294
295 295 group_member = relationship('UsersGroupMember', cascade='all')
296 296
297 297 notifications = relationship('UserNotification',)
298 298
299 299 @hybrid_property
300 300 def email(self):
301 301 return self._email
302 302
303 303 @email.setter
304 304 def email(self, val):
305 305 self._email = val.lower() if val else None
306 306
307 307 @property
308 308 def full_name(self):
309 309 return '%s %s' % (self.name, self.lastname)
310 310
311 311 @property
312 312 def full_name_or_username(self):
313 313 return ('%s %s' % (self.name, self.lastname)
314 314 if (self.name and self.lastname) else self.username)
315 315
316 316 @property
317 317 def full_contact(self):
318 318 return '%s %s <%s>' % (self.name, self.lastname, self.email)
319 319
320 320 @property
321 321 def short_contact(self):
322 322 return '%s %s' % (self.name, self.lastname)
323 323
324 324 @property
325 325 def is_admin(self):
326 326 return self.admin
327 327
328 328 def __repr__(self):
329 329 return "<%s('id:%s:%s')>" % (self.__class__.__name__,
330 330 self.user_id, self.username)
331 331
332 332 @classmethod
333 333 def get_by_username(cls, username, case_insensitive=False, cache=False):
334 334 if case_insensitive:
335 335 q = cls.query().filter(cls.username.ilike(username))
336 336 else:
337 337 q = cls.query().filter(cls.username == username)
338 338
339 339 if cache:
340 340 q = q.options(FromCache("sql_cache_short",
341 341 "get_user_%s" % username))
342 342 return q.scalar()
343 343
344 344 @classmethod
345 345 def get_by_api_key(cls, api_key, cache=False):
346 346 q = cls.query().filter(cls.api_key == api_key)
347 347
348 348 if cache:
349 349 q = q.options(FromCache("sql_cache_short",
350 350 "get_api_key_%s" % api_key))
351 351 return q.scalar()
352 352
353 353 @classmethod
354 354 def get_by_email(cls, email, case_insensitive=False, cache=False):
355 355 if case_insensitive:
356 356 q = cls.query().filter(cls.email.ilike(email))
357 357 else:
358 358 q = cls.query().filter(cls.email == email)
359 359
360 360 if cache:
361 361 q = q.options(FromCache("sql_cache_short",
362 362 "get_api_key_%s" % email))
363 363 return q.scalar()
364 364
365 365 def update_lastlogin(self):
366 366 """Update user lastlogin"""
367 367 self.last_login = datetime.datetime.now()
368 368 Session.add(self)
369 369 log.debug('updated user %s lastlogin' % self.username)
370 370
371 371 def __json__(self):
372 372 return dict(
373 373 email=self.email,
374 374 full_name=self.full_name,
375 375 full_name_or_username=self.full_name_or_username,
376 376 short_contact=self.short_contact,
377 377 full_contact=self.full_contact
378 378 )
379 379
380 380
381 381 class UserLog(Base, BaseModel):
382 382 __tablename__ = 'user_logs'
383 383 __table_args__ = {'extend_existing': True}
384 384 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
385 385 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
386 386 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True)
387 387 repository_name = Column("repository_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
388 388 user_ip = Column("user_ip", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
389 389 action = Column("action", UnicodeText(length=1200000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
390 390 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
391 391
392 392 @property
393 393 def action_as_day(self):
394 394 return datetime.date(*self.action_date.timetuple()[:3])
395 395
396 396 user = relationship('User')
397 397 repository = relationship('Repository',cascade='')
398 398
399 399
400 400 class UsersGroup(Base, BaseModel):
401 401 __tablename__ = 'users_groups'
402 402 __table_args__ = {'extend_existing': True}
403 403
404 404 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
405 405 users_group_name = Column("users_group_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
406 406 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
407 407
408 408 members = relationship('UsersGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
409 409
410 410 def __repr__(self):
411 411 return '<userGroup(%s)>' % (self.users_group_name)
412 412
413 413 @classmethod
414 414 def get_by_group_name(cls, group_name, cache=False,
415 415 case_insensitive=False):
416 416 if case_insensitive:
417 417 q = cls.query().filter(cls.users_group_name.ilike(group_name))
418 418 else:
419 419 q = cls.query().filter(cls.users_group_name == group_name)
420 420 if cache:
421 421 q = q.options(FromCache("sql_cache_short",
422 422 "get_user_%s" % group_name))
423 423 return q.scalar()
424 424
425 425 @classmethod
426 426 def get(cls, users_group_id, cache=False):
427 427 users_group = cls.query()
428 428 if cache:
429 429 users_group = users_group.options(FromCache("sql_cache_short",
430 430 "get_users_group_%s" % users_group_id))
431 431 return users_group.get(users_group_id)
432 432
433 433
434 434 class UsersGroupMember(Base, BaseModel):
435 435 __tablename__ = 'users_groups_members'
436 436 __table_args__ = {'extend_existing': True}
437 437
438 438 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
439 439 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
440 440 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
441 441
442 442 user = relationship('User', lazy='joined')
443 443 users_group = relationship('UsersGroup')
444 444
445 445 def __init__(self, gr_id='', u_id=''):
446 446 self.users_group_id = gr_id
447 447 self.user_id = u_id
448 448
449 449
450 450 class Repository(Base, BaseModel):
451 451 __tablename__ = 'repositories'
452 452 __table_args__ = (
453 453 UniqueConstraint('repo_name'),
454 454 {'extend_existing': True},
455 455 )
456 456
457 457 repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
458 458 repo_name = Column("repo_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
459 459 clone_uri = Column("clone_uri", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
460 460 repo_type = Column("repo_type", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default='hg')
461 461 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
462 462 private = Column("private", Boolean(), nullable=True, unique=None, default=None)
463 463 enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True)
464 464 enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True)
465 465 description = Column("description", String(length=10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
466 466 created_on = Column('created_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
467 467
468 468 fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None)
469 469 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None)
470 470
471 471 user = relationship('User')
472 472 fork = relationship('Repository', remote_side=repo_id)
473 473 group = relationship('RepoGroup')
474 474 repo_to_perm = relationship('UserRepoToPerm', cascade='all', order_by='UserRepoToPerm.repo_to_perm_id')
475 475 users_group_to_perm = relationship('UsersGroupRepoToPerm', cascade='all')
476 476 stats = relationship('Statistics', cascade='all', uselist=False)
477 477
478 478 followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id', cascade='all')
479 479
480 480 logs = relationship('UserLog')
481 481
482 482 def __repr__(self):
483 483 return "<%s('%s:%s')>" % (self.__class__.__name__,
484 484 self.repo_id, self.repo_name)
485 485
486 486 @classmethod
487 487 def url_sep(cls):
488 488 return '/'
489 489
490 490 @classmethod
491 491 def get_by_repo_name(cls, repo_name):
492 492 q = Session.query(cls).filter(cls.repo_name == repo_name)
493 493 q = q.options(joinedload(Repository.fork))\
494 494 .options(joinedload(Repository.user))\
495 495 .options(joinedload(Repository.group))
496 496 return q.scalar()
497 497
498 498 @classmethod
499 499 def get_repo_forks(cls, repo_id):
500 500 return cls.query().filter(Repository.fork_id == repo_id)
501 501
502 502 @classmethod
503 503 def base_path(cls):
504 504 """
505 505 Returns base path when all repos are stored
506 506
507 507 :param cls:
508 508 """
509 509 q = Session.query(RhodeCodeUi)\
510 510 .filter(RhodeCodeUi.ui_key == cls.url_sep())
511 511 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
512 512 return q.one().ui_value
513 513
514 514 @property
515 515 def just_name(self):
516 516 return self.repo_name.split(Repository.url_sep())[-1]
517 517
518 518 @property
519 519 def groups_with_parents(self):
520 520 groups = []
521 521 if self.group is None:
522 522 return groups
523 523
524 524 cur_gr = self.group
525 525 groups.insert(0, cur_gr)
526 526 while 1:
527 527 gr = getattr(cur_gr, 'parent_group', None)
528 528 cur_gr = cur_gr.parent_group
529 529 if gr is None:
530 530 break
531 531 groups.insert(0, gr)
532 532
533 533 return groups
534 534
535 535 @property
536 536 def groups_and_repo(self):
537 537 return self.groups_with_parents, self.just_name
538 538
539 539 @LazyProperty
540 540 def repo_path(self):
541 541 """
542 542 Returns base full path for that repository means where it actually
543 543 exists on a filesystem
544 544 """
545 545 q = Session.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key ==
546 546 Repository.url_sep())
547 547 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
548 548 return q.one().ui_value
549 549
550 550 @property
551 551 def repo_full_path(self):
552 552 p = [self.repo_path]
553 553 # we need to split the name by / since this is how we store the
554 554 # names in the database, but that eventually needs to be converted
555 555 # into a valid system path
556 556 p += self.repo_name.split(Repository.url_sep())
557 557 return os.path.join(*p)
558 558
559 559 def get_new_name(self, repo_name):
560 560 """
561 561 returns new full repository name based on assigned group and new new
562 562
563 563 :param group_name:
564 564 """
565 565 path_prefix = self.group.full_path_splitted if self.group else []
566 566 return Repository.url_sep().join(path_prefix + [repo_name])
567 567
568 568 @property
569 569 def _ui(self):
570 570 """
571 571 Creates an db based ui object for this repository
572 572 """
573 573 from mercurial import ui
574 574 from mercurial import config
575 575 baseui = ui.ui()
576 576
577 577 #clean the baseui object
578 578 baseui._ocfg = config.config()
579 579 baseui._ucfg = config.config()
580 580 baseui._tcfg = config.config()
581 581
582 582 ret = RhodeCodeUi.query()\
583 583 .options(FromCache("sql_cache_short", "repository_repo_ui")).all()
584 584
585 585 hg_ui = ret
586 586 for ui_ in hg_ui:
587 587 if ui_.ui_active:
588 588 log.debug('settings ui from db[%s]%s:%s', ui_.ui_section,
589 589 ui_.ui_key, ui_.ui_value)
590 590 baseui.setconfig(ui_.ui_section, ui_.ui_key, ui_.ui_value)
591 591
592 592 return baseui
593 593
594 594 @classmethod
595 595 def is_valid(cls, repo_name):
596 596 """
597 597 returns True if given repo name is a valid filesystem repository
598 598
599 599 :param cls:
600 600 :param repo_name:
601 601 """
602 602 from rhodecode.lib.utils import is_valid_repo
603 603
604 604 return is_valid_repo(repo_name, cls.base_path())
605 605
606 606 #==========================================================================
607 607 # SCM PROPERTIES
608 608 #==========================================================================
609 609
610 610 def get_changeset(self, rev):
611 611 return get_changeset_safe(self.scm_instance, rev)
612 612
613 613 @property
614 614 def tip(self):
615 615 return self.get_changeset('tip')
616 616
617 617 @property
618 618 def author(self):
619 619 return self.tip.author
620 620
621 621 @property
622 622 def last_change(self):
623 623 return self.scm_instance.last_change
624 624
625 625 def comments(self, revisions=None):
626 626 """
627 627 Returns comments for this repository grouped by revisions
628 628
629 629 :param revisions: filter query by revisions only
630 630 """
631 631 cmts = ChangesetComment.query()\
632 632 .filter(ChangesetComment.repo == self)
633 633 if revisions:
634 634 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
635 635 grouped = defaultdict(list)
636 636 for cmt in cmts.all():
637 637 grouped[cmt.revision].append(cmt)
638 638 return grouped
639 639
640 640 #==========================================================================
641 641 # SCM CACHE INSTANCE
642 642 #==========================================================================
643 643
644 644 @property
645 645 def invalidate(self):
646 646 return CacheInvalidation.invalidate(self.repo_name)
647 647
648 648 def set_invalidate(self):
649 649 """
650 650 set a cache for invalidation for this instance
651 651 """
652 652 CacheInvalidation.set_invalidate(self.repo_name)
653 653
654 654 @LazyProperty
655 655 def scm_instance(self):
656 656 return self.__get_instance()
657 657
658 658 @property
659 659 def scm_instance_cached(self):
660 660 @cache_region('long_term')
661 661 def _c(repo_name):
662 662 return self.__get_instance()
663 663 rn = self.repo_name
664 664 log.debug('Getting cached instance of repo')
665 665 inv = self.invalidate
666 666 if inv is not None:
667 667 region_invalidate(_c, None, rn)
668 668 # update our cache
669 669 CacheInvalidation.set_valid(inv.cache_key)
670 670 return _c(rn)
671 671
672 672 def __get_instance(self):
673 673 repo_full_path = self.repo_full_path
674 674 try:
675 675 alias = get_scm(repo_full_path)[0]
676 676 log.debug('Creating instance of %s repository' % alias)
677 677 backend = get_backend(alias)
678 678 except VCSError:
679 679 log.error(traceback.format_exc())
680 680 log.error('Perhaps this repository is in db and not in '
681 681 'filesystem run rescan repositories with '
682 682 '"destroy old data " option from admin panel')
683 683 return
684 684
685 685 if alias == 'hg':
686 686
687 687 repo = backend(safe_str(repo_full_path), create=False,
688 688 baseui=self._ui)
689 689 # skip hidden web repository
690 690 if repo._get_hidden():
691 691 return
692 692 else:
693 693 repo = backend(repo_full_path, create=False)
694 694
695 695 return repo
696 696
697 697
698 698 class RepoGroup(Base, BaseModel):
699 699 __tablename__ = 'groups'
700 700 __table_args__ = (
701 701 UniqueConstraint('group_name', 'group_parent_id'),
702 702 CheckConstraint('group_id != group_parent_id'),
703 703 {'extend_existing': True},
704 704 )
705 705 __mapper_args__ = {'order_by': 'group_name'}
706 706
707 707 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
708 708 group_name = Column("group_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
709 709 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
710 710 group_description = Column("group_description", String(length=10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
711 711
712 712 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
713 713 users_group_to_perm = relationship('UsersGroupRepoGroupToPerm', cascade='all')
714 714
715 715 parent_group = relationship('RepoGroup', remote_side=group_id)
716 716
717 717 def __init__(self, group_name='', parent_group=None):
718 718 self.group_name = group_name
719 719 self.parent_group = parent_group
720 720
721 721 def __repr__(self):
722 722 return "<%s('%s:%s')>" % (self.__class__.__name__, self.group_id,
723 723 self.group_name)
724 724
725 725 @classmethod
726 726 def groups_choices(cls):
727 727 from webhelpers.html import literal as _literal
728 728 repo_groups = [('', '')]
729 729 sep = ' &raquo; '
730 730 _name = lambda k: _literal(sep.join(k))
731 731
732 732 repo_groups.extend([(x.group_id, _name(x.full_path_splitted))
733 733 for x in cls.query().all()])
734 734
735 735 repo_groups = sorted(repo_groups, key=lambda t: t[1].split(sep)[0])
736 736 return repo_groups
737 737
738 738 @classmethod
739 739 def url_sep(cls):
740 740 return '/'
741 741
742 742 @classmethod
743 743 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
744 744 if case_insensitive:
745 745 gr = cls.query()\
746 746 .filter(cls.group_name.ilike(group_name))
747 747 else:
748 748 gr = cls.query()\
749 749 .filter(cls.group_name == group_name)
750 750 if cache:
751 751 gr = gr.options(FromCache("sql_cache_short",
752 752 "get_group_%s" % group_name))
753 753 return gr.scalar()
754 754
755 755 @property
756 756 def parents(self):
757 757 parents_recursion_limit = 5
758 758 groups = []
759 759 if self.parent_group is None:
760 760 return groups
761 761 cur_gr = self.parent_group
762 762 groups.insert(0, cur_gr)
763 763 cnt = 0
764 764 while 1:
765 765 cnt += 1
766 766 gr = getattr(cur_gr, 'parent_group', None)
767 767 cur_gr = cur_gr.parent_group
768 768 if gr is None:
769 769 break
770 770 if cnt == parents_recursion_limit:
771 771 # this will prevent accidental infinit loops
772 772 log.error('group nested more than %s' %
773 773 parents_recursion_limit)
774 774 break
775 775
776 776 groups.insert(0, gr)
777 777 return groups
778 778
779 779 @property
780 780 def children(self):
781 781 return RepoGroup.query().filter(RepoGroup.parent_group == self)
782 782
783 783 @property
784 784 def name(self):
785 785 return self.group_name.split(RepoGroup.url_sep())[-1]
786 786
787 787 @property
788 788 def full_path(self):
789 789 return self.group_name
790 790
791 791 @property
792 792 def full_path_splitted(self):
793 793 return self.group_name.split(RepoGroup.url_sep())
794 794
795 795 @property
796 796 def repositories(self):
797 797 return Repository.query().filter(Repository.group == self)
798 798
799 799 @property
800 800 def repositories_recursive_count(self):
801 801 cnt = self.repositories.count()
802 802
803 803 def children_count(group):
804 804 cnt = 0
805 805 for child in group.children:
806 806 cnt += child.repositories.count()
807 807 cnt += children_count(child)
808 808 return cnt
809 809
810 810 return cnt + children_count(self)
811 811
812 812 def get_new_name(self, group_name):
813 813 """
814 814 returns new full group name based on parent and new name
815 815
816 816 :param group_name:
817 817 """
818 818 path_prefix = (self.parent_group.full_path_splitted if
819 819 self.parent_group else [])
820 820 return RepoGroup.url_sep().join(path_prefix + [group_name])
821 821
822 822
823 823 class Permission(Base, BaseModel):
824 824 __tablename__ = 'permissions'
825 825 __table_args__ = {'extend_existing': True}
826 826 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
827 827 permission_name = Column("permission_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
828 828 permission_longname = Column("permission_longname", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
829 829
830 830 def __repr__(self):
831 831 return "<%s('%s:%s')>" % (
832 832 self.__class__.__name__, self.permission_id, self.permission_name
833 833 )
834 834
835 835 @classmethod
836 836 def get_by_key(cls, key):
837 837 return cls.query().filter(cls.permission_name == key).scalar()
838 838
839 839 @classmethod
840 840 def get_default_perms(cls, default_user_id):
841 841 q = Session.query(UserRepoToPerm, Repository, cls)\
842 842 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
843 843 .join((cls, UserRepoToPerm.permission_id == cls.permission_id))\
844 844 .filter(UserRepoToPerm.user_id == default_user_id)
845 845
846 846 return q.all()
847 847
848 848 @classmethod
849 849 def get_default_group_perms(cls, default_user_id):
850 850 q = Session.query(UserRepoGroupToPerm, RepoGroup, cls)\
851 851 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
852 852 .join((cls, UserRepoGroupToPerm.permission_id == cls.permission_id))\
853 853 .filter(UserRepoGroupToPerm.user_id == default_user_id)
854 854
855 855 return q.all()
856 856
857 857
858 858 class UserRepoToPerm(Base, BaseModel):
859 859 __tablename__ = 'repo_to_perm'
860 860 __table_args__ = (
861 861 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
862 862 {'extend_existing': True}
863 863 )
864 864 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
865 865 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
866 866 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
867 867 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
868 868
869 869 user = relationship('User')
870 870 repository = relationship('Repository')
871 871 permission = relationship('Permission')
872 872
873 873 @classmethod
874 874 def create(cls, user, repository, permission):
875 875 n = cls()
876 876 n.user = user
877 877 n.repository = repository
878 878 n.permission = permission
879 879 Session.add(n)
880 880 return n
881 881
882 882 def __repr__(self):
883 883 return '<user:%s => %s >' % (self.user, self.repository)
884 884
885 885
886 886 class UserToPerm(Base, BaseModel):
887 887 __tablename__ = 'user_to_perm'
888 888 __table_args__ = (
889 889 UniqueConstraint('user_id', 'permission_id'),
890 890 {'extend_existing': True}
891 891 )
892 892 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
893 893 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
894 894 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
895 895
896 896 user = relationship('User')
897 897 permission = relationship('Permission', lazy='joined')
898 898
899 899
900 900 class UsersGroupRepoToPerm(Base, BaseModel):
901 901 __tablename__ = 'users_group_repo_to_perm'
902 902 __table_args__ = (
903 903 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
904 904 {'extend_existing': True}
905 905 )
906 906 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
907 907 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
908 908 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
909 909 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
910 910
911 911 users_group = relationship('UsersGroup')
912 912 permission = relationship('Permission')
913 913 repository = relationship('Repository')
914 914
915 915 @classmethod
916 916 def create(cls, users_group, repository, permission):
917 917 n = cls()
918 918 n.users_group = users_group
919 919 n.repository = repository
920 920 n.permission = permission
921 921 Session.add(n)
922 922 return n
923 923
924 924 def __repr__(self):
925 925 return '<userGroup:%s => %s >' % (self.users_group, self.repository)
926 926
927 927
928 928 class UsersGroupToPerm(Base, BaseModel):
929 929 __tablename__ = 'users_group_to_perm'
930 930 __table_args__ = (
931 931 UniqueConstraint('users_group_id', 'permission_id',),
932 932 {'extend_existing': True}
933 933 )
934 934 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
935 935 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
936 936 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
937 937
938 938 users_group = relationship('UsersGroup')
939 939 permission = relationship('Permission')
940 940
941 941
942 942 class UserRepoGroupToPerm(Base, BaseModel):
943 943 __tablename__ = 'user_repo_group_to_perm'
944 944 __table_args__ = (
945 945 UniqueConstraint('user_id', 'group_id', 'permission_id'),
946 946 {'extend_existing': True}
947 947 )
948 948
949 949 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
950 950 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
951 951 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
952 952 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
953 953
954 954 user = relationship('User')
955 955 group = relationship('RepoGroup')
956 956 permission = relationship('Permission')
957 957
958 958
959 959 class UsersGroupRepoGroupToPerm(Base, BaseModel):
960 960 __tablename__ = 'users_group_repo_group_to_perm'
961 961 __table_args__ = (
962 962 UniqueConstraint('users_group_id', 'group_id'),
963 963 {'extend_existing': True}
964 964 )
965 965
966 966 users_group_repo_group_to_perm_id = Column("users_group_repo_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
967 967 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
968 968 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
969 969 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
970 970
971 971 users_group = relationship('UsersGroup')
972 972 permission = relationship('Permission')
973 973 group = relationship('RepoGroup')
974 974
975 975
976 976 class Statistics(Base, BaseModel):
977 977 __tablename__ = 'statistics'
978 978 __table_args__ = (UniqueConstraint('repository_id'), {'extend_existing': True})
979 979 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
980 980 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
981 981 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
982 982 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
983 983 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
984 984 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
985 985
986 986 repository = relationship('Repository', single_parent=True)
987 987
988 988
989 989 class UserFollowing(Base, BaseModel):
990 990 __tablename__ = 'user_followings'
991 991 __table_args__ = (
992 992 UniqueConstraint('user_id', 'follows_repository_id'),
993 993 UniqueConstraint('user_id', 'follows_user_id'),
994 994 {'extend_existing': True}
995 995 )
996 996
997 997 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
998 998 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
999 999 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
1000 1000 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1001 1001 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
1002 1002
1003 1003 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
1004 1004
1005 1005 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
1006 1006 follows_repository = relationship('Repository', order_by='Repository.repo_name')
1007 1007
1008 1008 @classmethod
1009 1009 def get_repo_followers(cls, repo_id):
1010 1010 return cls.query().filter(cls.follows_repo_id == repo_id)
1011 1011
1012 1012
1013 1013 class CacheInvalidation(Base, BaseModel):
1014 1014 __tablename__ = 'cache_invalidation'
1015 1015 __table_args__ = (UniqueConstraint('cache_key'), {'extend_existing': True})
1016 1016 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1017 1017 cache_key = Column("cache_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1018 1018 cache_args = Column("cache_args", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1019 1019 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
1020 1020
1021 1021 def __init__(self, cache_key, cache_args=''):
1022 1022 self.cache_key = cache_key
1023 1023 self.cache_args = cache_args
1024 1024 self.cache_active = False
1025 1025
1026 1026 def __repr__(self):
1027 1027 return "<%s('%s:%s')>" % (self.__class__.__name__,
1028 1028 self.cache_id, self.cache_key)
1029 1029
1030 1030 @classmethod
1031 1031 def _get_key(cls, key):
1032 1032 """
1033 1033 Wrapper for generating a key
1034 1034
1035 1035 :param key:
1036 1036 """
1037 return "%s" % (key)
1037 import rhodecode
1038 prefix = ''
1039 iid = rhodecode.CONFIG.get('instance_id')
1040 if iid:
1041 prefix = iid
1042 return "%s%s" % (prefix, key)
1038 1043
1039 1044 @classmethod
1040 1045 def get_by_key(cls, key):
1041 1046 return cls.query().filter(cls.cache_key == key).scalar()
1042 1047
1043 1048 @classmethod
1044 1049 def invalidate(cls, key):
1045 1050 """
1046 1051 Returns Invalidation object if this given key should be invalidated
1047 1052 None otherwise. `cache_active = False` means that this cache
1048 1053 state is not valid and needs to be invalidated
1049 1054
1050 1055 :param key:
1051 1056 """
1052 1057 return cls.query()\
1053 1058 .filter(CacheInvalidation.cache_key == key)\
1054 1059 .filter(CacheInvalidation.cache_active == False)\
1055 1060 .scalar()
1056 1061
1057 1062 @classmethod
1058 1063 def set_invalidate(cls, key):
1059 1064 """
1060 1065 Mark this Cache key for invalidation
1061 1066
1062 1067 :param key:
1063 1068 """
1064 1069
1065 1070 log.debug('marking %s for invalidation' % key)
1066 1071 inv_obj = Session.query(cls)\
1067 1072 .filter(cls.cache_key == key).scalar()
1068 1073 if inv_obj:
1069 1074 inv_obj.cache_active = False
1070 1075 else:
1071 1076 log.debug('cache key not found in invalidation db -> creating one')
1072 1077 inv_obj = CacheInvalidation(key)
1073 1078
1074 1079 try:
1075 1080 Session.add(inv_obj)
1076 1081 Session.commit()
1077 1082 except Exception:
1078 1083 log.error(traceback.format_exc())
1079 1084 Session.rollback()
1080 1085
1081 1086 @classmethod
1082 1087 def set_valid(cls, key):
1083 1088 """
1084 1089 Mark this cache key as active and currently cached
1085 1090
1086 1091 :param key:
1087 1092 """
1088 1093 inv_obj = cls.get_by_key(key)
1089 1094 inv_obj.cache_active = True
1090 1095 Session.add(inv_obj)
1091 1096 Session.commit()
1092 1097
1093 1098
1094 1099 class ChangesetComment(Base, BaseModel):
1095 1100 __tablename__ = 'changeset_comments'
1096 1101 __table_args__ = ({'extend_existing': True},)
1097 1102 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
1098 1103 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1099 1104 revision = Column('revision', String(40), nullable=False)
1100 1105 line_no = Column('line_no', Unicode(10), nullable=True)
1101 1106 f_path = Column('f_path', Unicode(1000), nullable=True)
1102 1107 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
1103 1108 text = Column('text', Unicode(25000), nullable=False)
1104 1109 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
1105 1110
1106 1111 author = relationship('User', lazy='joined')
1107 1112 repo = relationship('Repository')
1108 1113
1109 1114 @classmethod
1110 1115 def get_users(cls, revision):
1111 1116 """
1112 1117 Returns user associated with this changesetComment. ie those
1113 1118 who actually commented
1114 1119
1115 1120 :param cls:
1116 1121 :param revision:
1117 1122 """
1118 1123 return Session.query(User)\
1119 1124 .filter(cls.revision == revision)\
1120 1125 .join(ChangesetComment.author).all()
1121 1126
1122 1127
1123 1128 class Notification(Base, BaseModel):
1124 1129 __tablename__ = 'notifications'
1125 1130 __table_args__ = ({'extend_existing': True},)
1126 1131
1127 1132 TYPE_CHANGESET_COMMENT = u'cs_comment'
1128 1133 TYPE_MESSAGE = u'message'
1129 1134 TYPE_MENTION = u'mention'
1130 1135 TYPE_REGISTRATION = u'registration'
1131 1136
1132 1137 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
1133 1138 subject = Column('subject', Unicode(512), nullable=True)
1134 1139 body = Column('body', Unicode(50000), nullable=True)
1135 1140 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
1136 1141 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1137 1142 type_ = Column('type', Unicode(256))
1138 1143
1139 1144 created_by_user = relationship('User')
1140 1145 notifications_to_users = relationship('UserNotification', lazy='joined',
1141 1146 cascade="all, delete, delete-orphan")
1142 1147
1143 1148 @property
1144 1149 def recipients(self):
1145 1150 return [x.user for x in UserNotification.query()\
1146 1151 .filter(UserNotification.notification == self).all()]
1147 1152
1148 1153 @classmethod
1149 1154 def create(cls, created_by, subject, body, recipients, type_=None):
1150 1155 if type_ is None:
1151 1156 type_ = Notification.TYPE_MESSAGE
1152 1157
1153 1158 notification = cls()
1154 1159 notification.created_by_user = created_by
1155 1160 notification.subject = subject
1156 1161 notification.body = body
1157 1162 notification.type_ = type_
1158 1163 notification.created_on = datetime.datetime.now()
1159 1164
1160 1165 for u in recipients:
1161 1166 assoc = UserNotification()
1162 1167 assoc.notification = notification
1163 1168 u.notifications.append(assoc)
1164 1169 Session.add(notification)
1165 1170 return notification
1166 1171
1167 1172 @property
1168 1173 def description(self):
1169 1174 from rhodecode.model.notification import NotificationModel
1170 1175 return NotificationModel().make_description(self)
1171 1176
1172 1177
1173 1178 class UserNotification(Base, BaseModel):
1174 1179 __tablename__ = 'user_to_notification'
1175 1180 __table_args__ = (
1176 1181 UniqueConstraint('user_id', 'notification_id'),
1177 1182 {'extend_existing': True}
1178 1183 )
1179 1184 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
1180 1185 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
1181 1186 read = Column('read', Boolean, default=False)
1182 1187 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
1183 1188
1184 1189 user = relationship('User', lazy="joined")
1185 1190 notification = relationship('Notification', lazy="joined",
1186 1191 order_by=lambda: Notification.created_on.desc(),)
1187 1192
1188 1193 def mark_as_read(self):
1189 1194 self.read = True
1190 1195 Session.add(self)
1191 1196
1192 1197
1193 1198 class DbMigrateVersion(Base, BaseModel):
1194 1199 __tablename__ = 'db_migrate_version'
1195 1200 __table_args__ = {'extend_existing': True}
1196 1201 repository_id = Column('repository_id', String(250), primary_key=True)
1197 1202 repository_path = Column('repository_path', Text)
1198 1203 version = Column('version', Integer)
@@ -1,338 +1,338 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <%inherit file="root.html"/>
3 3
4 4 <!-- HEADER -->
5 5 <div id="header">
6 6 <div id="header-inner" class="title hover">
7 7 <div id="logo">
8 8 <h1><a href="${h.url('home')}">${c.rhodecode_name}</a></h1>
9 9 </div>
10 10 <!-- MENU -->
11 11 ${self.page_nav()}
12 12 <!-- END MENU -->
13 13 ${self.body()}
14 14 </div>
15 15 </div>
16 16 <!-- END HEADER -->
17 17
18 18 <!-- CONTENT -->
19 19 <div id="content">
20 20 <div class="flash_msg">
21 21 <% messages = h.flash.pop_messages() %>
22 22 % if messages:
23 23 <ul id="flash-messages">
24 24 % for message in messages:
25 25 <li class="${message.category}_msg">${message}</li>
26 26 % endfor
27 27 </ul>
28 28 % endif
29 29 </div>
30 30 <div id="main">
31 31 ${next.main()}
32 32 </div>
33 33 </div>
34 34 <!-- END CONTENT -->
35 35
36 36 <!-- FOOTER -->
37 37 <div id="footer">
38 38 <div id="footer-inner" class="title">
39 39 <div>
40 40 <p class="footer-link">
41 41 <a href="${h.url('bugtracker')}">${_('Submit a bug')}</a>
42 42 </p>
43 43 <p class="footer-link-right">
44 <a href="${h.url('rhodecode_official')}">RhodeCode</a>
44 <a href="${h.url('rhodecode_official')}">RhodeCode${'-%s' % c.rhodecode_instanceid if c.rhodecode_instanceid else ''}</a>
45 45 ${c.rhodecode_version} &copy; 2010-${h.datetime.today().year} by Marcin Kuzminski
46 46 </p>
47 47 </div>
48 48 </div>
49 49 </div>
50 50 <!-- END FOOTER -->
51 51
52 52 ### MAKO DEFS ###
53 53 <%def name="page_nav()">
54 54 ${self.menu()}
55 55 </%def>
56 56
57 57 <%def name="breadcrumbs()">
58 58 <div class="breadcrumbs">
59 59 ${self.breadcrumbs_links()}
60 60 </div>
61 61 </%def>
62 62
63 63 <%def name="usermenu()">
64 64 <div class="user-menu">
65 65 <div class="container">
66 66 <div class="gravatar" id="quick_login_link">
67 67 <img alt="gravatar" src="${h.gravatar_url(c.rhodecode_user.email,24)}" />
68 68 </div>
69 69 %if c.rhodecode_user.username != 'default' and c.unread_notifications != 0:
70 70 <div class="notifications">
71 71 <a id="notification_counter" href="${h.url('notifications')}">${c.unread_notifications}</a>
72 72 </div>
73 73 %endif
74 74 </div>
75 75 <div id="quick_login" style="display:none">
76 76 %if c.rhodecode_user.username == 'default':
77 77 <h4>${_('Login to your account')}</h4>
78 78 ${h.form(h.url('login_home',came_from=h.url.current()))}
79 79 <div class="form">
80 80 <div class="fields">
81 81 <div class="field">
82 82 <div class="label">
83 83 <label for="username">${_('Username')}:</label>
84 84 </div>
85 85 <div class="input">
86 86 ${h.text('username',class_='focus',size=40)}
87 87 </div>
88 88
89 89 </div>
90 90 <div class="field">
91 91 <div class="label">
92 92 <label for="password">${_('Password')}:</label>
93 93 </div>
94 94 <div class="input">
95 95 ${h.password('password',class_='focus',size=40)}
96 96 </div>
97 97
98 98 </div>
99 99 <div class="buttons">
100 100 <div class="password_forgoten">${h.link_to(_('Forgot password ?'),h.url('reset_password'))}</div>
101 101 <div class="register">
102 102 %if h.HasPermissionAny('hg.admin', 'hg.register.auto_activate', 'hg.register.manual_activate')():
103 103 ${h.link_to(_("Don't have an account ?"),h.url('register'))}
104 104 %endif
105 105 </div>
106 106 <div class="submit">
107 107 ${h.submit('sign_in',_('Log In'),class_="ui-btn xsmall")}
108 108 </div>
109 109 </div>
110 110 </div>
111 111 </div>
112 112 ${h.end_form()}
113 113 %else:
114 114 <div class="links_left">
115 115 <div class="full_name">${c.rhodecode_user.full_name_or_username}</div>
116 116 <div class="email">${c.rhodecode_user.email}</div>
117 117 <div class="big_gravatar"><img alt="gravatar" src="${h.gravatar_url(c.rhodecode_user.email,48)}" /></div>
118 118 <div class="inbox"><a href="${h.url('notifications')}">${_('Inbox')}: ${c.unread_notifications}</a></div>
119 119 </div>
120 120 <div class="links_right">
121 121 <ol class="links">
122 122 <li>${h.link_to(_(u'Home'),h.url('home'))}</li>
123 123 <li>${h.link_to(_(u'Journal'),h.url('journal'))}</li>
124 124 <li>${h.link_to(_(u'My account'),h.url('admin_settings_my_account'))}</li>
125 125 <li class="logout">${h.link_to(_(u'Log Out'),h.url('logout_home'))}</li>
126 126 </ol>
127 127 </div>
128 128 %endif
129 129 </div>
130 130 </div>
131 131 </%def>
132 132
133 133 <%def name="menu(current=None)">
134 134 <%
135 135 def is_current(selected):
136 136 if selected == current:
137 137 return h.literal('class="current"')
138 138 %>
139 139 %if current not in ['home','admin']:
140 140 ##REGULAR MENU
141 141 <ul id="quick">
142 142 <!-- repo switcher -->
143 143 <li>
144 144 <a class="menu_link" id="repo_switcher" title="${_('Switch repository')}" href="#">
145 145 <span class="icon">
146 146 <img src="${h.url('/images/icons/database.png')}" alt="${_('Products')}" />
147 147 </span>
148 148 <span>&darr;</span>
149 149 </a>
150 150 <ul id="repo_switcher_list" class="repo_switcher">
151 151 <li>
152 152 <a href="#">${_('loading...')}</a>
153 153 </li>
154 154 </ul>
155 155 </li>
156 156
157 157 <li ${is_current('summary')}>
158 158 <a class="menu_link" title="${_('Summary')}" href="${h.url('summary_home',repo_name=c.repo_name)}">
159 159 <span class="icon">
160 160 <img src="${h.url('/images/icons/clipboard_16.png')}" alt="${_('Summary')}" />
161 161 </span>
162 162 <span>${_('Summary')}</span>
163 163 </a>
164 164 </li>
165 165 <li ${is_current('changelog')}>
166 166 <a class="menu_link" title="${_('Changelog')}" href="${h.url('changelog_home',repo_name=c.repo_name)}">
167 167 <span class="icon">
168 168 <img src="${h.url('/images/icons/time.png')}" alt="${_('Changelog')}" />
169 169 </span>
170 170 <span>${_('Changelog')}</span>
171 171 </a>
172 172 </li>
173 173
174 174 <li ${is_current('switch_to')}>
175 175 <a class="menu_link" id="branch_tag_switcher" title="${_('Switch to')}" href="#">
176 176 <span class="icon">
177 177 <img src="${h.url('/images/icons/arrow_switch.png')}" alt="${_('Switch to')}" />
178 178 </span>
179 179 <span>${_('Switch to')}</span>
180 180 </a>
181 181 <ul id="switch_to_list" class="switch_to">
182 182 <li><a href="#">${_('loading...')}</a></li>
183 183 </ul>
184 184 </li>
185 185 <li ${is_current('files')}>
186 186 <a class="menu_link" title="${_('Files')}" href="${h.url('files_home',repo_name=c.repo_name)}">
187 187 <span class="icon">
188 188 <img src="${h.url('/images/icons/file.png')}" alt="${_('Files')}" />
189 189 </span>
190 190 <span>${_('Files')}</span>
191 191 </a>
192 192 </li>
193 193
194 194 <li ${is_current('options')}>
195 195 <a class="menu_link" title="${_('Options')}" href="#">
196 196 <span class="icon">
197 197 <img src="${h.url('/images/icons/table_gear.png')}" alt="${_('Admin')}" />
198 198 </span>
199 199 <span>${_('Options')}</span>
200 200 </a>
201 201 <ul>
202 202 %if h.HasRepoPermissionAll('repository.admin')(c.repo_name):
203 203 %if h.HasPermissionAll('hg.admin')('access settings on repository'):
204 204 <li>${h.link_to(_('settings'),h.url('edit_repo',repo_name=c.repo_name),class_='settings')}</li>
205 205 %else:
206 206 <li>${h.link_to(_('settings'),h.url('repo_settings_home',repo_name=c.repo_name),class_='settings')}</li>
207 207 %endif
208 208 %endif
209 209 <li>${h.link_to(_('fork'),h.url('repo_fork_home',repo_name=c.repo_name),class_='fork')}</li>
210 210 <li>${h.link_to(_('search'),h.url('search_repo',search_repo=c.repo_name),class_='search')}</li>
211 211
212 212 % if h.HasPermissionAll('hg.admin')('access admin main page'):
213 213 <li>
214 214 ${h.link_to(_('admin'),h.url('admin_home'),class_='admin')}
215 215 <%def name="admin_menu()">
216 216 <ul>
217 217 <li>${h.link_to(_('journal'),h.url('admin_home'),class_='journal')}</li>
218 218 <li>${h.link_to(_('repositories'),h.url('repos'),class_='repos')}</li>
219 219 <li>${h.link_to(_('repositories groups'),h.url('repos_groups'),class_='repos_groups')}</li>
220 220 <li>${h.link_to(_('users'),h.url('users'),class_='users')}</li>
221 221 <li>${h.link_to(_('users groups'),h.url('users_groups'),class_='groups')}</li>
222 222 <li>${h.link_to(_('permissions'),h.url('edit_permission',id='default'),class_='permissions')}</li>
223 223 <li>${h.link_to(_('ldap'),h.url('ldap_home'),class_='ldap')}</li>
224 224 <li class="last">${h.link_to(_('settings'),h.url('admin_settings'),class_='settings')}</li>
225 225 </ul>
226 226 </%def>
227 227
228 228 ${admin_menu()}
229 229 </li>
230 230 % endif
231 231 </ul>
232 232 </li>
233 233
234 234 <li>
235 235 <a class="menu_link" title="${_('Followers')}" href="${h.url('repo_followers_home',repo_name=c.repo_name)}">
236 236 <span class="icon_short">
237 237 <img src="${h.url('/images/icons/heart.png')}" alt="${_('Followers')}" />
238 238 </span>
239 239 <span id="current_followers_count" class="short">${c.repository_followers}</span>
240 240 </a>
241 241 </li>
242 242 <li>
243 243 <a class="menu_link" title="${_('Forks')}" href="${h.url('repo_forks_home',repo_name=c.repo_name)}">
244 244 <span class="icon_short">
245 245 <img src="${h.url('/images/icons/arrow_divide.png')}" alt="${_('Forks')}" />
246 246 </span>
247 247 <span class="short">${c.repository_forks}</span>
248 248 </a>
249 249 </li>
250 250 ${usermenu()}
251 251 </ul>
252 252 <script type="text/javascript">
253 253 YUE.on('repo_switcher','mouseover',function(){
254 254 function qfilter(){
255 255 var nodes = YUQ('ul#repo_switcher_list li a.repo_name');
256 256 var target = 'q_filter_rs';
257 257 var func = function(node){
258 258 return node.parentNode;
259 259 }
260 260 q_filter(target,nodes,func);
261 261 }
262 262 var loaded = YUD.hasClass('repo_switcher','loaded');
263 263 if(!loaded){
264 264 YUD.addClass('repo_switcher','loaded');
265 265 ypjax("${h.url('repo_switcher')}",'repo_switcher_list',
266 266 function(o){qfilter();},
267 267 function(o){YUD.removeClass('repo_switcher','loaded');}
268 268 ,null);
269 269 }
270 270 return false;
271 271 });
272 272
273 273 YUE.on('branch_tag_switcher','mouseover',function(){
274 274 var loaded = YUD.hasClass('branch_tag_switcher','loaded');
275 275 if(!loaded){
276 276 YUD.addClass('branch_tag_switcher','loaded');
277 277 ypjax("${h.url('branch_tag_switcher',repo_name=c.repo_name)}",'switch_to_list',
278 278 function(o){},
279 279 function(o){YUD.removeClass('branch_tag_switcher','loaded');}
280 280 ,null);
281 281 }
282 282 return false;
283 283 });
284 284 </script>
285 285 %else:
286 286 ##ROOT MENU
287 287 <ul id="quick">
288 288 <li>
289 289 <a class="menu_link" title="${_('Home')}" href="${h.url('home')}">
290 290 <span class="icon">
291 291 <img src="${h.url('/images/icons/home_16.png')}" alt="${_('Home')}" />
292 292 </span>
293 293 <span>${_('Home')}</span>
294 294 </a>
295 295 </li>
296 296 %if c.rhodecode_user.username != 'default':
297 297 <li>
298 298 <a class="menu_link" title="${_('Journal')}" href="${h.url('journal')}">
299 299 <span class="icon">
300 300 <img src="${h.url('/images/icons/book.png')}" alt="${_('Journal')}" />
301 301 </span>
302 302 <span>${_('Journal')}</span>
303 303 </a>
304 304 </li>
305 305 %else:
306 306 <li>
307 307 <a class="menu_link" title="${_('Public journal')}" href="${h.url('public_journal')}">
308 308 <span class="icon">
309 309 <img src="${h.url('/images/icons/book.png')}" alt="${_('Public journal')}" />
310 310 </span>
311 311 <span>${_('Public journal')}</span>
312 312 </a>
313 313 </li>
314 314 %endif
315 315 <li>
316 316 <a class="menu_link" title="${_('Search')}" href="${h.url('search')}">
317 317 <span class="icon">
318 318 <img src="${h.url('/images/icons/search_16.png')}" alt="${_('Search')}" />
319 319 </span>
320 320 <span>${_('Search')}</span>
321 321 </a>
322 322 </li>
323 323
324 324 %if h.HasPermissionAll('hg.admin')('access admin main page'):
325 325 <li ${is_current('admin')}>
326 326 <a class="menu_link" title="${_('Admin')}" href="${h.url('admin_home')}">
327 327 <span class="icon">
328 328 <img src="${h.url('/images/icons/cog_edit.png')}" alt="${_('Admin')}" />
329 329 </span>
330 330 <span>${_('Admin')}</span>
331 331 </a>
332 332 ${admin_menu()}
333 333 </li>
334 334 %endif
335 335 ${usermenu()}
336 336 </ul>
337 337 %endif
338 338 </%def>
@@ -1,698 +1,698 b''
1 1 <%inherit file="/base/base.html"/>
2 2
3 3 <%def name="title()">
4 4 ${c.repo_name} ${_('Summary')} - ${c.rhodecode_name}
5 5 </%def>
6 6
7 7 <%def name="breadcrumbs_links()">
8 8 ${h.link_to(u'Home',h.url('/'))}
9 9 &raquo;
10 10 ${h.link_to(c.dbrepo.just_name,h.url('summary_home',repo_name=c.repo_name))}
11 11 &raquo;
12 12 ${_('summary')}
13 13 </%def>
14 14
15 15 <%def name="page_nav()">
16 16 ${self.menu('summary')}
17 17 </%def>
18 18
19 19 <%def name="main()">
20 20 <%
21 21 summary = lambda n:{False:'summary-short'}.get(n)
22 22 %>
23 23 %if c.show_stats:
24 24 <div class="box box-left">
25 25 %else:
26 26 <div class="box">
27 27 %endif
28 28 <!-- box / title -->
29 29 <div class="title">
30 30 ${self.breadcrumbs()}
31 31 </div>
32 32 <!-- end box / title -->
33 33 <div class="form">
34 34 <div id="summary" class="fields">
35 35
36 36 <div class="field">
37 37 <div class="label-summary">
38 38 <label>${_('Name')}:</label>
39 39 </div>
40 40 <div class="input ${summary(c.show_stats)}">
41 41 <div style="float:right;padding:5px 0px 0px 5px">
42 42 %if c.rhodecode_user.username != 'default':
43 43 ${h.link_to(_('RSS'),h.url('rss_feed_home',repo_name=c.dbrepo.repo_name,api_key=c.rhodecode_user.api_key),class_='rss_icon')}
44 44 ${h.link_to(_('ATOM'),h.url('atom_feed_home',repo_name=c.dbrepo.repo_name,api_key=c.rhodecode_user.api_key),class_='atom_icon')}
45 45 %else:
46 46 ${h.link_to(_('RSS'),h.url('rss_feed_home',repo_name=c.dbrepo.repo_name),class_='rss_icon')}
47 47 ${h.link_to(_('ATOM'),h.url('atom_feed_home',repo_name=c.dbrepo.repo_name),class_='atom_icon')}
48 48 %endif
49 49 </div>
50 50 %if c.rhodecode_user.username != 'default':
51 51 %if c.following:
52 52 <span id="follow_toggle" class="following" title="${_('Stop following this repository')}"
53 53 onclick="javascript:toggleFollowingRepo(this,${c.dbrepo.repo_id},'${str(h.get_token())}')">
54 54 </span>
55 55 %else:
56 56 <span id="follow_toggle" class="follow" title="${_('Start following this repository')}"
57 57 onclick="javascript:toggleFollowingRepo(this,${c.dbrepo.repo_id},'${str(h.get_token())}')">
58 58 </span>
59 59 %endif
60 60 %endif:
61 61 ##REPO TYPE
62 62 %if h.is_hg(c.dbrepo):
63 63 <img style="margin-bottom:2px" class="icon" title="${_('Mercurial repository')}" alt="${_('Mercurial repository')}" src="${h.url('/images/icons/hgicon.png')}"/>
64 64 %endif
65 65 %if h.is_git(c.dbrepo):
66 66 <img style="margin-bottom:2px" class="icon" title="${_('Git repository')}" alt="${_('Git repository')}" src="${h.url('/images/icons/giticon.png')}"/>
67 67 %endif
68 68
69 69 ##PUBLIC/PRIVATE
70 70 %if c.dbrepo.private:
71 71 <img style="margin-bottom:2px" class="icon" title="${_('private repository')}" alt="${_('private repository')}" src="${h.url('/images/icons/lock.png')}"/>
72 72 %else:
73 73 <img style="margin-bottom:2px" class="icon" title="${_('public repository')}" alt="${_('public repository')}" src="${h.url('/images/icons/lock_open.png')}"/>
74 74 %endif
75 75
76 76 ##REPO NAME
77 77 <span class="repo_name" title="${_('Non changable ID %s') % c.dbrepo.repo_id}">${h.repo_link(c.dbrepo.groups_and_repo)}</span>
78 78
79 79 ##FORK
80 80 %if c.dbrepo.fork:
81 81 <div style="margin-top:5px;clear:both"">
82 82 <a href="${h.url('summary_home',repo_name=c.dbrepo.fork.repo_name)}"><img class="icon" alt="${_('public')}" title="${_('Fork of')} ${c.dbrepo.fork.repo_name}" src="${h.url('/images/icons/arrow_divide.png')}"/>
83 83 ${_('Fork of')} ${c.dbrepo.fork.repo_name}
84 84 </a>
85 85 </div>
86 86 %endif
87 87 ##REMOTE
88 88 %if c.dbrepo.clone_uri:
89 89 <div style="margin-top:5px;clear:both">
90 90 <a href="${h.url(str(h.hide_credentials(c.dbrepo.clone_uri)))}"><img class="icon" alt="${_('remote clone')}" title="${_('Clone from')} ${h.hide_credentials(c.dbrepo.clone_uri)}" src="${h.url('/images/icons/connect.png')}"/>
91 91 ${_('Clone from')} ${h.hide_credentials(c.dbrepo.clone_uri)}
92 92 </a>
93 93 </div>
94 94 %endif
95 95 </div>
96 96 </div>
97 97
98 98 <div class="field">
99 99 <div class="label-summary">
100 100 <label>${_('Description')}:</label>
101 101 </div>
102 102 <div class="input ${summary(c.show_stats)} desc">${h.urlify_text(c.dbrepo.description)}</div>
103 103 </div>
104 104
105 105 <div class="field">
106 106 <div class="label-summary">
107 107 <label>${_('Contact')}:</label>
108 108 </div>
109 109 <div class="input ${summary(c.show_stats)}">
110 110 <div class="gravatar">
111 111 <img alt="gravatar" src="${h.gravatar_url(c.dbrepo.user.email)}"/>
112 112 </div>
113 113 ${_('Username')}: ${c.dbrepo.user.username}<br/>
114 114 ${_('Name')}: ${c.dbrepo.user.name} ${c.dbrepo.user.lastname}<br/>
115 115 ${_('Email')}: <a href="mailto:${c.dbrepo.user.email}">${c.dbrepo.user.email}</a>
116 116 </div>
117 117 </div>
118 118
119 119 <div class="field">
120 120 <div class="label-summary">
121 121 <label>${_('Clone url')}:</label>
122 122 </div>
123 123 <div class="input ${summary(c.show_stats)}">
124 124 <div style="display:none" id="clone_by_name" class="ui-btn clone">${_('Show by Name')}</div>
125 125 <div id="clone_by_id" class="ui-btn clone">${_('Show by ID')}</div>
126 126 <input style="width:80%;margin-left:105px" type="text" id="clone_url" readonly="readonly" value="${c.clone_repo_url}"/>
127 127 <input style="display:none;width:80%;margin-left:105px" type="text" id="clone_url_id" readonly="readonly" value="${c.clone_repo_url_id}"/>
128 128 </div>
129 129 </div>
130 130
131 131 <div class="field">
132 132 <div class="label-summary">
133 133 <label>${_('Trending files')}:</label>
134 134 </div>
135 135 <div class="input ${summary(c.show_stats)}">
136 136 %if c.show_stats:
137 137 <div id="lang_stats"></div>
138 138 %else:
139 139 ${_('Statistics are disabled for this repository')}
140 140 %if h.HasPermissionAll('hg.admin')('enable stats on from summary'):
141 141 ${h.link_to(_('enable'),h.url('edit_repo',repo_name=c.repo_name),class_="ui-btn")}
142 142 %endif
143 143 %endif
144 144 </div>
145 145 </div>
146 146
147 147 <div class="field">
148 148 <div class="label-summary">
149 149 <label>${_('Download')}:</label>
150 150 </div>
151 151 <div class="input ${summary(c.show_stats)}">
152 152 %if len(c.rhodecode_repo.revisions) == 0:
153 153 ${_('There are no downloads yet')}
154 154 %elif c.enable_downloads is False:
155 155 ${_('Downloads are disabled for this repository')}
156 156 %if h.HasPermissionAll('hg.admin')('enable downloads on from summary'):
157 157 ${h.link_to(_('enable'),h.url('edit_repo',repo_name=c.repo_name),class_="ui-btn")}
158 158 %endif
159 159 %else:
160 160 ${h.select('download_options',c.rhodecode_repo.get_changeset().raw_id,c.download_options)}
161 161 <span id="${'zip_link'}">${h.link_to('Download as zip',h.url('files_archive_home',repo_name=c.dbrepo.repo_name,fname='tip.zip'),class_="archive_icon ui-btn")}</span>
162 162 <span style="vertical-align: bottom">
163 163 <input id="archive_subrepos" type="checkbox" name="subrepos" />
164 164 <label for="archive_subrepos" class="tooltip" title="${_('Check this to download archive with subrepos')}" >${_('with subrepos')}</label>
165 165 </span>
166 166 %endif
167 167 </div>
168 168 </div>
169 169 </div>
170 170 </div>
171 171 </div>
172 172
173 173 %if c.show_stats:
174 174 <div class="box box-right" style="min-height:455px">
175 175 <!-- box / title -->
176 176 <div class="title">
177 177 <h5>${_('Commit activity by day / author')}</h5>
178 178 </div>
179 179
180 180 <div class="graph">
181 181 <div style="padding:0 10px 10px 17px;">
182 182 %if c.no_data:
183 183 ${c.no_data_msg}
184 184 %if h.HasPermissionAll('hg.admin')('enable stats on from summary'):
185 185 ${h.link_to(_('enable'),h.url('edit_repo',repo_name=c.repo_name),class_="ui-btn")}
186 186 %endif
187 187 %else:
188 188 ${_('Stats gathered: ')} ${c.stats_percentage}%
189 189 %endif
190 190 </div>
191 191 <div id="commit_history" style="width:450px;height:300px;float:left"></div>
192 192 <div style="clear: both;height: 10px"></div>
193 193 <div id="overview" style="width:450px;height:100px;float:left"></div>
194 194
195 195 <div id="legend_data" style="clear:both;margin-top:10px;">
196 196 <div id="legend_container"></div>
197 197 <div id="legend_choices">
198 198 <table id="legend_choices_tables" class="noborder" style="font-size:smaller;color:#545454"></table>
199 199 </div>
200 200 </div>
201 201 </div>
202 202 </div>
203 203 %endif
204 204
205 205 <div class="box">
206 206 <div class="title">
207 207 <div class="breadcrumbs">
208 208 %if c.repo_changesets:
209 209 ${h.link_to(_('Shortlog'),h.url('shortlog_home',repo_name=c.repo_name))}
210 210 %else:
211 211 ${_('Quick start')}
212 212 %endif
213 213 </div>
214 214 </div>
215 215 <div class="table">
216 216 <div id="shortlog_data">
217 217 <%include file='../shortlog/shortlog_data.html'/>
218 218 </div>
219 219 </div>
220 220 </div>
221 221
222 222 %if c.readme_data:
223 223 <div class="box" style="background-color: #FAFAFA">
224 <div class="title">
224 <div id="readme" class="title">
225 225 <div class="breadcrumbs"><a href="${h.url('files_home',repo_name=c.repo_name,revision='tip',f_path=c.readme_file)}">${c.readme_file}</a></div>
226 226 </div>
227 227 <div class="readme">
228 228 <div class="readme_box">
229 229 ${c.readme_data|n}
230 230 </div>
231 231 </div>
232 232 </div>
233 233 %endif
234 234
235 235 <script type="text/javascript">
236 236 var clone_url = 'clone_url';
237 237 YUE.on(clone_url,'click',function(e){
238 238 if(YUD.hasClass(clone_url,'selected')){
239 239 return
240 240 }
241 241 else{
242 242 YUD.addClass(clone_url,'selected');
243 243 YUD.get(clone_url).select();
244 244 }
245 245 })
246 246
247 247 YUE.on('clone_by_name','click',function(e){
248 248 // show url by name and hide name button
249 249 YUD.setStyle('clone_url','display','');
250 250 YUD.setStyle('clone_by_name','display','none');
251 251
252 252 // hide url by id and show name button
253 253 YUD.setStyle('clone_by_id','display','');
254 254 YUD.setStyle('clone_url_id','display','none');
255 255
256 256 })
257 257 YUE.on('clone_by_id','click',function(e){
258 258
259 259 // show url by id and hide id button
260 260 YUD.setStyle('clone_by_id','display','none');
261 261 YUD.setStyle('clone_url_id','display','');
262 262
263 263 // hide url by name and show id button
264 264 YUD.setStyle('clone_by_name','display','');
265 265 YUD.setStyle('clone_url','display','none');
266 266 })
267 267
268 268
269 269 var tmpl_links = {};
270 270 %for cnt,archive in enumerate(c.rhodecode_repo._get_archives()):
271 271 tmpl_links["${archive['type']}"] = '${h.link_to('__NAME__', h.url('files_archive_home',repo_name=c.dbrepo.repo_name, fname='__CS__'+archive['extension'],subrepos='__SUB__'),class_='archive_icon ui-btn')}';
272 272 %endfor
273 273
274 274 YUE.on(['download_options','archive_subrepos'],'change',function(e){
275 275 var sm = YUD.get('download_options');
276 276 var new_cs = sm.options[sm.selectedIndex];
277 277
278 278 for(k in tmpl_links){
279 279 var s = YUD.get(k+'_link');
280 280 if(s){
281 281 var title_tmpl = "${_('Download %s as %s') % ('__CS_NAME__','__CS_EXT__')}";
282 282 title_tmpl= title_tmpl.replace('__CS_NAME__',new_cs.text);
283 283 title_tmpl = title_tmpl.replace('__CS_EXT__',k);
284 284
285 285 var url = tmpl_links[k].replace('__CS__',new_cs.value);
286 286 var subrepos = YUD.get('archive_subrepos').checked;
287 287 url = url.replace('__SUB__',subrepos);
288 288 url = url.replace('__NAME__',title_tmpl);
289 289 s.innerHTML = url
290 290 }
291 291 }
292 292 });
293 293 </script>
294 294 %if c.show_stats:
295 295 <script type="text/javascript">
296 296 var data = ${c.trending_languages|n};
297 297 var total = 0;
298 298 var no_data = true;
299 299 var tbl = document.createElement('table');
300 300 tbl.setAttribute('class','trending_language_tbl');
301 301 var cnt = 0;
302 302 for (var i=0;i<data.length;i++){
303 303 total+= data[i][1].count;
304 304 }
305 305 for (var i=0;i<data.length;i++){
306 306 cnt += 1;
307 307 no_data = false;
308 308
309 309 var hide = cnt>2;
310 310 var tr = document.createElement('tr');
311 311 if (hide){
312 312 tr.setAttribute('style','display:none');
313 313 tr.setAttribute('class','stats_hidden');
314 314 }
315 315 var k = data[i][0];
316 316 var obj = data[i][1];
317 317 var percentage = Math.round((obj.count/total*100),2);
318 318
319 319 var td1 = document.createElement('td');
320 320 td1.width = 150;
321 321 var trending_language_label = document.createElement('div');
322 322 trending_language_label.innerHTML = obj.desc+" ("+k+")";
323 323 td1.appendChild(trending_language_label);
324 324
325 325 var td2 = document.createElement('td');
326 326 td2.setAttribute('style','padding-right:14px !important');
327 327 var trending_language = document.createElement('div');
328 328 var nr_files = obj.count+" ${_('files')}";
329 329
330 330 trending_language.title = k+" "+nr_files;
331 331
332 332 if (percentage>22){
333 333 trending_language.innerHTML = "<b style='font-size:0.8em'>"+percentage+"% "+nr_files+ "</b>";
334 334 }
335 335 else{
336 336 trending_language.innerHTML = "<b style='font-size:0.8em'>"+percentage+"%</b>";
337 337 }
338 338
339 339 trending_language.setAttribute("class", 'trending_language top-right-rounded-corner bottom-right-rounded-corner');
340 340 trending_language.style.width=percentage+"%";
341 341 td2.appendChild(trending_language);
342 342
343 343 tr.appendChild(td1);
344 344 tr.appendChild(td2);
345 345 tbl.appendChild(tr);
346 346 if(cnt == 3){
347 347 var show_more = document.createElement('tr');
348 348 var td = document.createElement('td');
349 349 lnk = document.createElement('a');
350 350
351 351 lnk.href='#';
352 352 lnk.innerHTML = "${_('show more')}";
353 353 lnk.id='code_stats_show_more';
354 354 td.appendChild(lnk);
355 355
356 356 show_more.appendChild(td);
357 357 show_more.appendChild(document.createElement('td'));
358 358 tbl.appendChild(show_more);
359 359 }
360 360
361 361 }
362 362
363 363 YUD.get('lang_stats').appendChild(tbl);
364 364 YUE.on('code_stats_show_more','click',function(){
365 365 l = YUD.getElementsByClassName('stats_hidden')
366 366 for (e in l){
367 367 YUD.setStyle(l[e],'display','');
368 368 };
369 369 YUD.setStyle(YUD.get('code_stats_show_more'),
370 370 'display','none');
371 371 });
372 372 </script>
373 373 <script type="text/javascript">
374 374 /**
375 375 * Plots summary graph
376 376 *
377 377 * @class SummaryPlot
378 378 * @param {from} initial from for detailed graph
379 379 * @param {to} initial to for detailed graph
380 380 * @param {dataset}
381 381 * @param {overview_dataset}
382 382 */
383 383 function SummaryPlot(from,to,dataset,overview_dataset) {
384 384 var initial_ranges = {
385 385 "xaxis":{
386 386 "from":from,
387 387 "to":to,
388 388 },
389 389 };
390 390 var dataset = dataset;
391 391 var overview_dataset = [overview_dataset];
392 392 var choiceContainer = YUD.get("legend_choices");
393 393 var choiceContainerTable = YUD.get("legend_choices_tables");
394 394 var plotContainer = YUD.get('commit_history');
395 395 var overviewContainer = YUD.get('overview');
396 396
397 397 var plot_options = {
398 398 bars: {show:true,align:'center',lineWidth:4},
399 399 legend: {show:true, container:"legend_container"},
400 400 points: {show:true,radius:0,fill:false},
401 401 yaxis: {tickDecimals:0,},
402 402 xaxis: {
403 403 mode: "time",
404 404 timeformat: "%d/%m",
405 405 min:from,
406 406 max:to,
407 407 },
408 408 grid: {
409 409 hoverable: true,
410 410 clickable: true,
411 411 autoHighlight:true,
412 412 color: "#999"
413 413 },
414 414 //selection: {mode: "x"}
415 415 };
416 416 var overview_options = {
417 417 legend:{show:false},
418 418 bars: {show:true,barWidth: 2,},
419 419 shadowSize: 0,
420 420 xaxis: {mode: "time", timeformat: "%d/%m/%y",},
421 421 yaxis: {ticks: 3, min: 0,tickDecimals:0,},
422 422 grid: {color: "#999",},
423 423 selection: {mode: "x"}
424 424 };
425 425
426 426 /**
427 427 *get dummy data needed in few places
428 428 */
429 429 function getDummyData(label){
430 430 return {"label":label,
431 431 "data":[{"time":0,
432 432 "commits":0,
433 433 "added":0,
434 434 "changed":0,
435 435 "removed":0,
436 436 }],
437 437 "schema":["commits"],
438 438 "color":'#ffffff',
439 439 }
440 440 }
441 441
442 442 /**
443 443 * generate checkboxes accordindly to data
444 444 * @param keys
445 445 * @returns
446 446 */
447 447 function generateCheckboxes(data) {
448 448 //append checkboxes
449 449 var i = 0;
450 450 choiceContainerTable.innerHTML = '';
451 451 for(var pos in data) {
452 452
453 453 data[pos].color = i;
454 454 i++;
455 455 if(data[pos].label != ''){
456 456 choiceContainerTable.innerHTML +=
457 457 '<tr><td><input type="checkbox" id="id_user_{0}" name="{0}" checked="checked" /> \
458 458 <label for="id_user_{0}">{0}</label></td></tr>'.format(data[pos].label);
459 459 }
460 460 }
461 461 }
462 462
463 463 /**
464 464 * ToolTip show
465 465 */
466 466 function showTooltip(x, y, contents) {
467 467 var div=document.getElementById('tooltip');
468 468 if(!div) {
469 469 div = document.createElement('div');
470 470 div.id="tooltip";
471 471 div.style.position="absolute";
472 472 div.style.border='1px solid #fdd';
473 473 div.style.padding='2px';
474 474 div.style.backgroundColor='#fee';
475 475 document.body.appendChild(div);
476 476 }
477 477 YUD.setStyle(div, 'opacity', 0);
478 478 div.innerHTML = contents;
479 479 div.style.top=(y + 5) + "px";
480 480 div.style.left=(x + 5) + "px";
481 481
482 482 var anim = new YAHOO.util.Anim(div, {opacity: {to: 0.8}}, 0.2);
483 483 anim.animate();
484 484 }
485 485
486 486 /**
487 487 * This function will detect if selected period has some changesets
488 488 for this user if it does this data is then pushed for displaying
489 489 Additionally it will only display users that are selected by the checkbox
490 490 */
491 491 function getDataAccordingToRanges(ranges) {
492 492
493 493 var data = [];
494 494 var new_dataset = {};
495 495 var keys = [];
496 496 var max_commits = 0;
497 497 for(var key in dataset){
498 498
499 499 for(var ds in dataset[key].data){
500 500 commit_data = dataset[key].data[ds];
501 501 if (commit_data.time >= ranges.xaxis.from && commit_data.time <= ranges.xaxis.to){
502 502
503 503 if(new_dataset[key] === undefined){
504 504 new_dataset[key] = {data:[],schema:["commits"],label:key};
505 505 }
506 506 new_dataset[key].data.push(commit_data);
507 507 }
508 508 }
509 509 if (new_dataset[key] !== undefined){
510 510 data.push(new_dataset[key]);
511 511 }
512 512 }
513 513
514 514 if (data.length > 0){
515 515 return data;
516 516 }
517 517 else{
518 518 //just return dummy data for graph to plot itself
519 519 return [getDummyData('')];
520 520 }
521 521 }
522 522
523 523 /**
524 524 * redraw using new checkbox data
525 525 */
526 526 function plotchoiced(e,args){
527 527 var cur_data = args[0];
528 528 var cur_ranges = args[1];
529 529
530 530 var new_data = [];
531 531 var inputs = choiceContainer.getElementsByTagName("input");
532 532
533 533 //show only checked labels
534 534 for(var i=0; i<inputs.length; i++) {
535 535 var checkbox_key = inputs[i].name;
536 536
537 537 if(inputs[i].checked){
538 538 for(var d in cur_data){
539 539 if(cur_data[d].label == checkbox_key){
540 540 new_data.push(cur_data[d]);
541 541 }
542 542 }
543 543 }
544 544 else{
545 545 //push dummy data to not hide the label
546 546 new_data.push(getDummyData(checkbox_key));
547 547 }
548 548 }
549 549
550 550 var new_options = YAHOO.lang.merge(plot_options, {
551 551 xaxis: {
552 552 min: cur_ranges.xaxis.from,
553 553 max: cur_ranges.xaxis.to,
554 554 mode:"time",
555 555 timeformat: "%d/%m",
556 556 },
557 557 });
558 558 if (!new_data){
559 559 new_data = [[0,1]];
560 560 }
561 561 // do the zooming
562 562 plot = YAHOO.widget.Flot(plotContainer, new_data, new_options);
563 563
564 564 plot.subscribe("plotselected", plotselected);
565 565
566 566 //resubscribe plothover
567 567 plot.subscribe("plothover", plothover);
568 568
569 569 // don't fire event on the overview to prevent eternal loop
570 570 overview.setSelection(cur_ranges, true);
571 571
572 572 }
573 573
574 574 /**
575 575 * plot only selected items from overview
576 576 * @param ranges
577 577 * @returns
578 578 */
579 579 function plotselected(ranges,cur_data) {
580 580 //updates the data for new plot
581 581 var data = getDataAccordingToRanges(ranges);
582 582 generateCheckboxes(data);
583 583
584 584 var new_options = YAHOO.lang.merge(plot_options, {
585 585 xaxis: {
586 586 min: ranges.xaxis.from,
587 587 max: ranges.xaxis.to,
588 588 mode:"time",
589 589 timeformat: "%d/%m",
590 590 },
591 591 });
592 592 // do the zooming
593 593 plot = YAHOO.widget.Flot(plotContainer, data, new_options);
594 594
595 595 plot.subscribe("plotselected", plotselected);
596 596
597 597 //resubscribe plothover
598 598 plot.subscribe("plothover", plothover);
599 599
600 600 // don't fire event on the overview to prevent eternal loop
601 601 overview.setSelection(ranges, true);
602 602
603 603 //resubscribe choiced
604 604 YUE.on(choiceContainer.getElementsByTagName("input"), "click", plotchoiced, [data, ranges]);
605 605 }
606 606
607 607 var previousPoint = null;
608 608
609 609 function plothover(o) {
610 610 var pos = o.pos;
611 611 var item = o.item;
612 612
613 613 //YUD.get("x").innerHTML = pos.x.toFixed(2);
614 614 //YUD.get("y").innerHTML = pos.y.toFixed(2);
615 615 if (item) {
616 616 if (previousPoint != item.datapoint) {
617 617 previousPoint = item.datapoint;
618 618
619 619 var tooltip = YUD.get("tooltip");
620 620 if(tooltip) {
621 621 tooltip.parentNode.removeChild(tooltip);
622 622 }
623 623 var x = item.datapoint.x.toFixed(2);
624 624 var y = item.datapoint.y.toFixed(2);
625 625
626 626 if (!item.series.label){
627 627 item.series.label = 'commits';
628 628 }
629 629 var d = new Date(x*1000);
630 630 var fd = d.toDateString()
631 631 var nr_commits = parseInt(y);
632 632
633 633 var cur_data = dataset[item.series.label].data[item.dataIndex];
634 634 var added = cur_data.added;
635 635 var changed = cur_data.changed;
636 636 var removed = cur_data.removed;
637 637
638 638 var nr_commits_suffix = " ${_('commits')} ";
639 639 var added_suffix = " ${_('files added')} ";
640 640 var changed_suffix = " ${_('files changed')} ";
641 641 var removed_suffix = " ${_('files removed')} ";
642 642
643 643
644 644 if(nr_commits == 1){nr_commits_suffix = " ${_('commit')} ";}
645 645 if(added==1){added_suffix=" ${_('file added')} ";}
646 646 if(changed==1){changed_suffix=" ${_('file changed')} ";}
647 647 if(removed==1){removed_suffix=" ${_('file removed')} ";}
648 648
649 649 showTooltip(item.pageX, item.pageY, item.series.label + " on " + fd
650 650 +'<br/>'+
651 651 nr_commits + nr_commits_suffix+'<br/>'+
652 652 added + added_suffix +'<br/>'+
653 653 changed + changed_suffix + '<br/>'+
654 654 removed + removed_suffix + '<br/>');
655 655 }
656 656 }
657 657 else {
658 658 var tooltip = YUD.get("tooltip");
659 659
660 660 if(tooltip) {
661 661 tooltip.parentNode.removeChild(tooltip);
662 662 }
663 663 previousPoint = null;
664 664 }
665 665 }
666 666
667 667 /**
668 668 * MAIN EXECUTION
669 669 */
670 670
671 671 var data = getDataAccordingToRanges(initial_ranges);
672 672 generateCheckboxes(data);
673 673
674 674 //main plot
675 675 var plot = YAHOO.widget.Flot(plotContainer,data,plot_options);
676 676
677 677 //overview
678 678 var overview = YAHOO.widget.Flot(overviewContainer,
679 679 overview_dataset, overview_options);
680 680
681 681 //show initial selection on overview
682 682 overview.setSelection(initial_ranges);
683 683
684 684 plot.subscribe("plotselected", plotselected);
685 685 plot.subscribe("plothover", plothover)
686 686
687 687 overview.subscribe("plotselected", function (ranges) {
688 688 plot.setSelection(ranges);
689 689 });
690 690
691 691 // user choices on overview
692 692 YUE.on(choiceContainer.getElementsByTagName("input"), "click", plotchoiced, [data, initial_ranges]);
693 693 }
694 694 SummaryPlot(${c.ts_min},${c.ts_max},${c.commit_data|n},${c.overview_data|n});
695 695 </script>
696 696 %endif
697 697
698 698 </%def>
General Comments 0
You need to be logged in to leave comments. Login now