##// END OF EJS Templates
Added option to ini files for controlling full cache of VCS instances....
marcink -
r3025:f61adead beta
parent child Browse files
Show More
@@ -1,424 +1,425 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 use = egg:waitress#main
43 43 host = 0.0.0.0
44 44 port = 5000
45 45
46 46 [filter:proxy-prefix]
47 47 # prefix middleware for rc
48 48 use = egg:PasteDeploy#prefix
49 49 prefix = /<your-prefix>
50 50
51 51 [app:main]
52 52 use = egg:rhodecode
53 53 #filter-with = proxy-prefix
54 54 full_stack = true
55 55 static_files = true
56 56 # Optional Languages
57 57 # en, fr, ja, pt_BR, zh_CN, zh_TW
58 58 lang = en
59 59 cache_dir = %(here)s/data
60 60 index_dir = %(here)s/data/index
61 61 app_instance_uuid = rc-develop
62 62 cut_off_limit = 256000
63 vcs_full_cache = True
63 64 force_https = false
64 65 commit_parse_limit = 25
65 66 use_gravatar = true
66 67
67 68 ## alternative_gravatar_url allows you to use your own avatar server application
68 69 ## the following parts of the URL will be replaced
69 70 ## {email} user email
70 71 ## {md5email} md5 hash of the user email (like at gravatar.com)
71 72 ## {size} size of the image that is expected from the server application
72 73 ## {scheme} http/https from RhodeCode server
73 74 ## {netloc} network location from RhodeCode server
74 75 #alternative_gravatar_url = http://myavatarserver.com/getbyemail/{email}/{size}
75 76 #alternative_gravatar_url = http://myavatarserver.com/getbymd5/{md5email}?s={size}
76 77
77 78 container_auth_enabled = false
78 79 proxypass_auth_enabled = false
79 80 ## default encoding used to convert from and to unicode
80 81 ## can be also a comma seperated list of encoding in case of mixed encodings
81 82 default_encoding = utf8
82 83
83 84 ## overwrite schema of clone url
84 85 ## available vars:
85 86 ## scheme - http/https
86 87 ## user - current user
87 88 ## pass - password
88 89 ## netloc - network location
89 90 ## path - usually repo_name
90 91
91 92 #clone_uri = {scheme}://{user}{pass}{netloc}{path}
92 93
93 94 ## issue tracking mapping for commits messages
94 95 ## comment out issue_pat, issue_server, issue_prefix to enable
95 96
96 97 ## pattern to get the issues from commit messages
97 98 ## default one used here is #<numbers> with a regex passive group for `#`
98 99 ## {id} will be all groups matched from this pattern
99 100
100 101 issue_pat = (?:\s*#)(\d+)
101 102
102 103 ## server url to the issue, each {id} will be replaced with match
103 104 ## fetched from the regex and {repo} is replaced with full repository name
104 105 ## including groups {repo_name} is replaced with just name of repo
105 106
106 107 issue_server_link = https://myissueserver.com/{repo}/issue/{id}
107 108
108 109 ## prefix to add to link to indicate it's an url
109 110 ## #314 will be replaced by <issue_prefix><id>
110 111
111 112 issue_prefix = #
112 113
113 114 ## issue_pat, issue_server_link, issue_prefix can have suffixes to specify
114 115 ## multiple patterns, to other issues server, wiki or others
115 116 ## below an example how to create a wiki pattern
116 117 # #wiki-some-id -> https://mywiki.com/some-id
117 118
118 119 #issue_pat_wiki = (?:wiki-)(.+)
119 120 #issue_server_link_wiki = https://mywiki.com/{id}
120 121 #issue_prefix_wiki = WIKI-
121 122
122 123
123 124 ## instance-id prefix
124 125 ## a prefix key for this instance used for cache invalidation when running
125 126 ## multiple instances of rhodecode, make sure it's globally unique for
126 127 ## all running rhodecode instances. Leave empty if you don't use it
127 128 instance_id =
128 129
129 130 ## alternative return HTTP header for failed authentication. Default HTTP
130 131 ## response is 401 HTTPUnauthorized. Currently HG clients have troubles with
131 132 ## handling that. Set this variable to 403 to return HTTPForbidden
132 133 auth_ret_code =
133 134
134 135 ####################################
135 136 ### CELERY CONFIG ####
136 137 ####################################
137 138 use_celery = false
138 139 broker.host = localhost
139 140 broker.vhost = rabbitmqhost
140 141 broker.port = 5672
141 142 broker.user = rabbitmq
142 143 broker.password = qweqwe
143 144
144 145 celery.imports = rhodecode.lib.celerylib.tasks
145 146
146 147 celery.result.backend = amqp
147 148 celery.result.dburi = amqp://
148 149 celery.result.serialier = json
149 150
150 151 #celery.send.task.error.emails = true
151 152 #celery.amqp.task.result.expires = 18000
152 153
153 154 celeryd.concurrency = 2
154 155 #celeryd.log.file = celeryd.log
155 156 celeryd.log.level = debug
156 157 celeryd.max.tasks.per.child = 1
157 158
158 159 #tasks will never be sent to the queue, but executed locally instead.
159 160 celery.always.eager = false
160 161
161 162 ####################################
162 163 ### BEAKER CACHE ####
163 164 ####################################
164 165 beaker.cache.data_dir=%(here)s/data/cache/data
165 166 beaker.cache.lock_dir=%(here)s/data/cache/lock
166 167
167 168 beaker.cache.regions=super_short_term,short_term,long_term,sql_cache_short,sql_cache_med,sql_cache_long
168 169
169 170 beaker.cache.super_short_term.type=memory
170 171 beaker.cache.super_short_term.expire=10
171 172 beaker.cache.super_short_term.key_length = 256
172 173
173 174 beaker.cache.short_term.type=memory
174 175 beaker.cache.short_term.expire=60
175 176 beaker.cache.short_term.key_length = 256
176 177
177 178 beaker.cache.long_term.type=memory
178 179 beaker.cache.long_term.expire=36000
179 180 beaker.cache.long_term.key_length = 256
180 181
181 182 beaker.cache.sql_cache_short.type=memory
182 183 beaker.cache.sql_cache_short.expire=10
183 184 beaker.cache.sql_cache_short.key_length = 256
184 185
185 186 beaker.cache.sql_cache_med.type=memory
186 187 beaker.cache.sql_cache_med.expire=360
187 188 beaker.cache.sql_cache_med.key_length = 256
188 189
189 190 beaker.cache.sql_cache_long.type=file
190 191 beaker.cache.sql_cache_long.expire=3600
191 192 beaker.cache.sql_cache_long.key_length = 256
192 193
193 194 ####################################
194 195 ### BEAKER SESSION ####
195 196 ####################################
196 197 ## Type of storage used for the session, current types are
197 198 ## dbm, file, memcached, database, and memory.
198 199 ## The storage uses the Container API
199 200 ## that is also used by the cache system.
200 201
201 202 ## db session ##
202 203 #beaker.session.type = ext:database
203 204 #beaker.session.sa.url = postgresql://postgres:qwe@localhost/rhodecode
204 205 #beaker.session.table_name = db_session
205 206
206 207 ## encrypted cookie client side session, good for many instances ##
207 208 #beaker.session.type = cookie
208 209
209 210 ## file based cookies (default) ##
210 211 #beaker.session.type = file
211 212
212 213
213 214 beaker.session.key = rhodecode
214 215 ## secure cookie requires AES python libraries ##
215 216 #beaker.session.encrypt_key = g654dcno0-9873jhgfreyu
216 217 #beaker.session.validate_key = 9712sds2212c--zxc123
217 218 ## sets session as invalid if it haven't been accessed for given amount of time
218 219 beaker.session.timeout = 2592000
219 220 beaker.session.httponly = true
220 221 #beaker.session.cookie_path = /<your-prefix>
221 222
222 223 ## uncomment for https secure cookie ##
223 224 beaker.session.secure = false
224 225
225 226 ## auto save the session to not to use .save() ##
226 227 beaker.session.auto = False
227 228
228 229 ## default cookie expiration time in seconds `true` expire at browser close ##
229 230 #beaker.session.cookie_expires = 3600
230 231
231 232
232 233 ############################
233 234 ## ERROR HANDLING SYSTEMS ##
234 235 ############################
235 236
236 237 ####################
237 238 ### [errormator] ###
238 239 ####################
239 240
240 241 # Errormator is tailored to work with RhodeCode, see
241 242 # http://errormator.com for details how to obtain an account
242 243 # you must install python package `errormator_client` to make it work
243 244
244 245 # errormator enabled
245 246 errormator = true
246 247
247 248 errormator.server_url = https://api.errormator.com
248 249 errormator.api_key = YOUR_API_KEY
249 250
250 251 # TWEAK AMOUNT OF INFO SENT HERE
251 252
252 253 # enables 404 error logging (default False)
253 254 errormator.report_404 = false
254 255
255 256 # time in seconds after request is considered being slow (default 1)
256 257 errormator.slow_request_time = 1
257 258
258 259 # record slow requests in application
259 260 # (needs to be enabled for slow datastore recording and time tracking)
260 261 errormator.slow_requests = true
261 262
262 263 # enable hooking to application loggers
263 264 # errormator.logging = true
264 265
265 266 # minimum log level for log capture
266 267 # errormator.logging.level = WARNING
267 268
268 269 # send logs only from erroneous/slow requests
269 270 # (saves API quota for intensive logging)
270 271 errormator.logging_on_error = false
271 272
272 273 # list of additonal keywords that should be grabbed from environ object
273 274 # can be string with comma separated list of words in lowercase
274 275 # (by default client will always send following info:
275 276 # 'REMOTE_USER', 'REMOTE_ADDR', 'SERVER_NAME', 'CONTENT_TYPE' + all keys that
276 277 # start with HTTP* this list be extended with additional keywords here
277 278 errormator.environ_keys_whitelist =
278 279
279 280
280 281 # list of keywords that should be blanked from request object
281 282 # can be string with comma separated list of words in lowercase
282 283 # (by default client will always blank keys that contain following words
283 284 # 'password', 'passwd', 'pwd', 'auth_tkt', 'secret', 'csrf'
284 285 # this list be extended with additional keywords set here
285 286 errormator.request_keys_blacklist =
286 287
287 288
288 289 # list of namespaces that should be ignores when gathering log entries
289 290 # can be string with comma separated list of namespaces
290 291 # (by default the client ignores own entries: errormator_client.client)
291 292 errormator.log_namespace_blacklist =
292 293
293 294
294 295 ################
295 296 ### [sentry] ###
296 297 ################
297 298
298 299 # sentry is a alternative open source error aggregator
299 300 # you must install python packages `sentry` and `raven` to enable
300 301
301 302 sentry.dsn = YOUR_DNS
302 303 sentry.servers =
303 304 sentry.name =
304 305 sentry.key =
305 306 sentry.public_key =
306 307 sentry.secret_key =
307 308 sentry.project =
308 309 sentry.site =
309 310 sentry.include_paths =
310 311 sentry.exclude_paths =
311 312
312 313
313 314 ################################################################################
314 315 ## WARNING: *THE LINE BELOW MUST BE UNCOMMENTED ON A PRODUCTION ENVIRONMENT* ##
315 316 ## Debug mode will enable the interactive debugging tool, allowing ANYONE to ##
316 317 ## execute malicious code after an exception is raised. ##
317 318 ################################################################################
318 319 #set debug = false
319 320
320 321 ##################################
321 322 ### LOGVIEW CONFIG ###
322 323 ##################################
323 324 logview.sqlalchemy = #faa
324 325 logview.pylons.templating = #bfb
325 326 logview.pylons.util = #eee
326 327
327 328 #########################################################
328 329 ### DB CONFIGS - EACH DB WILL HAVE IT'S OWN CONFIG ###
329 330 #########################################################
330 331 #sqlalchemy.db1.url = sqlite:///%(here)s/rhodecode.db
331 332 sqlalchemy.db1.url = postgresql://postgres:qwe@localhost/rhodecode
332 333 sqlalchemy.db1.echo = false
333 334 sqlalchemy.db1.pool_recycle = 3600
334 335 sqlalchemy.db1.convert_unicode = true
335 336
336 337 ################################
337 338 ### LOGGING CONFIGURATION ####
338 339 ################################
339 340 [loggers]
340 341 keys = root, routes, rhodecode, sqlalchemy, beaker, templates, whoosh_indexer
341 342
342 343 [handlers]
343 344 keys = console, console_sql
344 345
345 346 [formatters]
346 347 keys = generic, color_formatter, color_formatter_sql
347 348
348 349 #############
349 350 ## LOGGERS ##
350 351 #############
351 352 [logger_root]
352 353 level = NOTSET
353 354 handlers = console
354 355
355 356 [logger_routes]
356 357 level = DEBUG
357 358 handlers =
358 359 qualname = routes.middleware
359 360 # "level = DEBUG" logs the route matched and routing variables.
360 361 propagate = 1
361 362
362 363 [logger_beaker]
363 364 level = DEBUG
364 365 handlers =
365 366 qualname = beaker.container
366 367 propagate = 1
367 368
368 369 [logger_templates]
369 370 level = INFO
370 371 handlers =
371 372 qualname = pylons.templating
372 373 propagate = 1
373 374
374 375 [logger_rhodecode]
375 376 level = DEBUG
376 377 handlers =
377 378 qualname = rhodecode
378 379 propagate = 1
379 380
380 381 [logger_sqlalchemy]
381 382 level = INFO
382 383 handlers = console_sql
383 384 qualname = sqlalchemy.engine
384 385 propagate = 0
385 386
386 387 [logger_whoosh_indexer]
387 388 level = DEBUG
388 389 handlers =
389 390 qualname = whoosh_indexer
390 391 propagate = 1
391 392
392 393 ##############
393 394 ## HANDLERS ##
394 395 ##############
395 396
396 397 [handler_console]
397 398 class = StreamHandler
398 399 args = (sys.stderr,)
399 400 level = DEBUG
400 401 formatter = color_formatter
401 402
402 403 [handler_console_sql]
403 404 class = StreamHandler
404 405 args = (sys.stderr,)
405 406 level = DEBUG
406 407 formatter = color_formatter_sql
407 408
408 409 ################
409 410 ## FORMATTERS ##
410 411 ################
411 412
412 413 [formatter_generic]
413 414 format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
414 415 datefmt = %Y-%m-%d %H:%M:%S
415 416
416 417 [formatter_color_formatter]
417 418 class=rhodecode.lib.colored_formatter.ColorFormatter
418 419 format= %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
419 420 datefmt = %Y-%m-%d %H:%M:%S
420 421
421 422 [formatter_color_formatter_sql]
422 423 class=rhodecode.lib.colored_formatter.ColorFormatterSql
423 424 format= %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
424 425 datefmt = %Y-%m-%d %H:%M:%S
@@ -1,424 +1,425 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 use = egg:waitress#main
43 43 host = 127.0.0.1
44 44 port = 8001
45 45
46 46 [filter:proxy-prefix]
47 47 # prefix middleware for rc
48 48 use = egg:PasteDeploy#prefix
49 49 prefix = /<your-prefix>
50 50
51 51 [app:main]
52 52 use = egg:rhodecode
53 53 #filter-with = proxy-prefix
54 54 full_stack = true
55 55 static_files = true
56 56 # Optional Languages
57 57 # en, fr, ja, pt_BR, zh_CN, zh_TW
58 58 lang = en
59 59 cache_dir = %(here)s/data
60 60 index_dir = %(here)s/data/index
61 61 app_instance_uuid = rc-production
62 62 cut_off_limit = 256000
63 vcs_full_cache = True
63 64 force_https = false
64 65 commit_parse_limit = 50
65 66 use_gravatar = true
66 67
67 68 ## alternative_gravatar_url allows you to use your own avatar server application
68 69 ## the following parts of the URL will be replaced
69 70 ## {email} user email
70 71 ## {md5email} md5 hash of the user email (like at gravatar.com)
71 72 ## {size} size of the image that is expected from the server application
72 73 ## {scheme} http/https from RhodeCode server
73 74 ## {netloc} network location from RhodeCode server
74 75 #alternative_gravatar_url = http://myavatarserver.com/getbyemail/{email}/{size}
75 76 #alternative_gravatar_url = http://myavatarserver.com/getbymd5/{md5email}?s={size}
76 77
77 78 container_auth_enabled = false
78 79 proxypass_auth_enabled = false
79 80 ## default encoding used to convert from and to unicode
80 81 ## can be also a comma seperated list of encoding in case of mixed encodings
81 82 default_encoding = utf8
82 83
83 84 ## overwrite schema of clone url
84 85 ## available vars:
85 86 ## scheme - http/https
86 87 ## user - current user
87 88 ## pass - password
88 89 ## netloc - network location
89 90 ## path - usually repo_name
90 91
91 92 #clone_uri = {scheme}://{user}{pass}{netloc}{path}
92 93
93 94 ## issue tracking mapping for commits messages
94 95 ## comment out issue_pat, issue_server, issue_prefix to enable
95 96
96 97 ## pattern to get the issues from commit messages
97 98 ## default one used here is #<numbers> with a regex passive group for `#`
98 99 ## {id} will be all groups matched from this pattern
99 100
100 101 issue_pat = (?:\s*#)(\d+)
101 102
102 103 ## server url to the issue, each {id} will be replaced with match
103 104 ## fetched from the regex and {repo} is replaced with full repository name
104 105 ## including groups {repo_name} is replaced with just name of repo
105 106
106 107 issue_server_link = https://myissueserver.com/{repo}/issue/{id}
107 108
108 109 ## prefix to add to link to indicate it's an url
109 110 ## #314 will be replaced by <issue_prefix><id>
110 111
111 112 issue_prefix = #
112 113
113 114 ## issue_pat, issue_server_link, issue_prefix can have suffixes to specify
114 115 ## multiple patterns, to other issues server, wiki or others
115 116 ## below an example how to create a wiki pattern
116 117 # #wiki-some-id -> https://mywiki.com/some-id
117 118
118 119 #issue_pat_wiki = (?:wiki-)(.+)
119 120 #issue_server_link_wiki = https://mywiki.com/{id}
120 121 #issue_prefix_wiki = WIKI-
121 122
122 123
123 124 ## instance-id prefix
124 125 ## a prefix key for this instance used for cache invalidation when running
125 126 ## multiple instances of rhodecode, make sure it's globally unique for
126 127 ## all running rhodecode instances. Leave empty if you don't use it
127 128 instance_id =
128 129
129 130 ## alternative return HTTP header for failed authentication. Default HTTP
130 131 ## response is 401 HTTPUnauthorized. Currently HG clients have troubles with
131 132 ## handling that. Set this variable to 403 to return HTTPForbidden
132 133 auth_ret_code =
133 134
134 135 ####################################
135 136 ### CELERY CONFIG ####
136 137 ####################################
137 138 use_celery = false
138 139 broker.host = localhost
139 140 broker.vhost = rabbitmqhost
140 141 broker.port = 5672
141 142 broker.user = rabbitmq
142 143 broker.password = qweqwe
143 144
144 145 celery.imports = rhodecode.lib.celerylib.tasks
145 146
146 147 celery.result.backend = amqp
147 148 celery.result.dburi = amqp://
148 149 celery.result.serialier = json
149 150
150 151 #celery.send.task.error.emails = true
151 152 #celery.amqp.task.result.expires = 18000
152 153
153 154 celeryd.concurrency = 2
154 155 #celeryd.log.file = celeryd.log
155 156 celeryd.log.level = debug
156 157 celeryd.max.tasks.per.child = 1
157 158
158 159 #tasks will never be sent to the queue, but executed locally instead.
159 160 celery.always.eager = false
160 161
161 162 ####################################
162 163 ### BEAKER CACHE ####
163 164 ####################################
164 165 beaker.cache.data_dir=%(here)s/data/cache/data
165 166 beaker.cache.lock_dir=%(here)s/data/cache/lock
166 167
167 168 beaker.cache.regions=super_short_term,short_term,long_term,sql_cache_short,sql_cache_med,sql_cache_long
168 169
169 170 beaker.cache.super_short_term.type=memory
170 171 beaker.cache.super_short_term.expire=10
171 172 beaker.cache.super_short_term.key_length = 256
172 173
173 174 beaker.cache.short_term.type=memory
174 175 beaker.cache.short_term.expire=60
175 176 beaker.cache.short_term.key_length = 256
176 177
177 178 beaker.cache.long_term.type=memory
178 179 beaker.cache.long_term.expire=36000
179 180 beaker.cache.long_term.key_length = 256
180 181
181 182 beaker.cache.sql_cache_short.type=memory
182 183 beaker.cache.sql_cache_short.expire=10
183 184 beaker.cache.sql_cache_short.key_length = 256
184 185
185 186 beaker.cache.sql_cache_med.type=memory
186 187 beaker.cache.sql_cache_med.expire=360
187 188 beaker.cache.sql_cache_med.key_length = 256
188 189
189 190 beaker.cache.sql_cache_long.type=file
190 191 beaker.cache.sql_cache_long.expire=3600
191 192 beaker.cache.sql_cache_long.key_length = 256
192 193
193 194 ####################################
194 195 ### BEAKER SESSION ####
195 196 ####################################
196 197 ## Type of storage used for the session, current types are
197 198 ## dbm, file, memcached, database, and memory.
198 199 ## The storage uses the Container API
199 200 ## that is also used by the cache system.
200 201
201 202 ## db session ##
202 203 #beaker.session.type = ext:database
203 204 #beaker.session.sa.url = postgresql://postgres:qwe@localhost/rhodecode
204 205 #beaker.session.table_name = db_session
205 206
206 207 ## encrypted cookie client side session, good for many instances ##
207 208 #beaker.session.type = cookie
208 209
209 210 ## file based cookies (default) ##
210 211 #beaker.session.type = file
211 212
212 213
213 214 beaker.session.key = rhodecode
214 215 ## secure cookie requires AES python libraries ##
215 216 #beaker.session.encrypt_key = g654dcno0-9873jhgfreyu
216 217 #beaker.session.validate_key = 9712sds2212c--zxc123
217 218 ## sets session as invalid if it haven't been accessed for given amount of time
218 219 beaker.session.timeout = 2592000
219 220 beaker.session.httponly = true
220 221 #beaker.session.cookie_path = /<your-prefix>
221 222
222 223 ## uncomment for https secure cookie ##
223 224 beaker.session.secure = false
224 225
225 226 ## auto save the session to not to use .save() ##
226 227 beaker.session.auto = False
227 228
228 229 ## default cookie expiration time in seconds `true` expire at browser close ##
229 230 #beaker.session.cookie_expires = 3600
230 231
231 232
232 233 ############################
233 234 ## ERROR HANDLING SYSTEMS ##
234 235 ############################
235 236
236 237 ####################
237 238 ### [errormator] ###
238 239 ####################
239 240
240 241 # Errormator is tailored to work with RhodeCode, see
241 242 # http://errormator.com for details how to obtain an account
242 243 # you must install python package `errormator_client` to make it work
243 244
244 245 # errormator enabled
245 246 errormator = true
246 247
247 248 errormator.server_url = https://api.errormator.com
248 249 errormator.api_key = YOUR_API_KEY
249 250
250 251 # TWEAK AMOUNT OF INFO SENT HERE
251 252
252 253 # enables 404 error logging (default False)
253 254 errormator.report_404 = false
254 255
255 256 # time in seconds after request is considered being slow (default 1)
256 257 errormator.slow_request_time = 1
257 258
258 259 # record slow requests in application
259 260 # (needs to be enabled for slow datastore recording and time tracking)
260 261 errormator.slow_requests = true
261 262
262 263 # enable hooking to application loggers
263 264 # errormator.logging = true
264 265
265 266 # minimum log level for log capture
266 267 # errormator.logging.level = WARNING
267 268
268 269 # send logs only from erroneous/slow requests
269 270 # (saves API quota for intensive logging)
270 271 errormator.logging_on_error = false
271 272
272 273 # list of additonal keywords that should be grabbed from environ object
273 274 # can be string with comma separated list of words in lowercase
274 275 # (by default client will always send following info:
275 276 # 'REMOTE_USER', 'REMOTE_ADDR', 'SERVER_NAME', 'CONTENT_TYPE' + all keys that
276 277 # start with HTTP* this list be extended with additional keywords here
277 278 errormator.environ_keys_whitelist =
278 279
279 280
280 281 # list of keywords that should be blanked from request object
281 282 # can be string with comma separated list of words in lowercase
282 283 # (by default client will always blank keys that contain following words
283 284 # 'password', 'passwd', 'pwd', 'auth_tkt', 'secret', 'csrf'
284 285 # this list be extended with additional keywords set here
285 286 errormator.request_keys_blacklist =
286 287
287 288
288 289 # list of namespaces that should be ignores when gathering log entries
289 290 # can be string with comma separated list of namespaces
290 291 # (by default the client ignores own entries: errormator_client.client)
291 292 errormator.log_namespace_blacklist =
292 293
293 294
294 295 ################
295 296 ### [sentry] ###
296 297 ################
297 298
298 299 # sentry is a alternative open source error aggregator
299 300 # you must install python packages `sentry` and `raven` to enable
300 301
301 302 sentry.dsn = YOUR_DNS
302 303 sentry.servers =
303 304 sentry.name =
304 305 sentry.key =
305 306 sentry.public_key =
306 307 sentry.secret_key =
307 308 sentry.project =
308 309 sentry.site =
309 310 sentry.include_paths =
310 311 sentry.exclude_paths =
311 312
312 313
313 314 ################################################################################
314 315 ## WARNING: *THE LINE BELOW MUST BE UNCOMMENTED ON A PRODUCTION ENVIRONMENT* ##
315 316 ## Debug mode will enable the interactive debugging tool, allowing ANYONE to ##
316 317 ## execute malicious code after an exception is raised. ##
317 318 ################################################################################
318 319 set debug = false
319 320
320 321 ##################################
321 322 ### LOGVIEW CONFIG ###
322 323 ##################################
323 324 logview.sqlalchemy = #faa
324 325 logview.pylons.templating = #bfb
325 326 logview.pylons.util = #eee
326 327
327 328 #########################################################
328 329 ### DB CONFIGS - EACH DB WILL HAVE IT'S OWN CONFIG ###
329 330 #########################################################
330 331 #sqlalchemy.db1.url = sqlite:///%(here)s/rhodecode.db
331 332 sqlalchemy.db1.url = postgresql://postgres:qwe@localhost/rhodecode
332 333 sqlalchemy.db1.echo = false
333 334 sqlalchemy.db1.pool_recycle = 3600
334 335 sqlalchemy.db1.convert_unicode = true
335 336
336 337 ################################
337 338 ### LOGGING CONFIGURATION ####
338 339 ################################
339 340 [loggers]
340 341 keys = root, routes, rhodecode, sqlalchemy, beaker, templates, whoosh_indexer
341 342
342 343 [handlers]
343 344 keys = console, console_sql
344 345
345 346 [formatters]
346 347 keys = generic, color_formatter, color_formatter_sql
347 348
348 349 #############
349 350 ## LOGGERS ##
350 351 #############
351 352 [logger_root]
352 353 level = NOTSET
353 354 handlers = console
354 355
355 356 [logger_routes]
356 357 level = DEBUG
357 358 handlers =
358 359 qualname = routes.middleware
359 360 # "level = DEBUG" logs the route matched and routing variables.
360 361 propagate = 1
361 362
362 363 [logger_beaker]
363 364 level = DEBUG
364 365 handlers =
365 366 qualname = beaker.container
366 367 propagate = 1
367 368
368 369 [logger_templates]
369 370 level = INFO
370 371 handlers =
371 372 qualname = pylons.templating
372 373 propagate = 1
373 374
374 375 [logger_rhodecode]
375 376 level = DEBUG
376 377 handlers =
377 378 qualname = rhodecode
378 379 propagate = 1
379 380
380 381 [logger_sqlalchemy]
381 382 level = INFO
382 383 handlers = console_sql
383 384 qualname = sqlalchemy.engine
384 385 propagate = 0
385 386
386 387 [logger_whoosh_indexer]
387 388 level = DEBUG
388 389 handlers =
389 390 qualname = whoosh_indexer
390 391 propagate = 1
391 392
392 393 ##############
393 394 ## HANDLERS ##
394 395 ##############
395 396
396 397 [handler_console]
397 398 class = StreamHandler
398 399 args = (sys.stderr,)
399 400 level = INFO
400 401 formatter = generic
401 402
402 403 [handler_console_sql]
403 404 class = StreamHandler
404 405 args = (sys.stderr,)
405 406 level = WARN
406 407 formatter = generic
407 408
408 409 ################
409 410 ## FORMATTERS ##
410 411 ################
411 412
412 413 [formatter_generic]
413 414 format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
414 415 datefmt = %Y-%m-%d %H:%M:%S
415 416
416 417 [formatter_color_formatter]
417 418 class=rhodecode.lib.colored_formatter.ColorFormatter
418 419 format= %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
419 420 datefmt = %Y-%m-%d %H:%M:%S
420 421
421 422 [formatter_color_formatter_sql]
422 423 class=rhodecode.lib.colored_formatter.ColorFormatterSql
423 424 format= %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
424 425 datefmt = %Y-%m-%d %H:%M:%S
@@ -1,434 +1,435 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 use = egg:waitress#main
43 43 host = 127.0.0.1
44 44 port = 5000
45 45
46 46 [filter:proxy-prefix]
47 47 # prefix middleware for rc
48 48 use = egg:PasteDeploy#prefix
49 49 prefix = /<your-prefix>
50 50
51 51 [app:main]
52 52 use = egg:rhodecode
53 53 #filter-with = proxy-prefix
54 54 full_stack = true
55 55 static_files = true
56 56 # Optional Languages
57 57 # en, fr, ja, pt_BR, zh_CN, zh_TW
58 58 lang = en
59 59 cache_dir = %(here)s/data
60 60 index_dir = %(here)s/data/index
61 61 app_instance_uuid = ${app_instance_uuid}
62 62 cut_off_limit = 256000
63 vcs_full_cache = True
63 64 force_https = false
64 65 commit_parse_limit = 50
65 66 use_gravatar = true
66 67
67 68 ## alternative_gravatar_url allows you to use your own avatar server application
68 69 ## the following parts of the URL will be replaced
69 70 ## {email} user email
70 71 ## {md5email} md5 hash of the user email (like at gravatar.com)
71 72 ## {size} size of the image that is expected from the server application
72 73 ## {scheme} http/https from RhodeCode server
73 74 ## {netloc} network location from RhodeCode server
74 75 #alternative_gravatar_url = http://myavatarserver.com/getbyemail/{email}/{size}
75 76 #alternative_gravatar_url = http://myavatarserver.com/getbymd5/{md5email}?s={size}
76 77
77 78 container_auth_enabled = false
78 79 proxypass_auth_enabled = false
79 80 ## default encoding used to convert from and to unicode
80 81 ## can be also a comma seperated list of encoding in case of mixed encodings
81 82 default_encoding = utf8
82 83
83 84 ## overwrite schema of clone url
84 85 ## available vars:
85 86 ## scheme - http/https
86 87 ## user - current user
87 88 ## pass - password
88 89 ## netloc - network location
89 90 ## path - usually repo_name
90 91
91 92 #clone_uri = {scheme}://{user}{pass}{netloc}{path}
92 93
93 94 ## issue tracking mapping for commits messages
94 95 ## comment out issue_pat, issue_server, issue_prefix to enable
95 96
96 97 ## pattern to get the issues from commit messages
97 98 ## default one used here is #<numbers> with a regex passive group for `#`
98 99 ## {id} will be all groups matched from this pattern
99 100
100 101 issue_pat = (?:\s*#)(\d+)
101 102
102 103 ## server url to the issue, each {id} will be replaced with match
103 104 ## fetched from the regex and {repo} is replaced with full repository name
104 105 ## including groups {repo_name} is replaced with just name of repo
105 106
106 107 issue_server_link = https://myissueserver.com/{repo}/issue/{id}
107 108
108 109 ## prefix to add to link to indicate it's an url
109 110 ## #314 will be replaced by <issue_prefix><id>
110 111
111 112 issue_prefix = #
112 113
113 114 ## issue_pat, issue_server_link, issue_prefix can have suffixes to specify
114 115 ## multiple patterns, to other issues server, wiki or others
115 116 ## below an example how to create a wiki pattern
116 117 # #wiki-some-id -> https://mywiki.com/some-id
117 118
118 119 #issue_pat_wiki = (?:wiki-)(.+)
119 120 #issue_server_link_wiki = https://mywiki.com/{id}
120 121 #issue_prefix_wiki = WIKI-
121 122
122 123
123 124 ## instance-id prefix
124 125 ## a prefix key for this instance used for cache invalidation when running
125 126 ## multiple instances of rhodecode, make sure it's globally unique for
126 127 ## all running rhodecode instances. Leave empty if you don't use it
127 128 instance_id =
128 129
129 130 ## alternative return HTTP header for failed authentication. Default HTTP
130 131 ## response is 401 HTTPUnauthorized. Currently HG clients have troubles with
131 132 ## handling that. Set this variable to 403 to return HTTPForbidden
132 133 auth_ret_code =
133 134
134 135 ####################################
135 136 ### CELERY CONFIG ####
136 137 ####################################
137 138 use_celery = false
138 139 broker.host = localhost
139 140 broker.vhost = rabbitmqhost
140 141 broker.port = 5672
141 142 broker.user = rabbitmq
142 143 broker.password = qweqwe
143 144
144 145 celery.imports = rhodecode.lib.celerylib.tasks
145 146
146 147 celery.result.backend = amqp
147 148 celery.result.dburi = amqp://
148 149 celery.result.serialier = json
149 150
150 151 #celery.send.task.error.emails = true
151 152 #celery.amqp.task.result.expires = 18000
152 153
153 154 celeryd.concurrency = 2
154 155 #celeryd.log.file = celeryd.log
155 156 celeryd.log.level = debug
156 157 celeryd.max.tasks.per.child = 1
157 158
158 159 #tasks will never be sent to the queue, but executed locally instead.
159 160 celery.always.eager = false
160 161
161 162 ####################################
162 163 ### BEAKER CACHE ####
163 164 ####################################
164 165 beaker.cache.data_dir=%(here)s/data/cache/data
165 166 beaker.cache.lock_dir=%(here)s/data/cache/lock
166 167
167 168 beaker.cache.regions=super_short_term,short_term,long_term,sql_cache_short,sql_cache_med,sql_cache_long
168 169
169 170 beaker.cache.super_short_term.type=memory
170 171 beaker.cache.super_short_term.expire=10
171 172 beaker.cache.super_short_term.key_length = 256
172 173
173 174 beaker.cache.short_term.type=memory
174 175 beaker.cache.short_term.expire=60
175 176 beaker.cache.short_term.key_length = 256
176 177
177 178 beaker.cache.long_term.type=memory
178 179 beaker.cache.long_term.expire=36000
179 180 beaker.cache.long_term.key_length = 256
180 181
181 182 beaker.cache.sql_cache_short.type=memory
182 183 beaker.cache.sql_cache_short.expire=10
183 184 beaker.cache.sql_cache_short.key_length = 256
184 185
185 186 beaker.cache.sql_cache_med.type=memory
186 187 beaker.cache.sql_cache_med.expire=360
187 188 beaker.cache.sql_cache_med.key_length = 256
188 189
189 190 beaker.cache.sql_cache_long.type=file
190 191 beaker.cache.sql_cache_long.expire=3600
191 192 beaker.cache.sql_cache_long.key_length = 256
192 193
193 194 ####################################
194 195 ### BEAKER SESSION ####
195 196 ####################################
196 197 ## Type of storage used for the session, current types are
197 198 ## dbm, file, memcached, database, and memory.
198 199 ## The storage uses the Container API
199 200 ## that is also used by the cache system.
200 201
201 202 ## db session ##
202 203 #beaker.session.type = ext:database
203 204 #beaker.session.sa.url = postgresql://postgres:qwe@localhost/rhodecode
204 205 #beaker.session.table_name = db_session
205 206
206 207 ## encrypted cookie client side session, good for many instances ##
207 208 #beaker.session.type = cookie
208 209
209 210 ## file based cookies (default) ##
210 211 #beaker.session.type = file
211 212
212 213
213 214 beaker.session.key = rhodecode
214 215 ## secure cookie requires AES python libraries ##
215 216 #beaker.session.encrypt_key = g654dcno0-9873jhgfreyu
216 217 #beaker.session.validate_key = 9712sds2212c--zxc123
217 218 ## sets session as invalid if it haven't been accessed for given amount of time
218 219 beaker.session.timeout = 2592000
219 220 beaker.session.httponly = true
220 221 #beaker.session.cookie_path = /<your-prefix>
221 222
222 223 ## uncomment for https secure cookie ##
223 224 beaker.session.secure = false
224 225
225 226 ## auto save the session to not to use .save() ##
226 227 beaker.session.auto = False
227 228
228 229 ## default cookie expiration time in seconds `true` expire at browser close ##
229 230 #beaker.session.cookie_expires = 3600
230 231
231 232
232 233 ############################
233 234 ## ERROR HANDLING SYSTEMS ##
234 235 ############################
235 236
236 237 ####################
237 238 ### [errormator] ###
238 239 ####################
239 240
240 241 # Errormator is tailored to work with RhodeCode, see
241 242 # http://errormator.com for details how to obtain an account
242 243 # you must install python package `errormator_client` to make it work
243 244
244 245 # errormator enabled
245 246 errormator = true
246 247
247 248 errormator.server_url = https://api.errormator.com
248 249 errormator.api_key = YOUR_API_KEY
249 250
250 251 # TWEAK AMOUNT OF INFO SENT HERE
251 252
252 253 # enables 404 error logging (default False)
253 254 errormator.report_404 = false
254 255
255 256 # time in seconds after request is considered being slow (default 1)
256 257 errormator.slow_request_time = 1
257 258
258 259 # record slow requests in application
259 260 # (needs to be enabled for slow datastore recording and time tracking)
260 261 errormator.slow_requests = true
261 262
262 263 # enable hooking to application loggers
263 264 # errormator.logging = true
264 265
265 266 # minimum log level for log capture
266 267 # errormator.logging.level = WARNING
267 268
268 269 # send logs only from erroneous/slow requests
269 270 # (saves API quota for intensive logging)
270 271 errormator.logging_on_error = false
271 272
272 273 # list of additonal keywords that should be grabbed from environ object
273 274 # can be string with comma separated list of words in lowercase
274 275 # (by default client will always send following info:
275 276 # 'REMOTE_USER', 'REMOTE_ADDR', 'SERVER_NAME', 'CONTENT_TYPE' + all keys that
276 277 # start with HTTP* this list be extended with additional keywords here
277 278 errormator.environ_keys_whitelist =
278 279
279 280
280 281 # list of keywords that should be blanked from request object
281 282 # can be string with comma separated list of words in lowercase
282 283 # (by default client will always blank keys that contain following words
283 284 # 'password', 'passwd', 'pwd', 'auth_tkt', 'secret', 'csrf'
284 285 # this list be extended with additional keywords set here
285 286 errormator.request_keys_blacklist =
286 287
287 288
288 289 # list of namespaces that should be ignores when gathering log entries
289 290 # can be string with comma separated list of namespaces
290 291 # (by default the client ignores own entries: errormator_client.client)
291 292 errormator.log_namespace_blacklist =
292 293
293 294
294 295 ################
295 296 ### [sentry] ###
296 297 ################
297 298
298 299 # sentry is a alternative open source error aggregator
299 300 # you must install python packages `sentry` and `raven` to enable
300 301
301 302 sentry.dsn = YOUR_DNS
302 303 sentry.servers =
303 304 sentry.name =
304 305 sentry.key =
305 306 sentry.public_key =
306 307 sentry.secret_key =
307 308 sentry.project =
308 309 sentry.site =
309 310 sentry.include_paths =
310 311 sentry.exclude_paths =
311 312
312 313
313 314 ################################################################################
314 315 ## WARNING: *THE LINE BELOW MUST BE UNCOMMENTED ON A PRODUCTION ENVIRONMENT* ##
315 316 ## Debug mode will enable the interactive debugging tool, allowing ANYONE to ##
316 317 ## execute malicious code after an exception is raised. ##
317 318 ################################################################################
318 319 set debug = false
319 320
320 321 ##################################
321 322 ### LOGVIEW CONFIG ###
322 323 ##################################
323 324 logview.sqlalchemy = #faa
324 325 logview.pylons.templating = #bfb
325 326 logview.pylons.util = #eee
326 327
327 328 #########################################################
328 329 ### DB CONFIGS - EACH DB WILL HAVE IT'S OWN CONFIG ###
329 330 #########################################################
330 331
331 332 # SQLITE [default]
332 333 sqlalchemy.db1.url = sqlite:///%(here)s/rhodecode.db
333 334
334 335 # POSTGRESQL
335 336 # sqlalchemy.db1.url = postgresql://user:pass@localhost/rhodecode
336 337
337 338 # MySQL
338 339 # sqlalchemy.db1.url = mysql://user:pass@localhost/rhodecode
339 340
340 341 # see sqlalchemy docs for others
341 342
342 343 sqlalchemy.db1.echo = false
343 344 sqlalchemy.db1.pool_recycle = 3600
344 345 sqlalchemy.db1.convert_unicode = true
345 346
346 347 ################################
347 348 ### LOGGING CONFIGURATION ####
348 349 ################################
349 350 [loggers]
350 351 keys = root, routes, rhodecode, sqlalchemy, beaker, templates, whoosh_indexer
351 352
352 353 [handlers]
353 354 keys = console, console_sql
354 355
355 356 [formatters]
356 357 keys = generic, color_formatter, color_formatter_sql
357 358
358 359 #############
359 360 ## LOGGERS ##
360 361 #############
361 362 [logger_root]
362 363 level = NOTSET
363 364 handlers = console
364 365
365 366 [logger_routes]
366 367 level = DEBUG
367 368 handlers =
368 369 qualname = routes.middleware
369 370 # "level = DEBUG" logs the route matched and routing variables.
370 371 propagate = 1
371 372
372 373 [logger_beaker]
373 374 level = DEBUG
374 375 handlers =
375 376 qualname = beaker.container
376 377 propagate = 1
377 378
378 379 [logger_templates]
379 380 level = INFO
380 381 handlers =
381 382 qualname = pylons.templating
382 383 propagate = 1
383 384
384 385 [logger_rhodecode]
385 386 level = DEBUG
386 387 handlers =
387 388 qualname = rhodecode
388 389 propagate = 1
389 390
390 391 [logger_sqlalchemy]
391 392 level = INFO
392 393 handlers = console_sql
393 394 qualname = sqlalchemy.engine
394 395 propagate = 0
395 396
396 397 [logger_whoosh_indexer]
397 398 level = DEBUG
398 399 handlers =
399 400 qualname = whoosh_indexer
400 401 propagate = 1
401 402
402 403 ##############
403 404 ## HANDLERS ##
404 405 ##############
405 406
406 407 [handler_console]
407 408 class = StreamHandler
408 409 args = (sys.stderr,)
409 410 level = INFO
410 411 formatter = generic
411 412
412 413 [handler_console_sql]
413 414 class = StreamHandler
414 415 args = (sys.stderr,)
415 416 level = WARN
416 417 formatter = generic
417 418
418 419 ################
419 420 ## FORMATTERS ##
420 421 ################
421 422
422 423 [formatter_generic]
423 424 format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
424 425 datefmt = %Y-%m-%d %H:%M:%S
425 426
426 427 [formatter_color_formatter]
427 428 class=rhodecode.lib.colored_formatter.ColorFormatter
428 429 format= %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
429 430 datefmt = %Y-%m-%d %H:%M:%S
430 431
431 432 [formatter_color_formatter_sql]
432 433 class=rhodecode.lib.colored_formatter.ColorFormatterSql
433 434 format= %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
434 435 datefmt = %Y-%m-%d %H:%M:%S
@@ -1,1811 +1,1814 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 import hashlib
31 31 import time
32 32 from collections import defaultdict
33 33
34 34 from sqlalchemy import *
35 35 from sqlalchemy.ext.hybrid import hybrid_property
36 36 from sqlalchemy.orm import relationship, joinedload, class_mapper, validates
37 37 from sqlalchemy.exc import DatabaseError
38 38 from beaker.cache import cache_region, region_invalidate
39 39 from webob.exc import HTTPNotFound
40 40
41 41 from pylons.i18n.translation import lazy_ugettext as _
42 42
43 43 from rhodecode.lib.vcs import get_backend
44 44 from rhodecode.lib.vcs.utils.helpers import get_scm
45 45 from rhodecode.lib.vcs.exceptions import VCSError
46 46 from rhodecode.lib.vcs.utils.lazy import LazyProperty
47 47
48 48 from rhodecode.lib.utils2 import str2bool, safe_str, get_changeset_safe, \
49 49 safe_unicode, remove_suffix
50 50 from rhodecode.lib.compat import json
51 51 from rhodecode.lib.caching_query import FromCache
52 52
53 53 from rhodecode.model.meta import Base, Session
54 54
55 55 URL_SEP = '/'
56 56 log = logging.getLogger(__name__)
57 57
58 58 #==============================================================================
59 59 # BASE CLASSES
60 60 #==============================================================================
61 61
62 62 _hash_key = lambda k: hashlib.md5(safe_str(k)).hexdigest()
63 63
64 64
65 65 class BaseModel(object):
66 66 """
67 67 Base Model for all classess
68 68 """
69 69
70 70 @classmethod
71 71 def _get_keys(cls):
72 72 """return column names for this model """
73 73 return class_mapper(cls).c.keys()
74 74
75 75 def get_dict(self):
76 76 """
77 77 return dict with keys and values corresponding
78 78 to this model data """
79 79
80 80 d = {}
81 81 for k in self._get_keys():
82 82 d[k] = getattr(self, k)
83 83
84 84 # also use __json__() if present to get additional fields
85 85 _json_attr = getattr(self, '__json__', None)
86 86 if _json_attr:
87 87 # update with attributes from __json__
88 88 if callable(_json_attr):
89 89 _json_attr = _json_attr()
90 90 for k, val in _json_attr.iteritems():
91 91 d[k] = val
92 92 return d
93 93
94 94 def get_appstruct(self):
95 95 """return list with keys and values tupples corresponding
96 96 to this model data """
97 97
98 98 l = []
99 99 for k in self._get_keys():
100 100 l.append((k, getattr(self, k),))
101 101 return l
102 102
103 103 def populate_obj(self, populate_dict):
104 104 """populate model with data from given populate_dict"""
105 105
106 106 for k in self._get_keys():
107 107 if k in populate_dict:
108 108 setattr(self, k, populate_dict[k])
109 109
110 110 @classmethod
111 111 def query(cls):
112 112 return Session().query(cls)
113 113
114 114 @classmethod
115 115 def get(cls, id_):
116 116 if id_:
117 117 return cls.query().get(id_)
118 118
119 119 @classmethod
120 120 def get_or_404(cls, id_):
121 121 try:
122 122 id_ = int(id_)
123 123 except (TypeError, ValueError):
124 124 raise HTTPNotFound
125 125
126 126 res = cls.query().get(id_)
127 127 if not res:
128 128 raise HTTPNotFound
129 129 return res
130 130
131 131 @classmethod
132 132 def getAll(cls):
133 133 return cls.query().all()
134 134
135 135 @classmethod
136 136 def delete(cls, id_):
137 137 obj = cls.query().get(id_)
138 138 Session().delete(obj)
139 139
140 140 def __repr__(self):
141 141 if hasattr(self, '__unicode__'):
142 142 # python repr needs to return str
143 143 return safe_str(self.__unicode__())
144 144 return '<DB:%s>' % (self.__class__.__name__)
145 145
146 146
147 147 class RhodeCodeSetting(Base, BaseModel):
148 148 __tablename__ = 'rhodecode_settings'
149 149 __table_args__ = (
150 150 UniqueConstraint('app_settings_name'),
151 151 {'extend_existing': True, 'mysql_engine': 'InnoDB',
152 152 'mysql_charset': 'utf8'}
153 153 )
154 154 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
155 155 app_settings_name = Column("app_settings_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
156 156 _app_settings_value = Column("app_settings_value", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
157 157
158 158 def __init__(self, k='', v=''):
159 159 self.app_settings_name = k
160 160 self.app_settings_value = v
161 161
162 162 @validates('_app_settings_value')
163 163 def validate_settings_value(self, key, val):
164 164 assert type(val) == unicode
165 165 return val
166 166
167 167 @hybrid_property
168 168 def app_settings_value(self):
169 169 v = self._app_settings_value
170 170 if self.app_settings_name == 'ldap_active':
171 171 v = str2bool(v)
172 172 return v
173 173
174 174 @app_settings_value.setter
175 175 def app_settings_value(self, val):
176 176 """
177 177 Setter that will always make sure we use unicode in app_settings_value
178 178
179 179 :param val:
180 180 """
181 181 self._app_settings_value = safe_unicode(val)
182 182
183 183 def __unicode__(self):
184 184 return u"<%s('%s:%s')>" % (
185 185 self.__class__.__name__,
186 186 self.app_settings_name, self.app_settings_value
187 187 )
188 188
189 189 @classmethod
190 190 def get_by_name(cls, key):
191 191 return cls.query()\
192 192 .filter(cls.app_settings_name == key).scalar()
193 193
194 194 @classmethod
195 195 def get_by_name_or_create(cls, key):
196 196 res = cls.get_by_name(key)
197 197 if not res:
198 198 res = cls(key)
199 199 return res
200 200
201 201 @classmethod
202 202 def get_app_settings(cls, cache=False):
203 203
204 204 ret = cls.query()
205 205
206 206 if cache:
207 207 ret = ret.options(FromCache("sql_cache_short", "get_hg_settings"))
208 208
209 209 if not ret:
210 210 raise Exception('Could not get application settings !')
211 211 settings = {}
212 212 for each in ret:
213 213 settings['rhodecode_' + each.app_settings_name] = \
214 214 each.app_settings_value
215 215
216 216 return settings
217 217
218 218 @classmethod
219 219 def get_ldap_settings(cls, cache=False):
220 220 ret = cls.query()\
221 221 .filter(cls.app_settings_name.startswith('ldap_')).all()
222 222 fd = {}
223 223 for row in ret:
224 224 fd.update({row.app_settings_name: row.app_settings_value})
225 225
226 226 return fd
227 227
228 228
229 229 class RhodeCodeUi(Base, BaseModel):
230 230 __tablename__ = 'rhodecode_ui'
231 231 __table_args__ = (
232 232 UniqueConstraint('ui_key'),
233 233 {'extend_existing': True, 'mysql_engine': 'InnoDB',
234 234 'mysql_charset': 'utf8'}
235 235 )
236 236
237 237 HOOK_UPDATE = 'changegroup.update'
238 238 HOOK_REPO_SIZE = 'changegroup.repo_size'
239 239 HOOK_PUSH = 'changegroup.push_logger'
240 240 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
241 241 HOOK_PULL = 'outgoing.pull_logger'
242 242 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
243 243
244 244 ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
245 245 ui_section = Column("ui_section", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
246 246 ui_key = Column("ui_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
247 247 ui_value = Column("ui_value", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
248 248 ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True)
249 249
250 250 @classmethod
251 251 def get_by_key(cls, key):
252 252 return cls.query().filter(cls.ui_key == key).scalar()
253 253
254 254 @classmethod
255 255 def get_builtin_hooks(cls):
256 256 q = cls.query()
257 257 q = q.filter(cls.ui_key.in_([cls.HOOK_UPDATE, cls.HOOK_REPO_SIZE,
258 258 cls.HOOK_PUSH, cls.HOOK_PRE_PUSH,
259 259 cls.HOOK_PULL, cls.HOOK_PRE_PULL]))
260 260 return q.all()
261 261
262 262 @classmethod
263 263 def get_custom_hooks(cls):
264 264 q = cls.query()
265 265 q = q.filter(~cls.ui_key.in_([cls.HOOK_UPDATE, cls.HOOK_REPO_SIZE,
266 266 cls.HOOK_PUSH, cls.HOOK_PRE_PUSH,
267 267 cls.HOOK_PULL, cls.HOOK_PRE_PULL]))
268 268 q = q.filter(cls.ui_section == 'hooks')
269 269 return q.all()
270 270
271 271 @classmethod
272 272 def get_repos_location(cls):
273 273 return cls.get_by_key('/').ui_value
274 274
275 275 @classmethod
276 276 def create_or_update_hook(cls, key, val):
277 277 new_ui = cls.get_by_key(key) or cls()
278 278 new_ui.ui_section = 'hooks'
279 279 new_ui.ui_active = True
280 280 new_ui.ui_key = key
281 281 new_ui.ui_value = val
282 282
283 283 Session().add(new_ui)
284 284
285 285 def __repr__(self):
286 286 return '<DB:%s[%s:%s]>' % (self.__class__.__name__, self.ui_key,
287 287 self.ui_value)
288 288
289 289
290 290 class User(Base, BaseModel):
291 291 __tablename__ = 'users'
292 292 __table_args__ = (
293 293 UniqueConstraint('username'), UniqueConstraint('email'),
294 294 Index('u_username_idx', 'username'),
295 295 Index('u_email_idx', 'email'),
296 296 {'extend_existing': True, 'mysql_engine': 'InnoDB',
297 297 'mysql_charset': 'utf8'}
298 298 )
299 299 DEFAULT_USER = 'default'
300 300 DEFAULT_PERMISSIONS = [
301 301 'hg.register.manual_activate', 'hg.create.repository',
302 302 'hg.fork.repository', 'repository.read'
303 303 ]
304 304 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
305 305 username = Column("username", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
306 306 password = Column("password", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
307 307 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
308 308 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
309 309 name = Column("firstname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
310 310 lastname = Column("lastname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
311 311 _email = Column("email", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
312 312 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
313 313 ldap_dn = Column("ldap_dn", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
314 314 api_key = Column("api_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
315 315 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
316 316
317 317 user_log = relationship('UserLog', cascade='all')
318 318 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
319 319
320 320 repositories = relationship('Repository')
321 321 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
322 322 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
323 323 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
324 324
325 325 group_member = relationship('UsersGroupMember', cascade='all')
326 326
327 327 notifications = relationship('UserNotification', cascade='all')
328 328 # notifications assigned to this user
329 329 user_created_notifications = relationship('Notification', cascade='all')
330 330 # comments created by this user
331 331 user_comments = relationship('ChangesetComment', cascade='all')
332 332 #extra emails for this user
333 333 user_emails = relationship('UserEmailMap', cascade='all')
334 334
335 335 @hybrid_property
336 336 def email(self):
337 337 return self._email
338 338
339 339 @email.setter
340 340 def email(self, val):
341 341 self._email = val.lower() if val else None
342 342
343 343 @property
344 344 def firstname(self):
345 345 # alias for future
346 346 return self.name
347 347
348 348 @property
349 349 def emails(self):
350 350 other = UserEmailMap.query().filter(UserEmailMap.user==self).all()
351 351 return [self.email] + [x.email for x in other]
352 352
353 353 @property
354 354 def username_and_name(self):
355 355 return '%s (%s %s)' % (self.username, self.firstname, self.lastname)
356 356
357 357 @property
358 358 def full_name(self):
359 359 return '%s %s' % (self.firstname, self.lastname)
360 360
361 361 @property
362 362 def full_name_or_username(self):
363 363 return ('%s %s' % (self.firstname, self.lastname)
364 364 if (self.firstname and self.lastname) else self.username)
365 365
366 366 @property
367 367 def full_contact(self):
368 368 return '%s %s <%s>' % (self.firstname, self.lastname, self.email)
369 369
370 370 @property
371 371 def short_contact(self):
372 372 return '%s %s' % (self.firstname, self.lastname)
373 373
374 374 @property
375 375 def is_admin(self):
376 376 return self.admin
377 377
378 378 def __unicode__(self):
379 379 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
380 380 self.user_id, self.username)
381 381
382 382 @classmethod
383 383 def get_by_username(cls, username, case_insensitive=False, cache=False):
384 384 if case_insensitive:
385 385 q = cls.query().filter(cls.username.ilike(username))
386 386 else:
387 387 q = cls.query().filter(cls.username == username)
388 388
389 389 if cache:
390 390 q = q.options(FromCache(
391 391 "sql_cache_short",
392 392 "get_user_%s" % _hash_key(username)
393 393 )
394 394 )
395 395 return q.scalar()
396 396
397 397 @classmethod
398 398 def get_by_api_key(cls, api_key, cache=False):
399 399 q = cls.query().filter(cls.api_key == api_key)
400 400
401 401 if cache:
402 402 q = q.options(FromCache("sql_cache_short",
403 403 "get_api_key_%s" % api_key))
404 404 return q.scalar()
405 405
406 406 @classmethod
407 407 def get_by_email(cls, email, case_insensitive=False, cache=False):
408 408 if case_insensitive:
409 409 q = cls.query().filter(cls.email.ilike(email))
410 410 else:
411 411 q = cls.query().filter(cls.email == email)
412 412
413 413 if cache:
414 414 q = q.options(FromCache("sql_cache_short",
415 415 "get_email_key_%s" % email))
416 416
417 417 ret = q.scalar()
418 418 if ret is None:
419 419 q = UserEmailMap.query()
420 420 # try fetching in alternate email map
421 421 if case_insensitive:
422 422 q = q.filter(UserEmailMap.email.ilike(email))
423 423 else:
424 424 q = q.filter(UserEmailMap.email == email)
425 425 q = q.options(joinedload(UserEmailMap.user))
426 426 if cache:
427 427 q = q.options(FromCache("sql_cache_short",
428 428 "get_email_map_key_%s" % email))
429 429 ret = getattr(q.scalar(), 'user', None)
430 430
431 431 return ret
432 432
433 433 def update_lastlogin(self):
434 434 """Update user lastlogin"""
435 435 self.last_login = datetime.datetime.now()
436 436 Session().add(self)
437 437 log.debug('updated user %s lastlogin' % self.username)
438 438
439 439 def get_api_data(self):
440 440 """
441 441 Common function for generating user related data for API
442 442 """
443 443 user = self
444 444 data = dict(
445 445 user_id=user.user_id,
446 446 username=user.username,
447 447 firstname=user.name,
448 448 lastname=user.lastname,
449 449 email=user.email,
450 450 emails=user.emails,
451 451 api_key=user.api_key,
452 452 active=user.active,
453 453 admin=user.admin,
454 454 ldap_dn=user.ldap_dn,
455 455 last_login=user.last_login,
456 456 )
457 457 return data
458 458
459 459 def __json__(self):
460 460 data = dict(
461 461 full_name=self.full_name,
462 462 full_name_or_username=self.full_name_or_username,
463 463 short_contact=self.short_contact,
464 464 full_contact=self.full_contact
465 465 )
466 466 data.update(self.get_api_data())
467 467 return data
468 468
469 469
470 470 class UserEmailMap(Base, BaseModel):
471 471 __tablename__ = 'user_email_map'
472 472 __table_args__ = (
473 473 Index('uem_email_idx', 'email'),
474 474 UniqueConstraint('email'),
475 475 {'extend_existing': True, 'mysql_engine': 'InnoDB',
476 476 'mysql_charset': 'utf8'}
477 477 )
478 478 __mapper_args__ = {}
479 479
480 480 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
481 481 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
482 482 _email = Column("email", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
483 483 user = relationship('User', lazy='joined')
484 484
485 485 @validates('_email')
486 486 def validate_email(self, key, email):
487 487 # check if this email is not main one
488 488 main_email = Session().query(User).filter(User.email == email).scalar()
489 489 if main_email is not None:
490 490 raise AttributeError('email %s is present is user table' % email)
491 491 return email
492 492
493 493 @hybrid_property
494 494 def email(self):
495 495 return self._email
496 496
497 497 @email.setter
498 498 def email(self, val):
499 499 self._email = val.lower() if val else None
500 500
501 501
502 502 class UserLog(Base, BaseModel):
503 503 __tablename__ = 'user_logs'
504 504 __table_args__ = (
505 505 {'extend_existing': True, 'mysql_engine': 'InnoDB',
506 506 'mysql_charset': 'utf8'},
507 507 )
508 508 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
509 509 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
510 510 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True)
511 511 repository_name = Column("repository_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
512 512 user_ip = Column("user_ip", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
513 513 action = Column("action", UnicodeText(1200000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
514 514 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
515 515
516 516 @property
517 517 def action_as_day(self):
518 518 return datetime.date(*self.action_date.timetuple()[:3])
519 519
520 520 user = relationship('User')
521 521 repository = relationship('Repository', cascade='')
522 522
523 523
524 524 class UsersGroup(Base, BaseModel):
525 525 __tablename__ = 'users_groups'
526 526 __table_args__ = (
527 527 {'extend_existing': True, 'mysql_engine': 'InnoDB',
528 528 'mysql_charset': 'utf8'},
529 529 )
530 530
531 531 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
532 532 users_group_name = Column("users_group_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
533 533 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
534 534 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
535 535
536 536 members = relationship('UsersGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
537 537 users_group_to_perm = relationship('UsersGroupToPerm', cascade='all')
538 538 users_group_repo_to_perm = relationship('UsersGroupRepoToPerm', cascade='all')
539 539
540 540 def __unicode__(self):
541 541 return u'<userGroup(%s)>' % (self.users_group_name)
542 542
543 543 @classmethod
544 544 def get_by_group_name(cls, group_name, cache=False,
545 545 case_insensitive=False):
546 546 if case_insensitive:
547 547 q = cls.query().filter(cls.users_group_name.ilike(group_name))
548 548 else:
549 549 q = cls.query().filter(cls.users_group_name == group_name)
550 550 if cache:
551 551 q = q.options(FromCache(
552 552 "sql_cache_short",
553 553 "get_user_%s" % _hash_key(group_name)
554 554 )
555 555 )
556 556 return q.scalar()
557 557
558 558 @classmethod
559 559 def get(cls, users_group_id, cache=False):
560 560 users_group = cls.query()
561 561 if cache:
562 562 users_group = users_group.options(FromCache("sql_cache_short",
563 563 "get_users_group_%s" % users_group_id))
564 564 return users_group.get(users_group_id)
565 565
566 566 def get_api_data(self):
567 567 users_group = self
568 568
569 569 data = dict(
570 570 users_group_id=users_group.users_group_id,
571 571 group_name=users_group.users_group_name,
572 572 active=users_group.users_group_active,
573 573 )
574 574
575 575 return data
576 576
577 577
578 578 class UsersGroupMember(Base, BaseModel):
579 579 __tablename__ = 'users_groups_members'
580 580 __table_args__ = (
581 581 {'extend_existing': True, 'mysql_engine': 'InnoDB',
582 582 'mysql_charset': 'utf8'},
583 583 )
584 584
585 585 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
586 586 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
587 587 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
588 588
589 589 user = relationship('User', lazy='joined')
590 590 users_group = relationship('UsersGroup')
591 591
592 592 def __init__(self, gr_id='', u_id=''):
593 593 self.users_group_id = gr_id
594 594 self.user_id = u_id
595 595
596 596
597 597 class Repository(Base, BaseModel):
598 598 __tablename__ = 'repositories'
599 599 __table_args__ = (
600 600 UniqueConstraint('repo_name'),
601 601 Index('r_repo_name_idx', 'repo_name'),
602 602 {'extend_existing': True, 'mysql_engine': 'InnoDB',
603 603 'mysql_charset': 'utf8'},
604 604 )
605 605
606 606 repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
607 607 repo_name = Column("repo_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
608 608 clone_uri = Column("clone_uri", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
609 609 repo_type = Column("repo_type", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default=None)
610 610 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
611 611 private = Column("private", Boolean(), nullable=True, unique=None, default=None)
612 612 enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True)
613 613 enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True)
614 614 description = Column("description", String(10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
615 615 created_on = Column('created_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
616 616 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
617 617 landing_rev = Column("landing_revision", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default=None)
618 618 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
619 619 _locked = Column("locked", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
620 620
621 621 fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None)
622 622 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None)
623 623
624 624 user = relationship('User')
625 625 fork = relationship('Repository', remote_side=repo_id)
626 626 group = relationship('RepoGroup')
627 627 repo_to_perm = relationship('UserRepoToPerm', cascade='all', order_by='UserRepoToPerm.repo_to_perm_id')
628 628 users_group_to_perm = relationship('UsersGroupRepoToPerm', cascade='all')
629 629 stats = relationship('Statistics', cascade='all', uselist=False)
630 630
631 631 followers = relationship('UserFollowing',
632 632 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
633 633 cascade='all')
634 634
635 635 logs = relationship('UserLog')
636 636 comments = relationship('ChangesetComment', cascade="all, delete, delete-orphan")
637 637
638 638 pull_requests_org = relationship('PullRequest',
639 639 primaryjoin='PullRequest.org_repo_id==Repository.repo_id',
640 640 cascade="all, delete, delete-orphan")
641 641
642 642 pull_requests_other = relationship('PullRequest',
643 643 primaryjoin='PullRequest.other_repo_id==Repository.repo_id',
644 644 cascade="all, delete, delete-orphan")
645 645
646 646 def __unicode__(self):
647 647 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
648 648 self.repo_name)
649 649
650 650 @hybrid_property
651 651 def locked(self):
652 652 # always should return [user_id, timelocked]
653 653 if self._locked:
654 654 _lock_info = self._locked.split(':')
655 655 return int(_lock_info[0]), _lock_info[1]
656 656 return [None, None]
657 657
658 658 @locked.setter
659 659 def locked(self, val):
660 660 if val and isinstance(val, (list, tuple)):
661 661 self._locked = ':'.join(map(str, val))
662 662 else:
663 663 self._locked = None
664 664
665 665 @classmethod
666 666 def url_sep(cls):
667 667 return URL_SEP
668 668
669 669 @classmethod
670 670 def get_by_repo_name(cls, repo_name):
671 671 q = Session().query(cls).filter(cls.repo_name == repo_name)
672 672 q = q.options(joinedload(Repository.fork))\
673 673 .options(joinedload(Repository.user))\
674 674 .options(joinedload(Repository.group))
675 675 return q.scalar()
676 676
677 677 @classmethod
678 678 def get_by_full_path(cls, repo_full_path):
679 679 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
680 680 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
681 681
682 682 @classmethod
683 683 def get_repo_forks(cls, repo_id):
684 684 return cls.query().filter(Repository.fork_id == repo_id)
685 685
686 686 @classmethod
687 687 def base_path(cls):
688 688 """
689 689 Returns base path when all repos are stored
690 690
691 691 :param cls:
692 692 """
693 693 q = Session().query(RhodeCodeUi)\
694 694 .filter(RhodeCodeUi.ui_key == cls.url_sep())
695 695 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
696 696 return q.one().ui_value
697 697
698 698 @property
699 699 def forks(self):
700 700 """
701 701 Return forks of this repo
702 702 """
703 703 return Repository.get_repo_forks(self.repo_id)
704 704
705 705 @property
706 706 def parent(self):
707 707 """
708 708 Returns fork parent
709 709 """
710 710 return self.fork
711 711
712 712 @property
713 713 def just_name(self):
714 714 return self.repo_name.split(Repository.url_sep())[-1]
715 715
716 716 @property
717 717 def groups_with_parents(self):
718 718 groups = []
719 719 if self.group is None:
720 720 return groups
721 721
722 722 cur_gr = self.group
723 723 groups.insert(0, cur_gr)
724 724 while 1:
725 725 gr = getattr(cur_gr, 'parent_group', None)
726 726 cur_gr = cur_gr.parent_group
727 727 if gr is None:
728 728 break
729 729 groups.insert(0, gr)
730 730
731 731 return groups
732 732
733 733 @property
734 734 def groups_and_repo(self):
735 735 return self.groups_with_parents, self.just_name
736 736
737 737 @LazyProperty
738 738 def repo_path(self):
739 739 """
740 740 Returns base full path for that repository means where it actually
741 741 exists on a filesystem
742 742 """
743 743 q = Session().query(RhodeCodeUi).filter(RhodeCodeUi.ui_key ==
744 744 Repository.url_sep())
745 745 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
746 746 return q.one().ui_value
747 747
748 748 @property
749 749 def repo_full_path(self):
750 750 p = [self.repo_path]
751 751 # we need to split the name by / since this is how we store the
752 752 # names in the database, but that eventually needs to be converted
753 753 # into a valid system path
754 754 p += self.repo_name.split(Repository.url_sep())
755 755 return os.path.join(*p)
756 756
757 757 @property
758 758 def cache_keys(self):
759 759 """
760 760 Returns associated cache keys for that repo
761 761 """
762 762 return CacheInvalidation.query()\
763 763 .filter(CacheInvalidation.cache_args == self.repo_name)\
764 764 .order_by(CacheInvalidation.cache_key)\
765 765 .all()
766 766
767 767 def get_new_name(self, repo_name):
768 768 """
769 769 returns new full repository name based on assigned group and new new
770 770
771 771 :param group_name:
772 772 """
773 773 path_prefix = self.group.full_path_splitted if self.group else []
774 774 return Repository.url_sep().join(path_prefix + [repo_name])
775 775
776 776 @property
777 777 def _ui(self):
778 778 """
779 779 Creates an db based ui object for this repository
780 780 """
781 781 from rhodecode.lib.utils import make_ui
782 782 return make_ui('db', clear_session=False)
783 783
784 784 @classmethod
785 785 def inject_ui(cls, repo, extras={}):
786 786 from rhodecode.lib.vcs.backends.hg import MercurialRepository
787 787 from rhodecode.lib.vcs.backends.git import GitRepository
788 788 required = (MercurialRepository, GitRepository)
789 789 if not isinstance(repo, required):
790 790 raise Exception('repo must be instance of %s' % required)
791 791
792 792 # inject ui extra param to log this action via push logger
793 793 for k, v in extras.items():
794 794 repo._repo.ui.setconfig('rhodecode_extras', k, v)
795 795
796 796 @classmethod
797 797 def is_valid(cls, repo_name):
798 798 """
799 799 returns True if given repo name is a valid filesystem repository
800 800
801 801 :param cls:
802 802 :param repo_name:
803 803 """
804 804 from rhodecode.lib.utils import is_valid_repo
805 805
806 806 return is_valid_repo(repo_name, cls.base_path())
807 807
808 808 def get_api_data(self):
809 809 """
810 810 Common function for generating repo api data
811 811
812 812 """
813 813 repo = self
814 814 data = dict(
815 815 repo_id=repo.repo_id,
816 816 repo_name=repo.repo_name,
817 817 repo_type=repo.repo_type,
818 818 clone_uri=repo.clone_uri,
819 819 private=repo.private,
820 820 created_on=repo.created_on,
821 821 description=repo.description,
822 822 landing_rev=repo.landing_rev,
823 823 owner=repo.user.username,
824 824 fork_of=repo.fork.repo_name if repo.fork else None
825 825 )
826 826
827 827 return data
828 828
829 829 @classmethod
830 830 def lock(cls, repo, user_id):
831 831 repo.locked = [user_id, time.time()]
832 832 Session().add(repo)
833 833 Session().commit()
834 834
835 835 @classmethod
836 836 def unlock(cls, repo):
837 837 repo.locked = None
838 838 Session().add(repo)
839 839 Session().commit()
840 840
841 841 @property
842 842 def last_db_change(self):
843 843 return self.updated_on
844 844
845 845 #==========================================================================
846 846 # SCM PROPERTIES
847 847 #==========================================================================
848 848
849 849 def get_changeset(self, rev=None):
850 850 return get_changeset_safe(self.scm_instance, rev)
851 851
852 852 def get_landing_changeset(self):
853 853 """
854 854 Returns landing changeset, or if that doesn't exist returns the tip
855 855 """
856 856 cs = self.get_changeset(self.landing_rev) or self.get_changeset()
857 857 return cs
858 858
859 859 def update_last_change(self, last_change=None):
860 860 if last_change is None:
861 861 last_change = datetime.datetime.now()
862 862 if self.updated_on is None or self.updated_on != last_change:
863 863 log.debug('updated repo %s with new date %s' % (self, last_change))
864 864 self.updated_on = last_change
865 865 Session().add(self)
866 866 Session().commit()
867 867
868 868 @property
869 869 def tip(self):
870 870 return self.get_changeset('tip')
871 871
872 872 @property
873 873 def author(self):
874 874 return self.tip.author
875 875
876 876 @property
877 877 def last_change(self):
878 878 return self.scm_instance.last_change
879 879
880 880 def get_comments(self, revisions=None):
881 881 """
882 882 Returns comments for this repository grouped by revisions
883 883
884 884 :param revisions: filter query by revisions only
885 885 """
886 886 cmts = ChangesetComment.query()\
887 887 .filter(ChangesetComment.repo == self)
888 888 if revisions:
889 889 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
890 890 grouped = defaultdict(list)
891 891 for cmt in cmts.all():
892 892 grouped[cmt.revision].append(cmt)
893 893 return grouped
894 894
895 895 def statuses(self, revisions=None):
896 896 """
897 897 Returns statuses for this repository
898 898
899 899 :param revisions: list of revisions to get statuses for
900 900 :type revisions: list
901 901 """
902 902
903 903 statuses = ChangesetStatus.query()\
904 904 .filter(ChangesetStatus.repo == self)\
905 905 .filter(ChangesetStatus.version == 0)
906 906 if revisions:
907 907 statuses = statuses.filter(ChangesetStatus.revision.in_(revisions))
908 908 grouped = {}
909 909
910 910 #maybe we have open new pullrequest without a status ?
911 911 stat = ChangesetStatus.STATUS_UNDER_REVIEW
912 912 status_lbl = ChangesetStatus.get_status_lbl(stat)
913 913 for pr in PullRequest.query().filter(PullRequest.org_repo == self).all():
914 914 for rev in pr.revisions:
915 915 pr_id = pr.pull_request_id
916 916 pr_repo = pr.other_repo.repo_name
917 917 grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
918 918
919 919 for stat in statuses.all():
920 920 pr_id = pr_repo = None
921 921 if stat.pull_request:
922 922 pr_id = stat.pull_request.pull_request_id
923 923 pr_repo = stat.pull_request.other_repo.repo_name
924 924 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
925 925 pr_id, pr_repo]
926 926 return grouped
927 927
928 928 #==========================================================================
929 929 # SCM CACHE INSTANCE
930 930 #==========================================================================
931 931
932 932 @property
933 933 def invalidate(self):
934 934 return CacheInvalidation.invalidate(self.repo_name)
935 935
936 936 def set_invalidate(self):
937 937 """
938 938 set a cache for invalidation for this instance
939 939 """
940 940 CacheInvalidation.set_invalidate(repo_name=self.repo_name)
941 941
942 942 @LazyProperty
943 943 def scm_instance(self):
944 return self.scm_instance_cached()
944 import rhodecode
945 full_cache = str2bool(rhodecode.CONFIG.get('vcs_full_cache'))
946 if full_cache:
947 return self.scm_instance_cached()
945 948 return self.__get_instance()
946 949
947 950 def scm_instance_cached(self, cache_map=None):
948 951 @cache_region('long_term')
949 952 def _c(repo_name):
950 953 return self.__get_instance()
951 954 rn = self.repo_name
952 955 log.debug('Getting cached instance of repo')
953 956
954 957 if cache_map:
955 958 # get using prefilled cache_map
956 959 invalidate_repo = cache_map[self.repo_name]
957 960 if invalidate_repo:
958 961 invalidate_repo = (None if invalidate_repo.cache_active
959 962 else invalidate_repo)
960 963 else:
961 964 # get from invalidate
962 965 invalidate_repo = self.invalidate
963 966
964 967 if invalidate_repo is not None:
965 968 region_invalidate(_c, None, rn)
966 969 # update our cache
967 970 CacheInvalidation.set_valid(invalidate_repo.cache_key)
968 971 return _c(rn)
969 972
970 973 def __get_instance(self):
971 974 repo_full_path = self.repo_full_path
972 975 try:
973 976 alias = get_scm(repo_full_path)[0]
974 977 log.debug('Creating instance of %s repository' % alias)
975 978 backend = get_backend(alias)
976 979 except VCSError:
977 980 log.error(traceback.format_exc())
978 981 log.error('Perhaps this repository is in db and not in '
979 982 'filesystem run rescan repositories with '
980 983 '"destroy old data " option from admin panel')
981 984 return
982 985
983 986 if alias == 'hg':
984 987
985 988 repo = backend(safe_str(repo_full_path), create=False,
986 989 baseui=self._ui)
987 990 # skip hidden web repository
988 991 if repo._get_hidden():
989 992 return
990 993 else:
991 994 repo = backend(repo_full_path, create=False)
992 995
993 996 return repo
994 997
995 998
996 999 class RepoGroup(Base, BaseModel):
997 1000 __tablename__ = 'groups'
998 1001 __table_args__ = (
999 1002 UniqueConstraint('group_name', 'group_parent_id'),
1000 1003 CheckConstraint('group_id != group_parent_id'),
1001 1004 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1002 1005 'mysql_charset': 'utf8'},
1003 1006 )
1004 1007 __mapper_args__ = {'order_by': 'group_name'}
1005 1008
1006 1009 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1007 1010 group_name = Column("group_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
1008 1011 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
1009 1012 group_description = Column("group_description", String(10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1010 1013 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
1011 1014
1012 1015 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
1013 1016 users_group_to_perm = relationship('UsersGroupRepoGroupToPerm', cascade='all')
1014 1017
1015 1018 parent_group = relationship('RepoGroup', remote_side=group_id)
1016 1019
1017 1020 def __init__(self, group_name='', parent_group=None):
1018 1021 self.group_name = group_name
1019 1022 self.parent_group = parent_group
1020 1023
1021 1024 def __unicode__(self):
1022 1025 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.group_id,
1023 1026 self.group_name)
1024 1027
1025 1028 @classmethod
1026 1029 def groups_choices(cls, check_perms=False):
1027 1030 from webhelpers.html import literal as _literal
1028 1031 from rhodecode.model.scm import ScmModel
1029 1032 groups = cls.query().all()
1030 1033 if check_perms:
1031 1034 #filter group user have access to, it's done
1032 1035 #magically inside ScmModel based on current user
1033 1036 groups = ScmModel().get_repos_groups(groups)
1034 1037 repo_groups = [('', '')]
1035 1038 sep = ' &raquo; '
1036 1039 _name = lambda k: _literal(sep.join(k))
1037 1040
1038 1041 repo_groups.extend([(x.group_id, _name(x.full_path_splitted))
1039 1042 for x in groups])
1040 1043
1041 1044 repo_groups = sorted(repo_groups, key=lambda t: t[1].split(sep)[0])
1042 1045 return repo_groups
1043 1046
1044 1047 @classmethod
1045 1048 def url_sep(cls):
1046 1049 return URL_SEP
1047 1050
1048 1051 @classmethod
1049 1052 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
1050 1053 if case_insensitive:
1051 1054 gr = cls.query()\
1052 1055 .filter(cls.group_name.ilike(group_name))
1053 1056 else:
1054 1057 gr = cls.query()\
1055 1058 .filter(cls.group_name == group_name)
1056 1059 if cache:
1057 1060 gr = gr.options(FromCache(
1058 1061 "sql_cache_short",
1059 1062 "get_group_%s" % _hash_key(group_name)
1060 1063 )
1061 1064 )
1062 1065 return gr.scalar()
1063 1066
1064 1067 @property
1065 1068 def parents(self):
1066 1069 parents_recursion_limit = 5
1067 1070 groups = []
1068 1071 if self.parent_group is None:
1069 1072 return groups
1070 1073 cur_gr = self.parent_group
1071 1074 groups.insert(0, cur_gr)
1072 1075 cnt = 0
1073 1076 while 1:
1074 1077 cnt += 1
1075 1078 gr = getattr(cur_gr, 'parent_group', None)
1076 1079 cur_gr = cur_gr.parent_group
1077 1080 if gr is None:
1078 1081 break
1079 1082 if cnt == parents_recursion_limit:
1080 1083 # this will prevent accidental infinit loops
1081 1084 log.error('group nested more than %s' %
1082 1085 parents_recursion_limit)
1083 1086 break
1084 1087
1085 1088 groups.insert(0, gr)
1086 1089 return groups
1087 1090
1088 1091 @property
1089 1092 def children(self):
1090 1093 return RepoGroup.query().filter(RepoGroup.parent_group == self)
1091 1094
1092 1095 @property
1093 1096 def name(self):
1094 1097 return self.group_name.split(RepoGroup.url_sep())[-1]
1095 1098
1096 1099 @property
1097 1100 def full_path(self):
1098 1101 return self.group_name
1099 1102
1100 1103 @property
1101 1104 def full_path_splitted(self):
1102 1105 return self.group_name.split(RepoGroup.url_sep())
1103 1106
1104 1107 @property
1105 1108 def repositories(self):
1106 1109 return Repository.query()\
1107 1110 .filter(Repository.group == self)\
1108 1111 .order_by(Repository.repo_name)
1109 1112
1110 1113 @property
1111 1114 def repositories_recursive_count(self):
1112 1115 cnt = self.repositories.count()
1113 1116
1114 1117 def children_count(group):
1115 1118 cnt = 0
1116 1119 for child in group.children:
1117 1120 cnt += child.repositories.count()
1118 1121 cnt += children_count(child)
1119 1122 return cnt
1120 1123
1121 1124 return cnt + children_count(self)
1122 1125
1123 1126 def recursive_groups_and_repos(self):
1124 1127 """
1125 1128 Recursive return all groups, with repositories in those groups
1126 1129 """
1127 1130 all_ = []
1128 1131
1129 1132 def _get_members(root_gr):
1130 1133 for r in root_gr.repositories:
1131 1134 all_.append(r)
1132 1135 childs = root_gr.children.all()
1133 1136 if childs:
1134 1137 for gr in childs:
1135 1138 all_.append(gr)
1136 1139 _get_members(gr)
1137 1140
1138 1141 _get_members(self)
1139 1142 return [self] + all_
1140 1143
1141 1144 def get_new_name(self, group_name):
1142 1145 """
1143 1146 returns new full group name based on parent and new name
1144 1147
1145 1148 :param group_name:
1146 1149 """
1147 1150 path_prefix = (self.parent_group.full_path_splitted if
1148 1151 self.parent_group else [])
1149 1152 return RepoGroup.url_sep().join(path_prefix + [group_name])
1150 1153
1151 1154
1152 1155 class Permission(Base, BaseModel):
1153 1156 __tablename__ = 'permissions'
1154 1157 __table_args__ = (
1155 1158 Index('p_perm_name_idx', 'permission_name'),
1156 1159 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1157 1160 'mysql_charset': 'utf8'},
1158 1161 )
1159 1162 PERMS = [
1160 1163 ('repository.none', _('Repository no access')),
1161 1164 ('repository.read', _('Repository read access')),
1162 1165 ('repository.write', _('Repository write access')),
1163 1166 ('repository.admin', _('Repository admin access')),
1164 1167
1165 1168 ('group.none', _('Repositories Group no access')),
1166 1169 ('group.read', _('Repositories Group read access')),
1167 1170 ('group.write', _('Repositories Group write access')),
1168 1171 ('group.admin', _('Repositories Group admin access')),
1169 1172
1170 1173 ('hg.admin', _('RhodeCode Administrator')),
1171 1174 ('hg.create.none', _('Repository creation disabled')),
1172 1175 ('hg.create.repository', _('Repository creation enabled')),
1173 1176 ('hg.fork.none', _('Repository forking disabled')),
1174 1177 ('hg.fork.repository', _('Repository forking enabled')),
1175 1178 ('hg.register.none', _('Register disabled')),
1176 1179 ('hg.register.manual_activate', _('Register new user with RhodeCode '
1177 1180 'with manual activation')),
1178 1181
1179 1182 ('hg.register.auto_activate', _('Register new user with RhodeCode '
1180 1183 'with auto activation')),
1181 1184 ]
1182 1185
1183 1186 # defines which permissions are more important higher the more important
1184 1187 PERM_WEIGHTS = {
1185 1188 'repository.none': 0,
1186 1189 'repository.read': 1,
1187 1190 'repository.write': 3,
1188 1191 'repository.admin': 4,
1189 1192
1190 1193 'group.none': 0,
1191 1194 'group.read': 1,
1192 1195 'group.write': 3,
1193 1196 'group.admin': 4,
1194 1197
1195 1198 'hg.fork.none': 0,
1196 1199 'hg.fork.repository': 1,
1197 1200 'hg.create.none': 0,
1198 1201 'hg.create.repository':1
1199 1202 }
1200 1203
1201 1204 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1202 1205 permission_name = Column("permission_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1203 1206 permission_longname = Column("permission_longname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1204 1207
1205 1208 def __unicode__(self):
1206 1209 return u"<%s('%s:%s')>" % (
1207 1210 self.__class__.__name__, self.permission_id, self.permission_name
1208 1211 )
1209 1212
1210 1213 @classmethod
1211 1214 def get_by_key(cls, key):
1212 1215 return cls.query().filter(cls.permission_name == key).scalar()
1213 1216
1214 1217 @classmethod
1215 1218 def get_default_perms(cls, default_user_id):
1216 1219 q = Session().query(UserRepoToPerm, Repository, cls)\
1217 1220 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
1218 1221 .join((cls, UserRepoToPerm.permission_id == cls.permission_id))\
1219 1222 .filter(UserRepoToPerm.user_id == default_user_id)
1220 1223
1221 1224 return q.all()
1222 1225
1223 1226 @classmethod
1224 1227 def get_default_group_perms(cls, default_user_id):
1225 1228 q = Session().query(UserRepoGroupToPerm, RepoGroup, cls)\
1226 1229 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
1227 1230 .join((cls, UserRepoGroupToPerm.permission_id == cls.permission_id))\
1228 1231 .filter(UserRepoGroupToPerm.user_id == default_user_id)
1229 1232
1230 1233 return q.all()
1231 1234
1232 1235
1233 1236 class UserRepoToPerm(Base, BaseModel):
1234 1237 __tablename__ = 'repo_to_perm'
1235 1238 __table_args__ = (
1236 1239 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
1237 1240 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1238 1241 'mysql_charset': 'utf8'}
1239 1242 )
1240 1243 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1241 1244 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1242 1245 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1243 1246 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1244 1247
1245 1248 user = relationship('User')
1246 1249 repository = relationship('Repository')
1247 1250 permission = relationship('Permission')
1248 1251
1249 1252 @classmethod
1250 1253 def create(cls, user, repository, permission):
1251 1254 n = cls()
1252 1255 n.user = user
1253 1256 n.repository = repository
1254 1257 n.permission = permission
1255 1258 Session().add(n)
1256 1259 return n
1257 1260
1258 1261 def __unicode__(self):
1259 1262 return u'<user:%s => %s >' % (self.user, self.repository)
1260 1263
1261 1264
1262 1265 class UserToPerm(Base, BaseModel):
1263 1266 __tablename__ = 'user_to_perm'
1264 1267 __table_args__ = (
1265 1268 UniqueConstraint('user_id', 'permission_id'),
1266 1269 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1267 1270 'mysql_charset': 'utf8'}
1268 1271 )
1269 1272 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1270 1273 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1271 1274 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1272 1275
1273 1276 user = relationship('User')
1274 1277 permission = relationship('Permission', lazy='joined')
1275 1278
1276 1279
1277 1280 class UsersGroupRepoToPerm(Base, BaseModel):
1278 1281 __tablename__ = 'users_group_repo_to_perm'
1279 1282 __table_args__ = (
1280 1283 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
1281 1284 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1282 1285 'mysql_charset': 'utf8'}
1283 1286 )
1284 1287 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1285 1288 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1286 1289 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1287 1290 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1288 1291
1289 1292 users_group = relationship('UsersGroup')
1290 1293 permission = relationship('Permission')
1291 1294 repository = relationship('Repository')
1292 1295
1293 1296 @classmethod
1294 1297 def create(cls, users_group, repository, permission):
1295 1298 n = cls()
1296 1299 n.users_group = users_group
1297 1300 n.repository = repository
1298 1301 n.permission = permission
1299 1302 Session().add(n)
1300 1303 return n
1301 1304
1302 1305 def __unicode__(self):
1303 1306 return u'<userGroup:%s => %s >' % (self.users_group, self.repository)
1304 1307
1305 1308
1306 1309 class UsersGroupToPerm(Base, BaseModel):
1307 1310 __tablename__ = 'users_group_to_perm'
1308 1311 __table_args__ = (
1309 1312 UniqueConstraint('users_group_id', 'permission_id',),
1310 1313 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1311 1314 'mysql_charset': 'utf8'}
1312 1315 )
1313 1316 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1314 1317 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1315 1318 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1316 1319
1317 1320 users_group = relationship('UsersGroup')
1318 1321 permission = relationship('Permission')
1319 1322
1320 1323
1321 1324 class UserRepoGroupToPerm(Base, BaseModel):
1322 1325 __tablename__ = 'user_repo_group_to_perm'
1323 1326 __table_args__ = (
1324 1327 UniqueConstraint('user_id', 'group_id', 'permission_id'),
1325 1328 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1326 1329 'mysql_charset': 'utf8'}
1327 1330 )
1328 1331
1329 1332 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1330 1333 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1331 1334 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
1332 1335 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1333 1336
1334 1337 user = relationship('User')
1335 1338 group = relationship('RepoGroup')
1336 1339 permission = relationship('Permission')
1337 1340
1338 1341
1339 1342 class UsersGroupRepoGroupToPerm(Base, BaseModel):
1340 1343 __tablename__ = 'users_group_repo_group_to_perm'
1341 1344 __table_args__ = (
1342 1345 UniqueConstraint('users_group_id', 'group_id'),
1343 1346 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1344 1347 'mysql_charset': 'utf8'}
1345 1348 )
1346 1349
1347 1350 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)
1348 1351 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1349 1352 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
1350 1353 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1351 1354
1352 1355 users_group = relationship('UsersGroup')
1353 1356 permission = relationship('Permission')
1354 1357 group = relationship('RepoGroup')
1355 1358
1356 1359
1357 1360 class Statistics(Base, BaseModel):
1358 1361 __tablename__ = 'statistics'
1359 1362 __table_args__ = (
1360 1363 UniqueConstraint('repository_id'),
1361 1364 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1362 1365 'mysql_charset': 'utf8'}
1363 1366 )
1364 1367 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1365 1368 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
1366 1369 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
1367 1370 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
1368 1371 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
1369 1372 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
1370 1373
1371 1374 repository = relationship('Repository', single_parent=True)
1372 1375
1373 1376
1374 1377 class UserFollowing(Base, BaseModel):
1375 1378 __tablename__ = 'user_followings'
1376 1379 __table_args__ = (
1377 1380 UniqueConstraint('user_id', 'follows_repository_id'),
1378 1381 UniqueConstraint('user_id', 'follows_user_id'),
1379 1382 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1380 1383 'mysql_charset': 'utf8'}
1381 1384 )
1382 1385
1383 1386 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1384 1387 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1385 1388 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
1386 1389 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1387 1390 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
1388 1391
1389 1392 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
1390 1393
1391 1394 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
1392 1395 follows_repository = relationship('Repository', order_by='Repository.repo_name')
1393 1396
1394 1397 @classmethod
1395 1398 def get_repo_followers(cls, repo_id):
1396 1399 return cls.query().filter(cls.follows_repo_id == repo_id)
1397 1400
1398 1401
1399 1402 class CacheInvalidation(Base, BaseModel):
1400 1403 __tablename__ = 'cache_invalidation'
1401 1404 __table_args__ = (
1402 1405 UniqueConstraint('cache_key'),
1403 1406 Index('key_idx', 'cache_key'),
1404 1407 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1405 1408 'mysql_charset': 'utf8'},
1406 1409 )
1407 1410 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1408 1411 cache_key = Column("cache_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1409 1412 cache_args = Column("cache_args", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1410 1413 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
1411 1414
1412 1415 def __init__(self, cache_key, cache_args=''):
1413 1416 self.cache_key = cache_key
1414 1417 self.cache_args = cache_args
1415 1418 self.cache_active = False
1416 1419
1417 1420 def __unicode__(self):
1418 1421 return u"<%s('%s:%s')>" % (self.__class__.__name__,
1419 1422 self.cache_id, self.cache_key)
1420 1423
1421 1424 @property
1422 1425 def prefix(self):
1423 1426 _split = self.cache_key.split(self.cache_args, 1)
1424 1427 if _split and len(_split) == 2:
1425 1428 return _split[0]
1426 1429 return ''
1427 1430
1428 1431 @classmethod
1429 1432 def clear_cache(cls):
1430 1433 cls.query().delete()
1431 1434
1432 1435 @classmethod
1433 1436 def _get_key(cls, key):
1434 1437 """
1435 1438 Wrapper for generating a key, together with a prefix
1436 1439
1437 1440 :param key:
1438 1441 """
1439 1442 import rhodecode
1440 1443 prefix = ''
1441 1444 org_key = key
1442 1445 iid = rhodecode.CONFIG.get('instance_id')
1443 1446 if iid:
1444 1447 prefix = iid
1445 1448
1446 1449 return "%s%s" % (prefix, key), prefix, org_key
1447 1450
1448 1451 @classmethod
1449 1452 def get_by_key(cls, key):
1450 1453 return cls.query().filter(cls.cache_key == key).scalar()
1451 1454
1452 1455 @classmethod
1453 1456 def get_by_repo_name(cls, repo_name):
1454 1457 return cls.query().filter(cls.cache_args == repo_name).all()
1455 1458
1456 1459 @classmethod
1457 1460 def _get_or_create_key(cls, key, repo_name, commit=True):
1458 1461 inv_obj = Session().query(cls).filter(cls.cache_key == key).scalar()
1459 1462 if not inv_obj:
1460 1463 try:
1461 1464 inv_obj = CacheInvalidation(key, repo_name)
1462 1465 Session().add(inv_obj)
1463 1466 if commit:
1464 1467 Session().commit()
1465 1468 except Exception:
1466 1469 log.error(traceback.format_exc())
1467 1470 Session().rollback()
1468 1471 return inv_obj
1469 1472
1470 1473 @classmethod
1471 1474 def invalidate(cls, key):
1472 1475 """
1473 1476 Returns Invalidation object if this given key should be invalidated
1474 1477 None otherwise. `cache_active = False` means that this cache
1475 1478 state is not valid and needs to be invalidated
1476 1479
1477 1480 :param key:
1478 1481 """
1479 1482 repo_name = key
1480 1483 repo_name = remove_suffix(repo_name, '_README')
1481 1484 repo_name = remove_suffix(repo_name, '_RSS')
1482 1485 repo_name = remove_suffix(repo_name, '_ATOM')
1483 1486
1484 1487 # adds instance prefix
1485 1488 key, _prefix, _org_key = cls._get_key(key)
1486 1489 inv = cls._get_or_create_key(key, repo_name)
1487 1490
1488 1491 if inv and inv.cache_active is False:
1489 1492 return inv
1490 1493
1491 1494 @classmethod
1492 1495 def set_invalidate(cls, key=None, repo_name=None):
1493 1496 """
1494 1497 Mark this Cache key for invalidation, either by key or whole
1495 1498 cache sets based on repo_name
1496 1499
1497 1500 :param key:
1498 1501 """
1499 1502 if key:
1500 1503 key, _prefix, _org_key = cls._get_key(key)
1501 1504 inv_objs = Session().query(cls).filter(cls.cache_key == key).all()
1502 1505 elif repo_name:
1503 1506 inv_objs = Session().query(cls).filter(cls.cache_args == repo_name).all()
1504 1507
1505 1508 log.debug('marking %s key[s] for invalidation based on key=%s,repo_name=%s'
1506 1509 % (len(inv_objs), key, repo_name))
1507 1510 try:
1508 1511 for inv_obj in inv_objs:
1509 1512 inv_obj.cache_active = False
1510 1513 Session().add(inv_obj)
1511 1514 Session().commit()
1512 1515 except Exception:
1513 1516 log.error(traceback.format_exc())
1514 1517 Session().rollback()
1515 1518
1516 1519 @classmethod
1517 1520 def set_valid(cls, key):
1518 1521 """
1519 1522 Mark this cache key as active and currently cached
1520 1523
1521 1524 :param key:
1522 1525 """
1523 1526 inv_obj = cls.get_by_key(key)
1524 1527 inv_obj.cache_active = True
1525 1528 Session().add(inv_obj)
1526 1529 Session().commit()
1527 1530
1528 1531 @classmethod
1529 1532 def get_cache_map(cls):
1530 1533
1531 1534 class cachemapdict(dict):
1532 1535
1533 1536 def __init__(self, *args, **kwargs):
1534 1537 fixkey = kwargs.get('fixkey')
1535 1538 if fixkey:
1536 1539 del kwargs['fixkey']
1537 1540 self.fixkey = fixkey
1538 1541 super(cachemapdict, self).__init__(*args, **kwargs)
1539 1542
1540 1543 def __getattr__(self, name):
1541 1544 key = name
1542 1545 if self.fixkey:
1543 1546 key, _prefix, _org_key = cls._get_key(key)
1544 1547 if key in self.__dict__:
1545 1548 return self.__dict__[key]
1546 1549 else:
1547 1550 return self[key]
1548 1551
1549 1552 def __getitem__(self, key):
1550 1553 if self.fixkey:
1551 1554 key, _prefix, _org_key = cls._get_key(key)
1552 1555 try:
1553 1556 return super(cachemapdict, self).__getitem__(key)
1554 1557 except KeyError:
1555 1558 return
1556 1559
1557 1560 cache_map = cachemapdict(fixkey=True)
1558 1561 for obj in cls.query().all():
1559 1562 cache_map[obj.cache_key] = cachemapdict(obj.get_dict())
1560 1563 return cache_map
1561 1564
1562 1565
1563 1566 class ChangesetComment(Base, BaseModel):
1564 1567 __tablename__ = 'changeset_comments'
1565 1568 __table_args__ = (
1566 1569 Index('cc_revision_idx', 'revision'),
1567 1570 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1568 1571 'mysql_charset': 'utf8'},
1569 1572 )
1570 1573 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
1571 1574 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1572 1575 revision = Column('revision', String(40), nullable=True)
1573 1576 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
1574 1577 line_no = Column('line_no', Unicode(10), nullable=True)
1575 1578 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
1576 1579 f_path = Column('f_path', Unicode(1000), nullable=True)
1577 1580 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
1578 1581 text = Column('text', UnicodeText(25000), nullable=False)
1579 1582 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1580 1583 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1581 1584
1582 1585 author = relationship('User', lazy='joined')
1583 1586 repo = relationship('Repository')
1584 1587 status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan")
1585 1588 pull_request = relationship('PullRequest', lazy='joined')
1586 1589
1587 1590 @classmethod
1588 1591 def get_users(cls, revision=None, pull_request_id=None):
1589 1592 """
1590 1593 Returns user associated with this ChangesetComment. ie those
1591 1594 who actually commented
1592 1595
1593 1596 :param cls:
1594 1597 :param revision:
1595 1598 """
1596 1599 q = Session().query(User)\
1597 1600 .join(ChangesetComment.author)
1598 1601 if revision:
1599 1602 q = q.filter(cls.revision == revision)
1600 1603 elif pull_request_id:
1601 1604 q = q.filter(cls.pull_request_id == pull_request_id)
1602 1605 return q.all()
1603 1606
1604 1607
1605 1608 class ChangesetStatus(Base, BaseModel):
1606 1609 __tablename__ = 'changeset_statuses'
1607 1610 __table_args__ = (
1608 1611 Index('cs_revision_idx', 'revision'),
1609 1612 Index('cs_version_idx', 'version'),
1610 1613 UniqueConstraint('repo_id', 'revision', 'version'),
1611 1614 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1612 1615 'mysql_charset': 'utf8'}
1613 1616 )
1614 1617 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
1615 1618 STATUS_APPROVED = 'approved'
1616 1619 STATUS_REJECTED = 'rejected'
1617 1620 STATUS_UNDER_REVIEW = 'under_review'
1618 1621
1619 1622 STATUSES = [
1620 1623 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
1621 1624 (STATUS_APPROVED, _("Approved")),
1622 1625 (STATUS_REJECTED, _("Rejected")),
1623 1626 (STATUS_UNDER_REVIEW, _("Under Review")),
1624 1627 ]
1625 1628
1626 1629 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
1627 1630 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1628 1631 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
1629 1632 revision = Column('revision', String(40), nullable=False)
1630 1633 status = Column('status', String(128), nullable=False, default=DEFAULT)
1631 1634 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
1632 1635 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
1633 1636 version = Column('version', Integer(), nullable=False, default=0)
1634 1637 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
1635 1638
1636 1639 author = relationship('User', lazy='joined')
1637 1640 repo = relationship('Repository')
1638 1641 comment = relationship('ChangesetComment', lazy='joined')
1639 1642 pull_request = relationship('PullRequest', lazy='joined')
1640 1643
1641 1644 def __unicode__(self):
1642 1645 return u"<%s('%s:%s')>" % (
1643 1646 self.__class__.__name__,
1644 1647 self.status, self.author
1645 1648 )
1646 1649
1647 1650 @classmethod
1648 1651 def get_status_lbl(cls, value):
1649 1652 return dict(cls.STATUSES).get(value)
1650 1653
1651 1654 @property
1652 1655 def status_lbl(self):
1653 1656 return ChangesetStatus.get_status_lbl(self.status)
1654 1657
1655 1658
1656 1659 class PullRequest(Base, BaseModel):
1657 1660 __tablename__ = 'pull_requests'
1658 1661 __table_args__ = (
1659 1662 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1660 1663 'mysql_charset': 'utf8'},
1661 1664 )
1662 1665
1663 1666 STATUS_NEW = u'new'
1664 1667 STATUS_OPEN = u'open'
1665 1668 STATUS_CLOSED = u'closed'
1666 1669
1667 1670 pull_request_id = Column('pull_request_id', Integer(), nullable=False, primary_key=True)
1668 1671 title = Column('title', Unicode(256), nullable=True)
1669 1672 description = Column('description', UnicodeText(10240), nullable=True)
1670 1673 status = Column('status', Unicode(256), nullable=False, default=STATUS_NEW)
1671 1674 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1672 1675 updated_on = Column('updated_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1673 1676 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
1674 1677 _revisions = Column('revisions', UnicodeText(20500)) # 500 revisions max
1675 1678 org_repo_id = Column('org_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1676 1679 org_ref = Column('org_ref', Unicode(256), nullable=False)
1677 1680 other_repo_id = Column('other_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1678 1681 other_ref = Column('other_ref', Unicode(256), nullable=False)
1679 1682
1680 1683 @hybrid_property
1681 1684 def revisions(self):
1682 1685 return self._revisions.split(':')
1683 1686
1684 1687 @revisions.setter
1685 1688 def revisions(self, val):
1686 1689 self._revisions = ':'.join(val)
1687 1690
1688 1691 author = relationship('User', lazy='joined')
1689 1692 reviewers = relationship('PullRequestReviewers',
1690 1693 cascade="all, delete, delete-orphan")
1691 1694 org_repo = relationship('Repository', primaryjoin='PullRequest.org_repo_id==Repository.repo_id')
1692 1695 other_repo = relationship('Repository', primaryjoin='PullRequest.other_repo_id==Repository.repo_id')
1693 1696 statuses = relationship('ChangesetStatus')
1694 1697 comments = relationship('ChangesetComment',
1695 1698 cascade="all, delete, delete-orphan")
1696 1699
1697 1700 def is_closed(self):
1698 1701 return self.status == self.STATUS_CLOSED
1699 1702
1700 1703 def __json__(self):
1701 1704 return dict(
1702 1705 revisions=self.revisions
1703 1706 )
1704 1707
1705 1708
1706 1709 class PullRequestReviewers(Base, BaseModel):
1707 1710 __tablename__ = 'pull_request_reviewers'
1708 1711 __table_args__ = (
1709 1712 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1710 1713 'mysql_charset': 'utf8'},
1711 1714 )
1712 1715
1713 1716 def __init__(self, user=None, pull_request=None):
1714 1717 self.user = user
1715 1718 self.pull_request = pull_request
1716 1719
1717 1720 pull_requests_reviewers_id = Column('pull_requests_reviewers_id', Integer(), nullable=False, primary_key=True)
1718 1721 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=False)
1719 1722 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
1720 1723
1721 1724 user = relationship('User')
1722 1725 pull_request = relationship('PullRequest')
1723 1726
1724 1727
1725 1728 class Notification(Base, BaseModel):
1726 1729 __tablename__ = 'notifications'
1727 1730 __table_args__ = (
1728 1731 Index('notification_type_idx', 'type'),
1729 1732 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1730 1733 'mysql_charset': 'utf8'},
1731 1734 )
1732 1735
1733 1736 TYPE_CHANGESET_COMMENT = u'cs_comment'
1734 1737 TYPE_MESSAGE = u'message'
1735 1738 TYPE_MENTION = u'mention'
1736 1739 TYPE_REGISTRATION = u'registration'
1737 1740 TYPE_PULL_REQUEST = u'pull_request'
1738 1741 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
1739 1742
1740 1743 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
1741 1744 subject = Column('subject', Unicode(512), nullable=True)
1742 1745 body = Column('body', UnicodeText(50000), nullable=True)
1743 1746 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
1744 1747 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1745 1748 type_ = Column('type', Unicode(256))
1746 1749
1747 1750 created_by_user = relationship('User')
1748 1751 notifications_to_users = relationship('UserNotification', lazy='joined',
1749 1752 cascade="all, delete, delete-orphan")
1750 1753
1751 1754 @property
1752 1755 def recipients(self):
1753 1756 return [x.user for x in UserNotification.query()\
1754 1757 .filter(UserNotification.notification == self)\
1755 1758 .order_by(UserNotification.user_id.asc()).all()]
1756 1759
1757 1760 @classmethod
1758 1761 def create(cls, created_by, subject, body, recipients, type_=None):
1759 1762 if type_ is None:
1760 1763 type_ = Notification.TYPE_MESSAGE
1761 1764
1762 1765 notification = cls()
1763 1766 notification.created_by_user = created_by
1764 1767 notification.subject = subject
1765 1768 notification.body = body
1766 1769 notification.type_ = type_
1767 1770 notification.created_on = datetime.datetime.now()
1768 1771
1769 1772 for u in recipients:
1770 1773 assoc = UserNotification()
1771 1774 assoc.notification = notification
1772 1775 u.notifications.append(assoc)
1773 1776 Session().add(notification)
1774 1777 return notification
1775 1778
1776 1779 @property
1777 1780 def description(self):
1778 1781 from rhodecode.model.notification import NotificationModel
1779 1782 return NotificationModel().make_description(self)
1780 1783
1781 1784
1782 1785 class UserNotification(Base, BaseModel):
1783 1786 __tablename__ = 'user_to_notification'
1784 1787 __table_args__ = (
1785 1788 UniqueConstraint('user_id', 'notification_id'),
1786 1789 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1787 1790 'mysql_charset': 'utf8'}
1788 1791 )
1789 1792 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
1790 1793 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
1791 1794 read = Column('read', Boolean, default=False)
1792 1795 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
1793 1796
1794 1797 user = relationship('User', lazy="joined")
1795 1798 notification = relationship('Notification', lazy="joined",
1796 1799 order_by=lambda: Notification.created_on.desc(),)
1797 1800
1798 1801 def mark_as_read(self):
1799 1802 self.read = True
1800 1803 Session().add(self)
1801 1804
1802 1805
1803 1806 class DbMigrateVersion(Base, BaseModel):
1804 1807 __tablename__ = 'db_migrate_version'
1805 1808 __table_args__ = (
1806 1809 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1807 1810 'mysql_charset': 'utf8'},
1808 1811 )
1809 1812 repository_id = Column('repository_id', String(250), primary_key=True)
1810 1813 repository_path = Column('repository_path', Text)
1811 1814 version = Column('version', Integer)
General Comments 0
You need to be logged in to leave comments. Login now