##// END OF EJS Templates
auto-healing of permissions for default user after upgrading from some old versions.
marcink -
r2798:091e99b2 beta
parent child Browse files
Show More
@@ -1,731 +1,737 b''
1 .. _changelog:
1 .. _changelog:
2
2
3 =========
3 =========
4 Changelog
4 Changelog
5 =========
5 =========
6
6
7
7
8 1.4.1 (**2012-XX-XX**)
8 1.4.1 (**2012-09-04**)
9 ----------------------
9 ----------------------
10
10
11 :status: in-progress
11 :status: in-progress
12 :branch: beta
12 :branch: beta
13
13
14 news
14 news
15 ++++
15 ++++
16
16
17 - always put a comment about code-review status change even if user send
17 - always put a comment about code-review status change even if user send
18 empty data
18 empty data
19 - modified_on column saves repository update and it's going to be used
20 later for light version of main page ref #500
19
21
20 fixes
22 fixes
21 +++++
23 +++++
22
24
23 - fixed migrations of permissions that can lead to inconsistency issue
25 - fixed migrations of permissions that can lead to inconsistency.
26 Some users sent feedback that after upgrading from older versions issues with updating
27 default permissions occured. RhodeCode detects that now and resets default user
28 permission to initial state if there is a need for that. Also forces users to set
29 the default value for new forking permission.
24
30
25
31
26 1.4.0 (**2012-09-03**)
32 1.4.0 (**2012-09-03**)
27 ----------------------
33 ----------------------
28
34
29 news
35 news
30 ++++
36 ++++
31
37
32 - new codereview system
38 - new codereview system
33 - email map, allowing users to have multiple email addresses mapped into
39 - email map, allowing users to have multiple email addresses mapped into
34 their accounts
40 their accounts
35 - improved git-hook system. Now all actions for git are logged into journal
41 - improved git-hook system. Now all actions for git are logged into journal
36 including pushed revisions, user and IP address
42 including pushed revisions, user and IP address
37 - changed setup-app into setup-rhodecode and added default options to it.
43 - changed setup-app into setup-rhodecode and added default options to it.
38 - new git repos are created as bare now by default
44 - new git repos are created as bare now by default
39 - #464 added links to groups in permission box
45 - #464 added links to groups in permission box
40 - #465 mentions autocomplete inside comments boxes
46 - #465 mentions autocomplete inside comments boxes
41 - #469 added --update-only option to whoosh to re-index only given list
47 - #469 added --update-only option to whoosh to re-index only given list
42 of repos in index
48 of repos in index
43 - rhodecode-api CLI client
49 - rhodecode-api CLI client
44 - new git http protocol replaced buggy dulwich implementation.
50 - new git http protocol replaced buggy dulwich implementation.
45 Now based on pygrack & gitweb
51 Now based on pygrack & gitweb
46 - Improved RSS/ATOM feeds. Discoverable by browsers using proper headers, and
52 - Improved RSS/ATOM feeds. Discoverable by browsers using proper headers, and
47 reformated based on user suggestions. Additional rss/atom feeds for user
53 reformated based on user suggestions. Additional rss/atom feeds for user
48 journal
54 journal
49 - various i18n improvements
55 - various i18n improvements
50 - #478 permissions overview for admin in user edit view
56 - #478 permissions overview for admin in user edit view
51 - File view now displays small gravatars off all authors of given file
57 - File view now displays small gravatars off all authors of given file
52 - Implemented landing revisions. Each repository will get landing_rev attribute
58 - Implemented landing revisions. Each repository will get landing_rev attribute
53 that defines 'default' revision/branch for generating readme files
59 that defines 'default' revision/branch for generating readme files
54 - Implemented #509, RhodeCode enforces SSL for push/pulling if requested at
60 - Implemented #509, RhodeCode enforces SSL for push/pulling if requested at
55 earliest possible call.
61 earliest possible call.
56 - Import remote svn repositories to mercurial using hgsubversion.
62 - Import remote svn repositories to mercurial using hgsubversion.
57 - Fixed #508 RhodeCode now has a option to explicitly set forking permissions
63 - Fixed #508 RhodeCode now has a option to explicitly set forking permissions
58 - RhodeCode can use alternative server for generating avatar icons
64 - RhodeCode can use alternative server for generating avatar icons
59 - implemented repositories locking. Pull locks, push unlocks. Also can be done
65 - implemented repositories locking. Pull locks, push unlocks. Also can be done
60 via API calls
66 via API calls
61 - #538 form for permissions can handle multiple users at once
67 - #538 form for permissions can handle multiple users at once
62
68
63 fixes
69 fixes
64 +++++
70 +++++
65
71
66 - improved translations
72 - improved translations
67 - fixes issue #455 Creating an archive generates an exception on Windows
73 - fixes issue #455 Creating an archive generates an exception on Windows
68 - fixes #448 Download ZIP archive keeps file in /tmp open and results
74 - fixes #448 Download ZIP archive keeps file in /tmp open and results
69 in out of disk space
75 in out of disk space
70 - fixes issue #454 Search results under Windows include proceeding
76 - fixes issue #454 Search results under Windows include proceeding
71 backslash
77 backslash
72 - fixed issue #450. Rhodecode no longer will crash when bad revision is
78 - fixed issue #450. Rhodecode no longer will crash when bad revision is
73 present in journal data.
79 present in journal data.
74 - fix for issue #417, git execution was broken on windows for certain
80 - fix for issue #417, git execution was broken on windows for certain
75 commands.
81 commands.
76 - fixed #413. Don't disable .git directory for bare repos on deleting
82 - fixed #413. Don't disable .git directory for bare repos on deleting
77 - fixed issue #459. Changed the way of obtaining logger in reindex task.
83 - fixed issue #459. Changed the way of obtaining logger in reindex task.
78 - fixed #453 added ID field in whoosh SCHEMA that solves the issue of
84 - fixed #453 added ID field in whoosh SCHEMA that solves the issue of
79 reindexing modified files
85 reindexing modified files
80 - fixed #481 rhodecode emails are sent without Date header
86 - fixed #481 rhodecode emails are sent without Date header
81 - fixed #458 wrong count when no repos are present
87 - fixed #458 wrong count when no repos are present
82 - fixed issue #492 missing `\ No newline at end of file` test at the end of
88 - fixed issue #492 missing `\ No newline at end of file` test at the end of
83 new chunk in html diff
89 new chunk in html diff
84 - full text search now works also for commit messages
90 - full text search now works also for commit messages
85
91
86 1.3.6 (**2012-05-17**)
92 1.3.6 (**2012-05-17**)
87 ----------------------
93 ----------------------
88
94
89 news
95 news
90 ++++
96 ++++
91
97
92 - chinese traditional translation
98 - chinese traditional translation
93 - changed setup-app into setup-rhodecode and added arguments for auto-setup
99 - changed setup-app into setup-rhodecode and added arguments for auto-setup
94 mode that doesn't need user interaction
100 mode that doesn't need user interaction
95
101
96 fixes
102 fixes
97 +++++
103 +++++
98
104
99 - fixed no scm found warning
105 - fixed no scm found warning
100 - fixed __future__ import error on rcextensions
106 - fixed __future__ import error on rcextensions
101 - made simplejson required lib for speedup on JSON encoding
107 - made simplejson required lib for speedup on JSON encoding
102 - fixes #449 bad regex could get more than revisions from parsing history
108 - fixes #449 bad regex could get more than revisions from parsing history
103 - don't clear DB session when CELERY_EAGER is turned ON
109 - don't clear DB session when CELERY_EAGER is turned ON
104
110
105 1.3.5 (**2012-05-10**)
111 1.3.5 (**2012-05-10**)
106 ----------------------
112 ----------------------
107
113
108 news
114 news
109 ++++
115 ++++
110
116
111 - use ext_json for json module
117 - use ext_json for json module
112 - unified annotation view with file source view
118 - unified annotation view with file source view
113 - notification improvements, better inbox + css
119 - notification improvements, better inbox + css
114 - #419 don't strip passwords for login forms, make rhodecode
120 - #419 don't strip passwords for login forms, make rhodecode
115 more compatible with LDAP servers
121 more compatible with LDAP servers
116 - Added HTTP_X_FORWARDED_FOR as another method of extracting
122 - Added HTTP_X_FORWARDED_FOR as another method of extracting
117 IP for pull/push logs. - moved all to base controller
123 IP for pull/push logs. - moved all to base controller
118 - #415: Adding comment to changeset causes reload.
124 - #415: Adding comment to changeset causes reload.
119 Comments are now added via ajax and doesn't reload the page
125 Comments are now added via ajax and doesn't reload the page
120 - #374 LDAP config is discarded when LDAP can't be activated
126 - #374 LDAP config is discarded when LDAP can't be activated
121 - limited push/pull operations are now logged for git in the journal
127 - limited push/pull operations are now logged for git in the journal
122 - bumped mercurial to 2.2.X series
128 - bumped mercurial to 2.2.X series
123 - added support for displaying submodules in file-browser
129 - added support for displaying submodules in file-browser
124 - #421 added bookmarks in changelog view
130 - #421 added bookmarks in changelog view
125
131
126 fixes
132 fixes
127 +++++
133 +++++
128
134
129 - fixed dev-version marker for stable when served from source codes
135 - fixed dev-version marker for stable when served from source codes
130 - fixed missing permission checks on show forks page
136 - fixed missing permission checks on show forks page
131 - #418 cast to unicode fixes in notification objects
137 - #418 cast to unicode fixes in notification objects
132 - #426 fixed mention extracting regex
138 - #426 fixed mention extracting regex
133 - fixed remote-pulling for git remotes remopositories
139 - fixed remote-pulling for git remotes remopositories
134 - fixed #434: Error when accessing files or changesets of a git repository
140 - fixed #434: Error when accessing files or changesets of a git repository
135 with submodules
141 with submodules
136 - fixed issue with empty APIKEYS for users after registration ref. #438
142 - fixed issue with empty APIKEYS for users after registration ref. #438
137 - fixed issue with getting README files from git repositories
143 - fixed issue with getting README files from git repositories
138
144
139 1.3.4 (**2012-03-28**)
145 1.3.4 (**2012-03-28**)
140 ----------------------
146 ----------------------
141
147
142 news
148 news
143 ++++
149 ++++
144
150
145 - Whoosh logging is now controlled by the .ini files logging setup
151 - Whoosh logging is now controlled by the .ini files logging setup
146 - added clone-url into edit form on /settings page
152 - added clone-url into edit form on /settings page
147 - added help text into repo add/edit forms
153 - added help text into repo add/edit forms
148 - created rcextensions module with additional mappings (ref #322) and
154 - created rcextensions module with additional mappings (ref #322) and
149 post push/pull/create repo hooks callbacks
155 post push/pull/create repo hooks callbacks
150 - implemented #377 Users view for his own permissions on account page
156 - implemented #377 Users view for his own permissions on account page
151 - #399 added inheritance of permissions for users group on repos groups
157 - #399 added inheritance of permissions for users group on repos groups
152 - #401 repository group is automatically pre-selected when adding repos
158 - #401 repository group is automatically pre-selected when adding repos
153 inside a repository group
159 inside a repository group
154 - added alternative HTTP 403 response when client failed to authenticate. Helps
160 - added alternative HTTP 403 response when client failed to authenticate. Helps
155 solving issues with Mercurial and LDAP
161 solving issues with Mercurial and LDAP
156 - #402 removed group prefix from repository name when listing repositories
162 - #402 removed group prefix from repository name when listing repositories
157 inside a group
163 inside a group
158 - added gravatars into permission view and permissions autocomplete
164 - added gravatars into permission view and permissions autocomplete
159 - #347 when running multiple RhodeCode instances, properly invalidates cache
165 - #347 when running multiple RhodeCode instances, properly invalidates cache
160 for all registered servers
166 for all registered servers
161
167
162 fixes
168 fixes
163 +++++
169 +++++
164
170
165 - fixed #390 cache invalidation problems on repos inside group
171 - fixed #390 cache invalidation problems on repos inside group
166 - fixed #385 clone by ID url was loosing proxy prefix in URL
172 - fixed #385 clone by ID url was loosing proxy prefix in URL
167 - fixed some unicode problems with waitress
173 - fixed some unicode problems with waitress
168 - fixed issue with escaping < and > in changeset commits
174 - fixed issue with escaping < and > in changeset commits
169 - fixed error occurring during recursive group creation in API
175 - fixed error occurring during recursive group creation in API
170 create_repo function
176 create_repo function
171 - fixed #393 py2.5 fixes for routes url generator
177 - fixed #393 py2.5 fixes for routes url generator
172 - fixed #397 Private repository groups shows up before login
178 - fixed #397 Private repository groups shows up before login
173 - fixed #396 fixed problems with revoking users in nested groups
179 - fixed #396 fixed problems with revoking users in nested groups
174 - fixed mysql unicode issues + specified InnoDB as default engine with
180 - fixed mysql unicode issues + specified InnoDB as default engine with
175 utf8 charset
181 utf8 charset
176 - #406 trim long branch/tag names in changelog to not break UI
182 - #406 trim long branch/tag names in changelog to not break UI
177
183
178 1.3.3 (**2012-03-02**)
184 1.3.3 (**2012-03-02**)
179 ----------------------
185 ----------------------
180
186
181 news
187 news
182 ++++
188 ++++
183
189
184
190
185 fixes
191 fixes
186 +++++
192 +++++
187
193
188 - fixed some python2.5 compatibility issues
194 - fixed some python2.5 compatibility issues
189 - fixed issues with removed repos was accidentally added as groups, after
195 - fixed issues with removed repos was accidentally added as groups, after
190 full rescan of paths
196 full rescan of paths
191 - fixes #376 Cannot edit user (using container auth)
197 - fixes #376 Cannot edit user (using container auth)
192 - fixes #378 Invalid image urls on changeset screen with proxy-prefix
198 - fixes #378 Invalid image urls on changeset screen with proxy-prefix
193 configuration
199 configuration
194 - fixed initial sorting of repos inside repo group
200 - fixed initial sorting of repos inside repo group
195 - fixes issue when user tried to resubmit same permission into user/user_groups
201 - fixes issue when user tried to resubmit same permission into user/user_groups
196 - bumped beaker version that fixes #375 leap error bug
202 - bumped beaker version that fixes #375 leap error bug
197 - fixed raw_changeset for git. It was generated with hg patch headers
203 - fixed raw_changeset for git. It was generated with hg patch headers
198 - fixed vcs issue with last_changeset for filenodes
204 - fixed vcs issue with last_changeset for filenodes
199 - fixed missing commit after hook delete
205 - fixed missing commit after hook delete
200 - fixed #372 issues with git operation detection that caused a security issue
206 - fixed #372 issues with git operation detection that caused a security issue
201 for git repos
207 for git repos
202
208
203 1.3.2 (**2012-02-28**)
209 1.3.2 (**2012-02-28**)
204 ----------------------
210 ----------------------
205
211
206 news
212 news
207 ++++
213 ++++
208
214
209
215
210 fixes
216 fixes
211 +++++
217 +++++
212
218
213 - fixed git protocol issues with repos-groups
219 - fixed git protocol issues with repos-groups
214 - fixed git remote repos validator that prevented from cloning remote git repos
220 - fixed git remote repos validator that prevented from cloning remote git repos
215 - fixes #370 ending slashes fixes for repo and groups
221 - fixes #370 ending slashes fixes for repo and groups
216 - fixes #368 improved git-protocol detection to handle other clients
222 - fixes #368 improved git-protocol detection to handle other clients
217 - fixes #366 When Setting Repository Group To Blank Repo Group Wont Be
223 - fixes #366 When Setting Repository Group To Blank Repo Group Wont Be
218 Moved To Root
224 Moved To Root
219 - fixes #371 fixed issues with beaker/sqlalchemy and non-ascii cache keys
225 - fixes #371 fixed issues with beaker/sqlalchemy and non-ascii cache keys
220 - fixed #373 missing cascade drop on user_group_to_perm table
226 - fixed #373 missing cascade drop on user_group_to_perm table
221
227
222 1.3.1 (**2012-02-27**)
228 1.3.1 (**2012-02-27**)
223 ----------------------
229 ----------------------
224
230
225 news
231 news
226 ++++
232 ++++
227
233
228
234
229 fixes
235 fixes
230 +++++
236 +++++
231
237
232 - redirection loop occurs when remember-me wasn't checked during login
238 - redirection loop occurs when remember-me wasn't checked during login
233 - fixes issues with git blob history generation
239 - fixes issues with git blob history generation
234 - don't fetch branch for git in file history dropdown. Causes unneeded slowness
240 - don't fetch branch for git in file history dropdown. Causes unneeded slowness
235
241
236 1.3.0 (**2012-02-26**)
242 1.3.0 (**2012-02-26**)
237 ----------------------
243 ----------------------
238
244
239 news
245 news
240 ++++
246 ++++
241
247
242 - code review, inspired by github code-comments
248 - code review, inspired by github code-comments
243 - #215 rst and markdown README files support
249 - #215 rst and markdown README files support
244 - #252 Container-based and proxy pass-through authentication support
250 - #252 Container-based and proxy pass-through authentication support
245 - #44 branch browser. Filtering of changelog by branches
251 - #44 branch browser. Filtering of changelog by branches
246 - mercurial bookmarks support
252 - mercurial bookmarks support
247 - new hover top menu, optimized to add maximum size for important views
253 - new hover top menu, optimized to add maximum size for important views
248 - configurable clone url template with possibility to specify protocol like
254 - configurable clone url template with possibility to specify protocol like
249 ssh:// or http:// and also manually alter other parts of clone_url.
255 ssh:// or http:// and also manually alter other parts of clone_url.
250 - enabled largefiles extension by default
256 - enabled largefiles extension by default
251 - optimized summary file pages and saved a lot of unused space in them
257 - optimized summary file pages and saved a lot of unused space in them
252 - #239 option to manually mark repository as fork
258 - #239 option to manually mark repository as fork
253 - #320 mapping of commit authors to RhodeCode users
259 - #320 mapping of commit authors to RhodeCode users
254 - #304 hashes are displayed using monospace font
260 - #304 hashes are displayed using monospace font
255 - diff configuration, toggle white lines and context lines
261 - diff configuration, toggle white lines and context lines
256 - #307 configurable diffs, whitespace toggle, increasing context lines
262 - #307 configurable diffs, whitespace toggle, increasing context lines
257 - sorting on branches, tags and bookmarks using YUI datatable
263 - sorting on branches, tags and bookmarks using YUI datatable
258 - improved file filter on files page
264 - improved file filter on files page
259 - implements #330 api method for listing nodes ar particular revision
265 - implements #330 api method for listing nodes ar particular revision
260 - #73 added linking issues in commit messages to chosen issue tracker url
266 - #73 added linking issues in commit messages to chosen issue tracker url
261 based on user defined regular expression
267 based on user defined regular expression
262 - added linking of changesets in commit messages
268 - added linking of changesets in commit messages
263 - new compact changelog with expandable commit messages
269 - new compact changelog with expandable commit messages
264 - firstname and lastname are optional in user creation
270 - firstname and lastname are optional in user creation
265 - #348 added post-create repository hook
271 - #348 added post-create repository hook
266 - #212 global encoding settings is now configurable from .ini files
272 - #212 global encoding settings is now configurable from .ini files
267 - #227 added repository groups permissions
273 - #227 added repository groups permissions
268 - markdown gets codehilite extensions
274 - markdown gets codehilite extensions
269 - new API methods, delete_repositories, grante/revoke permissions for groups
275 - new API methods, delete_repositories, grante/revoke permissions for groups
270 and repos
276 and repos
271
277
272
278
273 fixes
279 fixes
274 +++++
280 +++++
275
281
276 - rewrote dbsession management for atomic operations, and better error handling
282 - rewrote dbsession management for atomic operations, and better error handling
277 - fixed sorting of repo tables
283 - fixed sorting of repo tables
278 - #326 escape of special html entities in diffs
284 - #326 escape of special html entities in diffs
279 - normalized user_name => username in api attributes
285 - normalized user_name => username in api attributes
280 - fixes #298 ldap created users with mixed case emails created conflicts
286 - fixes #298 ldap created users with mixed case emails created conflicts
281 on saving a form
287 on saving a form
282 - fixes issue when owner of a repo couldn't revoke permissions for users
288 - fixes issue when owner of a repo couldn't revoke permissions for users
283 and groups
289 and groups
284 - fixes #271 rare JSON serialization problem with statistics
290 - fixes #271 rare JSON serialization problem with statistics
285 - fixes #337 missing validation check for conflicting names of a group with a
291 - fixes #337 missing validation check for conflicting names of a group with a
286 repositories group
292 repositories group
287 - #340 fixed session problem for mysql and celery tasks
293 - #340 fixed session problem for mysql and celery tasks
288 - fixed #331 RhodeCode mangles repository names if the a repository group
294 - fixed #331 RhodeCode mangles repository names if the a repository group
289 contains the "full path" to the repositories
295 contains the "full path" to the repositories
290 - #355 RhodeCode doesn't store encrypted LDAP passwords
296 - #355 RhodeCode doesn't store encrypted LDAP passwords
291
297
292 1.2.5 (**2012-01-28**)
298 1.2.5 (**2012-01-28**)
293 ----------------------
299 ----------------------
294
300
295 news
301 news
296 ++++
302 ++++
297
303
298 fixes
304 fixes
299 +++++
305 +++++
300
306
301 - #340 Celery complains about MySQL server gone away, added session cleanup
307 - #340 Celery complains about MySQL server gone away, added session cleanup
302 for celery tasks
308 for celery tasks
303 - #341 "scanning for repositories in None" log message during Rescan was missing
309 - #341 "scanning for repositories in None" log message during Rescan was missing
304 a parameter
310 a parameter
305 - fixed creating archives with subrepos. Some hooks were triggered during that
311 - fixed creating archives with subrepos. Some hooks were triggered during that
306 operation leading to crash.
312 operation leading to crash.
307 - fixed missing email in account page.
313 - fixed missing email in account page.
308 - Reverted Mercurial to 2.0.1 for windows due to bug in Mercurial that makes
314 - Reverted Mercurial to 2.0.1 for windows due to bug in Mercurial that makes
309 forking on windows impossible
315 forking on windows impossible
310
316
311 1.2.4 (**2012-01-19**)
317 1.2.4 (**2012-01-19**)
312 ----------------------
318 ----------------------
313
319
314 news
320 news
315 ++++
321 ++++
316
322
317 - RhodeCode is bundled with mercurial series 2.0.X by default, with
323 - RhodeCode is bundled with mercurial series 2.0.X by default, with
318 full support to largefiles extension. Enabled by default in new installations
324 full support to largefiles extension. Enabled by default in new installations
319 - #329 Ability to Add/Remove Groups to/from a Repository via AP
325 - #329 Ability to Add/Remove Groups to/from a Repository via AP
320 - added requires.txt file with requirements
326 - added requires.txt file with requirements
321
327
322 fixes
328 fixes
323 +++++
329 +++++
324
330
325 - fixes db session issues with celery when emailing admins
331 - fixes db session issues with celery when emailing admins
326 - #331 RhodeCode mangles repository names if the a repository group
332 - #331 RhodeCode mangles repository names if the a repository group
327 contains the "full path" to the repositories
333 contains the "full path" to the repositories
328 - #298 Conflicting e-mail addresses for LDAP and RhodeCode users
334 - #298 Conflicting e-mail addresses for LDAP and RhodeCode users
329 - DB session cleanup after hg protocol operations, fixes issues with
335 - DB session cleanup after hg protocol operations, fixes issues with
330 `mysql has gone away` errors
336 `mysql has gone away` errors
331 - #333 doc fixes for get_repo api function
337 - #333 doc fixes for get_repo api function
332 - #271 rare JSON serialization problem with statistics enabled
338 - #271 rare JSON serialization problem with statistics enabled
333 - #337 Fixes issues with validation of repository name conflicting with
339 - #337 Fixes issues with validation of repository name conflicting with
334 a group name. A proper message is now displayed.
340 a group name. A proper message is now displayed.
335 - #292 made ldap_dn in user edit readonly, to get rid of confusion that field
341 - #292 made ldap_dn in user edit readonly, to get rid of confusion that field
336 doesn't work
342 doesn't work
337 - #316 fixes issues with web description in hgrc files
343 - #316 fixes issues with web description in hgrc files
338
344
339 1.2.3 (**2011-11-02**)
345 1.2.3 (**2011-11-02**)
340 ----------------------
346 ----------------------
341
347
342 news
348 news
343 ++++
349 ++++
344
350
345 - added option to manage repos group for non admin users
351 - added option to manage repos group for non admin users
346 - added following API methods for get_users, create_user, get_users_groups,
352 - added following API methods for get_users, create_user, get_users_groups,
347 get_users_group, create_users_group, add_user_to_users_groups, get_repos,
353 get_users_group, create_users_group, add_user_to_users_groups, get_repos,
348 get_repo, create_repo, add_user_to_repo
354 get_repo, create_repo, add_user_to_repo
349 - implements #237 added password confirmation for my account
355 - implements #237 added password confirmation for my account
350 and admin edit user.
356 and admin edit user.
351 - implements #291 email notification for global events are now sent to all
357 - implements #291 email notification for global events are now sent to all
352 administrator users, and global config email.
358 administrator users, and global config email.
353
359
354 fixes
360 fixes
355 +++++
361 +++++
356
362
357 - added option for passing auth method for smtp mailer
363 - added option for passing auth method for smtp mailer
358 - #276 issue with adding a single user with id>10 to usergroups
364 - #276 issue with adding a single user with id>10 to usergroups
359 - #277 fixes windows LDAP settings in which missing values breaks the ldap auth
365 - #277 fixes windows LDAP settings in which missing values breaks the ldap auth
360 - #288 fixes managing of repos in a group for non admin user
366 - #288 fixes managing of repos in a group for non admin user
361
367
362 1.2.2 (**2011-10-17**)
368 1.2.2 (**2011-10-17**)
363 ----------------------
369 ----------------------
364
370
365 news
371 news
366 ++++
372 ++++
367
373
368 - #226 repo groups are available by path instead of numerical id
374 - #226 repo groups are available by path instead of numerical id
369
375
370 fixes
376 fixes
371 +++++
377 +++++
372
378
373 - #259 Groups with the same name but with different parent group
379 - #259 Groups with the same name but with different parent group
374 - #260 Put repo in group, then move group to another group -> repo becomes unavailable
380 - #260 Put repo in group, then move group to another group -> repo becomes unavailable
375 - #258 RhodeCode 1.2 assumes egg folder is writable (lockfiles problems)
381 - #258 RhodeCode 1.2 assumes egg folder is writable (lockfiles problems)
376 - #265 ldap save fails sometimes on converting attributes to booleans,
382 - #265 ldap save fails sometimes on converting attributes to booleans,
377 added getter and setter into model that will prevent from this on db model level
383 added getter and setter into model that will prevent from this on db model level
378 - fixed problems with timestamps issues #251 and #213
384 - fixed problems with timestamps issues #251 and #213
379 - fixes #266 RhodeCode allows to create repo with the same name and in
385 - fixes #266 RhodeCode allows to create repo with the same name and in
380 the same parent as group
386 the same parent as group
381 - fixes #245 Rescan of the repositories on Windows
387 - fixes #245 Rescan of the repositories on Windows
382 - fixes #248 cannot edit repos inside a group on windows
388 - fixes #248 cannot edit repos inside a group on windows
383 - fixes #219 forking problems on windows
389 - fixes #219 forking problems on windows
384
390
385 1.2.1 (**2011-10-08**)
391 1.2.1 (**2011-10-08**)
386 ----------------------
392 ----------------------
387
393
388 news
394 news
389 ++++
395 ++++
390
396
391
397
392 fixes
398 fixes
393 +++++
399 +++++
394
400
395 - fixed problems with basic auth and push problems
401 - fixed problems with basic auth and push problems
396 - gui fixes
402 - gui fixes
397 - fixed logger
403 - fixed logger
398
404
399 1.2.0 (**2011-10-07**)
405 1.2.0 (**2011-10-07**)
400 ----------------------
406 ----------------------
401
407
402 news
408 news
403 ++++
409 ++++
404
410
405 - implemented #47 repository groups
411 - implemented #47 repository groups
406 - implemented #89 Can setup google analytics code from settings menu
412 - implemented #89 Can setup google analytics code from settings menu
407 - implemented #91 added nicer looking archive urls with more download options
413 - implemented #91 added nicer looking archive urls with more download options
408 like tags, branches
414 like tags, branches
409 - implemented #44 into file browsing, and added follow branch option
415 - implemented #44 into file browsing, and added follow branch option
410 - implemented #84 downloads can be enabled/disabled for each repository
416 - implemented #84 downloads can be enabled/disabled for each repository
411 - anonymous repository can be cloned without having to pass default:default
417 - anonymous repository can be cloned without having to pass default:default
412 into clone url
418 into clone url
413 - fixed #90 whoosh indexer can index chooses repositories passed in command
419 - fixed #90 whoosh indexer can index chooses repositories passed in command
414 line
420 line
415 - extended journal with day aggregates and paging
421 - extended journal with day aggregates and paging
416 - implemented #107 source code lines highlight ranges
422 - implemented #107 source code lines highlight ranges
417 - implemented #93 customizable changelog on combined revision ranges -
423 - implemented #93 customizable changelog on combined revision ranges -
418 equivalent of githubs compare view
424 equivalent of githubs compare view
419 - implemented #108 extended and more powerful LDAP configuration
425 - implemented #108 extended and more powerful LDAP configuration
420 - implemented #56 users groups
426 - implemented #56 users groups
421 - major code rewrites optimized codes for speed and memory usage
427 - major code rewrites optimized codes for speed and memory usage
422 - raw and diff downloads are now in git format
428 - raw and diff downloads are now in git format
423 - setup command checks for write access to given path
429 - setup command checks for write access to given path
424 - fixed many issues with international characters and unicode. It uses utf8
430 - fixed many issues with international characters and unicode. It uses utf8
425 decode with replace to provide less errors even with non utf8 encoded strings
431 decode with replace to provide less errors even with non utf8 encoded strings
426 - #125 added API KEY access to feeds
432 - #125 added API KEY access to feeds
427 - #109 Repository can be created from external Mercurial link (aka. remote
433 - #109 Repository can be created from external Mercurial link (aka. remote
428 repository, and manually updated (via pull) from admin panel
434 repository, and manually updated (via pull) from admin panel
429 - beta git support - push/pull server + basic view for git repos
435 - beta git support - push/pull server + basic view for git repos
430 - added followers page and forks page
436 - added followers page and forks page
431 - server side file creation (with binary file upload interface)
437 - server side file creation (with binary file upload interface)
432 and edition with commits powered by codemirror
438 and edition with commits powered by codemirror
433 - #111 file browser file finder, quick lookup files on whole file tree
439 - #111 file browser file finder, quick lookup files on whole file tree
434 - added quick login sliding menu into main page
440 - added quick login sliding menu into main page
435 - changelog uses lazy loading of affected files details, in some scenarios
441 - changelog uses lazy loading of affected files details, in some scenarios
436 this can improve speed of changelog page dramatically especially for
442 this can improve speed of changelog page dramatically especially for
437 larger repositories.
443 larger repositories.
438 - implements #214 added support for downloading subrepos in download menu.
444 - implements #214 added support for downloading subrepos in download menu.
439 - Added basic API for direct operations on rhodecode via JSON
445 - Added basic API for direct operations on rhodecode via JSON
440 - Implemented advanced hook management
446 - Implemented advanced hook management
441
447
442 fixes
448 fixes
443 +++++
449 +++++
444
450
445 - fixed file browser bug, when switching into given form revision the url was
451 - fixed file browser bug, when switching into given form revision the url was
446 not changing
452 not changing
447 - fixed propagation to error controller on simplehg and simplegit middlewares
453 - fixed propagation to error controller on simplehg and simplegit middlewares
448 - fixed error when trying to make a download on empty repository
454 - fixed error when trying to make a download on empty repository
449 - fixed problem with '[' chars in commit messages in journal
455 - fixed problem with '[' chars in commit messages in journal
450 - fixed #99 Unicode errors, on file node paths with non utf-8 characters
456 - fixed #99 Unicode errors, on file node paths with non utf-8 characters
451 - journal fork fixes
457 - journal fork fixes
452 - removed issue with space inside renamed repository after deletion
458 - removed issue with space inside renamed repository after deletion
453 - fixed strange issue on formencode imports
459 - fixed strange issue on formencode imports
454 - fixed #126 Deleting repository on Windows, rename used incompatible chars.
460 - fixed #126 Deleting repository on Windows, rename used incompatible chars.
455 - #150 fixes for errors on repositories mapped in db but corrupted in
461 - #150 fixes for errors on repositories mapped in db but corrupted in
456 filesystem
462 filesystem
457 - fixed problem with ascendant characters in realm #181
463 - fixed problem with ascendant characters in realm #181
458 - fixed problem with sqlite file based database connection pool
464 - fixed problem with sqlite file based database connection pool
459 - whoosh indexer and code stats share the same dynamic extensions map
465 - whoosh indexer and code stats share the same dynamic extensions map
460 - fixes #188 - relationship delete of repo_to_perm entry on user removal
466 - fixes #188 - relationship delete of repo_to_perm entry on user removal
461 - fixes issue #189 Trending source files shows "show more" when no more exist
467 - fixes issue #189 Trending source files shows "show more" when no more exist
462 - fixes issue #197 Relative paths for pidlocks
468 - fixes issue #197 Relative paths for pidlocks
463 - fixes issue #198 password will require only 3 chars now for login form
469 - fixes issue #198 password will require only 3 chars now for login form
464 - fixes issue #199 wrong redirection for non admin users after creating a repository
470 - fixes issue #199 wrong redirection for non admin users after creating a repository
465 - fixes issues #202, bad db constraint made impossible to attach same group
471 - fixes issues #202, bad db constraint made impossible to attach same group
466 more than one time. Affects only mysql/postgres
472 more than one time. Affects only mysql/postgres
467 - fixes #218 os.kill patch for windows was missing sig param
473 - fixes #218 os.kill patch for windows was missing sig param
468 - improved rendering of dag (they are not trimmed anymore when number of
474 - improved rendering of dag (they are not trimmed anymore when number of
469 heads exceeds 5)
475 heads exceeds 5)
470
476
471 1.1.8 (**2011-04-12**)
477 1.1.8 (**2011-04-12**)
472 ----------------------
478 ----------------------
473
479
474 news
480 news
475 ++++
481 ++++
476
482
477 - improved windows support
483 - improved windows support
478
484
479 fixes
485 fixes
480 +++++
486 +++++
481
487
482 - fixed #140 freeze of python dateutil library, since new version is python2.x
488 - fixed #140 freeze of python dateutil library, since new version is python2.x
483 incompatible
489 incompatible
484 - setup-app will check for write permission in given path
490 - setup-app will check for write permission in given path
485 - cleaned up license info issue #149
491 - cleaned up license info issue #149
486 - fixes for issues #137,#116 and problems with unicode and accented characters.
492 - fixes for issues #137,#116 and problems with unicode and accented characters.
487 - fixes crashes on gravatar, when passed in email as unicode
493 - fixes crashes on gravatar, when passed in email as unicode
488 - fixed tooltip flickering problems
494 - fixed tooltip flickering problems
489 - fixed came_from redirection on windows
495 - fixed came_from redirection on windows
490 - fixed logging modules, and sql formatters
496 - fixed logging modules, and sql formatters
491 - windows fixes for os.kill issue #133
497 - windows fixes for os.kill issue #133
492 - fixes path splitting for windows issues #148
498 - fixes path splitting for windows issues #148
493 - fixed issue #143 wrong import on migration to 1.1.X
499 - fixed issue #143 wrong import on migration to 1.1.X
494 - fixed problems with displaying binary files, thanks to Thomas Waldmann
500 - fixed problems with displaying binary files, thanks to Thomas Waldmann
495 - removed name from archive files since it's breaking ui for long repo names
501 - removed name from archive files since it's breaking ui for long repo names
496 - fixed issue with archive headers sent to browser, thanks to Thomas Waldmann
502 - fixed issue with archive headers sent to browser, thanks to Thomas Waldmann
497 - fixed compatibility for 1024px displays, and larger dpi settings, thanks to
503 - fixed compatibility for 1024px displays, and larger dpi settings, thanks to
498 Thomas Waldmann
504 Thomas Waldmann
499 - fixed issue #166 summary pager was skipping 10 revisions on second page
505 - fixed issue #166 summary pager was skipping 10 revisions on second page
500
506
501
507
502 1.1.7 (**2011-03-23**)
508 1.1.7 (**2011-03-23**)
503 ----------------------
509 ----------------------
504
510
505 news
511 news
506 ++++
512 ++++
507
513
508 fixes
514 fixes
509 +++++
515 +++++
510
516
511 - fixed (again) #136 installation support for FreeBSD
517 - fixed (again) #136 installation support for FreeBSD
512
518
513
519
514 1.1.6 (**2011-03-21**)
520 1.1.6 (**2011-03-21**)
515 ----------------------
521 ----------------------
516
522
517 news
523 news
518 ++++
524 ++++
519
525
520 fixes
526 fixes
521 +++++
527 +++++
522
528
523 - fixed #136 installation support for FreeBSD
529 - fixed #136 installation support for FreeBSD
524 - RhodeCode will check for python version during installation
530 - RhodeCode will check for python version during installation
525
531
526 1.1.5 (**2011-03-17**)
532 1.1.5 (**2011-03-17**)
527 ----------------------
533 ----------------------
528
534
529 news
535 news
530 ++++
536 ++++
531
537
532 - basic windows support, by exchanging pybcrypt into sha256 for windows only
538 - basic windows support, by exchanging pybcrypt into sha256 for windows only
533 highly inspired by idea of mantis406
539 highly inspired by idea of mantis406
534
540
535 fixes
541 fixes
536 +++++
542 +++++
537
543
538 - fixed sorting by author in main page
544 - fixed sorting by author in main page
539 - fixed crashes with diffs on binary files
545 - fixed crashes with diffs on binary files
540 - fixed #131 problem with boolean values for LDAP
546 - fixed #131 problem with boolean values for LDAP
541 - fixed #122 mysql problems thanks to striker69
547 - fixed #122 mysql problems thanks to striker69
542 - fixed problem with errors on calling raw/raw_files/annotate functions
548 - fixed problem with errors on calling raw/raw_files/annotate functions
543 with unknown revisions
549 with unknown revisions
544 - fixed returned rawfiles attachment names with international character
550 - fixed returned rawfiles attachment names with international character
545 - cleaned out docs, big thanks to Jason Harris
551 - cleaned out docs, big thanks to Jason Harris
546
552
547 1.1.4 (**2011-02-19**)
553 1.1.4 (**2011-02-19**)
548 ----------------------
554 ----------------------
549
555
550 news
556 news
551 ++++
557 ++++
552
558
553 fixes
559 fixes
554 +++++
560 +++++
555
561
556 - fixed formencode import problem on settings page, that caused server crash
562 - fixed formencode import problem on settings page, that caused server crash
557 when that page was accessed as first after server start
563 when that page was accessed as first after server start
558 - journal fixes
564 - journal fixes
559 - fixed option to access repository just by entering http://server/<repo_name>
565 - fixed option to access repository just by entering http://server/<repo_name>
560
566
561 1.1.3 (**2011-02-16**)
567 1.1.3 (**2011-02-16**)
562 ----------------------
568 ----------------------
563
569
564 news
570 news
565 ++++
571 ++++
566
572
567 - implemented #102 allowing the '.' character in username
573 - implemented #102 allowing the '.' character in username
568 - added option to access repository just by entering http://server/<repo_name>
574 - added option to access repository just by entering http://server/<repo_name>
569 - celery task ignores result for better performance
575 - celery task ignores result for better performance
570
576
571 fixes
577 fixes
572 +++++
578 +++++
573
579
574 - fixed ehlo command and non auth mail servers on smtp_lib. Thanks to
580 - fixed ehlo command and non auth mail servers on smtp_lib. Thanks to
575 apollo13 and Johan Walles
581 apollo13 and Johan Walles
576 - small fixes in journal
582 - small fixes in journal
577 - fixed problems with getting setting for celery from .ini files
583 - fixed problems with getting setting for celery from .ini files
578 - registration, password reset and login boxes share the same title as main
584 - registration, password reset and login boxes share the same title as main
579 application now
585 application now
580 - fixed #113: to high permissions to fork repository
586 - fixed #113: to high permissions to fork repository
581 - fixed problem with '[' chars in commit messages in journal
587 - fixed problem with '[' chars in commit messages in journal
582 - removed issue with space inside renamed repository after deletion
588 - removed issue with space inside renamed repository after deletion
583 - db transaction fixes when filesystem repository creation failed
589 - db transaction fixes when filesystem repository creation failed
584 - fixed #106 relation issues on databases different than sqlite
590 - fixed #106 relation issues on databases different than sqlite
585 - fixed static files paths links to use of url() method
591 - fixed static files paths links to use of url() method
586
592
587 1.1.2 (**2011-01-12**)
593 1.1.2 (**2011-01-12**)
588 ----------------------
594 ----------------------
589
595
590 news
596 news
591 ++++
597 ++++
592
598
593
599
594 fixes
600 fixes
595 +++++
601 +++++
596
602
597 - fixes #98 protection against float division of percentage stats
603 - fixes #98 protection against float division of percentage stats
598 - fixed graph bug
604 - fixed graph bug
599 - forced webhelpers version since it was making troubles during installation
605 - forced webhelpers version since it was making troubles during installation
600
606
601 1.1.1 (**2011-01-06**)
607 1.1.1 (**2011-01-06**)
602 ----------------------
608 ----------------------
603
609
604 news
610 news
605 ++++
611 ++++
606
612
607 - added force https option into ini files for easier https usage (no need to
613 - added force https option into ini files for easier https usage (no need to
608 set server headers with this options)
614 set server headers with this options)
609 - small css updates
615 - small css updates
610
616
611 fixes
617 fixes
612 +++++
618 +++++
613
619
614 - fixed #96 redirect loop on files view on repositories without changesets
620 - fixed #96 redirect loop on files view on repositories without changesets
615 - fixed #97 unicode string passed into server header in special cases (mod_wsgi)
621 - fixed #97 unicode string passed into server header in special cases (mod_wsgi)
616 and server crashed with errors
622 and server crashed with errors
617 - fixed large tooltips problems on main page
623 - fixed large tooltips problems on main page
618 - fixed #92 whoosh indexer is more error proof
624 - fixed #92 whoosh indexer is more error proof
619
625
620 1.1.0 (**2010-12-18**)
626 1.1.0 (**2010-12-18**)
621 ----------------------
627 ----------------------
622
628
623 news
629 news
624 ++++
630 ++++
625
631
626 - rewrite of internals for vcs >=0.1.10
632 - rewrite of internals for vcs >=0.1.10
627 - uses mercurial 1.7 with dotencode disabled for maintaining compatibility
633 - uses mercurial 1.7 with dotencode disabled for maintaining compatibility
628 with older clients
634 with older clients
629 - anonymous access, authentication via ldap
635 - anonymous access, authentication via ldap
630 - performance upgrade for cached repos list - each repository has its own
636 - performance upgrade for cached repos list - each repository has its own
631 cache that's invalidated when needed.
637 cache that's invalidated when needed.
632 - performance upgrades on repositories with large amount of commits (20K+)
638 - performance upgrades on repositories with large amount of commits (20K+)
633 - main page quick filter for filtering repositories
639 - main page quick filter for filtering repositories
634 - user dashboards with ability to follow chosen repositories actions
640 - user dashboards with ability to follow chosen repositories actions
635 - sends email to admin on new user registration
641 - sends email to admin on new user registration
636 - added cache/statistics reset options into repository settings
642 - added cache/statistics reset options into repository settings
637 - more detailed action logger (based on hooks) with pushed changesets lists
643 - more detailed action logger (based on hooks) with pushed changesets lists
638 and options to disable those hooks from admin panel
644 and options to disable those hooks from admin panel
639 - introduced new enhanced changelog for merges that shows more accurate results
645 - introduced new enhanced changelog for merges that shows more accurate results
640 - new improved and faster code stats (based on pygments lexers mapping tables,
646 - new improved and faster code stats (based on pygments lexers mapping tables,
641 showing up to 10 trending sources for each repository. Additionally stats
647 showing up to 10 trending sources for each repository. Additionally stats
642 can be disabled in repository settings.
648 can be disabled in repository settings.
643 - gui optimizations, fixed application width to 1024px
649 - gui optimizations, fixed application width to 1024px
644 - added cut off (for large files/changesets) limit into config files
650 - added cut off (for large files/changesets) limit into config files
645 - whoosh, celeryd, upgrade moved to paster command
651 - whoosh, celeryd, upgrade moved to paster command
646 - other than sqlite database backends can be used
652 - other than sqlite database backends can be used
647
653
648 fixes
654 fixes
649 +++++
655 +++++
650
656
651 - fixes #61 forked repo was showing only after cache expired
657 - fixes #61 forked repo was showing only after cache expired
652 - fixes #76 no confirmation on user deletes
658 - fixes #76 no confirmation on user deletes
653 - fixes #66 Name field misspelled
659 - fixes #66 Name field misspelled
654 - fixes #72 block user removal when he owns repositories
660 - fixes #72 block user removal when he owns repositories
655 - fixes #69 added password confirmation fields
661 - fixes #69 added password confirmation fields
656 - fixes #87 RhodeCode crashes occasionally on updating repository owner
662 - fixes #87 RhodeCode crashes occasionally on updating repository owner
657 - fixes #82 broken annotations on files with more than 1 blank line at the end
663 - fixes #82 broken annotations on files with more than 1 blank line at the end
658 - a lot of fixes and tweaks for file browser
664 - a lot of fixes and tweaks for file browser
659 - fixed detached session issues
665 - fixed detached session issues
660 - fixed when user had no repos he would see all repos listed in my account
666 - fixed when user had no repos he would see all repos listed in my account
661 - fixed ui() instance bug when global hgrc settings was loaded for server
667 - fixed ui() instance bug when global hgrc settings was loaded for server
662 instance and all hgrc options were merged with our db ui() object
668 instance and all hgrc options were merged with our db ui() object
663 - numerous small bugfixes
669 - numerous small bugfixes
664
670
665 (special thanks for TkSoh for detailed feedback)
671 (special thanks for TkSoh for detailed feedback)
666
672
667
673
668 1.0.2 (**2010-11-12**)
674 1.0.2 (**2010-11-12**)
669 ----------------------
675 ----------------------
670
676
671 news
677 news
672 ++++
678 ++++
673
679
674 - tested under python2.7
680 - tested under python2.7
675 - bumped sqlalchemy and celery versions
681 - bumped sqlalchemy and celery versions
676
682
677 fixes
683 fixes
678 +++++
684 +++++
679
685
680 - fixed #59 missing graph.js
686 - fixed #59 missing graph.js
681 - fixed repo_size crash when repository had broken symlinks
687 - fixed repo_size crash when repository had broken symlinks
682 - fixed python2.5 crashes.
688 - fixed python2.5 crashes.
683
689
684
690
685 1.0.1 (**2010-11-10**)
691 1.0.1 (**2010-11-10**)
686 ----------------------
692 ----------------------
687
693
688 news
694 news
689 ++++
695 ++++
690
696
691 - small css updated
697 - small css updated
692
698
693 fixes
699 fixes
694 +++++
700 +++++
695
701
696 - fixed #53 python2.5 incompatible enumerate calls
702 - fixed #53 python2.5 incompatible enumerate calls
697 - fixed #52 disable mercurial extension for web
703 - fixed #52 disable mercurial extension for web
698 - fixed #51 deleting repositories don't delete it's dependent objects
704 - fixed #51 deleting repositories don't delete it's dependent objects
699
705
700
706
701 1.0.0 (**2010-11-02**)
707 1.0.0 (**2010-11-02**)
702 ----------------------
708 ----------------------
703
709
704 - security bugfix simplehg wasn't checking for permissions on commands
710 - security bugfix simplehg wasn't checking for permissions on commands
705 other than pull or push.
711 other than pull or push.
706 - fixed doubled messages after push or pull in admin journal
712 - fixed doubled messages after push or pull in admin journal
707 - templating and css corrections, fixed repo switcher on chrome, updated titles
713 - templating and css corrections, fixed repo switcher on chrome, updated titles
708 - admin menu accessible from options menu on repository view
714 - admin menu accessible from options menu on repository view
709 - permissions cached queries
715 - permissions cached queries
710
716
711 1.0.0rc4 (**2010-10-12**)
717 1.0.0rc4 (**2010-10-12**)
712 --------------------------
718 --------------------------
713
719
714 - fixed python2.5 missing simplejson imports (thanks to Jens BΓ€ckman)
720 - fixed python2.5 missing simplejson imports (thanks to Jens BΓ€ckman)
715 - removed cache_manager settings from sqlalchemy meta
721 - removed cache_manager settings from sqlalchemy meta
716 - added sqlalchemy cache settings to ini files
722 - added sqlalchemy cache settings to ini files
717 - validated password length and added second try of failure on paster setup-app
723 - validated password length and added second try of failure on paster setup-app
718 - fixed setup database destroy prompt even when there was no db
724 - fixed setup database destroy prompt even when there was no db
719
725
720
726
721 1.0.0rc3 (**2010-10-11**)
727 1.0.0rc3 (**2010-10-11**)
722 -------------------------
728 -------------------------
723
729
724 - fixed i18n during installation.
730 - fixed i18n during installation.
725
731
726 1.0.0rc2 (**2010-10-11**)
732 1.0.0rc2 (**2010-10-11**)
727 -------------------------
733 -------------------------
728
734
729 - Disabled dirsize in file browser, it's causing nasty bug when dir renames
735 - Disabled dirsize in file browser, it's causing nasty bug when dir renames
730 occure. After vcs is fixed it'll be put back again.
736 occure. After vcs is fixed it'll be put back again.
731 - templating/css rewrites, optimized css. No newline at end of file
737 - templating/css rewrites, optimized css.
@@ -1,625 +1,653 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.lib.db_manage
3 rhodecode.lib.db_manage
4 ~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 Database creation, and setup module for RhodeCode. Used for creation
6 Database creation, and setup module for RhodeCode. Used for creation
7 of database as well as for migration operations
7 of database as well as for migration operations
8
8
9 :created_on: Apr 10, 2010
9 :created_on: Apr 10, 2010
10 :author: marcink
10 :author: marcink
11 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
12 :license: GPLv3, see COPYING for more details.
12 :license: GPLv3, see COPYING for more details.
13 """
13 """
14 # This program is free software: you can redistribute it and/or modify
14 # This program is free software: you can redistribute it and/or modify
15 # it under the terms of the GNU General Public License as published by
15 # it under the terms of the GNU General Public License as published by
16 # the Free Software Foundation, either version 3 of the License, or
16 # the Free Software Foundation, either version 3 of the License, or
17 # (at your option) any later version.
17 # (at your option) any later version.
18 #
18 #
19 # This program is distributed in the hope that it will be useful,
19 # This program is distributed in the hope that it will be useful,
20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 # GNU General Public License for more details.
22 # GNU General Public License for more details.
23 #
23 #
24 # You should have received a copy of the GNU General Public License
24 # You should have received a copy of the GNU General Public License
25 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 # along with this program. If not, see <http://www.gnu.org/licenses/>.
26
26
27 import os
27 import os
28 import sys
28 import sys
29 import uuid
29 import uuid
30 import logging
30 import logging
31 from os.path import dirname as dn, join as jn
31 from os.path import dirname as dn, join as jn
32
32
33 from rhodecode import __dbversion__
33 from rhodecode import __dbversion__
34
34
35 from rhodecode.model.user import UserModel
35 from rhodecode.model.user import UserModel
36 from rhodecode.lib.utils import ask_ok
36 from rhodecode.lib.utils import ask_ok
37 from rhodecode.model import init_model
37 from rhodecode.model import init_model
38 from rhodecode.model.db import User, Permission, RhodeCodeUi, \
38 from rhodecode.model.db import User, Permission, RhodeCodeUi, \
39 RhodeCodeSetting, UserToPerm, DbMigrateVersion, RepoGroup, \
39 RhodeCodeSetting, UserToPerm, DbMigrateVersion, RepoGroup, \
40 UserRepoGroupToPerm
40 UserRepoGroupToPerm
41
41
42 from sqlalchemy.engine import create_engine
42 from sqlalchemy.engine import create_engine
43 from rhodecode.model.repos_group import ReposGroupModel
43 from rhodecode.model.repos_group import ReposGroupModel
44 #from rhodecode.model import meta
44 #from rhodecode.model import meta
45 from rhodecode.model.meta import Session, Base
45 from rhodecode.model.meta import Session, Base
46
46
47
47
48 log = logging.getLogger(__name__)
48 log = logging.getLogger(__name__)
49
49
50
50
51 def notify(msg):
51 def notify(msg):
52 """
52 """
53 Notification for migrations messages
53 Notification for migrations messages
54 """
54 """
55 ml = len(msg) + (4 * 2)
55 ml = len(msg) + (4 * 2)
56 print >> sys.stdout, ('*** %s ***\n%s' % (msg, '*' * ml)).upper()
56 print >> sys.stdout, ('*** %s ***\n%s' % (msg, '*' * ml)).upper()
57
57
58
58
59 class DbManage(object):
59 class DbManage(object):
60 def __init__(self, log_sql, dbconf, root, tests=False):
60 def __init__(self, log_sql, dbconf, root, tests=False):
61 self.dbname = dbconf.split('/')[-1]
61 self.dbname = dbconf.split('/')[-1]
62 self.tests = tests
62 self.tests = tests
63 self.root = root
63 self.root = root
64 self.dburi = dbconf
64 self.dburi = dbconf
65 self.log_sql = log_sql
65 self.log_sql = log_sql
66 self.db_exists = False
66 self.db_exists = False
67 self.init_db()
67 self.init_db()
68
68
69 def init_db(self):
69 def init_db(self):
70 engine = create_engine(self.dburi, echo=self.log_sql)
70 engine = create_engine(self.dburi, echo=self.log_sql)
71 init_model(engine)
71 init_model(engine)
72 self.sa = Session()
72 self.sa = Session()
73
73
74 def create_tables(self, override=False, defaults={}):
74 def create_tables(self, override=False, defaults={}):
75 """
75 """
76 Create a auth database
76 Create a auth database
77 """
77 """
78 quiet = defaults.get('quiet')
78 quiet = defaults.get('quiet')
79 log.info("Any existing database is going to be destroyed")
79 log.info("Any existing database is going to be destroyed")
80 if self.tests or quiet:
80 if self.tests or quiet:
81 destroy = True
81 destroy = True
82 else:
82 else:
83 destroy = ask_ok('Are you sure to destroy old database ? [y/n]')
83 destroy = ask_ok('Are you sure to destroy old database ? [y/n]')
84 if not destroy:
84 if not destroy:
85 sys.exit()
85 sys.exit()
86 if destroy:
86 if destroy:
87 Base.metadata.drop_all()
87 Base.metadata.drop_all()
88
88
89 checkfirst = not override
89 checkfirst = not override
90 Base.metadata.create_all(checkfirst=checkfirst)
90 Base.metadata.create_all(checkfirst=checkfirst)
91 log.info('Created tables for %s' % self.dbname)
91 log.info('Created tables for %s' % self.dbname)
92
92
93 def set_db_version(self):
93 def set_db_version(self):
94 ver = DbMigrateVersion()
94 ver = DbMigrateVersion()
95 ver.version = __dbversion__
95 ver.version = __dbversion__
96 ver.repository_id = 'rhodecode_db_migrations'
96 ver.repository_id = 'rhodecode_db_migrations'
97 ver.repository_path = 'versions'
97 ver.repository_path = 'versions'
98 self.sa.add(ver)
98 self.sa.add(ver)
99 log.info('db version set to: %s' % __dbversion__)
99 log.info('db version set to: %s' % __dbversion__)
100
100
101 def upgrade(self):
101 def upgrade(self):
102 """
102 """
103 Upgrades given database schema to given revision following
103 Upgrades given database schema to given revision following
104 all needed steps, to perform the upgrade
104 all needed steps, to perform the upgrade
105
105
106 """
106 """
107
107
108 from rhodecode.lib.dbmigrate.migrate.versioning import api
108 from rhodecode.lib.dbmigrate.migrate.versioning import api
109 from rhodecode.lib.dbmigrate.migrate.exceptions import \
109 from rhodecode.lib.dbmigrate.migrate.exceptions import \
110 DatabaseNotControlledError
110 DatabaseNotControlledError
111
111
112 if 'sqlite' in self.dburi:
112 if 'sqlite' in self.dburi:
113 print (
113 print (
114 '********************** WARNING **********************\n'
114 '********************** WARNING **********************\n'
115 'Make sure your version of sqlite is at least 3.7.X. \n'
115 'Make sure your version of sqlite is at least 3.7.X. \n'
116 'Earlier versions are known to fail on some migrations\n'
116 'Earlier versions are known to fail on some migrations\n'
117 '*****************************************************\n'
117 '*****************************************************\n'
118 )
118 )
119 upgrade = ask_ok('You are about to perform database upgrade, make '
119 upgrade = ask_ok('You are about to perform database upgrade, make '
120 'sure You backed up your database before. '
120 'sure You backed up your database before. '
121 'Continue ? [y/n]')
121 'Continue ? [y/n]')
122 if not upgrade:
122 if not upgrade:
123 sys.exit('Nothing done')
123 sys.exit('Nothing done')
124
124
125 repository_path = jn(dn(dn(dn(os.path.realpath(__file__)))),
125 repository_path = jn(dn(dn(dn(os.path.realpath(__file__)))),
126 'rhodecode/lib/dbmigrate')
126 'rhodecode/lib/dbmigrate')
127 db_uri = self.dburi
127 db_uri = self.dburi
128
128
129 try:
129 try:
130 curr_version = api.db_version(db_uri, repository_path)
130 curr_version = api.db_version(db_uri, repository_path)
131 msg = ('Found current database under version'
131 msg = ('Found current database under version'
132 ' control with version %s' % curr_version)
132 ' control with version %s' % curr_version)
133
133
134 except (RuntimeError, DatabaseNotControlledError):
134 except (RuntimeError, DatabaseNotControlledError):
135 curr_version = 1
135 curr_version = 1
136 msg = ('Current database is not under version control. Setting'
136 msg = ('Current database is not under version control. Setting'
137 ' as version %s' % curr_version)
137 ' as version %s' % curr_version)
138 api.version_control(db_uri, repository_path, curr_version)
138 api.version_control(db_uri, repository_path, curr_version)
139
139
140 notify(msg)
140 notify(msg)
141
141
142 if curr_version == __dbversion__:
142 if curr_version == __dbversion__:
143 sys.exit('This database is already at the newest version')
143 sys.exit('This database is already at the newest version')
144
144
145 #======================================================================
145 #======================================================================
146 # UPGRADE STEPS
146 # UPGRADE STEPS
147 #======================================================================
147 #======================================================================
148
148
149 class UpgradeSteps(object):
149 class UpgradeSteps(object):
150 """
150 """
151 Those steps follow schema versions so for example schema
151 Those steps follow schema versions so for example schema
152 for example schema with seq 002 == step_2 and so on.
152 for example schema with seq 002 == step_2 and so on.
153 """
153 """
154
154
155 def __init__(self, klass):
155 def __init__(self, klass):
156 self.klass = klass
156 self.klass = klass
157
157
158 def step_0(self):
158 def step_0(self):
159 # step 0 is the schema upgrade, and than follow proper upgrades
159 # step 0 is the schema upgrade, and than follow proper upgrades
160 notify('attempting to do database upgrade to version %s' \
160 notify('attempting to do database upgrade to version %s' \
161 % __dbversion__)
161 % __dbversion__)
162 api.upgrade(db_uri, repository_path, __dbversion__)
162 api.upgrade(db_uri, repository_path, __dbversion__)
163 notify('Schema upgrade completed')
163 notify('Schema upgrade completed')
164
164
165 def step_1(self):
165 def step_1(self):
166 pass
166 pass
167
167
168 def step_2(self):
168 def step_2(self):
169 notify('Patching repo paths for newer version of RhodeCode')
169 notify('Patching repo paths for newer version of RhodeCode')
170 self.klass.fix_repo_paths()
170 self.klass.fix_repo_paths()
171
171
172 notify('Patching default user of RhodeCode')
172 notify('Patching default user of RhodeCode')
173 self.klass.fix_default_user()
173 self.klass.fix_default_user()
174
174
175 log.info('Changing ui settings')
175 log.info('Changing ui settings')
176 self.klass.create_ui_settings()
176 self.klass.create_ui_settings()
177
177
178 def step_3(self):
178 def step_3(self):
179 notify('Adding additional settings into RhodeCode db')
179 notify('Adding additional settings into RhodeCode db')
180 self.klass.fix_settings()
180 self.klass.fix_settings()
181 notify('Adding ldap defaults')
181 notify('Adding ldap defaults')
182 self.klass.create_ldap_options(skip_existing=True)
182 self.klass.create_ldap_options(skip_existing=True)
183
183
184 def step_4(self):
184 def step_4(self):
185 notify('create permissions and fix groups')
185 notify('create permissions and fix groups')
186 self.klass.create_permissions()
186 self.klass.create_permissions()
187 self.klass.fixup_groups()
187 self.klass.fixup_groups()
188
188
189 def step_5(self):
189 def step_5(self):
190 pass
190 pass
191
191
192 def step_6(self):
192 def step_6(self):
193
193
194 notify('re-checking permissions')
194 notify('re-checking permissions')
195 self.klass.create_permissions()
195 self.klass.create_permissions()
196
196
197 notify('installing new UI options')
197 notify('installing new UI options')
198 sett4 = RhodeCodeSetting('show_public_icon', True)
198 sett4 = RhodeCodeSetting('show_public_icon', True)
199 Session().add(sett4)
199 Session().add(sett4)
200 sett5 = RhodeCodeSetting('show_private_icon', True)
200 sett5 = RhodeCodeSetting('show_private_icon', True)
201 Session().add(sett5)
201 Session().add(sett5)
202 sett6 = RhodeCodeSetting('stylify_metatags', False)
202 sett6 = RhodeCodeSetting('stylify_metatags', False)
203 Session().add(sett6)
203 Session().add(sett6)
204
204
205 notify('fixing old PULL hook')
205 notify('fixing old PULL hook')
206 _pull = RhodeCodeUi.get_by_key('preoutgoing.pull_logger')
206 _pull = RhodeCodeUi.get_by_key('preoutgoing.pull_logger')
207 if _pull:
207 if _pull:
208 _pull.ui_key = RhodeCodeUi.HOOK_PULL
208 _pull.ui_key = RhodeCodeUi.HOOK_PULL
209 Session().add(_pull)
209 Session().add(_pull)
210
210
211 notify('fixing old PUSH hook')
211 notify('fixing old PUSH hook')
212 _push = RhodeCodeUi.get_by_key('pretxnchangegroup.push_logger')
212 _push = RhodeCodeUi.get_by_key('pretxnchangegroup.push_logger')
213 if _push:
213 if _push:
214 _push.ui_key = RhodeCodeUi.HOOK_PUSH
214 _push.ui_key = RhodeCodeUi.HOOK_PUSH
215 Session().add(_push)
215 Session().add(_push)
216
216
217 notify('installing new pre-push hook')
217 notify('installing new pre-push hook')
218 hooks4 = RhodeCodeUi()
218 hooks4 = RhodeCodeUi()
219 hooks4.ui_section = 'hooks'
219 hooks4.ui_section = 'hooks'
220 hooks4.ui_key = RhodeCodeUi.HOOK_PRE_PUSH
220 hooks4.ui_key = RhodeCodeUi.HOOK_PRE_PUSH
221 hooks4.ui_value = 'python:rhodecode.lib.hooks.pre_push'
221 hooks4.ui_value = 'python:rhodecode.lib.hooks.pre_push'
222 Session().add(hooks4)
222 Session().add(hooks4)
223
223
224 notify('installing new pre-pull hook')
224 notify('installing new pre-pull hook')
225 hooks6 = RhodeCodeUi()
225 hooks6 = RhodeCodeUi()
226 hooks6.ui_section = 'hooks'
226 hooks6.ui_section = 'hooks'
227 hooks6.ui_key = RhodeCodeUi.HOOK_PRE_PULL
227 hooks6.ui_key = RhodeCodeUi.HOOK_PRE_PULL
228 hooks6.ui_value = 'python:rhodecode.lib.hooks.pre_pull'
228 hooks6.ui_value = 'python:rhodecode.lib.hooks.pre_pull'
229 Session().add(hooks6)
229 Session().add(hooks6)
230
230
231 notify('installing hgsubversion option')
231 notify('installing hgsubversion option')
232 # enable hgsubversion disabled by default
232 # enable hgsubversion disabled by default
233 hgsubversion = RhodeCodeUi()
233 hgsubversion = RhodeCodeUi()
234 hgsubversion.ui_section = 'extensions'
234 hgsubversion.ui_section = 'extensions'
235 hgsubversion.ui_key = 'hgsubversion'
235 hgsubversion.ui_key = 'hgsubversion'
236 hgsubversion.ui_value = ''
236 hgsubversion.ui_value = ''
237 hgsubversion.ui_active = False
237 hgsubversion.ui_active = False
238 Session().add(hgsubversion)
238 Session().add(hgsubversion)
239
239
240 notify('installing hg git option')
240 notify('installing hg git option')
241 # enable hggit disabled by default
241 # enable hggit disabled by default
242 hggit = RhodeCodeUi()
242 hggit = RhodeCodeUi()
243 hggit.ui_section = 'extensions'
243 hggit.ui_section = 'extensions'
244 hggit.ui_key = 'hggit'
244 hggit.ui_key = 'hggit'
245 hggit.ui_value = ''
245 hggit.ui_value = ''
246 hggit.ui_active = False
246 hggit.ui_active = False
247 Session().add(hggit)
247 Session().add(hggit)
248
248
249 notify('re-check default permissions')
249 notify('re-check default permissions')
250 default_user = User.get_by_username(User.DEFAULT_USER)
250 default_user = User.get_by_username(User.DEFAULT_USER)
251 perm = Permission.get_by_key('hg.fork.repository')
251 perm = Permission.get_by_key('hg.fork.repository')
252 reg_perm = UserToPerm()
252 reg_perm = UserToPerm()
253 reg_perm.user = default_user
253 reg_perm.user = default_user
254 reg_perm.permission = perm
254 reg_perm.permission = perm
255 Session().add(reg_perm)
255 Session().add(reg_perm)
256
256
257 def step_7(self):
257 def step_7(self):
258 pass
258 perm_fixes = self.klass.reset_permissions(User.DEFAULT_USER)
259 Session().commit()
260 if perm_fixes:
261 notify('There was an inconsistent state of permissions '
262 'detected for default user. Permissions are now '
263 'reset to the default value for default user. '
264 'Please validate and check default permissions '
265 'in admin panel')
259
266
260 upgrade_steps = [0] + range(curr_version + 1, __dbversion__ + 1)
267 upgrade_steps = [0] + range(curr_version + 1, __dbversion__ + 1)
261
268
262 # CALL THE PROPER ORDER OF STEPS TO PERFORM FULL UPGRADE
269 # CALL THE PROPER ORDER OF STEPS TO PERFORM FULL UPGRADE
263 _step = None
270 _step = None
264 for step in upgrade_steps:
271 for step in upgrade_steps:
265 notify('performing upgrade step %s' % step)
272 notify('performing upgrade step %s' % step)
266 getattr(UpgradeSteps(self), 'step_%s' % step)()
273 getattr(UpgradeSteps(self), 'step_%s' % step)()
267 self.sa.commit()
274 self.sa.commit()
268 _step = step
275 _step = step
269
276
270 notify('upgrade to version %s successful' % _step)
277 notify('upgrade to version %s successful' % _step)
271
278
272 def fix_repo_paths(self):
279 def fix_repo_paths(self):
273 """
280 """
274 Fixes a old rhodecode version path into new one without a '*'
281 Fixes a old rhodecode version path into new one without a '*'
275 """
282 """
276
283
277 paths = self.sa.query(RhodeCodeUi)\
284 paths = self.sa.query(RhodeCodeUi)\
278 .filter(RhodeCodeUi.ui_key == '/')\
285 .filter(RhodeCodeUi.ui_key == '/')\
279 .scalar()
286 .scalar()
280
287
281 paths.ui_value = paths.ui_value.replace('*', '')
288 paths.ui_value = paths.ui_value.replace('*', '')
282
289
283 try:
290 try:
284 self.sa.add(paths)
291 self.sa.add(paths)
285 self.sa.commit()
292 self.sa.commit()
286 except:
293 except:
287 self.sa.rollback()
294 self.sa.rollback()
288 raise
295 raise
289
296
290 def fix_default_user(self):
297 def fix_default_user(self):
291 """
298 """
292 Fixes a old default user with some 'nicer' default values,
299 Fixes a old default user with some 'nicer' default values,
293 used mostly for anonymous access
300 used mostly for anonymous access
294 """
301 """
295 def_user = self.sa.query(User)\
302 def_user = self.sa.query(User)\
296 .filter(User.username == 'default')\
303 .filter(User.username == 'default')\
297 .one()
304 .one()
298
305
299 def_user.name = 'Anonymous'
306 def_user.name = 'Anonymous'
300 def_user.lastname = 'User'
307 def_user.lastname = 'User'
301 def_user.email = 'anonymous@rhodecode.org'
308 def_user.email = 'anonymous@rhodecode.org'
302
309
303 try:
310 try:
304 self.sa.add(def_user)
311 self.sa.add(def_user)
305 self.sa.commit()
312 self.sa.commit()
306 except:
313 except:
307 self.sa.rollback()
314 self.sa.rollback()
308 raise
315 raise
309
316
310 def fix_settings(self):
317 def fix_settings(self):
311 """
318 """
312 Fixes rhodecode settings adds ga_code key for google analytics
319 Fixes rhodecode settings adds ga_code key for google analytics
313 """
320 """
314
321
315 hgsettings3 = RhodeCodeSetting('ga_code', '')
322 hgsettings3 = RhodeCodeSetting('ga_code', '')
316
323
317 try:
324 try:
318 self.sa.add(hgsettings3)
325 self.sa.add(hgsettings3)
319 self.sa.commit()
326 self.sa.commit()
320 except:
327 except:
321 self.sa.rollback()
328 self.sa.rollback()
322 raise
329 raise
323
330
324 def admin_prompt(self, second=False, defaults={}):
331 def admin_prompt(self, second=False, defaults={}):
325 if not self.tests:
332 if not self.tests:
326 import getpass
333 import getpass
327
334
328 # defaults
335 # defaults
329 username = defaults.get('username')
336 username = defaults.get('username')
330 password = defaults.get('password')
337 password = defaults.get('password')
331 email = defaults.get('email')
338 email = defaults.get('email')
332
339
333 def get_password():
340 def get_password():
334 password = getpass.getpass('Specify admin password '
341 password = getpass.getpass('Specify admin password '
335 '(min 6 chars):')
342 '(min 6 chars):')
336 confirm = getpass.getpass('Confirm password:')
343 confirm = getpass.getpass('Confirm password:')
337
344
338 if password != confirm:
345 if password != confirm:
339 log.error('passwords mismatch')
346 log.error('passwords mismatch')
340 return False
347 return False
341 if len(password) < 6:
348 if len(password) < 6:
342 log.error('password is to short use at least 6 characters')
349 log.error('password is to short use at least 6 characters')
343 return False
350 return False
344
351
345 return password
352 return password
346 if username is None:
353 if username is None:
347 username = raw_input('Specify admin username:')
354 username = raw_input('Specify admin username:')
348 if password is None:
355 if password is None:
349 password = get_password()
356 password = get_password()
350 if not password:
357 if not password:
351 #second try
358 #second try
352 password = get_password()
359 password = get_password()
353 if not password:
360 if not password:
354 sys.exit()
361 sys.exit()
355 if email is None:
362 if email is None:
356 email = raw_input('Specify admin email:')
363 email = raw_input('Specify admin email:')
357 self.create_user(username, password, email, True)
364 self.create_user(username, password, email, True)
358 else:
365 else:
359 log.info('creating admin and regular test users')
366 log.info('creating admin and regular test users')
360 from rhodecode.tests import TEST_USER_ADMIN_LOGIN, \
367 from rhodecode.tests import TEST_USER_ADMIN_LOGIN, \
361 TEST_USER_ADMIN_PASS, TEST_USER_ADMIN_EMAIL, \
368 TEST_USER_ADMIN_PASS, TEST_USER_ADMIN_EMAIL, \
362 TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS, \
369 TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS, \
363 TEST_USER_REGULAR_EMAIL, TEST_USER_REGULAR2_LOGIN, \
370 TEST_USER_REGULAR_EMAIL, TEST_USER_REGULAR2_LOGIN, \
364 TEST_USER_REGULAR2_PASS, TEST_USER_REGULAR2_EMAIL
371 TEST_USER_REGULAR2_PASS, TEST_USER_REGULAR2_EMAIL
365
372
366 self.create_user(TEST_USER_ADMIN_LOGIN, TEST_USER_ADMIN_PASS,
373 self.create_user(TEST_USER_ADMIN_LOGIN, TEST_USER_ADMIN_PASS,
367 TEST_USER_ADMIN_EMAIL, True)
374 TEST_USER_ADMIN_EMAIL, True)
368
375
369 self.create_user(TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS,
376 self.create_user(TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS,
370 TEST_USER_REGULAR_EMAIL, False)
377 TEST_USER_REGULAR_EMAIL, False)
371
378
372 self.create_user(TEST_USER_REGULAR2_LOGIN, TEST_USER_REGULAR2_PASS,
379 self.create_user(TEST_USER_REGULAR2_LOGIN, TEST_USER_REGULAR2_PASS,
373 TEST_USER_REGULAR2_EMAIL, False)
380 TEST_USER_REGULAR2_EMAIL, False)
374
381
375 def create_ui_settings(self):
382 def create_ui_settings(self):
376 """
383 """
377 Creates ui settings, fills out hooks
384 Creates ui settings, fills out hooks
378 and disables dotencode
385 and disables dotencode
379 """
386 """
380
387
381 #HOOKS
388 #HOOKS
382 hooks1_key = RhodeCodeUi.HOOK_UPDATE
389 hooks1_key = RhodeCodeUi.HOOK_UPDATE
383 hooks1_ = self.sa.query(RhodeCodeUi)\
390 hooks1_ = self.sa.query(RhodeCodeUi)\
384 .filter(RhodeCodeUi.ui_key == hooks1_key).scalar()
391 .filter(RhodeCodeUi.ui_key == hooks1_key).scalar()
385
392
386 hooks1 = RhodeCodeUi() if hooks1_ is None else hooks1_
393 hooks1 = RhodeCodeUi() if hooks1_ is None else hooks1_
387 hooks1.ui_section = 'hooks'
394 hooks1.ui_section = 'hooks'
388 hooks1.ui_key = hooks1_key
395 hooks1.ui_key = hooks1_key
389 hooks1.ui_value = 'hg update >&2'
396 hooks1.ui_value = 'hg update >&2'
390 hooks1.ui_active = False
397 hooks1.ui_active = False
391 self.sa.add(hooks1)
398 self.sa.add(hooks1)
392
399
393 hooks2_key = RhodeCodeUi.HOOK_REPO_SIZE
400 hooks2_key = RhodeCodeUi.HOOK_REPO_SIZE
394 hooks2_ = self.sa.query(RhodeCodeUi)\
401 hooks2_ = self.sa.query(RhodeCodeUi)\
395 .filter(RhodeCodeUi.ui_key == hooks2_key).scalar()
402 .filter(RhodeCodeUi.ui_key == hooks2_key).scalar()
396 hooks2 = RhodeCodeUi() if hooks2_ is None else hooks2_
403 hooks2 = RhodeCodeUi() if hooks2_ is None else hooks2_
397 hooks2.ui_section = 'hooks'
404 hooks2.ui_section = 'hooks'
398 hooks2.ui_key = hooks2_key
405 hooks2.ui_key = hooks2_key
399 hooks2.ui_value = 'python:rhodecode.lib.hooks.repo_size'
406 hooks2.ui_value = 'python:rhodecode.lib.hooks.repo_size'
400 self.sa.add(hooks2)
407 self.sa.add(hooks2)
401
408
402 hooks3 = RhodeCodeUi()
409 hooks3 = RhodeCodeUi()
403 hooks3.ui_section = 'hooks'
410 hooks3.ui_section = 'hooks'
404 hooks3.ui_key = RhodeCodeUi.HOOK_PUSH
411 hooks3.ui_key = RhodeCodeUi.HOOK_PUSH
405 hooks3.ui_value = 'python:rhodecode.lib.hooks.log_push_action'
412 hooks3.ui_value = 'python:rhodecode.lib.hooks.log_push_action'
406 self.sa.add(hooks3)
413 self.sa.add(hooks3)
407
414
408 hooks4 = RhodeCodeUi()
415 hooks4 = RhodeCodeUi()
409 hooks4.ui_section = 'hooks'
416 hooks4.ui_section = 'hooks'
410 hooks4.ui_key = RhodeCodeUi.HOOK_PRE_PUSH
417 hooks4.ui_key = RhodeCodeUi.HOOK_PRE_PUSH
411 hooks4.ui_value = 'python:rhodecode.lib.hooks.pre_push'
418 hooks4.ui_value = 'python:rhodecode.lib.hooks.pre_push'
412 self.sa.add(hooks4)
419 self.sa.add(hooks4)
413
420
414 hooks5 = RhodeCodeUi()
421 hooks5 = RhodeCodeUi()
415 hooks5.ui_section = 'hooks'
422 hooks5.ui_section = 'hooks'
416 hooks5.ui_key = RhodeCodeUi.HOOK_PULL
423 hooks5.ui_key = RhodeCodeUi.HOOK_PULL
417 hooks5.ui_value = 'python:rhodecode.lib.hooks.log_pull_action'
424 hooks5.ui_value = 'python:rhodecode.lib.hooks.log_pull_action'
418 self.sa.add(hooks5)
425 self.sa.add(hooks5)
419
426
420 hooks6 = RhodeCodeUi()
427 hooks6 = RhodeCodeUi()
421 hooks6.ui_section = 'hooks'
428 hooks6.ui_section = 'hooks'
422 hooks6.ui_key = RhodeCodeUi.HOOK_PRE_PULL
429 hooks6.ui_key = RhodeCodeUi.HOOK_PRE_PULL
423 hooks6.ui_value = 'python:rhodecode.lib.hooks.pre_pull'
430 hooks6.ui_value = 'python:rhodecode.lib.hooks.pre_pull'
424 self.sa.add(hooks6)
431 self.sa.add(hooks6)
425
432
426 # enable largefiles
433 # enable largefiles
427 largefiles = RhodeCodeUi()
434 largefiles = RhodeCodeUi()
428 largefiles.ui_section = 'extensions'
435 largefiles.ui_section = 'extensions'
429 largefiles.ui_key = 'largefiles'
436 largefiles.ui_key = 'largefiles'
430 largefiles.ui_value = ''
437 largefiles.ui_value = ''
431 self.sa.add(largefiles)
438 self.sa.add(largefiles)
432
439
433 # enable hgsubversion disabled by default
440 # enable hgsubversion disabled by default
434 hgsubversion = RhodeCodeUi()
441 hgsubversion = RhodeCodeUi()
435 hgsubversion.ui_section = 'extensions'
442 hgsubversion.ui_section = 'extensions'
436 hgsubversion.ui_key = 'hgsubversion'
443 hgsubversion.ui_key = 'hgsubversion'
437 hgsubversion.ui_value = ''
444 hgsubversion.ui_value = ''
438 hgsubversion.ui_active = False
445 hgsubversion.ui_active = False
439 self.sa.add(hgsubversion)
446 self.sa.add(hgsubversion)
440
447
441 # enable hggit disabled by default
448 # enable hggit disabled by default
442 hggit = RhodeCodeUi()
449 hggit = RhodeCodeUi()
443 hggit.ui_section = 'extensions'
450 hggit.ui_section = 'extensions'
444 hggit.ui_key = 'hggit'
451 hggit.ui_key = 'hggit'
445 hggit.ui_value = ''
452 hggit.ui_value = ''
446 hggit.ui_active = False
453 hggit.ui_active = False
447 self.sa.add(hggit)
454 self.sa.add(hggit)
448
455
449 def create_ldap_options(self, skip_existing=False):
456 def create_ldap_options(self, skip_existing=False):
450 """Creates ldap settings"""
457 """Creates ldap settings"""
451
458
452 for k, v in [('ldap_active', 'false'), ('ldap_host', ''),
459 for k, v in [('ldap_active', 'false'), ('ldap_host', ''),
453 ('ldap_port', '389'), ('ldap_tls_kind', 'PLAIN'),
460 ('ldap_port', '389'), ('ldap_tls_kind', 'PLAIN'),
454 ('ldap_tls_reqcert', ''), ('ldap_dn_user', ''),
461 ('ldap_tls_reqcert', ''), ('ldap_dn_user', ''),
455 ('ldap_dn_pass', ''), ('ldap_base_dn', ''),
462 ('ldap_dn_pass', ''), ('ldap_base_dn', ''),
456 ('ldap_filter', ''), ('ldap_search_scope', ''),
463 ('ldap_filter', ''), ('ldap_search_scope', ''),
457 ('ldap_attr_login', ''), ('ldap_attr_firstname', ''),
464 ('ldap_attr_login', ''), ('ldap_attr_firstname', ''),
458 ('ldap_attr_lastname', ''), ('ldap_attr_email', '')]:
465 ('ldap_attr_lastname', ''), ('ldap_attr_email', '')]:
459
466
460 if skip_existing and RhodeCodeSetting.get_by_name(k) != None:
467 if skip_existing and RhodeCodeSetting.get_by_name(k) != None:
461 log.debug('Skipping option %s' % k)
468 log.debug('Skipping option %s' % k)
462 continue
469 continue
463 setting = RhodeCodeSetting(k, v)
470 setting = RhodeCodeSetting(k, v)
464 self.sa.add(setting)
471 self.sa.add(setting)
465
472
466 def fixup_groups(self):
473 def fixup_groups(self):
467 def_usr = User.get_by_username('default')
474 def_usr = User.get_by_username('default')
468 for g in RepoGroup.query().all():
475 for g in RepoGroup.query().all():
469 g.group_name = g.get_new_name(g.name)
476 g.group_name = g.get_new_name(g.name)
470 self.sa.add(g)
477 self.sa.add(g)
471 # get default perm
478 # get default perm
472 default = UserRepoGroupToPerm.query()\
479 default = UserRepoGroupToPerm.query()\
473 .filter(UserRepoGroupToPerm.group == g)\
480 .filter(UserRepoGroupToPerm.group == g)\
474 .filter(UserRepoGroupToPerm.user == def_usr)\
481 .filter(UserRepoGroupToPerm.user == def_usr)\
475 .scalar()
482 .scalar()
476
483
477 if default is None:
484 if default is None:
478 log.debug('missing default permission for group %s adding' % g)
485 log.debug('missing default permission for group %s adding' % g)
479 ReposGroupModel()._create_default_perms(g)
486 ReposGroupModel()._create_default_perms(g)
480
487
488 def reset_permissions(self, username):
489 """
490 Resets permissions to default state, usefull when old systems had
491 bad permissions, we must clean them up
492
493 :param username:
494 :type username:
495 """
496 default_user = User.get_by_username(username)
497 if not default_user:
498 return
499
500 u2p = UserToPerm.query()\
501 .filter(UserToPerm.user == default_user).all()
502 fixed = False
503 if len(u2p) != len(User.DEFAULT_PERMISSIONS):
504 for p in u2p:
505 Session().delete(p)
506 fixed = True
507 self.populate_default_permissions()
508 return fixed
509
481 def config_prompt(self, test_repo_path='', retries=3, defaults={}):
510 def config_prompt(self, test_repo_path='', retries=3, defaults={}):
482 _path = defaults.get('repos_location')
511 _path = defaults.get('repos_location')
483 if retries == 3:
512 if retries == 3:
484 log.info('Setting up repositories config')
513 log.info('Setting up repositories config')
485
514
486 if _path is not None:
515 if _path is not None:
487 path = _path
516 path = _path
488 elif not self.tests and not test_repo_path:
517 elif not self.tests and not test_repo_path:
489 path = raw_input(
518 path = raw_input(
490 'Enter a valid absolute path to store repositories. '
519 'Enter a valid absolute path to store repositories. '
491 'All repositories in that path will be added automatically:'
520 'All repositories in that path will be added automatically:'
492 )
521 )
493 else:
522 else:
494 path = test_repo_path
523 path = test_repo_path
495 path_ok = True
524 path_ok = True
496
525
497 # check proper dir
526 # check proper dir
498 if not os.path.isdir(path):
527 if not os.path.isdir(path):
499 path_ok = False
528 path_ok = False
500 log.error('Given path %s is not a valid directory' % path)
529 log.error('Given path %s is not a valid directory' % path)
501
530
502 elif not os.path.isabs(path):
531 elif not os.path.isabs(path):
503 path_ok = False
532 path_ok = False
504 log.error('Given path %s is not an absolute path' % path)
533 log.error('Given path %s is not an absolute path' % path)
505
534
506 # check write access
535 # check write access
507 elif not os.access(path, os.W_OK) and path_ok:
536 elif not os.access(path, os.W_OK) and path_ok:
508 path_ok = False
537 path_ok = False
509 log.error('No write permission to given path %s' % path)
538 log.error('No write permission to given path %s' % path)
510
539
511 if retries == 0:
540 if retries == 0:
512 sys.exit('max retries reached')
541 sys.exit('max retries reached')
513 if path_ok is False:
542 if path_ok is False:
514 retries -= 1
543 retries -= 1
515 return self.config_prompt(test_repo_path, retries)
544 return self.config_prompt(test_repo_path, retries)
516
545
517 return path
546 return path
518
547
519 def create_settings(self, path):
548 def create_settings(self, path):
520
549
521 self.create_ui_settings()
550 self.create_ui_settings()
522
551
523 #HG UI OPTIONS
552 #HG UI OPTIONS
524 web1 = RhodeCodeUi()
553 web1 = RhodeCodeUi()
525 web1.ui_section = 'web'
554 web1.ui_section = 'web'
526 web1.ui_key = 'push_ssl'
555 web1.ui_key = 'push_ssl'
527 web1.ui_value = 'false'
556 web1.ui_value = 'false'
528
557
529 web2 = RhodeCodeUi()
558 web2 = RhodeCodeUi()
530 web2.ui_section = 'web'
559 web2.ui_section = 'web'
531 web2.ui_key = 'allow_archive'
560 web2.ui_key = 'allow_archive'
532 web2.ui_value = 'gz zip bz2'
561 web2.ui_value = 'gz zip bz2'
533
562
534 web3 = RhodeCodeUi()
563 web3 = RhodeCodeUi()
535 web3.ui_section = 'web'
564 web3.ui_section = 'web'
536 web3.ui_key = 'allow_push'
565 web3.ui_key = 'allow_push'
537 web3.ui_value = '*'
566 web3.ui_value = '*'
538
567
539 web4 = RhodeCodeUi()
568 web4 = RhodeCodeUi()
540 web4.ui_section = 'web'
569 web4.ui_section = 'web'
541 web4.ui_key = 'baseurl'
570 web4.ui_key = 'baseurl'
542 web4.ui_value = '/'
571 web4.ui_value = '/'
543
572
544 paths = RhodeCodeUi()
573 paths = RhodeCodeUi()
545 paths.ui_section = 'paths'
574 paths.ui_section = 'paths'
546 paths.ui_key = '/'
575 paths.ui_key = '/'
547 paths.ui_value = path
576 paths.ui_value = path
548
577
549 phases = RhodeCodeUi()
578 phases = RhodeCodeUi()
550 phases.ui_section = 'phases'
579 phases.ui_section = 'phases'
551 phases.ui_key = 'publish'
580 phases.ui_key = 'publish'
552 phases.ui_value = False
581 phases.ui_value = False
553
582
554 sett1 = RhodeCodeSetting('realm', 'RhodeCode authentication')
583 sett1 = RhodeCodeSetting('realm', 'RhodeCode authentication')
555 sett2 = RhodeCodeSetting('title', 'RhodeCode')
584 sett2 = RhodeCodeSetting('title', 'RhodeCode')
556 sett3 = RhodeCodeSetting('ga_code', '')
585 sett3 = RhodeCodeSetting('ga_code', '')
557
586
558 sett4 = RhodeCodeSetting('show_public_icon', True)
587 sett4 = RhodeCodeSetting('show_public_icon', True)
559 sett5 = RhodeCodeSetting('show_private_icon', True)
588 sett5 = RhodeCodeSetting('show_private_icon', True)
560 sett6 = RhodeCodeSetting('stylify_metatags', False)
589 sett6 = RhodeCodeSetting('stylify_metatags', False)
561
590
562 self.sa.add(web1)
591 self.sa.add(web1)
563 self.sa.add(web2)
592 self.sa.add(web2)
564 self.sa.add(web3)
593 self.sa.add(web3)
565 self.sa.add(web4)
594 self.sa.add(web4)
566 self.sa.add(paths)
595 self.sa.add(paths)
567 self.sa.add(sett1)
596 self.sa.add(sett1)
568 self.sa.add(sett2)
597 self.sa.add(sett2)
569 self.sa.add(sett3)
598 self.sa.add(sett3)
570 self.sa.add(sett4)
599 self.sa.add(sett4)
571 self.sa.add(sett5)
600 self.sa.add(sett5)
572 self.sa.add(sett6)
601 self.sa.add(sett6)
573
602
574 self.create_ldap_options()
603 self.create_ldap_options()
575
604
576 log.info('created ui config')
605 log.info('created ui config')
577
606
578 def create_user(self, username, password, email='', admin=False):
607 def create_user(self, username, password, email='', admin=False):
579 log.info('creating user %s' % username)
608 log.info('creating user %s' % username)
580 UserModel().create_or_update(username, password, email,
609 UserModel().create_or_update(username, password, email,
581 firstname='RhodeCode', lastname='Admin',
610 firstname='RhodeCode', lastname='Admin',
582 active=True, admin=admin)
611 active=True, admin=admin)
583
612
584 def create_default_user(self):
613 def create_default_user(self):
585 log.info('creating default user')
614 log.info('creating default user')
586 # create default user for handling default permissions.
615 # create default user for handling default permissions.
587 UserModel().create_or_update(username='default',
616 UserModel().create_or_update(username='default',
588 password=str(uuid.uuid1())[:8],
617 password=str(uuid.uuid1())[:8],
589 email='anonymous@rhodecode.org',
618 email='anonymous@rhodecode.org',
590 firstname='Anonymous', lastname='User')
619 firstname='Anonymous', lastname='User')
591
620
592 def create_permissions(self):
621 def create_permissions(self):
593 # module.(access|create|change|delete)_[name]
622 # module.(access|create|change|delete)_[name]
594 # module.(none|read|write|admin)
623 # module.(none|read|write|admin)
595
624
596 for p in Permission.PERMS:
625 for p in Permission.PERMS:
597 if not Permission.get_by_key(p[0]):
626 if not Permission.get_by_key(p[0]):
598 new_perm = Permission()
627 new_perm = Permission()
599 new_perm.permission_name = p[0]
628 new_perm.permission_name = p[0]
600 new_perm.permission_longname = p[0]
629 new_perm.permission_longname = p[0]
601 self.sa.add(new_perm)
630 self.sa.add(new_perm)
602
631
603 def populate_default_permissions(self):
632 def populate_default_permissions(self):
604 log.info('creating default user permissions')
633 log.info('creating default user permissions')
605
634
606 default_user = User.get_by_username('default')
635 default_user = User.get_by_username('default')
607
636
608 for def_perm in ['hg.register.manual_activate', 'hg.create.repository',
637 for def_perm in User.DEFAULT_PERMISSIONS:
609 'hg.fork.repository', 'repository.read']:
610
638
611 perm = self.sa.query(Permission)\
639 perm = self.sa.query(Permission)\
612 .filter(Permission.permission_name == def_perm)\
640 .filter(Permission.permission_name == def_perm)\
613 .scalar()
641 .scalar()
614 if not perm:
642 if not perm:
615 raise Exception(
643 raise Exception(
616 'CRITICAL: permission %s not found inside database !!'
644 'CRITICAL: permission %s not found inside database !!'
617 % def_perm
645 % def_perm
618 )
646 )
619 if not UserToPerm.query()\
647 if not UserToPerm.query()\
620 .filter(UserToPerm.permission == perm)\
648 .filter(UserToPerm.permission == perm)\
621 .filter(UserToPerm.user == default_user).scalar():
649 .filter(UserToPerm.user == default_user).scalar():
622 reg_perm = UserToPerm()
650 reg_perm = UserToPerm()
623 reg_perm.user = default_user
651 reg_perm.user = default_user
624 reg_perm.permission = perm
652 reg_perm.permission = perm
625 self.sa.add(reg_perm)
653 self.sa.add(reg_perm)
@@ -1,1771 +1,1774 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.model.db
3 rhodecode.model.db
4 ~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~
5
5
6 Database Models for RhodeCode
6 Database Models for RhodeCode
7
7
8 :created_on: Apr 08, 2010
8 :created_on: Apr 08, 2010
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software: you can redistribute it and/or modify
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
16 # (at your option) any later version.
17 #
17 #
18 # This program is distributed in the hope that it will be useful,
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # GNU General Public License for more details.
21 # GNU General Public License for more details.
22 #
22 #
23 # You should have received a copy of the GNU General Public License
23 # You should have received a copy of the GNU General Public License
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25
25
26 import os
26 import os
27 import logging
27 import logging
28 import datetime
28 import datetime
29 import traceback
29 import traceback
30 import hashlib
30 import hashlib
31 import time
31 import time
32 from collections import defaultdict
32 from collections import defaultdict
33
33
34 from sqlalchemy import *
34 from sqlalchemy import *
35 from sqlalchemy.ext.hybrid import hybrid_property
35 from sqlalchemy.ext.hybrid import hybrid_property
36 from sqlalchemy.orm import relationship, joinedload, class_mapper, validates
36 from sqlalchemy.orm import relationship, joinedload, class_mapper, validates
37 from sqlalchemy.exc import DatabaseError
37 from sqlalchemy.exc import DatabaseError
38 from beaker.cache import cache_region, region_invalidate
38 from beaker.cache import cache_region, region_invalidate
39 from webob.exc import HTTPNotFound
39 from webob.exc import HTTPNotFound
40
40
41 from pylons.i18n.translation import lazy_ugettext as _
41 from pylons.i18n.translation import lazy_ugettext as _
42
42
43 from rhodecode.lib.vcs import get_backend
43 from rhodecode.lib.vcs import get_backend
44 from rhodecode.lib.vcs.utils.helpers import get_scm
44 from rhodecode.lib.vcs.utils.helpers import get_scm
45 from rhodecode.lib.vcs.exceptions import VCSError
45 from rhodecode.lib.vcs.exceptions import VCSError
46 from rhodecode.lib.vcs.utils.lazy import LazyProperty
46 from rhodecode.lib.vcs.utils.lazy import LazyProperty
47
47
48 from rhodecode.lib.utils2 import str2bool, safe_str, get_changeset_safe, \
48 from rhodecode.lib.utils2 import str2bool, safe_str, get_changeset_safe, \
49 safe_unicode
49 safe_unicode
50 from rhodecode.lib.compat import json
50 from rhodecode.lib.compat import json
51 from rhodecode.lib.caching_query import FromCache
51 from rhodecode.lib.caching_query import FromCache
52
52
53 from rhodecode.model.meta import Base, Session
53 from rhodecode.model.meta import Base, Session
54
54
55 URL_SEP = '/'
55 URL_SEP = '/'
56 log = logging.getLogger(__name__)
56 log = logging.getLogger(__name__)
57
57
58 #==============================================================================
58 #==============================================================================
59 # BASE CLASSES
59 # BASE CLASSES
60 #==============================================================================
60 #==============================================================================
61
61
62 _hash_key = lambda k: hashlib.md5(safe_str(k)).hexdigest()
62 _hash_key = lambda k: hashlib.md5(safe_str(k)).hexdigest()
63
63
64
64
65 class BaseModel(object):
65 class BaseModel(object):
66 """
66 """
67 Base Model for all classess
67 Base Model for all classess
68 """
68 """
69
69
70 @classmethod
70 @classmethod
71 def _get_keys(cls):
71 def _get_keys(cls):
72 """return column names for this model """
72 """return column names for this model """
73 return class_mapper(cls).c.keys()
73 return class_mapper(cls).c.keys()
74
74
75 def get_dict(self):
75 def get_dict(self):
76 """
76 """
77 return dict with keys and values corresponding
77 return dict with keys and values corresponding
78 to this model data """
78 to this model data """
79
79
80 d = {}
80 d = {}
81 for k in self._get_keys():
81 for k in self._get_keys():
82 d[k] = getattr(self, k)
82 d[k] = getattr(self, k)
83
83
84 # also use __json__() if present to get additional fields
84 # also use __json__() if present to get additional fields
85 _json_attr = getattr(self, '__json__', None)
85 _json_attr = getattr(self, '__json__', None)
86 if _json_attr:
86 if _json_attr:
87 # update with attributes from __json__
87 # update with attributes from __json__
88 if callable(_json_attr):
88 if callable(_json_attr):
89 _json_attr = _json_attr()
89 _json_attr = _json_attr()
90 for k, val in _json_attr.iteritems():
90 for k, val in _json_attr.iteritems():
91 d[k] = val
91 d[k] = val
92 return d
92 return d
93
93
94 def get_appstruct(self):
94 def get_appstruct(self):
95 """return list with keys and values tupples corresponding
95 """return list with keys and values tupples corresponding
96 to this model data """
96 to this model data """
97
97
98 l = []
98 l = []
99 for k in self._get_keys():
99 for k in self._get_keys():
100 l.append((k, getattr(self, k),))
100 l.append((k, getattr(self, k),))
101 return l
101 return l
102
102
103 def populate_obj(self, populate_dict):
103 def populate_obj(self, populate_dict):
104 """populate model with data from given populate_dict"""
104 """populate model with data from given populate_dict"""
105
105
106 for k in self._get_keys():
106 for k in self._get_keys():
107 if k in populate_dict:
107 if k in populate_dict:
108 setattr(self, k, populate_dict[k])
108 setattr(self, k, populate_dict[k])
109
109
110 @classmethod
110 @classmethod
111 def query(cls):
111 def query(cls):
112 return Session().query(cls)
112 return Session().query(cls)
113
113
114 @classmethod
114 @classmethod
115 def get(cls, id_):
115 def get(cls, id_):
116 if id_:
116 if id_:
117 return cls.query().get(id_)
117 return cls.query().get(id_)
118
118
119 @classmethod
119 @classmethod
120 def get_or_404(cls, id_):
120 def get_or_404(cls, id_):
121 if id_:
121 if id_:
122 res = cls.query().get(id_)
122 res = cls.query().get(id_)
123 if not res:
123 if not res:
124 raise HTTPNotFound
124 raise HTTPNotFound
125 return res
125 return res
126
126
127 @classmethod
127 @classmethod
128 def getAll(cls):
128 def getAll(cls):
129 return cls.query().all()
129 return cls.query().all()
130
130
131 @classmethod
131 @classmethod
132 def delete(cls, id_):
132 def delete(cls, id_):
133 obj = cls.query().get(id_)
133 obj = cls.query().get(id_)
134 Session().delete(obj)
134 Session().delete(obj)
135
135
136 def __repr__(self):
136 def __repr__(self):
137 if hasattr(self, '__unicode__'):
137 if hasattr(self, '__unicode__'):
138 # python repr needs to return str
138 # python repr needs to return str
139 return safe_str(self.__unicode__())
139 return safe_str(self.__unicode__())
140 return '<DB:%s>' % (self.__class__.__name__)
140 return '<DB:%s>' % (self.__class__.__name__)
141
141
142
142
143 class RhodeCodeSetting(Base, BaseModel):
143 class RhodeCodeSetting(Base, BaseModel):
144 __tablename__ = 'rhodecode_settings'
144 __tablename__ = 'rhodecode_settings'
145 __table_args__ = (
145 __table_args__ = (
146 UniqueConstraint('app_settings_name'),
146 UniqueConstraint('app_settings_name'),
147 {'extend_existing': True, 'mysql_engine': 'InnoDB',
147 {'extend_existing': True, 'mysql_engine': 'InnoDB',
148 'mysql_charset': 'utf8'}
148 'mysql_charset': 'utf8'}
149 )
149 )
150 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
150 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
151 app_settings_name = Column("app_settings_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
151 app_settings_name = Column("app_settings_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
152 _app_settings_value = Column("app_settings_value", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
152 _app_settings_value = Column("app_settings_value", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
153
153
154 def __init__(self, k='', v=''):
154 def __init__(self, k='', v=''):
155 self.app_settings_name = k
155 self.app_settings_name = k
156 self.app_settings_value = v
156 self.app_settings_value = v
157
157
158 @validates('_app_settings_value')
158 @validates('_app_settings_value')
159 def validate_settings_value(self, key, val):
159 def validate_settings_value(self, key, val):
160 assert type(val) == unicode
160 assert type(val) == unicode
161 return val
161 return val
162
162
163 @hybrid_property
163 @hybrid_property
164 def app_settings_value(self):
164 def app_settings_value(self):
165 v = self._app_settings_value
165 v = self._app_settings_value
166 if self.app_settings_name == 'ldap_active':
166 if self.app_settings_name == 'ldap_active':
167 v = str2bool(v)
167 v = str2bool(v)
168 return v
168 return v
169
169
170 @app_settings_value.setter
170 @app_settings_value.setter
171 def app_settings_value(self, val):
171 def app_settings_value(self, val):
172 """
172 """
173 Setter that will always make sure we use unicode in app_settings_value
173 Setter that will always make sure we use unicode in app_settings_value
174
174
175 :param val:
175 :param val:
176 """
176 """
177 self._app_settings_value = safe_unicode(val)
177 self._app_settings_value = safe_unicode(val)
178
178
179 def __unicode__(self):
179 def __unicode__(self):
180 return u"<%s('%s:%s')>" % (
180 return u"<%s('%s:%s')>" % (
181 self.__class__.__name__,
181 self.__class__.__name__,
182 self.app_settings_name, self.app_settings_value
182 self.app_settings_name, self.app_settings_value
183 )
183 )
184
184
185 @classmethod
185 @classmethod
186 def get_by_name(cls, key):
186 def get_by_name(cls, key):
187 return cls.query()\
187 return cls.query()\
188 .filter(cls.app_settings_name == key).scalar()
188 .filter(cls.app_settings_name == key).scalar()
189
189
190 @classmethod
190 @classmethod
191 def get_by_name_or_create(cls, key):
191 def get_by_name_or_create(cls, key):
192 res = cls.get_by_name(key)
192 res = cls.get_by_name(key)
193 if not res:
193 if not res:
194 res = cls(key)
194 res = cls(key)
195 return res
195 return res
196
196
197 @classmethod
197 @classmethod
198 def get_app_settings(cls, cache=False):
198 def get_app_settings(cls, cache=False):
199
199
200 ret = cls.query()
200 ret = cls.query()
201
201
202 if cache:
202 if cache:
203 ret = ret.options(FromCache("sql_cache_short", "get_hg_settings"))
203 ret = ret.options(FromCache("sql_cache_short", "get_hg_settings"))
204
204
205 if not ret:
205 if not ret:
206 raise Exception('Could not get application settings !')
206 raise Exception('Could not get application settings !')
207 settings = {}
207 settings = {}
208 for each in ret:
208 for each in ret:
209 settings['rhodecode_' + each.app_settings_name] = \
209 settings['rhodecode_' + each.app_settings_name] = \
210 each.app_settings_value
210 each.app_settings_value
211
211
212 return settings
212 return settings
213
213
214 @classmethod
214 @classmethod
215 def get_ldap_settings(cls, cache=False):
215 def get_ldap_settings(cls, cache=False):
216 ret = cls.query()\
216 ret = cls.query()\
217 .filter(cls.app_settings_name.startswith('ldap_')).all()
217 .filter(cls.app_settings_name.startswith('ldap_')).all()
218 fd = {}
218 fd = {}
219 for row in ret:
219 for row in ret:
220 fd.update({row.app_settings_name: row.app_settings_value})
220 fd.update({row.app_settings_name: row.app_settings_value})
221
221
222 return fd
222 return fd
223
223
224
224
225 class RhodeCodeUi(Base, BaseModel):
225 class RhodeCodeUi(Base, BaseModel):
226 __tablename__ = 'rhodecode_ui'
226 __tablename__ = 'rhodecode_ui'
227 __table_args__ = (
227 __table_args__ = (
228 UniqueConstraint('ui_key'),
228 UniqueConstraint('ui_key'),
229 {'extend_existing': True, 'mysql_engine': 'InnoDB',
229 {'extend_existing': True, 'mysql_engine': 'InnoDB',
230 'mysql_charset': 'utf8'}
230 'mysql_charset': 'utf8'}
231 )
231 )
232
232
233 HOOK_UPDATE = 'changegroup.update'
233 HOOK_UPDATE = 'changegroup.update'
234 HOOK_REPO_SIZE = 'changegroup.repo_size'
234 HOOK_REPO_SIZE = 'changegroup.repo_size'
235 HOOK_PUSH = 'changegroup.push_logger'
235 HOOK_PUSH = 'changegroup.push_logger'
236 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
236 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
237 HOOK_PULL = 'outgoing.pull_logger'
237 HOOK_PULL = 'outgoing.pull_logger'
238 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
238 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
239
239
240 ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
240 ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
241 ui_section = Column("ui_section", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
241 ui_section = Column("ui_section", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
242 ui_key = Column("ui_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
242 ui_key = Column("ui_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
243 ui_value = Column("ui_value", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
243 ui_value = Column("ui_value", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
244 ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True)
244 ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True)
245
245
246 @classmethod
246 @classmethod
247 def get_by_key(cls, key):
247 def get_by_key(cls, key):
248 return cls.query().filter(cls.ui_key == key).scalar()
248 return cls.query().filter(cls.ui_key == key).scalar()
249
249
250 @classmethod
250 @classmethod
251 def get_builtin_hooks(cls):
251 def get_builtin_hooks(cls):
252 q = cls.query()
252 q = cls.query()
253 q = q.filter(cls.ui_key.in_([cls.HOOK_UPDATE, cls.HOOK_REPO_SIZE,
253 q = q.filter(cls.ui_key.in_([cls.HOOK_UPDATE, cls.HOOK_REPO_SIZE,
254 cls.HOOK_PUSH, cls.HOOK_PRE_PUSH,
254 cls.HOOK_PUSH, cls.HOOK_PRE_PUSH,
255 cls.HOOK_PULL, cls.HOOK_PRE_PULL]))
255 cls.HOOK_PULL, cls.HOOK_PRE_PULL]))
256 return q.all()
256 return q.all()
257
257
258 @classmethod
258 @classmethod
259 def get_custom_hooks(cls):
259 def get_custom_hooks(cls):
260 q = cls.query()
260 q = cls.query()
261 q = q.filter(~cls.ui_key.in_([cls.HOOK_UPDATE, cls.HOOK_REPO_SIZE,
261 q = q.filter(~cls.ui_key.in_([cls.HOOK_UPDATE, cls.HOOK_REPO_SIZE,
262 cls.HOOK_PUSH, cls.HOOK_PRE_PUSH,
262 cls.HOOK_PUSH, cls.HOOK_PRE_PUSH,
263 cls.HOOK_PULL, cls.HOOK_PRE_PULL]))
263 cls.HOOK_PULL, cls.HOOK_PRE_PULL]))
264 q = q.filter(cls.ui_section == 'hooks')
264 q = q.filter(cls.ui_section == 'hooks')
265 return q.all()
265 return q.all()
266
266
267 @classmethod
267 @classmethod
268 def get_repos_location(cls):
268 def get_repos_location(cls):
269 return cls.get_by_key('/').ui_value
269 return cls.get_by_key('/').ui_value
270
270
271 @classmethod
271 @classmethod
272 def create_or_update_hook(cls, key, val):
272 def create_or_update_hook(cls, key, val):
273 new_ui = cls.get_by_key(key) or cls()
273 new_ui = cls.get_by_key(key) or cls()
274 new_ui.ui_section = 'hooks'
274 new_ui.ui_section = 'hooks'
275 new_ui.ui_active = True
275 new_ui.ui_active = True
276 new_ui.ui_key = key
276 new_ui.ui_key = key
277 new_ui.ui_value = val
277 new_ui.ui_value = val
278
278
279 Session().add(new_ui)
279 Session().add(new_ui)
280
280
281
281
282 class User(Base, BaseModel):
282 class User(Base, BaseModel):
283 __tablename__ = 'users'
283 __tablename__ = 'users'
284 __table_args__ = (
284 __table_args__ = (
285 UniqueConstraint('username'), UniqueConstraint('email'),
285 UniqueConstraint('username'), UniqueConstraint('email'),
286 Index('u_username_idx', 'username'),
286 Index('u_username_idx', 'username'),
287 Index('u_email_idx', 'email'),
287 Index('u_email_idx', 'email'),
288 {'extend_existing': True, 'mysql_engine': 'InnoDB',
288 {'extend_existing': True, 'mysql_engine': 'InnoDB',
289 'mysql_charset': 'utf8'}
289 'mysql_charset': 'utf8'}
290 )
290 )
291 DEFAULT_USER = 'default'
291 DEFAULT_USER = 'default'
292
292 DEFAULT_PERMISSIONS = [
293 'hg.register.manual_activate', 'hg.create.repository',
294 'hg.fork.repository', 'repository.read'
295 ]
293 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
296 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
294 username = Column("username", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
297 username = Column("username", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
295 password = Column("password", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
298 password = Column("password", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
296 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
299 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
297 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
300 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
298 name = Column("firstname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
301 name = Column("firstname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
299 lastname = Column("lastname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
302 lastname = Column("lastname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
300 _email = Column("email", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
303 _email = Column("email", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
301 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
304 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
302 ldap_dn = Column("ldap_dn", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
305 ldap_dn = Column("ldap_dn", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
303 api_key = Column("api_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
306 api_key = Column("api_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
304 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
307 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
305
308
306 user_log = relationship('UserLog', cascade='all')
309 user_log = relationship('UserLog', cascade='all')
307 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
310 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
308
311
309 repositories = relationship('Repository')
312 repositories = relationship('Repository')
310 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
313 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
311 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
314 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
312 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
315 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
313
316
314 group_member = relationship('UsersGroupMember', cascade='all')
317 group_member = relationship('UsersGroupMember', cascade='all')
315
318
316 notifications = relationship('UserNotification', cascade='all')
319 notifications = relationship('UserNotification', cascade='all')
317 # notifications assigned to this user
320 # notifications assigned to this user
318 user_created_notifications = relationship('Notification', cascade='all')
321 user_created_notifications = relationship('Notification', cascade='all')
319 # comments created by this user
322 # comments created by this user
320 user_comments = relationship('ChangesetComment', cascade='all')
323 user_comments = relationship('ChangesetComment', cascade='all')
321 #extra emails for this user
324 #extra emails for this user
322 user_emails = relationship('UserEmailMap', cascade='all')
325 user_emails = relationship('UserEmailMap', cascade='all')
323
326
324 @hybrid_property
327 @hybrid_property
325 def email(self):
328 def email(self):
326 return self._email
329 return self._email
327
330
328 @email.setter
331 @email.setter
329 def email(self, val):
332 def email(self, val):
330 self._email = val.lower() if val else None
333 self._email = val.lower() if val else None
331
334
332 @property
335 @property
333 def firstname(self):
336 def firstname(self):
334 # alias for future
337 # alias for future
335 return self.name
338 return self.name
336
339
337 @property
340 @property
338 def emails(self):
341 def emails(self):
339 other = UserEmailMap.query().filter(UserEmailMap.user==self).all()
342 other = UserEmailMap.query().filter(UserEmailMap.user==self).all()
340 return [self.email] + [x.email for x in other]
343 return [self.email] + [x.email for x in other]
341
344
342 @property
345 @property
343 def username_and_name(self):
346 def username_and_name(self):
344 return '%s (%s %s)' % (self.username, self.firstname, self.lastname)
347 return '%s (%s %s)' % (self.username, self.firstname, self.lastname)
345
348
346 @property
349 @property
347 def full_name(self):
350 def full_name(self):
348 return '%s %s' % (self.firstname, self.lastname)
351 return '%s %s' % (self.firstname, self.lastname)
349
352
350 @property
353 @property
351 def full_name_or_username(self):
354 def full_name_or_username(self):
352 return ('%s %s' % (self.firstname, self.lastname)
355 return ('%s %s' % (self.firstname, self.lastname)
353 if (self.firstname and self.lastname) else self.username)
356 if (self.firstname and self.lastname) else self.username)
354
357
355 @property
358 @property
356 def full_contact(self):
359 def full_contact(self):
357 return '%s %s <%s>' % (self.firstname, self.lastname, self.email)
360 return '%s %s <%s>' % (self.firstname, self.lastname, self.email)
358
361
359 @property
362 @property
360 def short_contact(self):
363 def short_contact(self):
361 return '%s %s' % (self.firstname, self.lastname)
364 return '%s %s' % (self.firstname, self.lastname)
362
365
363 @property
366 @property
364 def is_admin(self):
367 def is_admin(self):
365 return self.admin
368 return self.admin
366
369
367 def __unicode__(self):
370 def __unicode__(self):
368 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
371 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
369 self.user_id, self.username)
372 self.user_id, self.username)
370
373
371 @classmethod
374 @classmethod
372 def get_by_username(cls, username, case_insensitive=False, cache=False):
375 def get_by_username(cls, username, case_insensitive=False, cache=False):
373 if case_insensitive:
376 if case_insensitive:
374 q = cls.query().filter(cls.username.ilike(username))
377 q = cls.query().filter(cls.username.ilike(username))
375 else:
378 else:
376 q = cls.query().filter(cls.username == username)
379 q = cls.query().filter(cls.username == username)
377
380
378 if cache:
381 if cache:
379 q = q.options(FromCache(
382 q = q.options(FromCache(
380 "sql_cache_short",
383 "sql_cache_short",
381 "get_user_%s" % _hash_key(username)
384 "get_user_%s" % _hash_key(username)
382 )
385 )
383 )
386 )
384 return q.scalar()
387 return q.scalar()
385
388
386 @classmethod
389 @classmethod
387 def get_by_api_key(cls, api_key, cache=False):
390 def get_by_api_key(cls, api_key, cache=False):
388 q = cls.query().filter(cls.api_key == api_key)
391 q = cls.query().filter(cls.api_key == api_key)
389
392
390 if cache:
393 if cache:
391 q = q.options(FromCache("sql_cache_short",
394 q = q.options(FromCache("sql_cache_short",
392 "get_api_key_%s" % api_key))
395 "get_api_key_%s" % api_key))
393 return q.scalar()
396 return q.scalar()
394
397
395 @classmethod
398 @classmethod
396 def get_by_email(cls, email, case_insensitive=False, cache=False):
399 def get_by_email(cls, email, case_insensitive=False, cache=False):
397 if case_insensitive:
400 if case_insensitive:
398 q = cls.query().filter(cls.email.ilike(email))
401 q = cls.query().filter(cls.email.ilike(email))
399 else:
402 else:
400 q = cls.query().filter(cls.email == email)
403 q = cls.query().filter(cls.email == email)
401
404
402 if cache:
405 if cache:
403 q = q.options(FromCache("sql_cache_short",
406 q = q.options(FromCache("sql_cache_short",
404 "get_email_key_%s" % email))
407 "get_email_key_%s" % email))
405
408
406 ret = q.scalar()
409 ret = q.scalar()
407 if ret is None:
410 if ret is None:
408 q = UserEmailMap.query()
411 q = UserEmailMap.query()
409 # try fetching in alternate email map
412 # try fetching in alternate email map
410 if case_insensitive:
413 if case_insensitive:
411 q = q.filter(UserEmailMap.email.ilike(email))
414 q = q.filter(UserEmailMap.email.ilike(email))
412 else:
415 else:
413 q = q.filter(UserEmailMap.email == email)
416 q = q.filter(UserEmailMap.email == email)
414 q = q.options(joinedload(UserEmailMap.user))
417 q = q.options(joinedload(UserEmailMap.user))
415 if cache:
418 if cache:
416 q = q.options(FromCache("sql_cache_short",
419 q = q.options(FromCache("sql_cache_short",
417 "get_email_map_key_%s" % email))
420 "get_email_map_key_%s" % email))
418 ret = getattr(q.scalar(), 'user', None)
421 ret = getattr(q.scalar(), 'user', None)
419
422
420 return ret
423 return ret
421
424
422 def update_lastlogin(self):
425 def update_lastlogin(self):
423 """Update user lastlogin"""
426 """Update user lastlogin"""
424 self.last_login = datetime.datetime.now()
427 self.last_login = datetime.datetime.now()
425 Session().add(self)
428 Session().add(self)
426 log.debug('updated user %s lastlogin' % self.username)
429 log.debug('updated user %s lastlogin' % self.username)
427
430
428 def get_api_data(self):
431 def get_api_data(self):
429 """
432 """
430 Common function for generating user related data for API
433 Common function for generating user related data for API
431 """
434 """
432 user = self
435 user = self
433 data = dict(
436 data = dict(
434 user_id=user.user_id,
437 user_id=user.user_id,
435 username=user.username,
438 username=user.username,
436 firstname=user.name,
439 firstname=user.name,
437 lastname=user.lastname,
440 lastname=user.lastname,
438 email=user.email,
441 email=user.email,
439 emails=user.emails,
442 emails=user.emails,
440 api_key=user.api_key,
443 api_key=user.api_key,
441 active=user.active,
444 active=user.active,
442 admin=user.admin,
445 admin=user.admin,
443 ldap_dn=user.ldap_dn,
446 ldap_dn=user.ldap_dn,
444 last_login=user.last_login,
447 last_login=user.last_login,
445 )
448 )
446 return data
449 return data
447
450
448 def __json__(self):
451 def __json__(self):
449 data = dict(
452 data = dict(
450 full_name=self.full_name,
453 full_name=self.full_name,
451 full_name_or_username=self.full_name_or_username,
454 full_name_or_username=self.full_name_or_username,
452 short_contact=self.short_contact,
455 short_contact=self.short_contact,
453 full_contact=self.full_contact
456 full_contact=self.full_contact
454 )
457 )
455 data.update(self.get_api_data())
458 data.update(self.get_api_data())
456 return data
459 return data
457
460
458
461
459 class UserEmailMap(Base, BaseModel):
462 class UserEmailMap(Base, BaseModel):
460 __tablename__ = 'user_email_map'
463 __tablename__ = 'user_email_map'
461 __table_args__ = (
464 __table_args__ = (
462 Index('uem_email_idx', 'email'),
465 Index('uem_email_idx', 'email'),
463 UniqueConstraint('email'),
466 UniqueConstraint('email'),
464 {'extend_existing': True, 'mysql_engine': 'InnoDB',
467 {'extend_existing': True, 'mysql_engine': 'InnoDB',
465 'mysql_charset': 'utf8'}
468 'mysql_charset': 'utf8'}
466 )
469 )
467 __mapper_args__ = {}
470 __mapper_args__ = {}
468
471
469 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
472 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
470 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
473 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
471 _email = Column("email", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
474 _email = Column("email", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
472 user = relationship('User', lazy='joined')
475 user = relationship('User', lazy='joined')
473
476
474 @validates('_email')
477 @validates('_email')
475 def validate_email(self, key, email):
478 def validate_email(self, key, email):
476 # check if this email is not main one
479 # check if this email is not main one
477 main_email = Session().query(User).filter(User.email == email).scalar()
480 main_email = Session().query(User).filter(User.email == email).scalar()
478 if main_email is not None:
481 if main_email is not None:
479 raise AttributeError('email %s is present is user table' % email)
482 raise AttributeError('email %s is present is user table' % email)
480 return email
483 return email
481
484
482 @hybrid_property
485 @hybrid_property
483 def email(self):
486 def email(self):
484 return self._email
487 return self._email
485
488
486 @email.setter
489 @email.setter
487 def email(self, val):
490 def email(self, val):
488 self._email = val.lower() if val else None
491 self._email = val.lower() if val else None
489
492
490
493
491 class UserLog(Base, BaseModel):
494 class UserLog(Base, BaseModel):
492 __tablename__ = 'user_logs'
495 __tablename__ = 'user_logs'
493 __table_args__ = (
496 __table_args__ = (
494 {'extend_existing': True, 'mysql_engine': 'InnoDB',
497 {'extend_existing': True, 'mysql_engine': 'InnoDB',
495 'mysql_charset': 'utf8'},
498 'mysql_charset': 'utf8'},
496 )
499 )
497 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
500 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
498 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
501 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
499 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True)
502 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True)
500 repository_name = Column("repository_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
503 repository_name = Column("repository_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
501 user_ip = Column("user_ip", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
504 user_ip = Column("user_ip", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
502 action = Column("action", UnicodeText(1200000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
505 action = Column("action", UnicodeText(1200000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
503 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
506 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
504
507
505 @property
508 @property
506 def action_as_day(self):
509 def action_as_day(self):
507 return datetime.date(*self.action_date.timetuple()[:3])
510 return datetime.date(*self.action_date.timetuple()[:3])
508
511
509 user = relationship('User')
512 user = relationship('User')
510 repository = relationship('Repository', cascade='')
513 repository = relationship('Repository', cascade='')
511
514
512
515
513 class UsersGroup(Base, BaseModel):
516 class UsersGroup(Base, BaseModel):
514 __tablename__ = 'users_groups'
517 __tablename__ = 'users_groups'
515 __table_args__ = (
518 __table_args__ = (
516 {'extend_existing': True, 'mysql_engine': 'InnoDB',
519 {'extend_existing': True, 'mysql_engine': 'InnoDB',
517 'mysql_charset': 'utf8'},
520 'mysql_charset': 'utf8'},
518 )
521 )
519
522
520 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
523 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
521 users_group_name = Column("users_group_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
524 users_group_name = Column("users_group_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
522 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
525 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
523 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
526 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
524
527
525 members = relationship('UsersGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
528 members = relationship('UsersGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
526 users_group_to_perm = relationship('UsersGroupToPerm', cascade='all')
529 users_group_to_perm = relationship('UsersGroupToPerm', cascade='all')
527 users_group_repo_to_perm = relationship('UsersGroupRepoToPerm', cascade='all')
530 users_group_repo_to_perm = relationship('UsersGroupRepoToPerm', cascade='all')
528
531
529 def __unicode__(self):
532 def __unicode__(self):
530 return u'<userGroup(%s)>' % (self.users_group_name)
533 return u'<userGroup(%s)>' % (self.users_group_name)
531
534
532 @classmethod
535 @classmethod
533 def get_by_group_name(cls, group_name, cache=False,
536 def get_by_group_name(cls, group_name, cache=False,
534 case_insensitive=False):
537 case_insensitive=False):
535 if case_insensitive:
538 if case_insensitive:
536 q = cls.query().filter(cls.users_group_name.ilike(group_name))
539 q = cls.query().filter(cls.users_group_name.ilike(group_name))
537 else:
540 else:
538 q = cls.query().filter(cls.users_group_name == group_name)
541 q = cls.query().filter(cls.users_group_name == group_name)
539 if cache:
542 if cache:
540 q = q.options(FromCache(
543 q = q.options(FromCache(
541 "sql_cache_short",
544 "sql_cache_short",
542 "get_user_%s" % _hash_key(group_name)
545 "get_user_%s" % _hash_key(group_name)
543 )
546 )
544 )
547 )
545 return q.scalar()
548 return q.scalar()
546
549
547 @classmethod
550 @classmethod
548 def get(cls, users_group_id, cache=False):
551 def get(cls, users_group_id, cache=False):
549 users_group = cls.query()
552 users_group = cls.query()
550 if cache:
553 if cache:
551 users_group = users_group.options(FromCache("sql_cache_short",
554 users_group = users_group.options(FromCache("sql_cache_short",
552 "get_users_group_%s" % users_group_id))
555 "get_users_group_%s" % users_group_id))
553 return users_group.get(users_group_id)
556 return users_group.get(users_group_id)
554
557
555 def get_api_data(self):
558 def get_api_data(self):
556 users_group = self
559 users_group = self
557
560
558 data = dict(
561 data = dict(
559 users_group_id=users_group.users_group_id,
562 users_group_id=users_group.users_group_id,
560 group_name=users_group.users_group_name,
563 group_name=users_group.users_group_name,
561 active=users_group.users_group_active,
564 active=users_group.users_group_active,
562 )
565 )
563
566
564 return data
567 return data
565
568
566
569
567 class UsersGroupMember(Base, BaseModel):
570 class UsersGroupMember(Base, BaseModel):
568 __tablename__ = 'users_groups_members'
571 __tablename__ = 'users_groups_members'
569 __table_args__ = (
572 __table_args__ = (
570 {'extend_existing': True, 'mysql_engine': 'InnoDB',
573 {'extend_existing': True, 'mysql_engine': 'InnoDB',
571 'mysql_charset': 'utf8'},
574 'mysql_charset': 'utf8'},
572 )
575 )
573
576
574 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
577 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
575 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
578 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
576 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
579 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
577
580
578 user = relationship('User', lazy='joined')
581 user = relationship('User', lazy='joined')
579 users_group = relationship('UsersGroup')
582 users_group = relationship('UsersGroup')
580
583
581 def __init__(self, gr_id='', u_id=''):
584 def __init__(self, gr_id='', u_id=''):
582 self.users_group_id = gr_id
585 self.users_group_id = gr_id
583 self.user_id = u_id
586 self.user_id = u_id
584
587
585
588
586 class Repository(Base, BaseModel):
589 class Repository(Base, BaseModel):
587 __tablename__ = 'repositories'
590 __tablename__ = 'repositories'
588 __table_args__ = (
591 __table_args__ = (
589 UniqueConstraint('repo_name'),
592 UniqueConstraint('repo_name'),
590 Index('r_repo_name_idx', 'repo_name'),
593 Index('r_repo_name_idx', 'repo_name'),
591 {'extend_existing': True, 'mysql_engine': 'InnoDB',
594 {'extend_existing': True, 'mysql_engine': 'InnoDB',
592 'mysql_charset': 'utf8'},
595 'mysql_charset': 'utf8'},
593 )
596 )
594
597
595 repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
598 repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
596 repo_name = Column("repo_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
599 repo_name = Column("repo_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
597 clone_uri = Column("clone_uri", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
600 clone_uri = Column("clone_uri", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
598 repo_type = Column("repo_type", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default=None)
601 repo_type = Column("repo_type", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default=None)
599 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
602 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
600 private = Column("private", Boolean(), nullable=True, unique=None, default=None)
603 private = Column("private", Boolean(), nullable=True, unique=None, default=None)
601 enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True)
604 enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True)
602 enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True)
605 enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True)
603 description = Column("description", String(10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
606 description = Column("description", String(10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
604 created_on = Column('created_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
607 created_on = Column('created_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
605 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
608 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
606 landing_rev = Column("landing_revision", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default=None)
609 landing_rev = Column("landing_revision", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default=None)
607 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
610 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
608 _locked = Column("locked", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
611 _locked = Column("locked", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
609
612
610 fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None)
613 fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None)
611 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None)
614 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None)
612
615
613 user = relationship('User')
616 user = relationship('User')
614 fork = relationship('Repository', remote_side=repo_id)
617 fork = relationship('Repository', remote_side=repo_id)
615 group = relationship('RepoGroup')
618 group = relationship('RepoGroup')
616 repo_to_perm = relationship('UserRepoToPerm', cascade='all', order_by='UserRepoToPerm.repo_to_perm_id')
619 repo_to_perm = relationship('UserRepoToPerm', cascade='all', order_by='UserRepoToPerm.repo_to_perm_id')
617 users_group_to_perm = relationship('UsersGroupRepoToPerm', cascade='all')
620 users_group_to_perm = relationship('UsersGroupRepoToPerm', cascade='all')
618 stats = relationship('Statistics', cascade='all', uselist=False)
621 stats = relationship('Statistics', cascade='all', uselist=False)
619
622
620 followers = relationship('UserFollowing',
623 followers = relationship('UserFollowing',
621 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
624 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
622 cascade='all')
625 cascade='all')
623
626
624 logs = relationship('UserLog')
627 logs = relationship('UserLog')
625 comments = relationship('ChangesetComment', cascade="all, delete, delete-orphan")
628 comments = relationship('ChangesetComment', cascade="all, delete, delete-orphan")
626
629
627 pull_requests_org = relationship('PullRequest',
630 pull_requests_org = relationship('PullRequest',
628 primaryjoin='PullRequest.org_repo_id==Repository.repo_id',
631 primaryjoin='PullRequest.org_repo_id==Repository.repo_id',
629 cascade="all, delete, delete-orphan")
632 cascade="all, delete, delete-orphan")
630
633
631 pull_requests_other = relationship('PullRequest',
634 pull_requests_other = relationship('PullRequest',
632 primaryjoin='PullRequest.other_repo_id==Repository.repo_id',
635 primaryjoin='PullRequest.other_repo_id==Repository.repo_id',
633 cascade="all, delete, delete-orphan")
636 cascade="all, delete, delete-orphan")
634
637
635 def __unicode__(self):
638 def __unicode__(self):
636 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
639 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
637 self.repo_name)
640 self.repo_name)
638
641
639 @hybrid_property
642 @hybrid_property
640 def locked(self):
643 def locked(self):
641 # always should return [user_id, timelocked]
644 # always should return [user_id, timelocked]
642 if self._locked:
645 if self._locked:
643 _lock_info = self._locked.split(':')
646 _lock_info = self._locked.split(':')
644 return int(_lock_info[0]), _lock_info[1]
647 return int(_lock_info[0]), _lock_info[1]
645 return [None, None]
648 return [None, None]
646
649
647 @locked.setter
650 @locked.setter
648 def locked(self, val):
651 def locked(self, val):
649 if val and isinstance(val, (list, tuple)):
652 if val and isinstance(val, (list, tuple)):
650 self._locked = ':'.join(map(str, val))
653 self._locked = ':'.join(map(str, val))
651 else:
654 else:
652 self._locked = None
655 self._locked = None
653
656
654 @classmethod
657 @classmethod
655 def url_sep(cls):
658 def url_sep(cls):
656 return URL_SEP
659 return URL_SEP
657
660
658 @classmethod
661 @classmethod
659 def get_by_repo_name(cls, repo_name):
662 def get_by_repo_name(cls, repo_name):
660 q = Session().query(cls).filter(cls.repo_name == repo_name)
663 q = Session().query(cls).filter(cls.repo_name == repo_name)
661 q = q.options(joinedload(Repository.fork))\
664 q = q.options(joinedload(Repository.fork))\
662 .options(joinedload(Repository.user))\
665 .options(joinedload(Repository.user))\
663 .options(joinedload(Repository.group))
666 .options(joinedload(Repository.group))
664 return q.scalar()
667 return q.scalar()
665
668
666 @classmethod
669 @classmethod
667 def get_by_full_path(cls, repo_full_path):
670 def get_by_full_path(cls, repo_full_path):
668 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
671 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
669 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
672 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
670
673
671 @classmethod
674 @classmethod
672 def get_repo_forks(cls, repo_id):
675 def get_repo_forks(cls, repo_id):
673 return cls.query().filter(Repository.fork_id == repo_id)
676 return cls.query().filter(Repository.fork_id == repo_id)
674
677
675 @classmethod
678 @classmethod
676 def base_path(cls):
679 def base_path(cls):
677 """
680 """
678 Returns base path when all repos are stored
681 Returns base path when all repos are stored
679
682
680 :param cls:
683 :param cls:
681 """
684 """
682 q = Session().query(RhodeCodeUi)\
685 q = Session().query(RhodeCodeUi)\
683 .filter(RhodeCodeUi.ui_key == cls.url_sep())
686 .filter(RhodeCodeUi.ui_key == cls.url_sep())
684 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
687 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
685 return q.one().ui_value
688 return q.one().ui_value
686
689
687 @property
690 @property
688 def forks(self):
691 def forks(self):
689 """
692 """
690 Return forks of this repo
693 Return forks of this repo
691 """
694 """
692 return Repository.get_repo_forks(self.repo_id)
695 return Repository.get_repo_forks(self.repo_id)
693
696
694 @property
697 @property
695 def parent(self):
698 def parent(self):
696 """
699 """
697 Returns fork parent
700 Returns fork parent
698 """
701 """
699 return self.fork
702 return self.fork
700
703
701 @property
704 @property
702 def just_name(self):
705 def just_name(self):
703 return self.repo_name.split(Repository.url_sep())[-1]
706 return self.repo_name.split(Repository.url_sep())[-1]
704
707
705 @property
708 @property
706 def groups_with_parents(self):
709 def groups_with_parents(self):
707 groups = []
710 groups = []
708 if self.group is None:
711 if self.group is None:
709 return groups
712 return groups
710
713
711 cur_gr = self.group
714 cur_gr = self.group
712 groups.insert(0, cur_gr)
715 groups.insert(0, cur_gr)
713 while 1:
716 while 1:
714 gr = getattr(cur_gr, 'parent_group', None)
717 gr = getattr(cur_gr, 'parent_group', None)
715 cur_gr = cur_gr.parent_group
718 cur_gr = cur_gr.parent_group
716 if gr is None:
719 if gr is None:
717 break
720 break
718 groups.insert(0, gr)
721 groups.insert(0, gr)
719
722
720 return groups
723 return groups
721
724
722 @property
725 @property
723 def groups_and_repo(self):
726 def groups_and_repo(self):
724 return self.groups_with_parents, self.just_name
727 return self.groups_with_parents, self.just_name
725
728
726 @LazyProperty
729 @LazyProperty
727 def repo_path(self):
730 def repo_path(self):
728 """
731 """
729 Returns base full path for that repository means where it actually
732 Returns base full path for that repository means where it actually
730 exists on a filesystem
733 exists on a filesystem
731 """
734 """
732 q = Session().query(RhodeCodeUi).filter(RhodeCodeUi.ui_key ==
735 q = Session().query(RhodeCodeUi).filter(RhodeCodeUi.ui_key ==
733 Repository.url_sep())
736 Repository.url_sep())
734 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
737 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
735 return q.one().ui_value
738 return q.one().ui_value
736
739
737 @property
740 @property
738 def repo_full_path(self):
741 def repo_full_path(self):
739 p = [self.repo_path]
742 p = [self.repo_path]
740 # we need to split the name by / since this is how we store the
743 # we need to split the name by / since this is how we store the
741 # names in the database, but that eventually needs to be converted
744 # names in the database, but that eventually needs to be converted
742 # into a valid system path
745 # into a valid system path
743 p += self.repo_name.split(Repository.url_sep())
746 p += self.repo_name.split(Repository.url_sep())
744 return os.path.join(*p)
747 return os.path.join(*p)
745
748
746 def get_new_name(self, repo_name):
749 def get_new_name(self, repo_name):
747 """
750 """
748 returns new full repository name based on assigned group and new new
751 returns new full repository name based on assigned group and new new
749
752
750 :param group_name:
753 :param group_name:
751 """
754 """
752 path_prefix = self.group.full_path_splitted if self.group else []
755 path_prefix = self.group.full_path_splitted if self.group else []
753 return Repository.url_sep().join(path_prefix + [repo_name])
756 return Repository.url_sep().join(path_prefix + [repo_name])
754
757
755 @property
758 @property
756 def _ui(self):
759 def _ui(self):
757 """
760 """
758 Creates an db based ui object for this repository
761 Creates an db based ui object for this repository
759 """
762 """
760 from mercurial import ui
763 from mercurial import ui
761 from mercurial import config
764 from mercurial import config
762 baseui = ui.ui()
765 baseui = ui.ui()
763
766
764 #clean the baseui object
767 #clean the baseui object
765 baseui._ocfg = config.config()
768 baseui._ocfg = config.config()
766 baseui._ucfg = config.config()
769 baseui._ucfg = config.config()
767 baseui._tcfg = config.config()
770 baseui._tcfg = config.config()
768
771
769 ret = RhodeCodeUi.query()\
772 ret = RhodeCodeUi.query()\
770 .options(FromCache("sql_cache_short", "repository_repo_ui")).all()
773 .options(FromCache("sql_cache_short", "repository_repo_ui")).all()
771
774
772 hg_ui = ret
775 hg_ui = ret
773 for ui_ in hg_ui:
776 for ui_ in hg_ui:
774 if ui_.ui_active:
777 if ui_.ui_active:
775 log.debug('settings ui from db[%s]%s:%s', ui_.ui_section,
778 log.debug('settings ui from db[%s]%s:%s', ui_.ui_section,
776 ui_.ui_key, ui_.ui_value)
779 ui_.ui_key, ui_.ui_value)
777 baseui.setconfig(ui_.ui_section, ui_.ui_key, ui_.ui_value)
780 baseui.setconfig(ui_.ui_section, ui_.ui_key, ui_.ui_value)
778 if ui_.ui_key == 'push_ssl':
781 if ui_.ui_key == 'push_ssl':
779 # force set push_ssl requirement to False, rhodecode
782 # force set push_ssl requirement to False, rhodecode
780 # handles that
783 # handles that
781 baseui.setconfig(ui_.ui_section, ui_.ui_key, False)
784 baseui.setconfig(ui_.ui_section, ui_.ui_key, False)
782
785
783 return baseui
786 return baseui
784
787
785 @classmethod
788 @classmethod
786 def inject_ui(cls, repo, extras={}):
789 def inject_ui(cls, repo, extras={}):
787 from rhodecode.lib.vcs.backends.hg import MercurialRepository
790 from rhodecode.lib.vcs.backends.hg import MercurialRepository
788 from rhodecode.lib.vcs.backends.git import GitRepository
791 from rhodecode.lib.vcs.backends.git import GitRepository
789 required = (MercurialRepository, GitRepository)
792 required = (MercurialRepository, GitRepository)
790 if not isinstance(repo, required):
793 if not isinstance(repo, required):
791 raise Exception('repo must be instance of %s' % required)
794 raise Exception('repo must be instance of %s' % required)
792
795
793 # inject ui extra param to log this action via push logger
796 # inject ui extra param to log this action via push logger
794 for k, v in extras.items():
797 for k, v in extras.items():
795 repo._repo.ui.setconfig('rhodecode_extras', k, v)
798 repo._repo.ui.setconfig('rhodecode_extras', k, v)
796
799
797 @classmethod
800 @classmethod
798 def is_valid(cls, repo_name):
801 def is_valid(cls, repo_name):
799 """
802 """
800 returns True if given repo name is a valid filesystem repository
803 returns True if given repo name is a valid filesystem repository
801
804
802 :param cls:
805 :param cls:
803 :param repo_name:
806 :param repo_name:
804 """
807 """
805 from rhodecode.lib.utils import is_valid_repo
808 from rhodecode.lib.utils import is_valid_repo
806
809
807 return is_valid_repo(repo_name, cls.base_path())
810 return is_valid_repo(repo_name, cls.base_path())
808
811
809 def get_api_data(self):
812 def get_api_data(self):
810 """
813 """
811 Common function for generating repo api data
814 Common function for generating repo api data
812
815
813 """
816 """
814 repo = self
817 repo = self
815 data = dict(
818 data = dict(
816 repo_id=repo.repo_id,
819 repo_id=repo.repo_id,
817 repo_name=repo.repo_name,
820 repo_name=repo.repo_name,
818 repo_type=repo.repo_type,
821 repo_type=repo.repo_type,
819 clone_uri=repo.clone_uri,
822 clone_uri=repo.clone_uri,
820 private=repo.private,
823 private=repo.private,
821 created_on=repo.created_on,
824 created_on=repo.created_on,
822 description=repo.description,
825 description=repo.description,
823 landing_rev=repo.landing_rev,
826 landing_rev=repo.landing_rev,
824 owner=repo.user.username,
827 owner=repo.user.username,
825 fork_of=repo.fork.repo_name if repo.fork else None
828 fork_of=repo.fork.repo_name if repo.fork else None
826 )
829 )
827
830
828 return data
831 return data
829
832
830 @classmethod
833 @classmethod
831 def lock(cls, repo, user_id):
834 def lock(cls, repo, user_id):
832 repo.locked = [user_id, time.time()]
835 repo.locked = [user_id, time.time()]
833 Session().add(repo)
836 Session().add(repo)
834 Session().commit()
837 Session().commit()
835
838
836 @classmethod
839 @classmethod
837 def unlock(cls, repo):
840 def unlock(cls, repo):
838 repo.locked = None
841 repo.locked = None
839 Session().add(repo)
842 Session().add(repo)
840 Session().commit()
843 Session().commit()
841
844
842 #==========================================================================
845 #==========================================================================
843 # SCM PROPERTIES
846 # SCM PROPERTIES
844 #==========================================================================
847 #==========================================================================
845
848
846 def get_changeset(self, rev=None):
849 def get_changeset(self, rev=None):
847 return get_changeset_safe(self.scm_instance, rev)
850 return get_changeset_safe(self.scm_instance, rev)
848
851
849 def get_landing_changeset(self):
852 def get_landing_changeset(self):
850 """
853 """
851 Returns landing changeset, or if that doesn't exist returns the tip
854 Returns landing changeset, or if that doesn't exist returns the tip
852 """
855 """
853 cs = self.get_changeset(self.landing_rev) or self.get_changeset()
856 cs = self.get_changeset(self.landing_rev) or self.get_changeset()
854 return cs
857 return cs
855
858
856 @property
859 @property
857 def tip(self):
860 def tip(self):
858 return self.get_changeset('tip')
861 return self.get_changeset('tip')
859
862
860 @property
863 @property
861 def author(self):
864 def author(self):
862 return self.tip.author
865 return self.tip.author
863
866
864 @property
867 @property
865 def last_change(self):
868 def last_change(self):
866 return self.scm_instance.last_change
869 return self.scm_instance.last_change
867
870
868 def get_comments(self, revisions=None):
871 def get_comments(self, revisions=None):
869 """
872 """
870 Returns comments for this repository grouped by revisions
873 Returns comments for this repository grouped by revisions
871
874
872 :param revisions: filter query by revisions only
875 :param revisions: filter query by revisions only
873 """
876 """
874 cmts = ChangesetComment.query()\
877 cmts = ChangesetComment.query()\
875 .filter(ChangesetComment.repo == self)
878 .filter(ChangesetComment.repo == self)
876 if revisions:
879 if revisions:
877 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
880 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
878 grouped = defaultdict(list)
881 grouped = defaultdict(list)
879 for cmt in cmts.all():
882 for cmt in cmts.all():
880 grouped[cmt.revision].append(cmt)
883 grouped[cmt.revision].append(cmt)
881 return grouped
884 return grouped
882
885
883 def statuses(self, revisions=None):
886 def statuses(self, revisions=None):
884 """
887 """
885 Returns statuses for this repository
888 Returns statuses for this repository
886
889
887 :param revisions: list of revisions to get statuses for
890 :param revisions: list of revisions to get statuses for
888 :type revisions: list
891 :type revisions: list
889 """
892 """
890
893
891 statuses = ChangesetStatus.query()\
894 statuses = ChangesetStatus.query()\
892 .filter(ChangesetStatus.repo == self)\
895 .filter(ChangesetStatus.repo == self)\
893 .filter(ChangesetStatus.version == 0)
896 .filter(ChangesetStatus.version == 0)
894 if revisions:
897 if revisions:
895 statuses = statuses.filter(ChangesetStatus.revision.in_(revisions))
898 statuses = statuses.filter(ChangesetStatus.revision.in_(revisions))
896 grouped = {}
899 grouped = {}
897
900
898 #maybe we have open new pullrequest without a status ?
901 #maybe we have open new pullrequest without a status ?
899 stat = ChangesetStatus.STATUS_UNDER_REVIEW
902 stat = ChangesetStatus.STATUS_UNDER_REVIEW
900 status_lbl = ChangesetStatus.get_status_lbl(stat)
903 status_lbl = ChangesetStatus.get_status_lbl(stat)
901 for pr in PullRequest.query().filter(PullRequest.org_repo == self).all():
904 for pr in PullRequest.query().filter(PullRequest.org_repo == self).all():
902 for rev in pr.revisions:
905 for rev in pr.revisions:
903 pr_id = pr.pull_request_id
906 pr_id = pr.pull_request_id
904 pr_repo = pr.other_repo.repo_name
907 pr_repo = pr.other_repo.repo_name
905 grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
908 grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
906
909
907 for stat in statuses.all():
910 for stat in statuses.all():
908 pr_id = pr_repo = None
911 pr_id = pr_repo = None
909 if stat.pull_request:
912 if stat.pull_request:
910 pr_id = stat.pull_request.pull_request_id
913 pr_id = stat.pull_request.pull_request_id
911 pr_repo = stat.pull_request.other_repo.repo_name
914 pr_repo = stat.pull_request.other_repo.repo_name
912 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
915 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
913 pr_id, pr_repo]
916 pr_id, pr_repo]
914 return grouped
917 return grouped
915
918
916 #==========================================================================
919 #==========================================================================
917 # SCM CACHE INSTANCE
920 # SCM CACHE INSTANCE
918 #==========================================================================
921 #==========================================================================
919
922
920 @property
923 @property
921 def invalidate(self):
924 def invalidate(self):
922 return CacheInvalidation.invalidate(self.repo_name)
925 return CacheInvalidation.invalidate(self.repo_name)
923
926
924 def set_invalidate(self):
927 def set_invalidate(self):
925 """
928 """
926 set a cache for invalidation for this instance
929 set a cache for invalidation for this instance
927 """
930 """
928 CacheInvalidation.set_invalidate(self.repo_name)
931 CacheInvalidation.set_invalidate(self.repo_name)
929
932
930 @LazyProperty
933 @LazyProperty
931 def scm_instance(self):
934 def scm_instance(self):
932 return self.__get_instance()
935 return self.__get_instance()
933
936
934 def scm_instance_cached(self, cache_map=None):
937 def scm_instance_cached(self, cache_map=None):
935 @cache_region('long_term')
938 @cache_region('long_term')
936 def _c(repo_name):
939 def _c(repo_name):
937 return self.__get_instance()
940 return self.__get_instance()
938 rn = self.repo_name
941 rn = self.repo_name
939 log.debug('Getting cached instance of repo')
942 log.debug('Getting cached instance of repo')
940
943
941 if cache_map:
944 if cache_map:
942 # get using prefilled cache_map
945 # get using prefilled cache_map
943 invalidate_repo = cache_map[self.repo_name]
946 invalidate_repo = cache_map[self.repo_name]
944 if invalidate_repo:
947 if invalidate_repo:
945 invalidate_repo = (None if invalidate_repo.cache_active
948 invalidate_repo = (None if invalidate_repo.cache_active
946 else invalidate_repo)
949 else invalidate_repo)
947 else:
950 else:
948 # get from invalidate
951 # get from invalidate
949 invalidate_repo = self.invalidate
952 invalidate_repo = self.invalidate
950
953
951 if invalidate_repo is not None:
954 if invalidate_repo is not None:
952 region_invalidate(_c, None, rn)
955 region_invalidate(_c, None, rn)
953 # update our cache
956 # update our cache
954 CacheInvalidation.set_valid(invalidate_repo.cache_key)
957 CacheInvalidation.set_valid(invalidate_repo.cache_key)
955 return _c(rn)
958 return _c(rn)
956
959
957 def __get_instance(self):
960 def __get_instance(self):
958 repo_full_path = self.repo_full_path
961 repo_full_path = self.repo_full_path
959 try:
962 try:
960 alias = get_scm(repo_full_path)[0]
963 alias = get_scm(repo_full_path)[0]
961 log.debug('Creating instance of %s repository' % alias)
964 log.debug('Creating instance of %s repository' % alias)
962 backend = get_backend(alias)
965 backend = get_backend(alias)
963 except VCSError:
966 except VCSError:
964 log.error(traceback.format_exc())
967 log.error(traceback.format_exc())
965 log.error('Perhaps this repository is in db and not in '
968 log.error('Perhaps this repository is in db and not in '
966 'filesystem run rescan repositories with '
969 'filesystem run rescan repositories with '
967 '"destroy old data " option from admin panel')
970 '"destroy old data " option from admin panel')
968 return
971 return
969
972
970 if alias == 'hg':
973 if alias == 'hg':
971
974
972 repo = backend(safe_str(repo_full_path), create=False,
975 repo = backend(safe_str(repo_full_path), create=False,
973 baseui=self._ui)
976 baseui=self._ui)
974 # skip hidden web repository
977 # skip hidden web repository
975 if repo._get_hidden():
978 if repo._get_hidden():
976 return
979 return
977 else:
980 else:
978 repo = backend(repo_full_path, create=False)
981 repo = backend(repo_full_path, create=False)
979
982
980 return repo
983 return repo
981
984
982
985
983 class RepoGroup(Base, BaseModel):
986 class RepoGroup(Base, BaseModel):
984 __tablename__ = 'groups'
987 __tablename__ = 'groups'
985 __table_args__ = (
988 __table_args__ = (
986 UniqueConstraint('group_name', 'group_parent_id'),
989 UniqueConstraint('group_name', 'group_parent_id'),
987 CheckConstraint('group_id != group_parent_id'),
990 CheckConstraint('group_id != group_parent_id'),
988 {'extend_existing': True, 'mysql_engine': 'InnoDB',
991 {'extend_existing': True, 'mysql_engine': 'InnoDB',
989 'mysql_charset': 'utf8'},
992 'mysql_charset': 'utf8'},
990 )
993 )
991 __mapper_args__ = {'order_by': 'group_name'}
994 __mapper_args__ = {'order_by': 'group_name'}
992
995
993 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
996 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
994 group_name = Column("group_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
997 group_name = Column("group_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
995 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
998 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
996 group_description = Column("group_description", String(10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
999 group_description = Column("group_description", String(10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
997 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
1000 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
998
1001
999 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
1002 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
1000 users_group_to_perm = relationship('UsersGroupRepoGroupToPerm', cascade='all')
1003 users_group_to_perm = relationship('UsersGroupRepoGroupToPerm', cascade='all')
1001
1004
1002 parent_group = relationship('RepoGroup', remote_side=group_id)
1005 parent_group = relationship('RepoGroup', remote_side=group_id)
1003
1006
1004 def __init__(self, group_name='', parent_group=None):
1007 def __init__(self, group_name='', parent_group=None):
1005 self.group_name = group_name
1008 self.group_name = group_name
1006 self.parent_group = parent_group
1009 self.parent_group = parent_group
1007
1010
1008 def __unicode__(self):
1011 def __unicode__(self):
1009 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.group_id,
1012 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.group_id,
1010 self.group_name)
1013 self.group_name)
1011
1014
1012 @classmethod
1015 @classmethod
1013 def groups_choices(cls):
1016 def groups_choices(cls):
1014 from webhelpers.html import literal as _literal
1017 from webhelpers.html import literal as _literal
1015 repo_groups = [('', '')]
1018 repo_groups = [('', '')]
1016 sep = ' &raquo; '
1019 sep = ' &raquo; '
1017 _name = lambda k: _literal(sep.join(k))
1020 _name = lambda k: _literal(sep.join(k))
1018
1021
1019 repo_groups.extend([(x.group_id, _name(x.full_path_splitted))
1022 repo_groups.extend([(x.group_id, _name(x.full_path_splitted))
1020 for x in cls.query().all()])
1023 for x in cls.query().all()])
1021
1024
1022 repo_groups = sorted(repo_groups, key=lambda t: t[1].split(sep)[0])
1025 repo_groups = sorted(repo_groups, key=lambda t: t[1].split(sep)[0])
1023 return repo_groups
1026 return repo_groups
1024
1027
1025 @classmethod
1028 @classmethod
1026 def url_sep(cls):
1029 def url_sep(cls):
1027 return URL_SEP
1030 return URL_SEP
1028
1031
1029 @classmethod
1032 @classmethod
1030 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
1033 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
1031 if case_insensitive:
1034 if case_insensitive:
1032 gr = cls.query()\
1035 gr = cls.query()\
1033 .filter(cls.group_name.ilike(group_name))
1036 .filter(cls.group_name.ilike(group_name))
1034 else:
1037 else:
1035 gr = cls.query()\
1038 gr = cls.query()\
1036 .filter(cls.group_name == group_name)
1039 .filter(cls.group_name == group_name)
1037 if cache:
1040 if cache:
1038 gr = gr.options(FromCache(
1041 gr = gr.options(FromCache(
1039 "sql_cache_short",
1042 "sql_cache_short",
1040 "get_group_%s" % _hash_key(group_name)
1043 "get_group_%s" % _hash_key(group_name)
1041 )
1044 )
1042 )
1045 )
1043 return gr.scalar()
1046 return gr.scalar()
1044
1047
1045 @property
1048 @property
1046 def parents(self):
1049 def parents(self):
1047 parents_recursion_limit = 5
1050 parents_recursion_limit = 5
1048 groups = []
1051 groups = []
1049 if self.parent_group is None:
1052 if self.parent_group is None:
1050 return groups
1053 return groups
1051 cur_gr = self.parent_group
1054 cur_gr = self.parent_group
1052 groups.insert(0, cur_gr)
1055 groups.insert(0, cur_gr)
1053 cnt = 0
1056 cnt = 0
1054 while 1:
1057 while 1:
1055 cnt += 1
1058 cnt += 1
1056 gr = getattr(cur_gr, 'parent_group', None)
1059 gr = getattr(cur_gr, 'parent_group', None)
1057 cur_gr = cur_gr.parent_group
1060 cur_gr = cur_gr.parent_group
1058 if gr is None:
1061 if gr is None:
1059 break
1062 break
1060 if cnt == parents_recursion_limit:
1063 if cnt == parents_recursion_limit:
1061 # this will prevent accidental infinit loops
1064 # this will prevent accidental infinit loops
1062 log.error('group nested more than %s' %
1065 log.error('group nested more than %s' %
1063 parents_recursion_limit)
1066 parents_recursion_limit)
1064 break
1067 break
1065
1068
1066 groups.insert(0, gr)
1069 groups.insert(0, gr)
1067 return groups
1070 return groups
1068
1071
1069 @property
1072 @property
1070 def children(self):
1073 def children(self):
1071 return RepoGroup.query().filter(RepoGroup.parent_group == self)
1074 return RepoGroup.query().filter(RepoGroup.parent_group == self)
1072
1075
1073 @property
1076 @property
1074 def name(self):
1077 def name(self):
1075 return self.group_name.split(RepoGroup.url_sep())[-1]
1078 return self.group_name.split(RepoGroup.url_sep())[-1]
1076
1079
1077 @property
1080 @property
1078 def full_path(self):
1081 def full_path(self):
1079 return self.group_name
1082 return self.group_name
1080
1083
1081 @property
1084 @property
1082 def full_path_splitted(self):
1085 def full_path_splitted(self):
1083 return self.group_name.split(RepoGroup.url_sep())
1086 return self.group_name.split(RepoGroup.url_sep())
1084
1087
1085 @property
1088 @property
1086 def repositories(self):
1089 def repositories(self):
1087 return Repository.query()\
1090 return Repository.query()\
1088 .filter(Repository.group == self)\
1091 .filter(Repository.group == self)\
1089 .order_by(Repository.repo_name)
1092 .order_by(Repository.repo_name)
1090
1093
1091 @property
1094 @property
1092 def repositories_recursive_count(self):
1095 def repositories_recursive_count(self):
1093 cnt = self.repositories.count()
1096 cnt = self.repositories.count()
1094
1097
1095 def children_count(group):
1098 def children_count(group):
1096 cnt = 0
1099 cnt = 0
1097 for child in group.children:
1100 for child in group.children:
1098 cnt += child.repositories.count()
1101 cnt += child.repositories.count()
1099 cnt += children_count(child)
1102 cnt += children_count(child)
1100 return cnt
1103 return cnt
1101
1104
1102 return cnt + children_count(self)
1105 return cnt + children_count(self)
1103
1106
1104 def recursive_groups_and_repos(self):
1107 def recursive_groups_and_repos(self):
1105 """
1108 """
1106 Recursive return all groups, with repositories in those groups
1109 Recursive return all groups, with repositories in those groups
1107 """
1110 """
1108 all_ = []
1111 all_ = []
1109
1112
1110 def _get_members(root_gr):
1113 def _get_members(root_gr):
1111 for r in root_gr.repositories:
1114 for r in root_gr.repositories:
1112 all_.append(r)
1115 all_.append(r)
1113 childs = root_gr.children.all()
1116 childs = root_gr.children.all()
1114 if childs:
1117 if childs:
1115 for gr in childs:
1118 for gr in childs:
1116 all_.append(gr)
1119 all_.append(gr)
1117 _get_members(gr)
1120 _get_members(gr)
1118
1121
1119 _get_members(self)
1122 _get_members(self)
1120 return [self] + all_
1123 return [self] + all_
1121
1124
1122 def get_new_name(self, group_name):
1125 def get_new_name(self, group_name):
1123 """
1126 """
1124 returns new full group name based on parent and new name
1127 returns new full group name based on parent and new name
1125
1128
1126 :param group_name:
1129 :param group_name:
1127 """
1130 """
1128 path_prefix = (self.parent_group.full_path_splitted if
1131 path_prefix = (self.parent_group.full_path_splitted if
1129 self.parent_group else [])
1132 self.parent_group else [])
1130 return RepoGroup.url_sep().join(path_prefix + [group_name])
1133 return RepoGroup.url_sep().join(path_prefix + [group_name])
1131
1134
1132
1135
1133 class Permission(Base, BaseModel):
1136 class Permission(Base, BaseModel):
1134 __tablename__ = 'permissions'
1137 __tablename__ = 'permissions'
1135 __table_args__ = (
1138 __table_args__ = (
1136 Index('p_perm_name_idx', 'permission_name'),
1139 Index('p_perm_name_idx', 'permission_name'),
1137 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1140 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1138 'mysql_charset': 'utf8'},
1141 'mysql_charset': 'utf8'},
1139 )
1142 )
1140 PERMS = [
1143 PERMS = [
1141 ('repository.none', _('Repository no access')),
1144 ('repository.none', _('Repository no access')),
1142 ('repository.read', _('Repository read access')),
1145 ('repository.read', _('Repository read access')),
1143 ('repository.write', _('Repository write access')),
1146 ('repository.write', _('Repository write access')),
1144 ('repository.admin', _('Repository admin access')),
1147 ('repository.admin', _('Repository admin access')),
1145
1148
1146 ('group.none', _('Repositories Group no access')),
1149 ('group.none', _('Repositories Group no access')),
1147 ('group.read', _('Repositories Group read access')),
1150 ('group.read', _('Repositories Group read access')),
1148 ('group.write', _('Repositories Group write access')),
1151 ('group.write', _('Repositories Group write access')),
1149 ('group.admin', _('Repositories Group admin access')),
1152 ('group.admin', _('Repositories Group admin access')),
1150
1153
1151 ('hg.admin', _('RhodeCode Administrator')),
1154 ('hg.admin', _('RhodeCode Administrator')),
1152 ('hg.create.none', _('Repository creation disabled')),
1155 ('hg.create.none', _('Repository creation disabled')),
1153 ('hg.create.repository', _('Repository creation enabled')),
1156 ('hg.create.repository', _('Repository creation enabled')),
1154 ('hg.fork.none', _('Repository forking disabled')),
1157 ('hg.fork.none', _('Repository forking disabled')),
1155 ('hg.fork.repository', _('Repository forking enabled')),
1158 ('hg.fork.repository', _('Repository forking enabled')),
1156 ('hg.register.none', _('Register disabled')),
1159 ('hg.register.none', _('Register disabled')),
1157 ('hg.register.manual_activate', _('Register new user with RhodeCode '
1160 ('hg.register.manual_activate', _('Register new user with RhodeCode '
1158 'with manual activation')),
1161 'with manual activation')),
1159
1162
1160 ('hg.register.auto_activate', _('Register new user with RhodeCode '
1163 ('hg.register.auto_activate', _('Register new user with RhodeCode '
1161 'with auto activation')),
1164 'with auto activation')),
1162 ]
1165 ]
1163
1166
1164 # defines which permissions are more important higher the more important
1167 # defines which permissions are more important higher the more important
1165 PERM_WEIGHTS = {
1168 PERM_WEIGHTS = {
1166 'repository.none': 0,
1169 'repository.none': 0,
1167 'repository.read': 1,
1170 'repository.read': 1,
1168 'repository.write': 3,
1171 'repository.write': 3,
1169 'repository.admin': 4,
1172 'repository.admin': 4,
1170
1173
1171 'group.none': 0,
1174 'group.none': 0,
1172 'group.read': 1,
1175 'group.read': 1,
1173 'group.write': 3,
1176 'group.write': 3,
1174 'group.admin': 4,
1177 'group.admin': 4,
1175
1178
1176 'hg.fork.none': 0,
1179 'hg.fork.none': 0,
1177 'hg.fork.repository': 1,
1180 'hg.fork.repository': 1,
1178 'hg.create.none': 0,
1181 'hg.create.none': 0,
1179 'hg.create.repository':1
1182 'hg.create.repository':1
1180 }
1183 }
1181
1184
1182 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1185 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1183 permission_name = Column("permission_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1186 permission_name = Column("permission_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1184 permission_longname = Column("permission_longname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1187 permission_longname = Column("permission_longname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1185
1188
1186 def __unicode__(self):
1189 def __unicode__(self):
1187 return u"<%s('%s:%s')>" % (
1190 return u"<%s('%s:%s')>" % (
1188 self.__class__.__name__, self.permission_id, self.permission_name
1191 self.__class__.__name__, self.permission_id, self.permission_name
1189 )
1192 )
1190
1193
1191 @classmethod
1194 @classmethod
1192 def get_by_key(cls, key):
1195 def get_by_key(cls, key):
1193 return cls.query().filter(cls.permission_name == key).scalar()
1196 return cls.query().filter(cls.permission_name == key).scalar()
1194
1197
1195 @classmethod
1198 @classmethod
1196 def get_default_perms(cls, default_user_id):
1199 def get_default_perms(cls, default_user_id):
1197 q = Session().query(UserRepoToPerm, Repository, cls)\
1200 q = Session().query(UserRepoToPerm, Repository, cls)\
1198 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
1201 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
1199 .join((cls, UserRepoToPerm.permission_id == cls.permission_id))\
1202 .join((cls, UserRepoToPerm.permission_id == cls.permission_id))\
1200 .filter(UserRepoToPerm.user_id == default_user_id)
1203 .filter(UserRepoToPerm.user_id == default_user_id)
1201
1204
1202 return q.all()
1205 return q.all()
1203
1206
1204 @classmethod
1207 @classmethod
1205 def get_default_group_perms(cls, default_user_id):
1208 def get_default_group_perms(cls, default_user_id):
1206 q = Session().query(UserRepoGroupToPerm, RepoGroup, cls)\
1209 q = Session().query(UserRepoGroupToPerm, RepoGroup, cls)\
1207 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
1210 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
1208 .join((cls, UserRepoGroupToPerm.permission_id == cls.permission_id))\
1211 .join((cls, UserRepoGroupToPerm.permission_id == cls.permission_id))\
1209 .filter(UserRepoGroupToPerm.user_id == default_user_id)
1212 .filter(UserRepoGroupToPerm.user_id == default_user_id)
1210
1213
1211 return q.all()
1214 return q.all()
1212
1215
1213
1216
1214 class UserRepoToPerm(Base, BaseModel):
1217 class UserRepoToPerm(Base, BaseModel):
1215 __tablename__ = 'repo_to_perm'
1218 __tablename__ = 'repo_to_perm'
1216 __table_args__ = (
1219 __table_args__ = (
1217 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
1220 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
1218 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1221 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1219 'mysql_charset': 'utf8'}
1222 'mysql_charset': 'utf8'}
1220 )
1223 )
1221 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1224 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1222 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1225 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1223 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1226 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1224 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1227 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1225
1228
1226 user = relationship('User')
1229 user = relationship('User')
1227 repository = relationship('Repository')
1230 repository = relationship('Repository')
1228 permission = relationship('Permission')
1231 permission = relationship('Permission')
1229
1232
1230 @classmethod
1233 @classmethod
1231 def create(cls, user, repository, permission):
1234 def create(cls, user, repository, permission):
1232 n = cls()
1235 n = cls()
1233 n.user = user
1236 n.user = user
1234 n.repository = repository
1237 n.repository = repository
1235 n.permission = permission
1238 n.permission = permission
1236 Session().add(n)
1239 Session().add(n)
1237 return n
1240 return n
1238
1241
1239 def __unicode__(self):
1242 def __unicode__(self):
1240 return u'<user:%s => %s >' % (self.user, self.repository)
1243 return u'<user:%s => %s >' % (self.user, self.repository)
1241
1244
1242
1245
1243 class UserToPerm(Base, BaseModel):
1246 class UserToPerm(Base, BaseModel):
1244 __tablename__ = 'user_to_perm'
1247 __tablename__ = 'user_to_perm'
1245 __table_args__ = (
1248 __table_args__ = (
1246 UniqueConstraint('user_id', 'permission_id'),
1249 UniqueConstraint('user_id', 'permission_id'),
1247 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1250 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1248 'mysql_charset': 'utf8'}
1251 'mysql_charset': 'utf8'}
1249 )
1252 )
1250 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1253 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1251 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1254 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1252 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1255 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1253
1256
1254 user = relationship('User')
1257 user = relationship('User')
1255 permission = relationship('Permission', lazy='joined')
1258 permission = relationship('Permission', lazy='joined')
1256
1259
1257
1260
1258 class UsersGroupRepoToPerm(Base, BaseModel):
1261 class UsersGroupRepoToPerm(Base, BaseModel):
1259 __tablename__ = 'users_group_repo_to_perm'
1262 __tablename__ = 'users_group_repo_to_perm'
1260 __table_args__ = (
1263 __table_args__ = (
1261 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
1264 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
1262 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1265 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1263 'mysql_charset': 'utf8'}
1266 'mysql_charset': 'utf8'}
1264 )
1267 )
1265 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1268 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1266 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1269 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1267 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1270 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1268 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1271 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1269
1272
1270 users_group = relationship('UsersGroup')
1273 users_group = relationship('UsersGroup')
1271 permission = relationship('Permission')
1274 permission = relationship('Permission')
1272 repository = relationship('Repository')
1275 repository = relationship('Repository')
1273
1276
1274 @classmethod
1277 @classmethod
1275 def create(cls, users_group, repository, permission):
1278 def create(cls, users_group, repository, permission):
1276 n = cls()
1279 n = cls()
1277 n.users_group = users_group
1280 n.users_group = users_group
1278 n.repository = repository
1281 n.repository = repository
1279 n.permission = permission
1282 n.permission = permission
1280 Session().add(n)
1283 Session().add(n)
1281 return n
1284 return n
1282
1285
1283 def __unicode__(self):
1286 def __unicode__(self):
1284 return u'<userGroup:%s => %s >' % (self.users_group, self.repository)
1287 return u'<userGroup:%s => %s >' % (self.users_group, self.repository)
1285
1288
1286
1289
1287 class UsersGroupToPerm(Base, BaseModel):
1290 class UsersGroupToPerm(Base, BaseModel):
1288 __tablename__ = 'users_group_to_perm'
1291 __tablename__ = 'users_group_to_perm'
1289 __table_args__ = (
1292 __table_args__ = (
1290 UniqueConstraint('users_group_id', 'permission_id',),
1293 UniqueConstraint('users_group_id', 'permission_id',),
1291 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1294 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1292 'mysql_charset': 'utf8'}
1295 'mysql_charset': 'utf8'}
1293 )
1296 )
1294 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1297 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1295 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1298 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1296 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1299 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1297
1300
1298 users_group = relationship('UsersGroup')
1301 users_group = relationship('UsersGroup')
1299 permission = relationship('Permission')
1302 permission = relationship('Permission')
1300
1303
1301
1304
1302 class UserRepoGroupToPerm(Base, BaseModel):
1305 class UserRepoGroupToPerm(Base, BaseModel):
1303 __tablename__ = 'user_repo_group_to_perm'
1306 __tablename__ = 'user_repo_group_to_perm'
1304 __table_args__ = (
1307 __table_args__ = (
1305 UniqueConstraint('user_id', 'group_id', 'permission_id'),
1308 UniqueConstraint('user_id', 'group_id', 'permission_id'),
1306 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1309 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1307 'mysql_charset': 'utf8'}
1310 'mysql_charset': 'utf8'}
1308 )
1311 )
1309
1312
1310 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1313 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1311 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1314 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1312 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
1315 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
1313 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1316 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1314
1317
1315 user = relationship('User')
1318 user = relationship('User')
1316 group = relationship('RepoGroup')
1319 group = relationship('RepoGroup')
1317 permission = relationship('Permission')
1320 permission = relationship('Permission')
1318
1321
1319
1322
1320 class UsersGroupRepoGroupToPerm(Base, BaseModel):
1323 class UsersGroupRepoGroupToPerm(Base, BaseModel):
1321 __tablename__ = 'users_group_repo_group_to_perm'
1324 __tablename__ = 'users_group_repo_group_to_perm'
1322 __table_args__ = (
1325 __table_args__ = (
1323 UniqueConstraint('users_group_id', 'group_id'),
1326 UniqueConstraint('users_group_id', 'group_id'),
1324 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1327 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1325 'mysql_charset': 'utf8'}
1328 'mysql_charset': 'utf8'}
1326 )
1329 )
1327
1330
1328 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)
1331 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)
1329 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1332 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1330 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
1333 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
1331 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1334 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1332
1335
1333 users_group = relationship('UsersGroup')
1336 users_group = relationship('UsersGroup')
1334 permission = relationship('Permission')
1337 permission = relationship('Permission')
1335 group = relationship('RepoGroup')
1338 group = relationship('RepoGroup')
1336
1339
1337
1340
1338 class Statistics(Base, BaseModel):
1341 class Statistics(Base, BaseModel):
1339 __tablename__ = 'statistics'
1342 __tablename__ = 'statistics'
1340 __table_args__ = (
1343 __table_args__ = (
1341 UniqueConstraint('repository_id'),
1344 UniqueConstraint('repository_id'),
1342 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1345 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1343 'mysql_charset': 'utf8'}
1346 'mysql_charset': 'utf8'}
1344 )
1347 )
1345 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1348 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1346 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
1349 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
1347 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
1350 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
1348 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
1351 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
1349 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
1352 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
1350 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
1353 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
1351
1354
1352 repository = relationship('Repository', single_parent=True)
1355 repository = relationship('Repository', single_parent=True)
1353
1356
1354
1357
1355 class UserFollowing(Base, BaseModel):
1358 class UserFollowing(Base, BaseModel):
1356 __tablename__ = 'user_followings'
1359 __tablename__ = 'user_followings'
1357 __table_args__ = (
1360 __table_args__ = (
1358 UniqueConstraint('user_id', 'follows_repository_id'),
1361 UniqueConstraint('user_id', 'follows_repository_id'),
1359 UniqueConstraint('user_id', 'follows_user_id'),
1362 UniqueConstraint('user_id', 'follows_user_id'),
1360 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1363 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1361 'mysql_charset': 'utf8'}
1364 'mysql_charset': 'utf8'}
1362 )
1365 )
1363
1366
1364 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1367 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1365 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1368 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1366 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
1369 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
1367 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1370 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1368 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
1371 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
1369
1372
1370 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
1373 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
1371
1374
1372 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
1375 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
1373 follows_repository = relationship('Repository', order_by='Repository.repo_name')
1376 follows_repository = relationship('Repository', order_by='Repository.repo_name')
1374
1377
1375 @classmethod
1378 @classmethod
1376 def get_repo_followers(cls, repo_id):
1379 def get_repo_followers(cls, repo_id):
1377 return cls.query().filter(cls.follows_repo_id == repo_id)
1380 return cls.query().filter(cls.follows_repo_id == repo_id)
1378
1381
1379
1382
1380 class CacheInvalidation(Base, BaseModel):
1383 class CacheInvalidation(Base, BaseModel):
1381 __tablename__ = 'cache_invalidation'
1384 __tablename__ = 'cache_invalidation'
1382 __table_args__ = (
1385 __table_args__ = (
1383 UniqueConstraint('cache_key'),
1386 UniqueConstraint('cache_key'),
1384 Index('key_idx', 'cache_key'),
1387 Index('key_idx', 'cache_key'),
1385 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1388 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1386 'mysql_charset': 'utf8'},
1389 'mysql_charset': 'utf8'},
1387 )
1390 )
1388 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1391 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1389 cache_key = Column("cache_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1392 cache_key = Column("cache_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1390 cache_args = Column("cache_args", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1393 cache_args = Column("cache_args", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1391 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
1394 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
1392
1395
1393 def __init__(self, cache_key, cache_args=''):
1396 def __init__(self, cache_key, cache_args=''):
1394 self.cache_key = cache_key
1397 self.cache_key = cache_key
1395 self.cache_args = cache_args
1398 self.cache_args = cache_args
1396 self.cache_active = False
1399 self.cache_active = False
1397
1400
1398 def __unicode__(self):
1401 def __unicode__(self):
1399 return u"<%s('%s:%s')>" % (self.__class__.__name__,
1402 return u"<%s('%s:%s')>" % (self.__class__.__name__,
1400 self.cache_id, self.cache_key)
1403 self.cache_id, self.cache_key)
1401
1404
1402 @classmethod
1405 @classmethod
1403 def clear_cache(cls):
1406 def clear_cache(cls):
1404 cls.query().delete()
1407 cls.query().delete()
1405
1408
1406 @classmethod
1409 @classmethod
1407 def _get_key(cls, key):
1410 def _get_key(cls, key):
1408 """
1411 """
1409 Wrapper for generating a key, together with a prefix
1412 Wrapper for generating a key, together with a prefix
1410
1413
1411 :param key:
1414 :param key:
1412 """
1415 """
1413 import rhodecode
1416 import rhodecode
1414 prefix = ''
1417 prefix = ''
1415 iid = rhodecode.CONFIG.get('instance_id')
1418 iid = rhodecode.CONFIG.get('instance_id')
1416 if iid:
1419 if iid:
1417 prefix = iid
1420 prefix = iid
1418 return "%s%s" % (prefix, key), prefix, key.rstrip('_README')
1421 return "%s%s" % (prefix, key), prefix, key.rstrip('_README')
1419
1422
1420 @classmethod
1423 @classmethod
1421 def get_by_key(cls, key):
1424 def get_by_key(cls, key):
1422 return cls.query().filter(cls.cache_key == key).scalar()
1425 return cls.query().filter(cls.cache_key == key).scalar()
1423
1426
1424 @classmethod
1427 @classmethod
1425 def _get_or_create_key(cls, key, prefix, org_key):
1428 def _get_or_create_key(cls, key, prefix, org_key):
1426 inv_obj = Session().query(cls).filter(cls.cache_key == key).scalar()
1429 inv_obj = Session().query(cls).filter(cls.cache_key == key).scalar()
1427 if not inv_obj:
1430 if not inv_obj:
1428 try:
1431 try:
1429 inv_obj = CacheInvalidation(key, org_key)
1432 inv_obj = CacheInvalidation(key, org_key)
1430 Session().add(inv_obj)
1433 Session().add(inv_obj)
1431 Session().commit()
1434 Session().commit()
1432 except Exception:
1435 except Exception:
1433 log.error(traceback.format_exc())
1436 log.error(traceback.format_exc())
1434 Session().rollback()
1437 Session().rollback()
1435 return inv_obj
1438 return inv_obj
1436
1439
1437 @classmethod
1440 @classmethod
1438 def invalidate(cls, key):
1441 def invalidate(cls, key):
1439 """
1442 """
1440 Returns Invalidation object if this given key should be invalidated
1443 Returns Invalidation object if this given key should be invalidated
1441 None otherwise. `cache_active = False` means that this cache
1444 None otherwise. `cache_active = False` means that this cache
1442 state is not valid and needs to be invalidated
1445 state is not valid and needs to be invalidated
1443
1446
1444 :param key:
1447 :param key:
1445 """
1448 """
1446
1449
1447 key, _prefix, _org_key = cls._get_key(key)
1450 key, _prefix, _org_key = cls._get_key(key)
1448 inv = cls._get_or_create_key(key, _prefix, _org_key)
1451 inv = cls._get_or_create_key(key, _prefix, _org_key)
1449
1452
1450 if inv and inv.cache_active is False:
1453 if inv and inv.cache_active is False:
1451 return inv
1454 return inv
1452
1455
1453 @classmethod
1456 @classmethod
1454 def set_invalidate(cls, key):
1457 def set_invalidate(cls, key):
1455 """
1458 """
1456 Mark this Cache key for invalidation
1459 Mark this Cache key for invalidation
1457
1460
1458 :param key:
1461 :param key:
1459 """
1462 """
1460
1463
1461 key, _prefix, _org_key = cls._get_key(key)
1464 key, _prefix, _org_key = cls._get_key(key)
1462 inv_objs = Session().query(cls).filter(cls.cache_args == _org_key).all()
1465 inv_objs = Session().query(cls).filter(cls.cache_args == _org_key).all()
1463 log.debug('marking %s key[s] %s for invalidation' % (len(inv_objs),
1466 log.debug('marking %s key[s] %s for invalidation' % (len(inv_objs),
1464 _org_key))
1467 _org_key))
1465 try:
1468 try:
1466 for inv_obj in inv_objs:
1469 for inv_obj in inv_objs:
1467 if inv_obj:
1470 if inv_obj:
1468 inv_obj.cache_active = False
1471 inv_obj.cache_active = False
1469
1472
1470 Session().add(inv_obj)
1473 Session().add(inv_obj)
1471 Session().commit()
1474 Session().commit()
1472 except Exception:
1475 except Exception:
1473 log.error(traceback.format_exc())
1476 log.error(traceback.format_exc())
1474 Session().rollback()
1477 Session().rollback()
1475
1478
1476 @classmethod
1479 @classmethod
1477 def set_valid(cls, key):
1480 def set_valid(cls, key):
1478 """
1481 """
1479 Mark this cache key as active and currently cached
1482 Mark this cache key as active and currently cached
1480
1483
1481 :param key:
1484 :param key:
1482 """
1485 """
1483 inv_obj = cls.get_by_key(key)
1486 inv_obj = cls.get_by_key(key)
1484 inv_obj.cache_active = True
1487 inv_obj.cache_active = True
1485 Session().add(inv_obj)
1488 Session().add(inv_obj)
1486 Session().commit()
1489 Session().commit()
1487
1490
1488 @classmethod
1491 @classmethod
1489 def get_cache_map(cls):
1492 def get_cache_map(cls):
1490
1493
1491 class cachemapdict(dict):
1494 class cachemapdict(dict):
1492
1495
1493 def __init__(self, *args, **kwargs):
1496 def __init__(self, *args, **kwargs):
1494 fixkey = kwargs.get('fixkey')
1497 fixkey = kwargs.get('fixkey')
1495 if fixkey:
1498 if fixkey:
1496 del kwargs['fixkey']
1499 del kwargs['fixkey']
1497 self.fixkey = fixkey
1500 self.fixkey = fixkey
1498 super(cachemapdict, self).__init__(*args, **kwargs)
1501 super(cachemapdict, self).__init__(*args, **kwargs)
1499
1502
1500 def __getattr__(self, name):
1503 def __getattr__(self, name):
1501 key = name
1504 key = name
1502 if self.fixkey:
1505 if self.fixkey:
1503 key, _prefix, _org_key = cls._get_key(key)
1506 key, _prefix, _org_key = cls._get_key(key)
1504 if key in self.__dict__:
1507 if key in self.__dict__:
1505 return self.__dict__[key]
1508 return self.__dict__[key]
1506 else:
1509 else:
1507 return self[key]
1510 return self[key]
1508
1511
1509 def __getitem__(self, key):
1512 def __getitem__(self, key):
1510 if self.fixkey:
1513 if self.fixkey:
1511 key, _prefix, _org_key = cls._get_key(key)
1514 key, _prefix, _org_key = cls._get_key(key)
1512 try:
1515 try:
1513 return super(cachemapdict, self).__getitem__(key)
1516 return super(cachemapdict, self).__getitem__(key)
1514 except KeyError:
1517 except KeyError:
1515 return
1518 return
1516
1519
1517 cache_map = cachemapdict(fixkey=True)
1520 cache_map = cachemapdict(fixkey=True)
1518 for obj in cls.query().all():
1521 for obj in cls.query().all():
1519 cache_map[obj.cache_key] = cachemapdict(obj.get_dict())
1522 cache_map[obj.cache_key] = cachemapdict(obj.get_dict())
1520 return cache_map
1523 return cache_map
1521
1524
1522
1525
1523 class ChangesetComment(Base, BaseModel):
1526 class ChangesetComment(Base, BaseModel):
1524 __tablename__ = 'changeset_comments'
1527 __tablename__ = 'changeset_comments'
1525 __table_args__ = (
1528 __table_args__ = (
1526 Index('cc_revision_idx', 'revision'),
1529 Index('cc_revision_idx', 'revision'),
1527 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1530 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1528 'mysql_charset': 'utf8'},
1531 'mysql_charset': 'utf8'},
1529 )
1532 )
1530 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
1533 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
1531 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1534 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1532 revision = Column('revision', String(40), nullable=True)
1535 revision = Column('revision', String(40), nullable=True)
1533 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
1536 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
1534 line_no = Column('line_no', Unicode(10), nullable=True)
1537 line_no = Column('line_no', Unicode(10), nullable=True)
1535 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
1538 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
1536 f_path = Column('f_path', Unicode(1000), nullable=True)
1539 f_path = Column('f_path', Unicode(1000), nullable=True)
1537 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
1540 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
1538 text = Column('text', UnicodeText(25000), nullable=False)
1541 text = Column('text', UnicodeText(25000), nullable=False)
1539 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1542 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1540 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1543 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1541
1544
1542 author = relationship('User', lazy='joined')
1545 author = relationship('User', lazy='joined')
1543 repo = relationship('Repository')
1546 repo = relationship('Repository')
1544 status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan")
1547 status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan")
1545 pull_request = relationship('PullRequest', lazy='joined')
1548 pull_request = relationship('PullRequest', lazy='joined')
1546
1549
1547 @classmethod
1550 @classmethod
1548 def get_users(cls, revision=None, pull_request_id=None):
1551 def get_users(cls, revision=None, pull_request_id=None):
1549 """
1552 """
1550 Returns user associated with this ChangesetComment. ie those
1553 Returns user associated with this ChangesetComment. ie those
1551 who actually commented
1554 who actually commented
1552
1555
1553 :param cls:
1556 :param cls:
1554 :param revision:
1557 :param revision:
1555 """
1558 """
1556 q = Session().query(User)\
1559 q = Session().query(User)\
1557 .join(ChangesetComment.author)
1560 .join(ChangesetComment.author)
1558 if revision:
1561 if revision:
1559 q = q.filter(cls.revision == revision)
1562 q = q.filter(cls.revision == revision)
1560 elif pull_request_id:
1563 elif pull_request_id:
1561 q = q.filter(cls.pull_request_id == pull_request_id)
1564 q = q.filter(cls.pull_request_id == pull_request_id)
1562 return q.all()
1565 return q.all()
1563
1566
1564
1567
1565 class ChangesetStatus(Base, BaseModel):
1568 class ChangesetStatus(Base, BaseModel):
1566 __tablename__ = 'changeset_statuses'
1569 __tablename__ = 'changeset_statuses'
1567 __table_args__ = (
1570 __table_args__ = (
1568 Index('cs_revision_idx', 'revision'),
1571 Index('cs_revision_idx', 'revision'),
1569 Index('cs_version_idx', 'version'),
1572 Index('cs_version_idx', 'version'),
1570 UniqueConstraint('repo_id', 'revision', 'version'),
1573 UniqueConstraint('repo_id', 'revision', 'version'),
1571 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1574 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1572 'mysql_charset': 'utf8'}
1575 'mysql_charset': 'utf8'}
1573 )
1576 )
1574 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
1577 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
1575 STATUS_APPROVED = 'approved'
1578 STATUS_APPROVED = 'approved'
1576 STATUS_REJECTED = 'rejected'
1579 STATUS_REJECTED = 'rejected'
1577 STATUS_UNDER_REVIEW = 'under_review'
1580 STATUS_UNDER_REVIEW = 'under_review'
1578
1581
1579 STATUSES = [
1582 STATUSES = [
1580 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
1583 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
1581 (STATUS_APPROVED, _("Approved")),
1584 (STATUS_APPROVED, _("Approved")),
1582 (STATUS_REJECTED, _("Rejected")),
1585 (STATUS_REJECTED, _("Rejected")),
1583 (STATUS_UNDER_REVIEW, _("Under Review")),
1586 (STATUS_UNDER_REVIEW, _("Under Review")),
1584 ]
1587 ]
1585
1588
1586 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
1589 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
1587 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1590 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1588 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
1591 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
1589 revision = Column('revision', String(40), nullable=False)
1592 revision = Column('revision', String(40), nullable=False)
1590 status = Column('status', String(128), nullable=False, default=DEFAULT)
1593 status = Column('status', String(128), nullable=False, default=DEFAULT)
1591 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
1594 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
1592 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
1595 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
1593 version = Column('version', Integer(), nullable=False, default=0)
1596 version = Column('version', Integer(), nullable=False, default=0)
1594 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
1597 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
1595
1598
1596 author = relationship('User', lazy='joined')
1599 author = relationship('User', lazy='joined')
1597 repo = relationship('Repository')
1600 repo = relationship('Repository')
1598 comment = relationship('ChangesetComment', lazy='joined')
1601 comment = relationship('ChangesetComment', lazy='joined')
1599 pull_request = relationship('PullRequest', lazy='joined')
1602 pull_request = relationship('PullRequest', lazy='joined')
1600
1603
1601 def __unicode__(self):
1604 def __unicode__(self):
1602 return u"<%s('%s:%s')>" % (
1605 return u"<%s('%s:%s')>" % (
1603 self.__class__.__name__,
1606 self.__class__.__name__,
1604 self.status, self.author
1607 self.status, self.author
1605 )
1608 )
1606
1609
1607 @classmethod
1610 @classmethod
1608 def get_status_lbl(cls, value):
1611 def get_status_lbl(cls, value):
1609 return dict(cls.STATUSES).get(value)
1612 return dict(cls.STATUSES).get(value)
1610
1613
1611 @property
1614 @property
1612 def status_lbl(self):
1615 def status_lbl(self):
1613 return ChangesetStatus.get_status_lbl(self.status)
1616 return ChangesetStatus.get_status_lbl(self.status)
1614
1617
1615
1618
1616 class PullRequest(Base, BaseModel):
1619 class PullRequest(Base, BaseModel):
1617 __tablename__ = 'pull_requests'
1620 __tablename__ = 'pull_requests'
1618 __table_args__ = (
1621 __table_args__ = (
1619 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1622 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1620 'mysql_charset': 'utf8'},
1623 'mysql_charset': 'utf8'},
1621 )
1624 )
1622
1625
1623 STATUS_NEW = u'new'
1626 STATUS_NEW = u'new'
1624 STATUS_OPEN = u'open'
1627 STATUS_OPEN = u'open'
1625 STATUS_CLOSED = u'closed'
1628 STATUS_CLOSED = u'closed'
1626
1629
1627 pull_request_id = Column('pull_request_id', Integer(), nullable=False, primary_key=True)
1630 pull_request_id = Column('pull_request_id', Integer(), nullable=False, primary_key=True)
1628 title = Column('title', Unicode(256), nullable=True)
1631 title = Column('title', Unicode(256), nullable=True)
1629 description = Column('description', UnicodeText(10240), nullable=True)
1632 description = Column('description', UnicodeText(10240), nullable=True)
1630 status = Column('status', Unicode(256), nullable=False, default=STATUS_NEW)
1633 status = Column('status', Unicode(256), nullable=False, default=STATUS_NEW)
1631 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1634 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1632 updated_on = Column('updated_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1635 updated_on = Column('updated_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1633 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
1636 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
1634 _revisions = Column('revisions', UnicodeText(20500)) # 500 revisions max
1637 _revisions = Column('revisions', UnicodeText(20500)) # 500 revisions max
1635 org_repo_id = Column('org_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1638 org_repo_id = Column('org_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1636 org_ref = Column('org_ref', Unicode(256), nullable=False)
1639 org_ref = Column('org_ref', Unicode(256), nullable=False)
1637 other_repo_id = Column('other_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1640 other_repo_id = Column('other_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1638 other_ref = Column('other_ref', Unicode(256), nullable=False)
1641 other_ref = Column('other_ref', Unicode(256), nullable=False)
1639
1642
1640 @hybrid_property
1643 @hybrid_property
1641 def revisions(self):
1644 def revisions(self):
1642 return self._revisions.split(':')
1645 return self._revisions.split(':')
1643
1646
1644 @revisions.setter
1647 @revisions.setter
1645 def revisions(self, val):
1648 def revisions(self, val):
1646 self._revisions = ':'.join(val)
1649 self._revisions = ':'.join(val)
1647
1650
1648 author = relationship('User', lazy='joined')
1651 author = relationship('User', lazy='joined')
1649 reviewers = relationship('PullRequestReviewers',
1652 reviewers = relationship('PullRequestReviewers',
1650 cascade="all, delete, delete-orphan")
1653 cascade="all, delete, delete-orphan")
1651 org_repo = relationship('Repository', primaryjoin='PullRequest.org_repo_id==Repository.repo_id')
1654 org_repo = relationship('Repository', primaryjoin='PullRequest.org_repo_id==Repository.repo_id')
1652 other_repo = relationship('Repository', primaryjoin='PullRequest.other_repo_id==Repository.repo_id')
1655 other_repo = relationship('Repository', primaryjoin='PullRequest.other_repo_id==Repository.repo_id')
1653 statuses = relationship('ChangesetStatus')
1656 statuses = relationship('ChangesetStatus')
1654 comments = relationship('ChangesetComment',
1657 comments = relationship('ChangesetComment',
1655 cascade="all, delete, delete-orphan")
1658 cascade="all, delete, delete-orphan")
1656
1659
1657 def is_closed(self):
1660 def is_closed(self):
1658 return self.status == self.STATUS_CLOSED
1661 return self.status == self.STATUS_CLOSED
1659
1662
1660 def __json__(self):
1663 def __json__(self):
1661 return dict(
1664 return dict(
1662 revisions=self.revisions
1665 revisions=self.revisions
1663 )
1666 )
1664
1667
1665
1668
1666 class PullRequestReviewers(Base, BaseModel):
1669 class PullRequestReviewers(Base, BaseModel):
1667 __tablename__ = 'pull_request_reviewers'
1670 __tablename__ = 'pull_request_reviewers'
1668 __table_args__ = (
1671 __table_args__ = (
1669 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1672 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1670 'mysql_charset': 'utf8'},
1673 'mysql_charset': 'utf8'},
1671 )
1674 )
1672
1675
1673 def __init__(self, user=None, pull_request=None):
1676 def __init__(self, user=None, pull_request=None):
1674 self.user = user
1677 self.user = user
1675 self.pull_request = pull_request
1678 self.pull_request = pull_request
1676
1679
1677 pull_requests_reviewers_id = Column('pull_requests_reviewers_id', Integer(), nullable=False, primary_key=True)
1680 pull_requests_reviewers_id = Column('pull_requests_reviewers_id', Integer(), nullable=False, primary_key=True)
1678 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=False)
1681 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=False)
1679 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
1682 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
1680
1683
1681 user = relationship('User')
1684 user = relationship('User')
1682 pull_request = relationship('PullRequest')
1685 pull_request = relationship('PullRequest')
1683
1686
1684
1687
1685 class Notification(Base, BaseModel):
1688 class Notification(Base, BaseModel):
1686 __tablename__ = 'notifications'
1689 __tablename__ = 'notifications'
1687 __table_args__ = (
1690 __table_args__ = (
1688 Index('notification_type_idx', 'type'),
1691 Index('notification_type_idx', 'type'),
1689 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1692 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1690 'mysql_charset': 'utf8'},
1693 'mysql_charset': 'utf8'},
1691 )
1694 )
1692
1695
1693 TYPE_CHANGESET_COMMENT = u'cs_comment'
1696 TYPE_CHANGESET_COMMENT = u'cs_comment'
1694 TYPE_MESSAGE = u'message'
1697 TYPE_MESSAGE = u'message'
1695 TYPE_MENTION = u'mention'
1698 TYPE_MENTION = u'mention'
1696 TYPE_REGISTRATION = u'registration'
1699 TYPE_REGISTRATION = u'registration'
1697 TYPE_PULL_REQUEST = u'pull_request'
1700 TYPE_PULL_REQUEST = u'pull_request'
1698 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
1701 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
1699
1702
1700 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
1703 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
1701 subject = Column('subject', Unicode(512), nullable=True)
1704 subject = Column('subject', Unicode(512), nullable=True)
1702 body = Column('body', UnicodeText(50000), nullable=True)
1705 body = Column('body', UnicodeText(50000), nullable=True)
1703 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
1706 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
1704 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1707 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1705 type_ = Column('type', Unicode(256))
1708 type_ = Column('type', Unicode(256))
1706
1709
1707 created_by_user = relationship('User')
1710 created_by_user = relationship('User')
1708 notifications_to_users = relationship('UserNotification', lazy='joined',
1711 notifications_to_users = relationship('UserNotification', lazy='joined',
1709 cascade="all, delete, delete-orphan")
1712 cascade="all, delete, delete-orphan")
1710
1713
1711 @property
1714 @property
1712 def recipients(self):
1715 def recipients(self):
1713 return [x.user for x in UserNotification.query()\
1716 return [x.user for x in UserNotification.query()\
1714 .filter(UserNotification.notification == self)\
1717 .filter(UserNotification.notification == self)\
1715 .order_by(UserNotification.user_id.asc()).all()]
1718 .order_by(UserNotification.user_id.asc()).all()]
1716
1719
1717 @classmethod
1720 @classmethod
1718 def create(cls, created_by, subject, body, recipients, type_=None):
1721 def create(cls, created_by, subject, body, recipients, type_=None):
1719 if type_ is None:
1722 if type_ is None:
1720 type_ = Notification.TYPE_MESSAGE
1723 type_ = Notification.TYPE_MESSAGE
1721
1724
1722 notification = cls()
1725 notification = cls()
1723 notification.created_by_user = created_by
1726 notification.created_by_user = created_by
1724 notification.subject = subject
1727 notification.subject = subject
1725 notification.body = body
1728 notification.body = body
1726 notification.type_ = type_
1729 notification.type_ = type_
1727 notification.created_on = datetime.datetime.now()
1730 notification.created_on = datetime.datetime.now()
1728
1731
1729 for u in recipients:
1732 for u in recipients:
1730 assoc = UserNotification()
1733 assoc = UserNotification()
1731 assoc.notification = notification
1734 assoc.notification = notification
1732 u.notifications.append(assoc)
1735 u.notifications.append(assoc)
1733 Session().add(notification)
1736 Session().add(notification)
1734 return notification
1737 return notification
1735
1738
1736 @property
1739 @property
1737 def description(self):
1740 def description(self):
1738 from rhodecode.model.notification import NotificationModel
1741 from rhodecode.model.notification import NotificationModel
1739 return NotificationModel().make_description(self)
1742 return NotificationModel().make_description(self)
1740
1743
1741
1744
1742 class UserNotification(Base, BaseModel):
1745 class UserNotification(Base, BaseModel):
1743 __tablename__ = 'user_to_notification'
1746 __tablename__ = 'user_to_notification'
1744 __table_args__ = (
1747 __table_args__ = (
1745 UniqueConstraint('user_id', 'notification_id'),
1748 UniqueConstraint('user_id', 'notification_id'),
1746 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1749 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1747 'mysql_charset': 'utf8'}
1750 'mysql_charset': 'utf8'}
1748 )
1751 )
1749 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
1752 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
1750 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
1753 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
1751 read = Column('read', Boolean, default=False)
1754 read = Column('read', Boolean, default=False)
1752 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
1755 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
1753
1756
1754 user = relationship('User', lazy="joined")
1757 user = relationship('User', lazy="joined")
1755 notification = relationship('Notification', lazy="joined",
1758 notification = relationship('Notification', lazy="joined",
1756 order_by=lambda: Notification.created_on.desc(),)
1759 order_by=lambda: Notification.created_on.desc(),)
1757
1760
1758 def mark_as_read(self):
1761 def mark_as_read(self):
1759 self.read = True
1762 self.read = True
1760 Session().add(self)
1763 Session().add(self)
1761
1764
1762
1765
1763 class DbMigrateVersion(Base, BaseModel):
1766 class DbMigrateVersion(Base, BaseModel):
1764 __tablename__ = 'db_migrate_version'
1767 __tablename__ = 'db_migrate_version'
1765 __table_args__ = (
1768 __table_args__ = (
1766 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1769 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1767 'mysql_charset': 'utf8'},
1770 'mysql_charset': 'utf8'},
1768 )
1771 )
1769 repository_id = Column('repository_id', String(250), primary_key=True)
1772 repository_id = Column('repository_id', String(250), primary_key=True)
1770 repository_path = Column('repository_path', Text)
1773 repository_path = Column('repository_path', Text)
1771 version = Column('version', Integer)
1774 version = Column('version', Integer)
@@ -1,133 +1,133 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.model.permission
3 rhodecode.model.permission
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 permissions model for RhodeCode
6 permissions model for RhodeCode
7
7
8 :created_on: Aug 20, 2010
8 :created_on: Aug 20, 2010
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software: you can redistribute it and/or modify
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
16 # (at your option) any later version.
17 #
17 #
18 # This program is distributed in the hope that it will be useful,
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # GNU General Public License for more details.
21 # GNU General Public License for more details.
22 #
22 #
23 # You should have received a copy of the GNU General Public License
23 # You should have received a copy of the GNU General Public License
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25
25
26 import logging
26 import logging
27 import traceback
27 import traceback
28
28
29 from sqlalchemy.exc import DatabaseError
29 from sqlalchemy.exc import DatabaseError
30
30
31 from rhodecode.lib.caching_query import FromCache
31 from rhodecode.lib.caching_query import FromCache
32
32
33 from rhodecode.model import BaseModel
33 from rhodecode.model import BaseModel
34 from rhodecode.model.db import User, Permission, UserToPerm, UserRepoToPerm,\
34 from rhodecode.model.db import User, Permission, UserToPerm, UserRepoToPerm,\
35 UserRepoGroupToPerm
35 UserRepoGroupToPerm
36
36
37 log = logging.getLogger(__name__)
37 log = logging.getLogger(__name__)
38
38
39
39
40 class PermissionModel(BaseModel):
40 class PermissionModel(BaseModel):
41 """
41 """
42 Permissions model for RhodeCode
42 Permissions model for RhodeCode
43 """
43 """
44
44
45 cls = Permission
45 cls = Permission
46
46
47 def get_permission(self, permission_id, cache=False):
47 def get_permission(self, permission_id, cache=False):
48 """
48 """
49 Get's permissions by id
49 Get's permissions by id
50
50
51 :param permission_id: id of permission to get from database
51 :param permission_id: id of permission to get from database
52 :param cache: use Cache for this query
52 :param cache: use Cache for this query
53 """
53 """
54 perm = self.sa.query(Permission)
54 perm = self.sa.query(Permission)
55 if cache:
55 if cache:
56 perm = perm.options(FromCache("sql_cache_short",
56 perm = perm.options(FromCache("sql_cache_short",
57 "get_permission_%s" % permission_id))
57 "get_permission_%s" % permission_id))
58 return perm.get(permission_id)
58 return perm.get(permission_id)
59
59
60 def get_permission_by_name(self, name, cache=False):
60 def get_permission_by_name(self, name, cache=False):
61 """
61 """
62 Get's permissions by given name
62 Get's permissions by given name
63
63
64 :param name: name to fetch
64 :param name: name to fetch
65 :param cache: Use cache for this query
65 :param cache: Use cache for this query
66 """
66 """
67 perm = self.sa.query(Permission)\
67 perm = self.sa.query(Permission)\
68 .filter(Permission.permission_name == name)
68 .filter(Permission.permission_name == name)
69 if cache:
69 if cache:
70 perm = perm.options(FromCache("sql_cache_short",
70 perm = perm.options(FromCache("sql_cache_short",
71 "get_permission_%s" % name))
71 "get_permission_%s" % name))
72 return perm.scalar()
72 return perm.scalar()
73
73
74 def update(self, form_result):
74 def update(self, form_result):
75 perm_user = self.sa.query(User)\
75 perm_user = self.sa.query(User)\
76 .filter(User.username ==
76 .filter(User.username ==
77 form_result['perm_user_name']).scalar()
77 form_result['perm_user_name']).scalar()
78 u2p = self.sa.query(UserToPerm).filter(UserToPerm.user ==
78 u2p = self.sa.query(UserToPerm).filter(UserToPerm.user ==
79 perm_user).all()
79 perm_user).all()
80 if len(u2p) != 4:
80 if len(u2p) != len(User.DEFAULT_PERMISSIONS):
81 raise Exception('Defined: %s should be 4 permissions for default'
81 raise Exception('Defined: %s should be 4 permissions for default'
82 ' user. This should not happen please verify'
82 ' user. This should not happen please verify'
83 ' your database' % len(u2p))
83 ' your database' % len(u2p))
84
84
85 try:
85 try:
86 # stage 1 change defaults
86 # stage 1 change defaults
87 for p in u2p:
87 for p in u2p:
88 if p.permission.permission_name.startswith('repository.'):
88 if p.permission.permission_name.startswith('repository.'):
89 p.permission = self.get_permission_by_name(
89 p.permission = self.get_permission_by_name(
90 form_result['default_perm'])
90 form_result['default_perm'])
91 self.sa.add(p)
91 self.sa.add(p)
92
92
93 elif p.permission.permission_name.startswith('hg.register.'):
93 elif p.permission.permission_name.startswith('hg.register.'):
94 p.permission = self.get_permission_by_name(
94 p.permission = self.get_permission_by_name(
95 form_result['default_register'])
95 form_result['default_register'])
96 self.sa.add(p)
96 self.sa.add(p)
97
97
98 elif p.permission.permission_name.startswith('hg.create.'):
98 elif p.permission.permission_name.startswith('hg.create.'):
99 p.permission = self.get_permission_by_name(
99 p.permission = self.get_permission_by_name(
100 form_result['default_create'])
100 form_result['default_create'])
101 self.sa.add(p)
101 self.sa.add(p)
102
102
103 elif p.permission.permission_name.startswith('hg.fork.'):
103 elif p.permission.permission_name.startswith('hg.fork.'):
104 p.permission = self.get_permission_by_name(
104 p.permission = self.get_permission_by_name(
105 form_result['default_fork'])
105 form_result['default_fork'])
106 self.sa.add(p)
106 self.sa.add(p)
107
107
108 _def_name = form_result['default_perm'].split('repository.')[-1]
108 _def_name = form_result['default_perm'].split('repository.')[-1]
109 #stage 2 update all default permissions for repos if checked
109 #stage 2 update all default permissions for repos if checked
110 if form_result['overwrite_default'] == True:
110 if form_result['overwrite_default'] == True:
111 _def = self.get_permission_by_name('repository.' + _def_name)
111 _def = self.get_permission_by_name('repository.' + _def_name)
112 # repos
112 # repos
113 for r2p in self.sa.query(UserRepoToPerm)\
113 for r2p in self.sa.query(UserRepoToPerm)\
114 .filter(UserRepoToPerm.user == perm_user)\
114 .filter(UserRepoToPerm.user == perm_user)\
115 .all():
115 .all():
116 r2p.permission = _def
116 r2p.permission = _def
117 self.sa.add(r2p)
117 self.sa.add(r2p)
118 # groups
118 # groups
119 _def = self.get_permission_by_name('group.' + _def_name)
119 _def = self.get_permission_by_name('group.' + _def_name)
120 for g2p in self.sa.query(UserRepoGroupToPerm)\
120 for g2p in self.sa.query(UserRepoGroupToPerm)\
121 .filter(UserRepoGroupToPerm.user == perm_user)\
121 .filter(UserRepoGroupToPerm.user == perm_user)\
122 .all():
122 .all():
123 g2p.permission = _def
123 g2p.permission = _def
124 self.sa.add(g2p)
124 self.sa.add(g2p)
125
125
126 # stage 3 set anonymous access
126 # stage 3 set anonymous access
127 if perm_user.username == 'default':
127 if perm_user.username == 'default':
128 perm_user.active = bool(form_result['anonymous'])
128 perm_user.active = bool(form_result['anonymous'])
129 self.sa.add(perm_user)
129 self.sa.add(perm_user)
130
130
131 except (DatabaseError,):
131 except (DatabaseError,):
132 log.error(traceback.format_exc())
132 log.error(traceback.format_exc())
133 raise
133 raise
General Comments 0
You need to be logged in to leave comments. Login now