##// END OF EJS Templates
fixed issue with empty APIKEYS on registration #438
marcink -
r2248:72542dc5 beta
parent child Browse files
Show More
@@ -1,634 +1,635 b''
1 .. _changelog:
1 .. _changelog:
2
2
3 =========
3 =========
4 Changelog
4 Changelog
5 =========
5 =========
6
6
7 1.3.5 (**2012-XX-XX**)
7 1.3.5 (**2012-XX-XX**)
8 ----------------------
8 ----------------------
9
9
10 :status: in-progress
10 :status: in-progress
11 :branch: beta
11 :branch: beta
12
12
13 news
13 news
14 ++++
14 ++++
15
15
16 - use ext_json for json module
16 - use ext_json for json module
17 - unified annotation view with file source view
17 - unified annotation view with file source view
18 - notification improvements, better inbox + css
18 - notification improvements, better inbox + css
19 - #419 don't strip passwords for login forms, make rhodecode
19 - #419 don't strip passwords for login forms, make rhodecode
20 more compatible with LDAP servers
20 more compatible with LDAP servers
21 - Added HTTP_X_FORWARDED_FOR as another method of extracting
21 - Added HTTP_X_FORWARDED_FOR as another method of extracting
22 IP for pull/push logs. - moved all to base controller
22 IP for pull/push logs. - moved all to base controller
23 - #415: Adding comment to changeset causes reload.
23 - #415: Adding comment to changeset causes reload.
24 Comments are now added via ajax and doesn't reload the page
24 Comments are now added via ajax and doesn't reload the page
25 - #374 LDAP config is discarded when LDAP can't be activated
25 - #374 LDAP config is discarded when LDAP can't be activated
26 - limited push/pull operations are now logged for git in the journal
26 - limited push/pull operations are now logged for git in the journal
27 - bumped mercurial to 2.2.X series
27 - bumped mercurial to 2.2.X series
28 - added support for displaying submodules in file-browser
28 - added support for displaying submodules in file-browser
29 - #421 added bookmarks in changlog view
29 - #421 added bookmarks in changlog view
30
30
31 fixes
31 fixes
32 +++++
32 +++++
33
33
34 - fixed dev-version marker for stable when served from source codes
34 - fixed dev-version marker for stable when served from source codes
35 - fixed missing permission checks on show forks page
35 - fixed missing permission checks on show forks page
36 - #418 cast to unicode fixes in notification objects
36 - #418 cast to unicode fixes in notification objects
37 - #426 fixed mention extracting regex
37 - #426 fixed mention extracting regex
38 - fixed remote-pulling for git remotes remopositories
38 - fixed remote-pulling for git remotes remopositories
39 - fixed #434: Error when accessing files or changesets of a git repository
39 - fixed #434: Error when accessing files or changesets of a git repository
40 with submodules
40 with submodules
41 - fixed issue with empty APIKEYS for users after registration ref. #438
41
42
42 1.3.4 (**2012-03-28**)
43 1.3.4 (**2012-03-28**)
43 ----------------------
44 ----------------------
44
45
45 news
46 news
46 ++++
47 ++++
47
48
48 - Whoosh logging is now controlled by the .ini files logging setup
49 - Whoosh logging is now controlled by the .ini files logging setup
49 - added clone-url into edit form on /settings page
50 - added clone-url into edit form on /settings page
50 - added help text into repo add/edit forms
51 - added help text into repo add/edit forms
51 - created rcextensions module with additional mappings (ref #322) and
52 - created rcextensions module with additional mappings (ref #322) and
52 post push/pull/create repo hooks callbacks
53 post push/pull/create repo hooks callbacks
53 - implemented #377 Users view for his own permissions on account page
54 - implemented #377 Users view for his own permissions on account page
54 - #399 added inheritance of permissions for users group on repos groups
55 - #399 added inheritance of permissions for users group on repos groups
55 - #401 repository group is automatically pre-selected when adding repos
56 - #401 repository group is automatically pre-selected when adding repos
56 inside a repository group
57 inside a repository group
57 - added alternative HTTP 403 response when client failed to authenticate. Helps
58 - added alternative HTTP 403 response when client failed to authenticate. Helps
58 solving issues with Mercurial and LDAP
59 solving issues with Mercurial and LDAP
59 - #402 removed group prefix from repository name when listing repositories
60 - #402 removed group prefix from repository name when listing repositories
60 inside a group
61 inside a group
61 - added gravatars into permission view and permissions autocomplete
62 - added gravatars into permission view and permissions autocomplete
62 - #347 when running multiple RhodeCode instances, properly invalidates cache
63 - #347 when running multiple RhodeCode instances, properly invalidates cache
63 for all registered servers
64 for all registered servers
64
65
65 fixes
66 fixes
66 +++++
67 +++++
67
68
68 - fixed #390 cache invalidation problems on repos inside group
69 - fixed #390 cache invalidation problems on repos inside group
69 - fixed #385 clone by ID url was loosing proxy prefix in URL
70 - fixed #385 clone by ID url was loosing proxy prefix in URL
70 - fixed some unicode problems with waitress
71 - fixed some unicode problems with waitress
71 - fixed issue with escaping < and > in changeset commits
72 - fixed issue with escaping < and > in changeset commits
72 - fixed error occurring during recursive group creation in API
73 - fixed error occurring during recursive group creation in API
73 create_repo function
74 create_repo function
74 - fixed #393 py2.5 fixes for routes url generator
75 - fixed #393 py2.5 fixes for routes url generator
75 - fixed #397 Private repository groups shows up before login
76 - fixed #397 Private repository groups shows up before login
76 - fixed #396 fixed problems with revoking users in nested groups
77 - fixed #396 fixed problems with revoking users in nested groups
77 - fixed mysql unicode issues + specified InnoDB as default engine with
78 - fixed mysql unicode issues + specified InnoDB as default engine with
78 utf8 charset
79 utf8 charset
79 - #406 trim long branch/tag names in changelog to not break UI
80 - #406 trim long branch/tag names in changelog to not break UI
80
81
81 1.3.3 (**2012-03-02**)
82 1.3.3 (**2012-03-02**)
82 ----------------------
83 ----------------------
83
84
84 news
85 news
85 ++++
86 ++++
86
87
87
88
88 fixes
89 fixes
89 +++++
90 +++++
90
91
91 - fixed some python2.5 compatibility issues
92 - fixed some python2.5 compatibility issues
92 - fixed issues with removed repos was accidentally added as groups, after
93 - fixed issues with removed repos was accidentally added as groups, after
93 full rescan of paths
94 full rescan of paths
94 - fixes #376 Cannot edit user (using container auth)
95 - fixes #376 Cannot edit user (using container auth)
95 - fixes #378 Invalid image urls on changeset screen with proxy-prefix
96 - fixes #378 Invalid image urls on changeset screen with proxy-prefix
96 configuration
97 configuration
97 - fixed initial sorting of repos inside repo group
98 - fixed initial sorting of repos inside repo group
98 - fixes issue when user tried to resubmit same permission into user/user_groups
99 - fixes issue when user tried to resubmit same permission into user/user_groups
99 - bumped beaker version that fixes #375 leap error bug
100 - bumped beaker version that fixes #375 leap error bug
100 - fixed raw_changeset for git. It was generated with hg patch headers
101 - fixed raw_changeset for git. It was generated with hg patch headers
101 - fixed vcs issue with last_changeset for filenodes
102 - fixed vcs issue with last_changeset for filenodes
102 - fixed missing commit after hook delete
103 - fixed missing commit after hook delete
103 - fixed #372 issues with git operation detection that caused a security issue
104 - fixed #372 issues with git operation detection that caused a security issue
104 for git repos
105 for git repos
105
106
106 1.3.2 (**2012-02-28**)
107 1.3.2 (**2012-02-28**)
107 ----------------------
108 ----------------------
108
109
109 news
110 news
110 ++++
111 ++++
111
112
112
113
113 fixes
114 fixes
114 +++++
115 +++++
115
116
116 - fixed git protocol issues with repos-groups
117 - fixed git protocol issues with repos-groups
117 - fixed git remote repos validator that prevented from cloning remote git repos
118 - fixed git remote repos validator that prevented from cloning remote git repos
118 - fixes #370 ending slashes fixes for repo and groups
119 - fixes #370 ending slashes fixes for repo and groups
119 - fixes #368 improved git-protocol detection to handle other clients
120 - fixes #368 improved git-protocol detection to handle other clients
120 - fixes #366 When Setting Repository Group To Blank Repo Group Wont Be
121 - fixes #366 When Setting Repository Group To Blank Repo Group Wont Be
121 Moved To Root
122 Moved To Root
122 - fixes #371 fixed issues with beaker/sqlalchemy and non-ascii cache keys
123 - fixes #371 fixed issues with beaker/sqlalchemy and non-ascii cache keys
123 - fixed #373 missing cascade drop on user_group_to_perm table
124 - fixed #373 missing cascade drop on user_group_to_perm table
124
125
125 1.3.1 (**2012-02-27**)
126 1.3.1 (**2012-02-27**)
126 ----------------------
127 ----------------------
127
128
128 news
129 news
129 ++++
130 ++++
130
131
131
132
132 fixes
133 fixes
133 +++++
134 +++++
134
135
135 - redirection loop occurs when remember-me wasn't checked during login
136 - redirection loop occurs when remember-me wasn't checked during login
136 - fixes issues with git blob history generation
137 - fixes issues with git blob history generation
137 - don't fetch branch for git in file history dropdown. Causes unneeded slowness
138 - don't fetch branch for git in file history dropdown. Causes unneeded slowness
138
139
139 1.3.0 (**2012-02-26**)
140 1.3.0 (**2012-02-26**)
140 ----------------------
141 ----------------------
141
142
142 news
143 news
143 ++++
144 ++++
144
145
145 - code review, inspired by github code-comments
146 - code review, inspired by github code-comments
146 - #215 rst and markdown README files support
147 - #215 rst and markdown README files support
147 - #252 Container-based and proxy pass-through authentication support
148 - #252 Container-based and proxy pass-through authentication support
148 - #44 branch browser. Filtering of changelog by branches
149 - #44 branch browser. Filtering of changelog by branches
149 - mercurial bookmarks support
150 - mercurial bookmarks support
150 - new hover top menu, optimized to add maximum size for important views
151 - new hover top menu, optimized to add maximum size for important views
151 - configurable clone url template with possibility to specify protocol like
152 - configurable clone url template with possibility to specify protocol like
152 ssh:// or http:// and also manually alter other parts of clone_url.
153 ssh:// or http:// and also manually alter other parts of clone_url.
153 - enabled largefiles extension by default
154 - enabled largefiles extension by default
154 - optimized summary file pages and saved a lot of unused space in them
155 - optimized summary file pages and saved a lot of unused space in them
155 - #239 option to manually mark repository as fork
156 - #239 option to manually mark repository as fork
156 - #320 mapping of commit authors to RhodeCode users
157 - #320 mapping of commit authors to RhodeCode users
157 - #304 hashes are displayed using monospace font
158 - #304 hashes are displayed using monospace font
158 - diff configuration, toggle white lines and context lines
159 - diff configuration, toggle white lines and context lines
159 - #307 configurable diffs, whitespace toggle, increasing context lines
160 - #307 configurable diffs, whitespace toggle, increasing context lines
160 - sorting on branches, tags and bookmarks using YUI datatable
161 - sorting on branches, tags and bookmarks using YUI datatable
161 - improved file filter on files page
162 - improved file filter on files page
162 - implements #330 api method for listing nodes ar particular revision
163 - implements #330 api method for listing nodes ar particular revision
163 - #73 added linking issues in commit messages to chosen issue tracker url
164 - #73 added linking issues in commit messages to chosen issue tracker url
164 based on user defined regular expression
165 based on user defined regular expression
165 - added linking of changesets in commit messages
166 - added linking of changesets in commit messages
166 - new compact changelog with expandable commit messages
167 - new compact changelog with expandable commit messages
167 - firstname and lastname are optional in user creation
168 - firstname and lastname are optional in user creation
168 - #348 added post-create repository hook
169 - #348 added post-create repository hook
169 - #212 global encoding settings is now configurable from .ini files
170 - #212 global encoding settings is now configurable from .ini files
170 - #227 added repository groups permissions
171 - #227 added repository groups permissions
171 - markdown gets codehilite extensions
172 - markdown gets codehilite extensions
172 - new API methods, delete_repositories, grante/revoke permissions for groups
173 - new API methods, delete_repositories, grante/revoke permissions for groups
173 and repos
174 and repos
174
175
175
176
176 fixes
177 fixes
177 +++++
178 +++++
178
179
179 - rewrote dbsession management for atomic operations, and better error handling
180 - rewrote dbsession management for atomic operations, and better error handling
180 - fixed sorting of repo tables
181 - fixed sorting of repo tables
181 - #326 escape of special html entities in diffs
182 - #326 escape of special html entities in diffs
182 - normalized user_name => username in api attributes
183 - normalized user_name => username in api attributes
183 - fixes #298 ldap created users with mixed case emails created conflicts
184 - fixes #298 ldap created users with mixed case emails created conflicts
184 on saving a form
185 on saving a form
185 - fixes issue when owner of a repo couldn't revoke permissions for users
186 - fixes issue when owner of a repo couldn't revoke permissions for users
186 and groups
187 and groups
187 - fixes #271 rare JSON serialization problem with statistics
188 - fixes #271 rare JSON serialization problem with statistics
188 - fixes #337 missing validation check for conflicting names of a group with a
189 - fixes #337 missing validation check for conflicting names of a group with a
189 repositories group
190 repositories group
190 - #340 fixed session problem for mysql and celery tasks
191 - #340 fixed session problem for mysql and celery tasks
191 - fixed #331 RhodeCode mangles repository names if the a repository group
192 - fixed #331 RhodeCode mangles repository names if the a repository group
192 contains the "full path" to the repositories
193 contains the "full path" to the repositories
193 - #355 RhodeCode doesn't store encrypted LDAP passwords
194 - #355 RhodeCode doesn't store encrypted LDAP passwords
194
195
195 1.2.5 (**2012-01-28**)
196 1.2.5 (**2012-01-28**)
196 ----------------------
197 ----------------------
197
198
198 news
199 news
199 ++++
200 ++++
200
201
201 fixes
202 fixes
202 +++++
203 +++++
203
204
204 - #340 Celery complains about MySQL server gone away, added session cleanup
205 - #340 Celery complains about MySQL server gone away, added session cleanup
205 for celery tasks
206 for celery tasks
206 - #341 "scanning for repositories in None" log message during Rescan was missing
207 - #341 "scanning for repositories in None" log message during Rescan was missing
207 a parameter
208 a parameter
208 - fixed creating archives with subrepos. Some hooks were triggered during that
209 - fixed creating archives with subrepos. Some hooks were triggered during that
209 operation leading to crash.
210 operation leading to crash.
210 - fixed missing email in account page.
211 - fixed missing email in account page.
211 - Reverted Mercurial to 2.0.1 for windows due to bug in Mercurial that makes
212 - Reverted Mercurial to 2.0.1 for windows due to bug in Mercurial that makes
212 forking on windows impossible
213 forking on windows impossible
213
214
214 1.2.4 (**2012-01-19**)
215 1.2.4 (**2012-01-19**)
215 ----------------------
216 ----------------------
216
217
217 news
218 news
218 ++++
219 ++++
219
220
220 - RhodeCode is bundled with mercurial series 2.0.X by default, with
221 - RhodeCode is bundled with mercurial series 2.0.X by default, with
221 full support to largefiles extension. Enabled by default in new installations
222 full support to largefiles extension. Enabled by default in new installations
222 - #329 Ability to Add/Remove Groups to/from a Repository via AP
223 - #329 Ability to Add/Remove Groups to/from a Repository via AP
223 - added requires.txt file with requirements
224 - added requires.txt file with requirements
224
225
225 fixes
226 fixes
226 +++++
227 +++++
227
228
228 - fixes db session issues with celery when emailing admins
229 - fixes db session issues with celery when emailing admins
229 - #331 RhodeCode mangles repository names if the a repository group
230 - #331 RhodeCode mangles repository names if the a repository group
230 contains the "full path" to the repositories
231 contains the "full path" to the repositories
231 - #298 Conflicting e-mail addresses for LDAP and RhodeCode users
232 - #298 Conflicting e-mail addresses for LDAP and RhodeCode users
232 - DB session cleanup after hg protocol operations, fixes issues with
233 - DB session cleanup after hg protocol operations, fixes issues with
233 `mysql has gone away` errors
234 `mysql has gone away` errors
234 - #333 doc fixes for get_repo api function
235 - #333 doc fixes for get_repo api function
235 - #271 rare JSON serialization problem with statistics enabled
236 - #271 rare JSON serialization problem with statistics enabled
236 - #337 Fixes issues with validation of repository name conflicting with
237 - #337 Fixes issues with validation of repository name conflicting with
237 a group name. A proper message is now displayed.
238 a group name. A proper message is now displayed.
238 - #292 made ldap_dn in user edit readonly, to get rid of confusion that field
239 - #292 made ldap_dn in user edit readonly, to get rid of confusion that field
239 doesn't work
240 doesn't work
240 - #316 fixes issues with web description in hgrc files
241 - #316 fixes issues with web description in hgrc files
241
242
242 1.2.3 (**2011-11-02**)
243 1.2.3 (**2011-11-02**)
243 ----------------------
244 ----------------------
244
245
245 news
246 news
246 ++++
247 ++++
247
248
248 - added option to manage repos group for non admin users
249 - added option to manage repos group for non admin users
249 - added following API methods for get_users, create_user, get_users_groups,
250 - added following API methods for get_users, create_user, get_users_groups,
250 get_users_group, create_users_group, add_user_to_users_groups, get_repos,
251 get_users_group, create_users_group, add_user_to_users_groups, get_repos,
251 get_repo, create_repo, add_user_to_repo
252 get_repo, create_repo, add_user_to_repo
252 - implements #237 added password confirmation for my account
253 - implements #237 added password confirmation for my account
253 and admin edit user.
254 and admin edit user.
254 - implements #291 email notification for global events are now sent to all
255 - implements #291 email notification for global events are now sent to all
255 administrator users, and global config email.
256 administrator users, and global config email.
256
257
257 fixes
258 fixes
258 +++++
259 +++++
259
260
260 - added option for passing auth method for smtp mailer
261 - added option for passing auth method for smtp mailer
261 - #276 issue with adding a single user with id>10 to usergroups
262 - #276 issue with adding a single user with id>10 to usergroups
262 - #277 fixes windows LDAP settings in which missing values breaks the ldap auth
263 - #277 fixes windows LDAP settings in which missing values breaks the ldap auth
263 - #288 fixes managing of repos in a group for non admin user
264 - #288 fixes managing of repos in a group for non admin user
264
265
265 1.2.2 (**2011-10-17**)
266 1.2.2 (**2011-10-17**)
266 ----------------------
267 ----------------------
267
268
268 news
269 news
269 ++++
270 ++++
270
271
271 - #226 repo groups are available by path instead of numerical id
272 - #226 repo groups are available by path instead of numerical id
272
273
273 fixes
274 fixes
274 +++++
275 +++++
275
276
276 - #259 Groups with the same name but with different parent group
277 - #259 Groups with the same name but with different parent group
277 - #260 Put repo in group, then move group to another group -> repo becomes unavailable
278 - #260 Put repo in group, then move group to another group -> repo becomes unavailable
278 - #258 RhodeCode 1.2 assumes egg folder is writable (lockfiles problems)
279 - #258 RhodeCode 1.2 assumes egg folder is writable (lockfiles problems)
279 - #265 ldap save fails sometimes on converting attributes to booleans,
280 - #265 ldap save fails sometimes on converting attributes to booleans,
280 added getter and setter into model that will prevent from this on db model level
281 added getter and setter into model that will prevent from this on db model level
281 - fixed problems with timestamps issues #251 and #213
282 - fixed problems with timestamps issues #251 and #213
282 - fixes #266 RhodeCode allows to create repo with the same name and in
283 - fixes #266 RhodeCode allows to create repo with the same name and in
283 the same parent as group
284 the same parent as group
284 - fixes #245 Rescan of the repositories on Windows
285 - fixes #245 Rescan of the repositories on Windows
285 - fixes #248 cannot edit repos inside a group on windows
286 - fixes #248 cannot edit repos inside a group on windows
286 - fixes #219 forking problems on windows
287 - fixes #219 forking problems on windows
287
288
288 1.2.1 (**2011-10-08**)
289 1.2.1 (**2011-10-08**)
289 ----------------------
290 ----------------------
290
291
291 news
292 news
292 ++++
293 ++++
293
294
294
295
295 fixes
296 fixes
296 +++++
297 +++++
297
298
298 - fixed problems with basic auth and push problems
299 - fixed problems with basic auth and push problems
299 - gui fixes
300 - gui fixes
300 - fixed logger
301 - fixed logger
301
302
302 1.2.0 (**2011-10-07**)
303 1.2.0 (**2011-10-07**)
303 ----------------------
304 ----------------------
304
305
305 news
306 news
306 ++++
307 ++++
307
308
308 - implemented #47 repository groups
309 - implemented #47 repository groups
309 - implemented #89 Can setup google analytics code from settings menu
310 - implemented #89 Can setup google analytics code from settings menu
310 - implemented #91 added nicer looking archive urls with more download options
311 - implemented #91 added nicer looking archive urls with more download options
311 like tags, branches
312 like tags, branches
312 - implemented #44 into file browsing, and added follow branch option
313 - implemented #44 into file browsing, and added follow branch option
313 - implemented #84 downloads can be enabled/disabled for each repository
314 - implemented #84 downloads can be enabled/disabled for each repository
314 - anonymous repository can be cloned without having to pass default:default
315 - anonymous repository can be cloned without having to pass default:default
315 into clone url
316 into clone url
316 - fixed #90 whoosh indexer can index chooses repositories passed in command
317 - fixed #90 whoosh indexer can index chooses repositories passed in command
317 line
318 line
318 - extended journal with day aggregates and paging
319 - extended journal with day aggregates and paging
319 - implemented #107 source code lines highlight ranges
320 - implemented #107 source code lines highlight ranges
320 - implemented #93 customizable changelog on combined revision ranges -
321 - implemented #93 customizable changelog on combined revision ranges -
321 equivalent of githubs compare view
322 equivalent of githubs compare view
322 - implemented #108 extended and more powerful LDAP configuration
323 - implemented #108 extended and more powerful LDAP configuration
323 - implemented #56 users groups
324 - implemented #56 users groups
324 - major code rewrites optimized codes for speed and memory usage
325 - major code rewrites optimized codes for speed and memory usage
325 - raw and diff downloads are now in git format
326 - raw and diff downloads are now in git format
326 - setup command checks for write access to given path
327 - setup command checks for write access to given path
327 - fixed many issues with international characters and unicode. It uses utf8
328 - fixed many issues with international characters and unicode. It uses utf8
328 decode with replace to provide less errors even with non utf8 encoded strings
329 decode with replace to provide less errors even with non utf8 encoded strings
329 - #125 added API KEY access to feeds
330 - #125 added API KEY access to feeds
330 - #109 Repository can be created from external Mercurial link (aka. remote
331 - #109 Repository can be created from external Mercurial link (aka. remote
331 repository, and manually updated (via pull) from admin panel
332 repository, and manually updated (via pull) from admin panel
332 - beta git support - push/pull server + basic view for git repos
333 - beta git support - push/pull server + basic view for git repos
333 - added followers page and forks page
334 - added followers page and forks page
334 - server side file creation (with binary file upload interface)
335 - server side file creation (with binary file upload interface)
335 and edition with commits powered by codemirror
336 and edition with commits powered by codemirror
336 - #111 file browser file finder, quick lookup files on whole file tree
337 - #111 file browser file finder, quick lookup files on whole file tree
337 - added quick login sliding menu into main page
338 - added quick login sliding menu into main page
338 - changelog uses lazy loading of affected files details, in some scenarios
339 - changelog uses lazy loading of affected files details, in some scenarios
339 this can improve speed of changelog page dramatically especially for
340 this can improve speed of changelog page dramatically especially for
340 larger repositories.
341 larger repositories.
341 - implements #214 added support for downloading subrepos in download menu.
342 - implements #214 added support for downloading subrepos in download menu.
342 - Added basic API for direct operations on rhodecode via JSON
343 - Added basic API for direct operations on rhodecode via JSON
343 - Implemented advanced hook management
344 - Implemented advanced hook management
344
345
345 fixes
346 fixes
346 +++++
347 +++++
347
348
348 - fixed file browser bug, when switching into given form revision the url was
349 - fixed file browser bug, when switching into given form revision the url was
349 not changing
350 not changing
350 - fixed propagation to error controller on simplehg and simplegit middlewares
351 - fixed propagation to error controller on simplehg and simplegit middlewares
351 - fixed error when trying to make a download on empty repository
352 - fixed error when trying to make a download on empty repository
352 - fixed problem with '[' chars in commit messages in journal
353 - fixed problem with '[' chars in commit messages in journal
353 - fixed #99 Unicode errors, on file node paths with non utf-8 characters
354 - fixed #99 Unicode errors, on file node paths with non utf-8 characters
354 - journal fork fixes
355 - journal fork fixes
355 - removed issue with space inside renamed repository after deletion
356 - removed issue with space inside renamed repository after deletion
356 - fixed strange issue on formencode imports
357 - fixed strange issue on formencode imports
357 - fixed #126 Deleting repository on Windows, rename used incompatible chars.
358 - fixed #126 Deleting repository on Windows, rename used incompatible chars.
358 - #150 fixes for errors on repositories mapped in db but corrupted in
359 - #150 fixes for errors on repositories mapped in db but corrupted in
359 filesystem
360 filesystem
360 - fixed problem with ascendant characters in realm #181
361 - fixed problem with ascendant characters in realm #181
361 - fixed problem with sqlite file based database connection pool
362 - fixed problem with sqlite file based database connection pool
362 - whoosh indexer and code stats share the same dynamic extensions map
363 - whoosh indexer and code stats share the same dynamic extensions map
363 - fixes #188 - relationship delete of repo_to_perm entry on user removal
364 - fixes #188 - relationship delete of repo_to_perm entry on user removal
364 - fixes issue #189 Trending source files shows "show more" when no more exist
365 - fixes issue #189 Trending source files shows "show more" when no more exist
365 - fixes issue #197 Relative paths for pidlocks
366 - fixes issue #197 Relative paths for pidlocks
366 - fixes issue #198 password will require only 3 chars now for login form
367 - fixes issue #198 password will require only 3 chars now for login form
367 - fixes issue #199 wrong redirection for non admin users after creating a repository
368 - fixes issue #199 wrong redirection for non admin users after creating a repository
368 - fixes issues #202, bad db constraint made impossible to attach same group
369 - fixes issues #202, bad db constraint made impossible to attach same group
369 more than one time. Affects only mysql/postgres
370 more than one time. Affects only mysql/postgres
370 - fixes #218 os.kill patch for windows was missing sig param
371 - fixes #218 os.kill patch for windows was missing sig param
371 - improved rendering of dag (they are not trimmed anymore when number of
372 - improved rendering of dag (they are not trimmed anymore when number of
372 heads exceeds 5)
373 heads exceeds 5)
373
374
374 1.1.8 (**2011-04-12**)
375 1.1.8 (**2011-04-12**)
375 ----------------------
376 ----------------------
376
377
377 news
378 news
378 ++++
379 ++++
379
380
380 - improved windows support
381 - improved windows support
381
382
382 fixes
383 fixes
383 +++++
384 +++++
384
385
385 - fixed #140 freeze of python dateutil library, since new version is python2.x
386 - fixed #140 freeze of python dateutil library, since new version is python2.x
386 incompatible
387 incompatible
387 - setup-app will check for write permission in given path
388 - setup-app will check for write permission in given path
388 - cleaned up license info issue #149
389 - cleaned up license info issue #149
389 - fixes for issues #137,#116 and problems with unicode and accented characters.
390 - fixes for issues #137,#116 and problems with unicode and accented characters.
390 - fixes crashes on gravatar, when passed in email as unicode
391 - fixes crashes on gravatar, when passed in email as unicode
391 - fixed tooltip flickering problems
392 - fixed tooltip flickering problems
392 - fixed came_from redirection on windows
393 - fixed came_from redirection on windows
393 - fixed logging modules, and sql formatters
394 - fixed logging modules, and sql formatters
394 - windows fixes for os.kill issue #133
395 - windows fixes for os.kill issue #133
395 - fixes path splitting for windows issues #148
396 - fixes path splitting for windows issues #148
396 - fixed issue #143 wrong import on migration to 1.1.X
397 - fixed issue #143 wrong import on migration to 1.1.X
397 - fixed problems with displaying binary files, thanks to Thomas Waldmann
398 - fixed problems with displaying binary files, thanks to Thomas Waldmann
398 - removed name from archive files since it's breaking ui for long repo names
399 - removed name from archive files since it's breaking ui for long repo names
399 - fixed issue with archive headers sent to browser, thanks to Thomas Waldmann
400 - fixed issue with archive headers sent to browser, thanks to Thomas Waldmann
400 - fixed compatibility for 1024px displays, and larger dpi settings, thanks to
401 - fixed compatibility for 1024px displays, and larger dpi settings, thanks to
401 Thomas Waldmann
402 Thomas Waldmann
402 - fixed issue #166 summary pager was skipping 10 revisions on second page
403 - fixed issue #166 summary pager was skipping 10 revisions on second page
403
404
404
405
405 1.1.7 (**2011-03-23**)
406 1.1.7 (**2011-03-23**)
406 ----------------------
407 ----------------------
407
408
408 news
409 news
409 ++++
410 ++++
410
411
411 fixes
412 fixes
412 +++++
413 +++++
413
414
414 - fixed (again) #136 installation support for FreeBSD
415 - fixed (again) #136 installation support for FreeBSD
415
416
416
417
417 1.1.6 (**2011-03-21**)
418 1.1.6 (**2011-03-21**)
418 ----------------------
419 ----------------------
419
420
420 news
421 news
421 ++++
422 ++++
422
423
423 fixes
424 fixes
424 +++++
425 +++++
425
426
426 - fixed #136 installation support for FreeBSD
427 - fixed #136 installation support for FreeBSD
427 - RhodeCode will check for python version during installation
428 - RhodeCode will check for python version during installation
428
429
429 1.1.5 (**2011-03-17**)
430 1.1.5 (**2011-03-17**)
430 ----------------------
431 ----------------------
431
432
432 news
433 news
433 ++++
434 ++++
434
435
435 - basic windows support, by exchanging pybcrypt into sha256 for windows only
436 - basic windows support, by exchanging pybcrypt into sha256 for windows only
436 highly inspired by idea of mantis406
437 highly inspired by idea of mantis406
437
438
438 fixes
439 fixes
439 +++++
440 +++++
440
441
441 - fixed sorting by author in main page
442 - fixed sorting by author in main page
442 - fixed crashes with diffs on binary files
443 - fixed crashes with diffs on binary files
443 - fixed #131 problem with boolean values for LDAP
444 - fixed #131 problem with boolean values for LDAP
444 - fixed #122 mysql problems thanks to striker69
445 - fixed #122 mysql problems thanks to striker69
445 - fixed problem with errors on calling raw/raw_files/annotate functions
446 - fixed problem with errors on calling raw/raw_files/annotate functions
446 with unknown revisions
447 with unknown revisions
447 - fixed returned rawfiles attachment names with international character
448 - fixed returned rawfiles attachment names with international character
448 - cleaned out docs, big thanks to Jason Harris
449 - cleaned out docs, big thanks to Jason Harris
449
450
450 1.1.4 (**2011-02-19**)
451 1.1.4 (**2011-02-19**)
451 ----------------------
452 ----------------------
452
453
453 news
454 news
454 ++++
455 ++++
455
456
456 fixes
457 fixes
457 +++++
458 +++++
458
459
459 - fixed formencode import problem on settings page, that caused server crash
460 - fixed formencode import problem on settings page, that caused server crash
460 when that page was accessed as first after server start
461 when that page was accessed as first after server start
461 - journal fixes
462 - journal fixes
462 - fixed option to access repository just by entering http://server/<repo_name>
463 - fixed option to access repository just by entering http://server/<repo_name>
463
464
464 1.1.3 (**2011-02-16**)
465 1.1.3 (**2011-02-16**)
465 ----------------------
466 ----------------------
466
467
467 news
468 news
468 ++++
469 ++++
469
470
470 - implemented #102 allowing the '.' character in username
471 - implemented #102 allowing the '.' character in username
471 - added option to access repository just by entering http://server/<repo_name>
472 - added option to access repository just by entering http://server/<repo_name>
472 - celery task ignores result for better performance
473 - celery task ignores result for better performance
473
474
474 fixes
475 fixes
475 +++++
476 +++++
476
477
477 - fixed ehlo command and non auth mail servers on smtp_lib. Thanks to
478 - fixed ehlo command and non auth mail servers on smtp_lib. Thanks to
478 apollo13 and Johan Walles
479 apollo13 and Johan Walles
479 - small fixes in journal
480 - small fixes in journal
480 - fixed problems with getting setting for celery from .ini files
481 - fixed problems with getting setting for celery from .ini files
481 - registration, password reset and login boxes share the same title as main
482 - registration, password reset and login boxes share the same title as main
482 application now
483 application now
483 - fixed #113: to high permissions to fork repository
484 - fixed #113: to high permissions to fork repository
484 - fixed problem with '[' chars in commit messages in journal
485 - fixed problem with '[' chars in commit messages in journal
485 - removed issue with space inside renamed repository after deletion
486 - removed issue with space inside renamed repository after deletion
486 - db transaction fixes when filesystem repository creation failed
487 - db transaction fixes when filesystem repository creation failed
487 - fixed #106 relation issues on databases different than sqlite
488 - fixed #106 relation issues on databases different than sqlite
488 - fixed static files paths links to use of url() method
489 - fixed static files paths links to use of url() method
489
490
490 1.1.2 (**2011-01-12**)
491 1.1.2 (**2011-01-12**)
491 ----------------------
492 ----------------------
492
493
493 news
494 news
494 ++++
495 ++++
495
496
496
497
497 fixes
498 fixes
498 +++++
499 +++++
499
500
500 - fixes #98 protection against float division of percentage stats
501 - fixes #98 protection against float division of percentage stats
501 - fixed graph bug
502 - fixed graph bug
502 - forced webhelpers version since it was making troubles during installation
503 - forced webhelpers version since it was making troubles during installation
503
504
504 1.1.1 (**2011-01-06**)
505 1.1.1 (**2011-01-06**)
505 ----------------------
506 ----------------------
506
507
507 news
508 news
508 ++++
509 ++++
509
510
510 - added force https option into ini files for easier https usage (no need to
511 - added force https option into ini files for easier https usage (no need to
511 set server headers with this options)
512 set server headers with this options)
512 - small css updates
513 - small css updates
513
514
514 fixes
515 fixes
515 +++++
516 +++++
516
517
517 - fixed #96 redirect loop on files view on repositories without changesets
518 - fixed #96 redirect loop on files view on repositories without changesets
518 - fixed #97 unicode string passed into server header in special cases (mod_wsgi)
519 - fixed #97 unicode string passed into server header in special cases (mod_wsgi)
519 and server crashed with errors
520 and server crashed with errors
520 - fixed large tooltips problems on main page
521 - fixed large tooltips problems on main page
521 - fixed #92 whoosh indexer is more error proof
522 - fixed #92 whoosh indexer is more error proof
522
523
523 1.1.0 (**2010-12-18**)
524 1.1.0 (**2010-12-18**)
524 ----------------------
525 ----------------------
525
526
526 news
527 news
527 ++++
528 ++++
528
529
529 - rewrite of internals for vcs >=0.1.10
530 - rewrite of internals for vcs >=0.1.10
530 - uses mercurial 1.7 with dotencode disabled for maintaining compatibility
531 - uses mercurial 1.7 with dotencode disabled for maintaining compatibility
531 with older clients
532 with older clients
532 - anonymous access, authentication via ldap
533 - anonymous access, authentication via ldap
533 - performance upgrade for cached repos list - each repository has its own
534 - performance upgrade for cached repos list - each repository has its own
534 cache that's invalidated when needed.
535 cache that's invalidated when needed.
535 - performance upgrades on repositories with large amount of commits (20K+)
536 - performance upgrades on repositories with large amount of commits (20K+)
536 - main page quick filter for filtering repositories
537 - main page quick filter for filtering repositories
537 - user dashboards with ability to follow chosen repositories actions
538 - user dashboards with ability to follow chosen repositories actions
538 - sends email to admin on new user registration
539 - sends email to admin on new user registration
539 - added cache/statistics reset options into repository settings
540 - added cache/statistics reset options into repository settings
540 - more detailed action logger (based on hooks) with pushed changesets lists
541 - more detailed action logger (based on hooks) with pushed changesets lists
541 and options to disable those hooks from admin panel
542 and options to disable those hooks from admin panel
542 - introduced new enhanced changelog for merges that shows more accurate results
543 - introduced new enhanced changelog for merges that shows more accurate results
543 - new improved and faster code stats (based on pygments lexers mapping tables,
544 - new improved and faster code stats (based on pygments lexers mapping tables,
544 showing up to 10 trending sources for each repository. Additionally stats
545 showing up to 10 trending sources for each repository. Additionally stats
545 can be disabled in repository settings.
546 can be disabled in repository settings.
546 - gui optimizations, fixed application width to 1024px
547 - gui optimizations, fixed application width to 1024px
547 - added cut off (for large files/changesets) limit into config files
548 - added cut off (for large files/changesets) limit into config files
548 - whoosh, celeryd, upgrade moved to paster command
549 - whoosh, celeryd, upgrade moved to paster command
549 - other than sqlite database backends can be used
550 - other than sqlite database backends can be used
550
551
551 fixes
552 fixes
552 +++++
553 +++++
553
554
554 - fixes #61 forked repo was showing only after cache expired
555 - fixes #61 forked repo was showing only after cache expired
555 - fixes #76 no confirmation on user deletes
556 - fixes #76 no confirmation on user deletes
556 - fixes #66 Name field misspelled
557 - fixes #66 Name field misspelled
557 - fixes #72 block user removal when he owns repositories
558 - fixes #72 block user removal when he owns repositories
558 - fixes #69 added password confirmation fields
559 - fixes #69 added password confirmation fields
559 - fixes #87 RhodeCode crashes occasionally on updating repository owner
560 - fixes #87 RhodeCode crashes occasionally on updating repository owner
560 - fixes #82 broken annotations on files with more than 1 blank line at the end
561 - fixes #82 broken annotations on files with more than 1 blank line at the end
561 - a lot of fixes and tweaks for file browser
562 - a lot of fixes and tweaks for file browser
562 - fixed detached session issues
563 - fixed detached session issues
563 - fixed when user had no repos he would see all repos listed in my account
564 - fixed when user had no repos he would see all repos listed in my account
564 - fixed ui() instance bug when global hgrc settings was loaded for server
565 - fixed ui() instance bug when global hgrc settings was loaded for server
565 instance and all hgrc options were merged with our db ui() object
566 instance and all hgrc options were merged with our db ui() object
566 - numerous small bugfixes
567 - numerous small bugfixes
567
568
568 (special thanks for TkSoh for detailed feedback)
569 (special thanks for TkSoh for detailed feedback)
569
570
570
571
571 1.0.2 (**2010-11-12**)
572 1.0.2 (**2010-11-12**)
572 ----------------------
573 ----------------------
573
574
574 news
575 news
575 ++++
576 ++++
576
577
577 - tested under python2.7
578 - tested under python2.7
578 - bumped sqlalchemy and celery versions
579 - bumped sqlalchemy and celery versions
579
580
580 fixes
581 fixes
581 +++++
582 +++++
582
583
583 - fixed #59 missing graph.js
584 - fixed #59 missing graph.js
584 - fixed repo_size crash when repository had broken symlinks
585 - fixed repo_size crash when repository had broken symlinks
585 - fixed python2.5 crashes.
586 - fixed python2.5 crashes.
586
587
587
588
588 1.0.1 (**2010-11-10**)
589 1.0.1 (**2010-11-10**)
589 ----------------------
590 ----------------------
590
591
591 news
592 news
592 ++++
593 ++++
593
594
594 - small css updated
595 - small css updated
595
596
596 fixes
597 fixes
597 +++++
598 +++++
598
599
599 - fixed #53 python2.5 incompatible enumerate calls
600 - fixed #53 python2.5 incompatible enumerate calls
600 - fixed #52 disable mercurial extension for web
601 - fixed #52 disable mercurial extension for web
601 - fixed #51 deleting repositories don't delete it's dependent objects
602 - fixed #51 deleting repositories don't delete it's dependent objects
602
603
603
604
604 1.0.0 (**2010-11-02**)
605 1.0.0 (**2010-11-02**)
605 ----------------------
606 ----------------------
606
607
607 - security bugfix simplehg wasn't checking for permissions on commands
608 - security bugfix simplehg wasn't checking for permissions on commands
608 other than pull or push.
609 other than pull or push.
609 - fixed doubled messages after push or pull in admin journal
610 - fixed doubled messages after push or pull in admin journal
610 - templating and css corrections, fixed repo switcher on chrome, updated titles
611 - templating and css corrections, fixed repo switcher on chrome, updated titles
611 - admin menu accessible from options menu on repository view
612 - admin menu accessible from options menu on repository view
612 - permissions cached queries
613 - permissions cached queries
613
614
614 1.0.0rc4 (**2010-10-12**)
615 1.0.0rc4 (**2010-10-12**)
615 --------------------------
616 --------------------------
616
617
617 - fixed python2.5 missing simplejson imports (thanks to Jens BΓ€ckman)
618 - fixed python2.5 missing simplejson imports (thanks to Jens BΓ€ckman)
618 - removed cache_manager settings from sqlalchemy meta
619 - removed cache_manager settings from sqlalchemy meta
619 - added sqlalchemy cache settings to ini files
620 - added sqlalchemy cache settings to ini files
620 - validated password length and added second try of failure on paster setup-app
621 - validated password length and added second try of failure on paster setup-app
621 - fixed setup database destroy prompt even when there was no db
622 - fixed setup database destroy prompt even when there was no db
622
623
623
624
624 1.0.0rc3 (**2010-10-11**)
625 1.0.0rc3 (**2010-10-11**)
625 -------------------------
626 -------------------------
626
627
627 - fixed i18n during installation.
628 - fixed i18n during installation.
628
629
629 1.0.0rc2 (**2010-10-11**)
630 1.0.0rc2 (**2010-10-11**)
630 -------------------------
631 -------------------------
631
632
632 - Disabled dirsize in file browser, it's causing nasty bug when dir renames
633 - Disabled dirsize in file browser, it's causing nasty bug when dir renames
633 occure. After vcs is fixed it'll be put back again.
634 occure. After vcs is fixed it'll be put back again.
634 - templating/css rewrites, optimized css. No newline at end of file
635 - templating/css rewrites, optimized css.
@@ -1,1292 +1,1293 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 from collections import defaultdict
30 from collections import defaultdict
31
31
32 from sqlalchemy import *
32 from sqlalchemy import *
33 from sqlalchemy.ext.hybrid import hybrid_property
33 from sqlalchemy.ext.hybrid import hybrid_property
34 from sqlalchemy.orm import relationship, joinedload, class_mapper, validates
34 from sqlalchemy.orm import relationship, joinedload, class_mapper, validates
35 from beaker.cache import cache_region, region_invalidate
35 from beaker.cache import cache_region, region_invalidate
36
36
37 from rhodecode.lib.vcs import get_backend
37 from rhodecode.lib.vcs import get_backend
38 from rhodecode.lib.vcs.utils.helpers import get_scm
38 from rhodecode.lib.vcs.utils.helpers import get_scm
39 from rhodecode.lib.vcs.exceptions import VCSError
39 from rhodecode.lib.vcs.exceptions import VCSError
40 from rhodecode.lib.vcs.utils.lazy import LazyProperty
40 from rhodecode.lib.vcs.utils.lazy import LazyProperty
41
41
42 from rhodecode.lib.utils2 import str2bool, safe_str, get_changeset_safe, \
42 from rhodecode.lib.utils2 import str2bool, safe_str, get_changeset_safe, \
43 safe_unicode
43 safe_unicode
44 from rhodecode.lib.compat import json
44 from rhodecode.lib.compat import json
45 from rhodecode.lib.caching_query import FromCache
45 from rhodecode.lib.caching_query import FromCache
46
46
47 from rhodecode.model.meta import Base, Session
47 from rhodecode.model.meta import Base, Session
48 import hashlib
48 import hashlib
49
49
50
50
51 log = logging.getLogger(__name__)
51 log = logging.getLogger(__name__)
52
52
53 #==============================================================================
53 #==============================================================================
54 # BASE CLASSES
54 # BASE CLASSES
55 #==============================================================================
55 #==============================================================================
56
56
57 _hash_key = lambda k: hashlib.md5(safe_str(k)).hexdigest()
57 _hash_key = lambda k: hashlib.md5(safe_str(k)).hexdigest()
58
58
59
59
60 class ModelSerializer(json.JSONEncoder):
60 class ModelSerializer(json.JSONEncoder):
61 """
61 """
62 Simple Serializer for JSON,
62 Simple Serializer for JSON,
63
63
64 usage::
64 usage::
65
65
66 to make object customized for serialization implement a __json__
66 to make object customized for serialization implement a __json__
67 method that will return a dict for serialization into json
67 method that will return a dict for serialization into json
68
68
69 example::
69 example::
70
70
71 class Task(object):
71 class Task(object):
72
72
73 def __init__(self, name, value):
73 def __init__(self, name, value):
74 self.name = name
74 self.name = name
75 self.value = value
75 self.value = value
76
76
77 def __json__(self):
77 def __json__(self):
78 return dict(name=self.name,
78 return dict(name=self.name,
79 value=self.value)
79 value=self.value)
80
80
81 """
81 """
82
82
83 def default(self, obj):
83 def default(self, obj):
84
84
85 if hasattr(obj, '__json__'):
85 if hasattr(obj, '__json__'):
86 return obj.__json__()
86 return obj.__json__()
87 else:
87 else:
88 return json.JSONEncoder.default(self, obj)
88 return json.JSONEncoder.default(self, obj)
89
89
90
90
91 class BaseModel(object):
91 class BaseModel(object):
92 """
92 """
93 Base Model for all classess
93 Base Model for all classess
94 """
94 """
95
95
96 @classmethod
96 @classmethod
97 def _get_keys(cls):
97 def _get_keys(cls):
98 """return column names for this model """
98 """return column names for this model """
99 return class_mapper(cls).c.keys()
99 return class_mapper(cls).c.keys()
100
100
101 def get_dict(self):
101 def get_dict(self):
102 """
102 """
103 return dict with keys and values corresponding
103 return dict with keys and values corresponding
104 to this model data """
104 to this model data """
105
105
106 d = {}
106 d = {}
107 for k in self._get_keys():
107 for k in self._get_keys():
108 d[k] = getattr(self, k)
108 d[k] = getattr(self, k)
109
109
110 # also use __json__() if present to get additional fields
110 # also use __json__() if present to get additional fields
111 for k, val in getattr(self, '__json__', lambda: {})().iteritems():
111 for k, val in getattr(self, '__json__', lambda: {})().iteritems():
112 d[k] = val
112 d[k] = val
113 return d
113 return d
114
114
115 def get_appstruct(self):
115 def get_appstruct(self):
116 """return list with keys and values tupples corresponding
116 """return list with keys and values tupples corresponding
117 to this model data """
117 to this model data """
118
118
119 l = []
119 l = []
120 for k in self._get_keys():
120 for k in self._get_keys():
121 l.append((k, getattr(self, k),))
121 l.append((k, getattr(self, k),))
122 return l
122 return l
123
123
124 def populate_obj(self, populate_dict):
124 def populate_obj(self, populate_dict):
125 """populate model with data from given populate_dict"""
125 """populate model with data from given populate_dict"""
126
126
127 for k in self._get_keys():
127 for k in self._get_keys():
128 if k in populate_dict:
128 if k in populate_dict:
129 setattr(self, k, populate_dict[k])
129 setattr(self, k, populate_dict[k])
130
130
131 @classmethod
131 @classmethod
132 def query(cls):
132 def query(cls):
133 return Session.query(cls)
133 return Session.query(cls)
134
134
135 @classmethod
135 @classmethod
136 def get(cls, id_):
136 def get(cls, id_):
137 if id_:
137 if id_:
138 return cls.query().get(id_)
138 return cls.query().get(id_)
139
139
140 @classmethod
140 @classmethod
141 def getAll(cls):
141 def getAll(cls):
142 return cls.query().all()
142 return cls.query().all()
143
143
144 @classmethod
144 @classmethod
145 def delete(cls, id_):
145 def delete(cls, id_):
146 obj = cls.query().get(id_)
146 obj = cls.query().get(id_)
147 Session.delete(obj)
147 Session.delete(obj)
148
148
149 def __repr__(self):
149 def __repr__(self):
150 if hasattr(self, '__unicode__'):
150 if hasattr(self, '__unicode__'):
151 # python repr needs to return str
151 # python repr needs to return str
152 return safe_str(self.__unicode__())
152 return safe_str(self.__unicode__())
153 return '<DB:%s>' % (self.__class__.__name__)
153 return '<DB:%s>' % (self.__class__.__name__)
154
154
155 class RhodeCodeSetting(Base, BaseModel):
155 class RhodeCodeSetting(Base, BaseModel):
156 __tablename__ = 'rhodecode_settings'
156 __tablename__ = 'rhodecode_settings'
157 __table_args__ = (
157 __table_args__ = (
158 UniqueConstraint('app_settings_name'),
158 UniqueConstraint('app_settings_name'),
159 {'extend_existing': True, 'mysql_engine':'InnoDB',
159 {'extend_existing': True, 'mysql_engine':'InnoDB',
160 'mysql_charset': 'utf8'}
160 'mysql_charset': 'utf8'}
161 )
161 )
162 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
162 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
163 app_settings_name = Column("app_settings_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
163 app_settings_name = Column("app_settings_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
164 _app_settings_value = Column("app_settings_value", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
164 _app_settings_value = Column("app_settings_value", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
165
165
166 def __init__(self, k='', v=''):
166 def __init__(self, k='', v=''):
167 self.app_settings_name = k
167 self.app_settings_name = k
168 self.app_settings_value = v
168 self.app_settings_value = v
169
169
170 @validates('_app_settings_value')
170 @validates('_app_settings_value')
171 def validate_settings_value(self, key, val):
171 def validate_settings_value(self, key, val):
172 assert type(val) == unicode
172 assert type(val) == unicode
173 return val
173 return val
174
174
175 @hybrid_property
175 @hybrid_property
176 def app_settings_value(self):
176 def app_settings_value(self):
177 v = self._app_settings_value
177 v = self._app_settings_value
178 if self.app_settings_name == 'ldap_active':
178 if self.app_settings_name == 'ldap_active':
179 v = str2bool(v)
179 v = str2bool(v)
180 return v
180 return v
181
181
182 @app_settings_value.setter
182 @app_settings_value.setter
183 def app_settings_value(self, val):
183 def app_settings_value(self, val):
184 """
184 """
185 Setter that will always make sure we use unicode in app_settings_value
185 Setter that will always make sure we use unicode in app_settings_value
186
186
187 :param val:
187 :param val:
188 """
188 """
189 self._app_settings_value = safe_unicode(val)
189 self._app_settings_value = safe_unicode(val)
190
190
191 def __unicode__(self):
191 def __unicode__(self):
192 return u"<%s('%s:%s')>" % (
192 return u"<%s('%s:%s')>" % (
193 self.__class__.__name__,
193 self.__class__.__name__,
194 self.app_settings_name, self.app_settings_value
194 self.app_settings_name, self.app_settings_value
195 )
195 )
196
196
197 @classmethod
197 @classmethod
198 def get_by_name(cls, ldap_key):
198 def get_by_name(cls, ldap_key):
199 return cls.query()\
199 return cls.query()\
200 .filter(cls.app_settings_name == ldap_key).scalar()
200 .filter(cls.app_settings_name == ldap_key).scalar()
201
201
202 @classmethod
202 @classmethod
203 def get_app_settings(cls, cache=False):
203 def get_app_settings(cls, cache=False):
204
204
205 ret = cls.query()
205 ret = cls.query()
206
206
207 if cache:
207 if cache:
208 ret = ret.options(FromCache("sql_cache_short", "get_hg_settings"))
208 ret = ret.options(FromCache("sql_cache_short", "get_hg_settings"))
209
209
210 if not ret:
210 if not ret:
211 raise Exception('Could not get application settings !')
211 raise Exception('Could not get application settings !')
212 settings = {}
212 settings = {}
213 for each in ret:
213 for each in ret:
214 settings['rhodecode_' + each.app_settings_name] = \
214 settings['rhodecode_' + each.app_settings_name] = \
215 each.app_settings_value
215 each.app_settings_value
216
216
217 return settings
217 return settings
218
218
219 @classmethod
219 @classmethod
220 def get_ldap_settings(cls, cache=False):
220 def get_ldap_settings(cls, cache=False):
221 ret = cls.query()\
221 ret = cls.query()\
222 .filter(cls.app_settings_name.startswith('ldap_')).all()
222 .filter(cls.app_settings_name.startswith('ldap_')).all()
223 fd = {}
223 fd = {}
224 for row in ret:
224 for row in ret:
225 fd.update({row.app_settings_name:row.app_settings_value})
225 fd.update({row.app_settings_name:row.app_settings_value})
226
226
227 return fd
227 return fd
228
228
229
229
230 class RhodeCodeUi(Base, BaseModel):
230 class RhodeCodeUi(Base, BaseModel):
231 __tablename__ = 'rhodecode_ui'
231 __tablename__ = 'rhodecode_ui'
232 __table_args__ = (
232 __table_args__ = (
233 UniqueConstraint('ui_key'),
233 UniqueConstraint('ui_key'),
234 {'extend_existing': True, 'mysql_engine':'InnoDB',
234 {'extend_existing': True, 'mysql_engine':'InnoDB',
235 'mysql_charset': 'utf8'}
235 'mysql_charset': 'utf8'}
236 )
236 )
237
237
238 HOOK_UPDATE = 'changegroup.update'
238 HOOK_UPDATE = 'changegroup.update'
239 HOOK_REPO_SIZE = 'changegroup.repo_size'
239 HOOK_REPO_SIZE = 'changegroup.repo_size'
240 HOOK_PUSH = 'pretxnchangegroup.push_logger'
240 HOOK_PUSH = 'pretxnchangegroup.push_logger'
241 HOOK_PULL = 'preoutgoing.pull_logger'
241 HOOK_PULL = 'preoutgoing.pull_logger'
242
242
243 ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
243 ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
244 ui_section = Column("ui_section", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
244 ui_section = Column("ui_section", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
245 ui_key = Column("ui_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
245 ui_key = Column("ui_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
246 ui_value = Column("ui_value", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
246 ui_value = Column("ui_value", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
247 ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True)
247 ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True)
248
248
249 @classmethod
249 @classmethod
250 def get_by_key(cls, key):
250 def get_by_key(cls, key):
251 return cls.query().filter(cls.ui_key == key)
251 return cls.query().filter(cls.ui_key == key)
252
252
253 @classmethod
253 @classmethod
254 def get_builtin_hooks(cls):
254 def get_builtin_hooks(cls):
255 q = cls.query()
255 q = cls.query()
256 q = q.filter(cls.ui_key.in_([cls.HOOK_UPDATE,
256 q = q.filter(cls.ui_key.in_([cls.HOOK_UPDATE,
257 cls.HOOK_REPO_SIZE,
257 cls.HOOK_REPO_SIZE,
258 cls.HOOK_PUSH, cls.HOOK_PULL]))
258 cls.HOOK_PUSH, cls.HOOK_PULL]))
259 return q.all()
259 return q.all()
260
260
261 @classmethod
261 @classmethod
262 def get_custom_hooks(cls):
262 def get_custom_hooks(cls):
263 q = cls.query()
263 q = cls.query()
264 q = q.filter(~cls.ui_key.in_([cls.HOOK_UPDATE,
264 q = q.filter(~cls.ui_key.in_([cls.HOOK_UPDATE,
265 cls.HOOK_REPO_SIZE,
265 cls.HOOK_REPO_SIZE,
266 cls.HOOK_PUSH, cls.HOOK_PULL]))
266 cls.HOOK_PUSH, cls.HOOK_PULL]))
267 q = q.filter(cls.ui_section == 'hooks')
267 q = q.filter(cls.ui_section == 'hooks')
268 return q.all()
268 return q.all()
269
269
270 @classmethod
270 @classmethod
271 def create_or_update_hook(cls, key, val):
271 def create_or_update_hook(cls, key, val):
272 new_ui = cls.get_by_key(key).scalar() or cls()
272 new_ui = cls.get_by_key(key).scalar() or cls()
273 new_ui.ui_section = 'hooks'
273 new_ui.ui_section = 'hooks'
274 new_ui.ui_active = True
274 new_ui.ui_active = True
275 new_ui.ui_key = key
275 new_ui.ui_key = key
276 new_ui.ui_value = val
276 new_ui.ui_value = val
277
277
278 Session.add(new_ui)
278 Session.add(new_ui)
279
279
280
280
281 class User(Base, BaseModel):
281 class User(Base, BaseModel):
282 __tablename__ = 'users'
282 __tablename__ = 'users'
283 __table_args__ = (
283 __table_args__ = (
284 UniqueConstraint('username'), UniqueConstraint('email'),
284 UniqueConstraint('username'), UniqueConstraint('email'),
285 {'extend_existing': True, 'mysql_engine':'InnoDB',
285 {'extend_existing': True, 'mysql_engine':'InnoDB',
286 'mysql_charset': 'utf8'}
286 'mysql_charset': 'utf8'}
287 )
287 )
288 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
288 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
289 username = Column("username", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
289 username = Column("username", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
290 password = Column("password", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
290 password = Column("password", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
291 active = Column("active", Boolean(), nullable=True, unique=None, default=None)
291 active = Column("active", Boolean(), nullable=True, unique=None, default=None)
292 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
292 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
293 name = Column("name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
293 name = Column("name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
294 lastname = Column("lastname", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
294 lastname = Column("lastname", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
295 _email = Column("email", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
295 _email = Column("email", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
296 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
296 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
297 ldap_dn = Column("ldap_dn", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
297 ldap_dn = Column("ldap_dn", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
298 api_key = Column("api_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
298 api_key = Column("api_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
299
299
300 user_log = relationship('UserLog', cascade='all')
300 user_log = relationship('UserLog', cascade='all')
301 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
301 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
302
302
303 repositories = relationship('Repository')
303 repositories = relationship('Repository')
304 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
304 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
305 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
305 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
306 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
306 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
307
307
308 group_member = relationship('UsersGroupMember', cascade='all')
308 group_member = relationship('UsersGroupMember', cascade='all')
309
309
310 notifications = relationship('UserNotification', cascade='all')
310 notifications = relationship('UserNotification', cascade='all')
311 # notifications assigned to this user
311 # notifications assigned to this user
312 user_created_notifications = relationship('Notification', cascade='all')
312 user_created_notifications = relationship('Notification', cascade='all')
313 # comments created by this user
313 # comments created by this user
314 user_comments = relationship('ChangesetComment', cascade='all')
314 user_comments = relationship('ChangesetComment', cascade='all')
315
315
316 @hybrid_property
316 @hybrid_property
317 def email(self):
317 def email(self):
318 return self._email
318 return self._email
319
319
320 @email.setter
320 @email.setter
321 def email(self, val):
321 def email(self, val):
322 self._email = val.lower() if val else None
322 self._email = val.lower() if val else None
323
323
324 @property
324 @property
325 def full_name(self):
325 def full_name(self):
326 return '%s %s' % (self.name, self.lastname)
326 return '%s %s' % (self.name, self.lastname)
327
327
328 @property
328 @property
329 def full_name_or_username(self):
329 def full_name_or_username(self):
330 return ('%s %s' % (self.name, self.lastname)
330 return ('%s %s' % (self.name, self.lastname)
331 if (self.name and self.lastname) else self.username)
331 if (self.name and self.lastname) else self.username)
332
332
333 @property
333 @property
334 def full_contact(self):
334 def full_contact(self):
335 return '%s %s <%s>' % (self.name, self.lastname, self.email)
335 return '%s %s <%s>' % (self.name, self.lastname, self.email)
336
336
337 @property
337 @property
338 def short_contact(self):
338 def short_contact(self):
339 return '%s %s' % (self.name, self.lastname)
339 return '%s %s' % (self.name, self.lastname)
340
340
341 @property
341 @property
342 def is_admin(self):
342 def is_admin(self):
343 return self.admin
343 return self.admin
344
344
345 def __unicode__(self):
345 def __unicode__(self):
346 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
346 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
347 self.user_id, self.username)
347 self.user_id, self.username)
348
348
349 @classmethod
349 @classmethod
350 def get_by_username(cls, username, case_insensitive=False, cache=False):
350 def get_by_username(cls, username, case_insensitive=False, cache=False):
351 if case_insensitive:
351 if case_insensitive:
352 q = cls.query().filter(cls.username.ilike(username))
352 q = cls.query().filter(cls.username.ilike(username))
353 else:
353 else:
354 q = cls.query().filter(cls.username == username)
354 q = cls.query().filter(cls.username == username)
355
355
356 if cache:
356 if cache:
357 q = q.options(FromCache(
357 q = q.options(FromCache(
358 "sql_cache_short",
358 "sql_cache_short",
359 "get_user_%s" % _hash_key(username)
359 "get_user_%s" % _hash_key(username)
360 )
360 )
361 )
361 )
362 return q.scalar()
362 return q.scalar()
363
363
364 @classmethod
364 @classmethod
365 def get_by_api_key(cls, api_key, cache=False):
365 def get_by_api_key(cls, api_key, cache=False):
366 q = cls.query().filter(cls.api_key == api_key)
366 q = cls.query().filter(cls.api_key == api_key)
367
367
368 if cache:
368 if cache:
369 q = q.options(FromCache("sql_cache_short",
369 q = q.options(FromCache("sql_cache_short",
370 "get_api_key_%s" % api_key))
370 "get_api_key_%s" % api_key))
371 return q.scalar()
371 return q.scalar()
372
372
373 @classmethod
373 @classmethod
374 def get_by_email(cls, email, case_insensitive=False, cache=False):
374 def get_by_email(cls, email, case_insensitive=False, cache=False):
375 if case_insensitive:
375 if case_insensitive:
376 q = cls.query().filter(cls.email.ilike(email))
376 q = cls.query().filter(cls.email.ilike(email))
377 else:
377 else:
378 q = cls.query().filter(cls.email == email)
378 q = cls.query().filter(cls.email == email)
379
379
380 if cache:
380 if cache:
381 q = q.options(FromCache("sql_cache_short",
381 q = q.options(FromCache("sql_cache_short",
382 "get_api_key_%s" % email))
382 "get_api_key_%s" % email))
383 return q.scalar()
383 return q.scalar()
384
384
385 def update_lastlogin(self):
385 def update_lastlogin(self):
386 """Update user lastlogin"""
386 """Update user lastlogin"""
387 self.last_login = datetime.datetime.now()
387 self.last_login = datetime.datetime.now()
388 Session.add(self)
388 Session.add(self)
389 log.debug('updated user %s lastlogin' % self.username)
389 log.debug('updated user %s lastlogin' % self.username)
390
390
391 def __json__(self):
391 def __json__(self):
392 return dict(
392 return dict(
393 user_id=self.user_id,
393 user_id=self.user_id,
394 first_name=self.name,
394 first_name=self.name,
395 last_name=self.lastname,
395 last_name=self.lastname,
396 email=self.email,
396 email=self.email,
397 full_name=self.full_name,
397 full_name=self.full_name,
398 full_name_or_username=self.full_name_or_username,
398 full_name_or_username=self.full_name_or_username,
399 short_contact=self.short_contact,
399 short_contact=self.short_contact,
400 full_contact=self.full_contact
400 full_contact=self.full_contact
401 )
401 )
402
402
403
403
404 class UserLog(Base, BaseModel):
404 class UserLog(Base, BaseModel):
405 __tablename__ = 'user_logs'
405 __tablename__ = 'user_logs'
406 __table_args__ = (
406 __table_args__ = (
407 {'extend_existing': True, 'mysql_engine':'InnoDB',
407 {'extend_existing': True, 'mysql_engine':'InnoDB',
408 'mysql_charset': 'utf8'},
408 'mysql_charset': 'utf8'},
409 )
409 )
410 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
410 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
411 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
411 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
412 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True)
412 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True)
413 repository_name = Column("repository_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
413 repository_name = Column("repository_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
414 user_ip = Column("user_ip", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
414 user_ip = Column("user_ip", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
415 action = Column("action", UnicodeText(length=1200000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
415 action = Column("action", UnicodeText(length=1200000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
416 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
416 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
417
417
418 @property
418 @property
419 def action_as_day(self):
419 def action_as_day(self):
420 return datetime.date(*self.action_date.timetuple()[:3])
420 return datetime.date(*self.action_date.timetuple()[:3])
421
421
422 user = relationship('User')
422 user = relationship('User')
423 repository = relationship('Repository', cascade='')
423 repository = relationship('Repository', cascade='')
424
424
425
425
426 class UsersGroup(Base, BaseModel):
426 class UsersGroup(Base, BaseModel):
427 __tablename__ = 'users_groups'
427 __tablename__ = 'users_groups'
428 __table_args__ = (
428 __table_args__ = (
429 {'extend_existing': True, 'mysql_engine':'InnoDB',
429 {'extend_existing': True, 'mysql_engine':'InnoDB',
430 'mysql_charset': 'utf8'},
430 'mysql_charset': 'utf8'},
431 )
431 )
432
432
433 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
433 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
434 users_group_name = Column("users_group_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
434 users_group_name = Column("users_group_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
435 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
435 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
436
436
437 members = relationship('UsersGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
437 members = relationship('UsersGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
438 users_group_to_perm = relationship('UsersGroupToPerm', cascade='all')
438 users_group_to_perm = relationship('UsersGroupToPerm', cascade='all')
439 users_group_repo_to_perm = relationship('UsersGroupRepoToPerm', cascade='all')
439 users_group_repo_to_perm = relationship('UsersGroupRepoToPerm', cascade='all')
440
440
441 def __unicode__(self):
441 def __unicode__(self):
442 return u'<userGroup(%s)>' % (self.users_group_name)
442 return u'<userGroup(%s)>' % (self.users_group_name)
443
443
444 @classmethod
444 @classmethod
445 def get_by_group_name(cls, group_name, cache=False,
445 def get_by_group_name(cls, group_name, cache=False,
446 case_insensitive=False):
446 case_insensitive=False):
447 if case_insensitive:
447 if case_insensitive:
448 q = cls.query().filter(cls.users_group_name.ilike(group_name))
448 q = cls.query().filter(cls.users_group_name.ilike(group_name))
449 else:
449 else:
450 q = cls.query().filter(cls.users_group_name == group_name)
450 q = cls.query().filter(cls.users_group_name == group_name)
451 if cache:
451 if cache:
452 q = q.options(FromCache(
452 q = q.options(FromCache(
453 "sql_cache_short",
453 "sql_cache_short",
454 "get_user_%s" % _hash_key(group_name)
454 "get_user_%s" % _hash_key(group_name)
455 )
455 )
456 )
456 )
457 return q.scalar()
457 return q.scalar()
458
458
459 @classmethod
459 @classmethod
460 def get(cls, users_group_id, cache=False):
460 def get(cls, users_group_id, cache=False):
461 users_group = cls.query()
461 users_group = cls.query()
462 if cache:
462 if cache:
463 users_group = users_group.options(FromCache("sql_cache_short",
463 users_group = users_group.options(FromCache("sql_cache_short",
464 "get_users_group_%s" % users_group_id))
464 "get_users_group_%s" % users_group_id))
465 return users_group.get(users_group_id)
465 return users_group.get(users_group_id)
466
466
467
467
468 class UsersGroupMember(Base, BaseModel):
468 class UsersGroupMember(Base, BaseModel):
469 __tablename__ = 'users_groups_members'
469 __tablename__ = 'users_groups_members'
470 __table_args__ = (
470 __table_args__ = (
471 {'extend_existing': True, 'mysql_engine':'InnoDB',
471 {'extend_existing': True, 'mysql_engine':'InnoDB',
472 'mysql_charset': 'utf8'},
472 'mysql_charset': 'utf8'},
473 )
473 )
474
474
475 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
475 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
476 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
476 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
477 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
477 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
478
478
479 user = relationship('User', lazy='joined')
479 user = relationship('User', lazy='joined')
480 users_group = relationship('UsersGroup')
480 users_group = relationship('UsersGroup')
481
481
482 def __init__(self, gr_id='', u_id=''):
482 def __init__(self, gr_id='', u_id=''):
483 self.users_group_id = gr_id
483 self.users_group_id = gr_id
484 self.user_id = u_id
484 self.user_id = u_id
485
485
486
486
487 class Repository(Base, BaseModel):
487 class Repository(Base, BaseModel):
488 __tablename__ = 'repositories'
488 __tablename__ = 'repositories'
489 __table_args__ = (
489 __table_args__ = (
490 UniqueConstraint('repo_name'),
490 UniqueConstraint('repo_name'),
491 {'extend_existing': True, 'mysql_engine':'InnoDB',
491 {'extend_existing': True, 'mysql_engine':'InnoDB',
492 'mysql_charset': 'utf8'},
492 'mysql_charset': 'utf8'},
493 )
493 )
494
494
495 repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
495 repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
496 repo_name = Column("repo_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
496 repo_name = Column("repo_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
497 clone_uri = Column("clone_uri", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
497 clone_uri = Column("clone_uri", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
498 repo_type = Column("repo_type", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default='hg')
498 repo_type = Column("repo_type", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default='hg')
499 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
499 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
500 private = Column("private", Boolean(), nullable=True, unique=None, default=None)
500 private = Column("private", Boolean(), nullable=True, unique=None, default=None)
501 enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True)
501 enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True)
502 enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True)
502 enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True)
503 description = Column("description", String(length=10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
503 description = Column("description", String(length=10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
504 created_on = Column('created_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
504 created_on = Column('created_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
505
505
506 fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None)
506 fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None)
507 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None)
507 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None)
508
508
509 user = relationship('User')
509 user = relationship('User')
510 fork = relationship('Repository', remote_side=repo_id)
510 fork = relationship('Repository', remote_side=repo_id)
511 group = relationship('RepoGroup')
511 group = relationship('RepoGroup')
512 repo_to_perm = relationship('UserRepoToPerm', cascade='all', order_by='UserRepoToPerm.repo_to_perm_id')
512 repo_to_perm = relationship('UserRepoToPerm', cascade='all', order_by='UserRepoToPerm.repo_to_perm_id')
513 users_group_to_perm = relationship('UsersGroupRepoToPerm', cascade='all')
513 users_group_to_perm = relationship('UsersGroupRepoToPerm', cascade='all')
514 stats = relationship('Statistics', cascade='all', uselist=False)
514 stats = relationship('Statistics', cascade='all', uselist=False)
515
515
516 followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id', cascade='all')
516 followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id', cascade='all')
517
517
518 logs = relationship('UserLog')
518 logs = relationship('UserLog')
519
519
520 def __unicode__(self):
520 def __unicode__(self):
521 return u"<%s('%s:%s')>" % (self.__class__.__name__,self.repo_id,
521 return u"<%s('%s:%s')>" % (self.__class__.__name__,self.repo_id,
522 self.repo_name)
522 self.repo_name)
523
523
524 @classmethod
524 @classmethod
525 def url_sep(cls):
525 def url_sep(cls):
526 return '/'
526 return '/'
527
527
528 @classmethod
528 @classmethod
529 def get_by_repo_name(cls, repo_name):
529 def get_by_repo_name(cls, repo_name):
530 q = Session.query(cls).filter(cls.repo_name == repo_name)
530 q = Session.query(cls).filter(cls.repo_name == repo_name)
531 q = q.options(joinedload(Repository.fork))\
531 q = q.options(joinedload(Repository.fork))\
532 .options(joinedload(Repository.user))\
532 .options(joinedload(Repository.user))\
533 .options(joinedload(Repository.group))
533 .options(joinedload(Repository.group))
534 return q.scalar()
534 return q.scalar()
535
535
536 @classmethod
536 @classmethod
537 def get_repo_forks(cls, repo_id):
537 def get_repo_forks(cls, repo_id):
538 return cls.query().filter(Repository.fork_id == repo_id)
538 return cls.query().filter(Repository.fork_id == repo_id)
539
539
540 @classmethod
540 @classmethod
541 def base_path(cls):
541 def base_path(cls):
542 """
542 """
543 Returns base path when all repos are stored
543 Returns base path when all repos are stored
544
544
545 :param cls:
545 :param cls:
546 """
546 """
547 q = Session.query(RhodeCodeUi)\
547 q = Session.query(RhodeCodeUi)\
548 .filter(RhodeCodeUi.ui_key == cls.url_sep())
548 .filter(RhodeCodeUi.ui_key == cls.url_sep())
549 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
549 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
550 return q.one().ui_value
550 return q.one().ui_value
551
551
552 @property
552 @property
553 def just_name(self):
553 def just_name(self):
554 return self.repo_name.split(Repository.url_sep())[-1]
554 return self.repo_name.split(Repository.url_sep())[-1]
555
555
556 @property
556 @property
557 def groups_with_parents(self):
557 def groups_with_parents(self):
558 groups = []
558 groups = []
559 if self.group is None:
559 if self.group is None:
560 return groups
560 return groups
561
561
562 cur_gr = self.group
562 cur_gr = self.group
563 groups.insert(0, cur_gr)
563 groups.insert(0, cur_gr)
564 while 1:
564 while 1:
565 gr = getattr(cur_gr, 'parent_group', None)
565 gr = getattr(cur_gr, 'parent_group', None)
566 cur_gr = cur_gr.parent_group
566 cur_gr = cur_gr.parent_group
567 if gr is None:
567 if gr is None:
568 break
568 break
569 groups.insert(0, gr)
569 groups.insert(0, gr)
570
570
571 return groups
571 return groups
572
572
573 @property
573 @property
574 def groups_and_repo(self):
574 def groups_and_repo(self):
575 return self.groups_with_parents, self.just_name
575 return self.groups_with_parents, self.just_name
576
576
577 @LazyProperty
577 @LazyProperty
578 def repo_path(self):
578 def repo_path(self):
579 """
579 """
580 Returns base full path for that repository means where it actually
580 Returns base full path for that repository means where it actually
581 exists on a filesystem
581 exists on a filesystem
582 """
582 """
583 q = Session.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key ==
583 q = Session.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key ==
584 Repository.url_sep())
584 Repository.url_sep())
585 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
585 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
586 return q.one().ui_value
586 return q.one().ui_value
587
587
588 @property
588 @property
589 def repo_full_path(self):
589 def repo_full_path(self):
590 p = [self.repo_path]
590 p = [self.repo_path]
591 # we need to split the name by / since this is how we store the
591 # we need to split the name by / since this is how we store the
592 # names in the database, but that eventually needs to be converted
592 # names in the database, but that eventually needs to be converted
593 # into a valid system path
593 # into a valid system path
594 p += self.repo_name.split(Repository.url_sep())
594 p += self.repo_name.split(Repository.url_sep())
595 return os.path.join(*p)
595 return os.path.join(*p)
596
596
597 def get_new_name(self, repo_name):
597 def get_new_name(self, repo_name):
598 """
598 """
599 returns new full repository name based on assigned group and new new
599 returns new full repository name based on assigned group and new new
600
600
601 :param group_name:
601 :param group_name:
602 """
602 """
603 path_prefix = self.group.full_path_splitted if self.group else []
603 path_prefix = self.group.full_path_splitted if self.group else []
604 return Repository.url_sep().join(path_prefix + [repo_name])
604 return Repository.url_sep().join(path_prefix + [repo_name])
605
605
606 @property
606 @property
607 def _ui(self):
607 def _ui(self):
608 """
608 """
609 Creates an db based ui object for this repository
609 Creates an db based ui object for this repository
610 """
610 """
611 from mercurial import ui
611 from mercurial import ui
612 from mercurial import config
612 from mercurial import config
613 baseui = ui.ui()
613 baseui = ui.ui()
614
614
615 #clean the baseui object
615 #clean the baseui object
616 baseui._ocfg = config.config()
616 baseui._ocfg = config.config()
617 baseui._ucfg = config.config()
617 baseui._ucfg = config.config()
618 baseui._tcfg = config.config()
618 baseui._tcfg = config.config()
619
619
620 ret = RhodeCodeUi.query()\
620 ret = RhodeCodeUi.query()\
621 .options(FromCache("sql_cache_short", "repository_repo_ui")).all()
621 .options(FromCache("sql_cache_short", "repository_repo_ui")).all()
622
622
623 hg_ui = ret
623 hg_ui = ret
624 for ui_ in hg_ui:
624 for ui_ in hg_ui:
625 if ui_.ui_active:
625 if ui_.ui_active:
626 log.debug('settings ui from db[%s]%s:%s', ui_.ui_section,
626 log.debug('settings ui from db[%s]%s:%s', ui_.ui_section,
627 ui_.ui_key, ui_.ui_value)
627 ui_.ui_key, ui_.ui_value)
628 baseui.setconfig(ui_.ui_section, ui_.ui_key, ui_.ui_value)
628 baseui.setconfig(ui_.ui_section, ui_.ui_key, ui_.ui_value)
629
629
630 return baseui
630 return baseui
631
631
632 @classmethod
632 @classmethod
633 def is_valid(cls, repo_name):
633 def is_valid(cls, repo_name):
634 """
634 """
635 returns True if given repo name is a valid filesystem repository
635 returns True if given repo name is a valid filesystem repository
636
636
637 :param cls:
637 :param cls:
638 :param repo_name:
638 :param repo_name:
639 """
639 """
640 from rhodecode.lib.utils import is_valid_repo
640 from rhodecode.lib.utils import is_valid_repo
641
641
642 return is_valid_repo(repo_name, cls.base_path())
642 return is_valid_repo(repo_name, cls.base_path())
643
643
644 #==========================================================================
644 #==========================================================================
645 # SCM PROPERTIES
645 # SCM PROPERTIES
646 #==========================================================================
646 #==========================================================================
647
647
648 def get_changeset(self, rev):
648 def get_changeset(self, rev):
649 return get_changeset_safe(self.scm_instance, rev)
649 return get_changeset_safe(self.scm_instance, rev)
650
650
651 @property
651 @property
652 def tip(self):
652 def tip(self):
653 return self.get_changeset('tip')
653 return self.get_changeset('tip')
654
654
655 @property
655 @property
656 def author(self):
656 def author(self):
657 return self.tip.author
657 return self.tip.author
658
658
659 @property
659 @property
660 def last_change(self):
660 def last_change(self):
661 return self.scm_instance.last_change
661 return self.scm_instance.last_change
662
662
663 def comments(self, revisions=None):
663 def comments(self, revisions=None):
664 """
664 """
665 Returns comments for this repository grouped by revisions
665 Returns comments for this repository grouped by revisions
666
666
667 :param revisions: filter query by revisions only
667 :param revisions: filter query by revisions only
668 """
668 """
669 cmts = ChangesetComment.query()\
669 cmts = ChangesetComment.query()\
670 .filter(ChangesetComment.repo == self)
670 .filter(ChangesetComment.repo == self)
671 if revisions:
671 if revisions:
672 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
672 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
673 grouped = defaultdict(list)
673 grouped = defaultdict(list)
674 for cmt in cmts.all():
674 for cmt in cmts.all():
675 grouped[cmt.revision].append(cmt)
675 grouped[cmt.revision].append(cmt)
676 return grouped
676 return grouped
677
677
678 #==========================================================================
678 #==========================================================================
679 # SCM CACHE INSTANCE
679 # SCM CACHE INSTANCE
680 #==========================================================================
680 #==========================================================================
681
681
682 @property
682 @property
683 def invalidate(self):
683 def invalidate(self):
684 return CacheInvalidation.invalidate(self.repo_name)
684 return CacheInvalidation.invalidate(self.repo_name)
685
685
686 def set_invalidate(self):
686 def set_invalidate(self):
687 """
687 """
688 set a cache for invalidation for this instance
688 set a cache for invalidation for this instance
689 """
689 """
690 CacheInvalidation.set_invalidate(self.repo_name)
690 CacheInvalidation.set_invalidate(self.repo_name)
691
691
692 @LazyProperty
692 @LazyProperty
693 def scm_instance(self):
693 def scm_instance(self):
694 return self.__get_instance()
694 return self.__get_instance()
695
695
696 @property
696 @property
697 def scm_instance_cached(self):
697 def scm_instance_cached(self):
698 @cache_region('long_term')
698 @cache_region('long_term')
699 def _c(repo_name):
699 def _c(repo_name):
700 return self.__get_instance()
700 return self.__get_instance()
701 rn = self.repo_name
701 rn = self.repo_name
702 log.debug('Getting cached instance of repo')
702 log.debug('Getting cached instance of repo')
703 inv = self.invalidate
703 inv = self.invalidate
704 if inv is not None:
704 if inv is not None:
705 region_invalidate(_c, None, rn)
705 region_invalidate(_c, None, rn)
706 # update our cache
706 # update our cache
707 CacheInvalidation.set_valid(inv.cache_key)
707 CacheInvalidation.set_valid(inv.cache_key)
708 return _c(rn)
708 return _c(rn)
709
709
710 def __get_instance(self):
710 def __get_instance(self):
711 repo_full_path = self.repo_full_path
711 repo_full_path = self.repo_full_path
712 try:
712 try:
713 alias = get_scm(repo_full_path)[0]
713 alias = get_scm(repo_full_path)[0]
714 log.debug('Creating instance of %s repository' % alias)
714 log.debug('Creating instance of %s repository' % alias)
715 backend = get_backend(alias)
715 backend = get_backend(alias)
716 except VCSError:
716 except VCSError:
717 log.error(traceback.format_exc())
717 log.error(traceback.format_exc())
718 log.error('Perhaps this repository is in db and not in '
718 log.error('Perhaps this repository is in db and not in '
719 'filesystem run rescan repositories with '
719 'filesystem run rescan repositories with '
720 '"destroy old data " option from admin panel')
720 '"destroy old data " option from admin panel')
721 return
721 return
722
722
723 if alias == 'hg':
723 if alias == 'hg':
724
724
725 repo = backend(safe_str(repo_full_path), create=False,
725 repo = backend(safe_str(repo_full_path), create=False,
726 baseui=self._ui)
726 baseui=self._ui)
727 # skip hidden web repository
727 # skip hidden web repository
728 if repo._get_hidden():
728 if repo._get_hidden():
729 return
729 return
730 else:
730 else:
731 repo = backend(repo_full_path, create=False)
731 repo = backend(repo_full_path, create=False)
732
732
733 return repo
733 return repo
734
734
735
735
736 class RepoGroup(Base, BaseModel):
736 class RepoGroup(Base, BaseModel):
737 __tablename__ = 'groups'
737 __tablename__ = 'groups'
738 __table_args__ = (
738 __table_args__ = (
739 UniqueConstraint('group_name', 'group_parent_id'),
739 UniqueConstraint('group_name', 'group_parent_id'),
740 CheckConstraint('group_id != group_parent_id'),
740 CheckConstraint('group_id != group_parent_id'),
741 {'extend_existing': True, 'mysql_engine':'InnoDB',
741 {'extend_existing': True, 'mysql_engine':'InnoDB',
742 'mysql_charset': 'utf8'},
742 'mysql_charset': 'utf8'},
743 )
743 )
744 __mapper_args__ = {'order_by': 'group_name'}
744 __mapper_args__ = {'order_by': 'group_name'}
745
745
746 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
746 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
747 group_name = Column("group_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
747 group_name = Column("group_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
748 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
748 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
749 group_description = Column("group_description", String(length=10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
749 group_description = Column("group_description", String(length=10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
750
750
751 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
751 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
752 users_group_to_perm = relationship('UsersGroupRepoGroupToPerm', cascade='all')
752 users_group_to_perm = relationship('UsersGroupRepoGroupToPerm', cascade='all')
753
753
754 parent_group = relationship('RepoGroup', remote_side=group_id)
754 parent_group = relationship('RepoGroup', remote_side=group_id)
755
755
756 def __init__(self, group_name='', parent_group=None):
756 def __init__(self, group_name='', parent_group=None):
757 self.group_name = group_name
757 self.group_name = group_name
758 self.parent_group = parent_group
758 self.parent_group = parent_group
759
759
760 def __unicode__(self):
760 def __unicode__(self):
761 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.group_id,
761 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.group_id,
762 self.group_name)
762 self.group_name)
763
763
764 @classmethod
764 @classmethod
765 def groups_choices(cls):
765 def groups_choices(cls):
766 from webhelpers.html import literal as _literal
766 from webhelpers.html import literal as _literal
767 repo_groups = [('', '')]
767 repo_groups = [('', '')]
768 sep = ' &raquo; '
768 sep = ' &raquo; '
769 _name = lambda k: _literal(sep.join(k))
769 _name = lambda k: _literal(sep.join(k))
770
770
771 repo_groups.extend([(x.group_id, _name(x.full_path_splitted))
771 repo_groups.extend([(x.group_id, _name(x.full_path_splitted))
772 for x in cls.query().all()])
772 for x in cls.query().all()])
773
773
774 repo_groups = sorted(repo_groups, key=lambda t: t[1].split(sep)[0])
774 repo_groups = sorted(repo_groups, key=lambda t: t[1].split(sep)[0])
775 return repo_groups
775 return repo_groups
776
776
777 @classmethod
777 @classmethod
778 def url_sep(cls):
778 def url_sep(cls):
779 return '/'
779 return '/'
780
780
781 @classmethod
781 @classmethod
782 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
782 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
783 if case_insensitive:
783 if case_insensitive:
784 gr = cls.query()\
784 gr = cls.query()\
785 .filter(cls.group_name.ilike(group_name))
785 .filter(cls.group_name.ilike(group_name))
786 else:
786 else:
787 gr = cls.query()\
787 gr = cls.query()\
788 .filter(cls.group_name == group_name)
788 .filter(cls.group_name == group_name)
789 if cache:
789 if cache:
790 gr = gr.options(FromCache(
790 gr = gr.options(FromCache(
791 "sql_cache_short",
791 "sql_cache_short",
792 "get_group_%s" % _hash_key(group_name)
792 "get_group_%s" % _hash_key(group_name)
793 )
793 )
794 )
794 )
795 return gr.scalar()
795 return gr.scalar()
796
796
797 @property
797 @property
798 def parents(self):
798 def parents(self):
799 parents_recursion_limit = 5
799 parents_recursion_limit = 5
800 groups = []
800 groups = []
801 if self.parent_group is None:
801 if self.parent_group is None:
802 return groups
802 return groups
803 cur_gr = self.parent_group
803 cur_gr = self.parent_group
804 groups.insert(0, cur_gr)
804 groups.insert(0, cur_gr)
805 cnt = 0
805 cnt = 0
806 while 1:
806 while 1:
807 cnt += 1
807 cnt += 1
808 gr = getattr(cur_gr, 'parent_group', None)
808 gr = getattr(cur_gr, 'parent_group', None)
809 cur_gr = cur_gr.parent_group
809 cur_gr = cur_gr.parent_group
810 if gr is None:
810 if gr is None:
811 break
811 break
812 if cnt == parents_recursion_limit:
812 if cnt == parents_recursion_limit:
813 # this will prevent accidental infinit loops
813 # this will prevent accidental infinit loops
814 log.error('group nested more than %s' %
814 log.error('group nested more than %s' %
815 parents_recursion_limit)
815 parents_recursion_limit)
816 break
816 break
817
817
818 groups.insert(0, gr)
818 groups.insert(0, gr)
819 return groups
819 return groups
820
820
821 @property
821 @property
822 def children(self):
822 def children(self):
823 return RepoGroup.query().filter(RepoGroup.parent_group == self)
823 return RepoGroup.query().filter(RepoGroup.parent_group == self)
824
824
825 @property
825 @property
826 def name(self):
826 def name(self):
827 return self.group_name.split(RepoGroup.url_sep())[-1]
827 return self.group_name.split(RepoGroup.url_sep())[-1]
828
828
829 @property
829 @property
830 def full_path(self):
830 def full_path(self):
831 return self.group_name
831 return self.group_name
832
832
833 @property
833 @property
834 def full_path_splitted(self):
834 def full_path_splitted(self):
835 return self.group_name.split(RepoGroup.url_sep())
835 return self.group_name.split(RepoGroup.url_sep())
836
836
837 @property
837 @property
838 def repositories(self):
838 def repositories(self):
839 return Repository.query()\
839 return Repository.query()\
840 .filter(Repository.group == self)\
840 .filter(Repository.group == self)\
841 .order_by(Repository.repo_name)
841 .order_by(Repository.repo_name)
842
842
843 @property
843 @property
844 def repositories_recursive_count(self):
844 def repositories_recursive_count(self):
845 cnt = self.repositories.count()
845 cnt = self.repositories.count()
846
846
847 def children_count(group):
847 def children_count(group):
848 cnt = 0
848 cnt = 0
849 for child in group.children:
849 for child in group.children:
850 cnt += child.repositories.count()
850 cnt += child.repositories.count()
851 cnt += children_count(child)
851 cnt += children_count(child)
852 return cnt
852 return cnt
853
853
854 return cnt + children_count(self)
854 return cnt + children_count(self)
855
855
856 def get_new_name(self, group_name):
856 def get_new_name(self, group_name):
857 """
857 """
858 returns new full group name based on parent and new name
858 returns new full group name based on parent and new name
859
859
860 :param group_name:
860 :param group_name:
861 """
861 """
862 path_prefix = (self.parent_group.full_path_splitted if
862 path_prefix = (self.parent_group.full_path_splitted if
863 self.parent_group else [])
863 self.parent_group else [])
864 return RepoGroup.url_sep().join(path_prefix + [group_name])
864 return RepoGroup.url_sep().join(path_prefix + [group_name])
865
865
866
866
867 class Permission(Base, BaseModel):
867 class Permission(Base, BaseModel):
868 __tablename__ = 'permissions'
868 __tablename__ = 'permissions'
869 __table_args__ = (
869 __table_args__ = (
870 {'extend_existing': True, 'mysql_engine':'InnoDB',
870 {'extend_existing': True, 'mysql_engine':'InnoDB',
871 'mysql_charset': 'utf8'},
871 'mysql_charset': 'utf8'},
872 )
872 )
873 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
873 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
874 permission_name = Column("permission_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
874 permission_name = Column("permission_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
875 permission_longname = Column("permission_longname", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
875 permission_longname = Column("permission_longname", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
876
876
877 def __unicode__(self):
877 def __unicode__(self):
878 return u"<%s('%s:%s')>" % (
878 return u"<%s('%s:%s')>" % (
879 self.__class__.__name__, self.permission_id, self.permission_name
879 self.__class__.__name__, self.permission_id, self.permission_name
880 )
880 )
881
881
882 @classmethod
882 @classmethod
883 def get_by_key(cls, key):
883 def get_by_key(cls, key):
884 return cls.query().filter(cls.permission_name == key).scalar()
884 return cls.query().filter(cls.permission_name == key).scalar()
885
885
886 @classmethod
886 @classmethod
887 def get_default_perms(cls, default_user_id):
887 def get_default_perms(cls, default_user_id):
888 q = Session.query(UserRepoToPerm, Repository, cls)\
888 q = Session.query(UserRepoToPerm, Repository, cls)\
889 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
889 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
890 .join((cls, UserRepoToPerm.permission_id == cls.permission_id))\
890 .join((cls, UserRepoToPerm.permission_id == cls.permission_id))\
891 .filter(UserRepoToPerm.user_id == default_user_id)
891 .filter(UserRepoToPerm.user_id == default_user_id)
892
892
893 return q.all()
893 return q.all()
894
894
895 @classmethod
895 @classmethod
896 def get_default_group_perms(cls, default_user_id):
896 def get_default_group_perms(cls, default_user_id):
897 q = Session.query(UserRepoGroupToPerm, RepoGroup, cls)\
897 q = Session.query(UserRepoGroupToPerm, RepoGroup, cls)\
898 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
898 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
899 .join((cls, UserRepoGroupToPerm.permission_id == cls.permission_id))\
899 .join((cls, UserRepoGroupToPerm.permission_id == cls.permission_id))\
900 .filter(UserRepoGroupToPerm.user_id == default_user_id)
900 .filter(UserRepoGroupToPerm.user_id == default_user_id)
901
901
902 return q.all()
902 return q.all()
903
903
904
904
905 class UserRepoToPerm(Base, BaseModel):
905 class UserRepoToPerm(Base, BaseModel):
906 __tablename__ = 'repo_to_perm'
906 __tablename__ = 'repo_to_perm'
907 __table_args__ = (
907 __table_args__ = (
908 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
908 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
909 {'extend_existing': True, 'mysql_engine':'InnoDB',
909 {'extend_existing': True, 'mysql_engine':'InnoDB',
910 'mysql_charset': 'utf8'}
910 'mysql_charset': 'utf8'}
911 )
911 )
912 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
912 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
913 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
913 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
914 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
914 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
915 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
915 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
916
916
917 user = relationship('User')
917 user = relationship('User')
918 repository = relationship('Repository')
918 repository = relationship('Repository')
919 permission = relationship('Permission')
919 permission = relationship('Permission')
920
920
921 @classmethod
921 @classmethod
922 def create(cls, user, repository, permission):
922 def create(cls, user, repository, permission):
923 n = cls()
923 n = cls()
924 n.user = user
924 n.user = user
925 n.repository = repository
925 n.repository = repository
926 n.permission = permission
926 n.permission = permission
927 Session.add(n)
927 Session.add(n)
928 return n
928 return n
929
929
930 def __unicode__(self):
930 def __unicode__(self):
931 return u'<user:%s => %s >' % (self.user, self.repository)
931 return u'<user:%s => %s >' % (self.user, self.repository)
932
932
933
933
934 class UserToPerm(Base, BaseModel):
934 class UserToPerm(Base, BaseModel):
935 __tablename__ = 'user_to_perm'
935 __tablename__ = 'user_to_perm'
936 __table_args__ = (
936 __table_args__ = (
937 UniqueConstraint('user_id', 'permission_id'),
937 UniqueConstraint('user_id', 'permission_id'),
938 {'extend_existing': True, 'mysql_engine':'InnoDB',
938 {'extend_existing': True, 'mysql_engine':'InnoDB',
939 'mysql_charset': 'utf8'}
939 'mysql_charset': 'utf8'}
940 )
940 )
941 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
941 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
942 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
942 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
943 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
943 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
944
944
945 user = relationship('User')
945 user = relationship('User')
946 permission = relationship('Permission', lazy='joined')
946 permission = relationship('Permission', lazy='joined')
947
947
948
948
949 class UsersGroupRepoToPerm(Base, BaseModel):
949 class UsersGroupRepoToPerm(Base, BaseModel):
950 __tablename__ = 'users_group_repo_to_perm'
950 __tablename__ = 'users_group_repo_to_perm'
951 __table_args__ = (
951 __table_args__ = (
952 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
952 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
953 {'extend_existing': True, 'mysql_engine':'InnoDB',
953 {'extend_existing': True, 'mysql_engine':'InnoDB',
954 'mysql_charset': 'utf8'}
954 'mysql_charset': 'utf8'}
955 )
955 )
956 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
956 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
957 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
957 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
958 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
958 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
959 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
959 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
960
960
961 users_group = relationship('UsersGroup')
961 users_group = relationship('UsersGroup')
962 permission = relationship('Permission')
962 permission = relationship('Permission')
963 repository = relationship('Repository')
963 repository = relationship('Repository')
964
964
965 @classmethod
965 @classmethod
966 def create(cls, users_group, repository, permission):
966 def create(cls, users_group, repository, permission):
967 n = cls()
967 n = cls()
968 n.users_group = users_group
968 n.users_group = users_group
969 n.repository = repository
969 n.repository = repository
970 n.permission = permission
970 n.permission = permission
971 Session.add(n)
971 Session.add(n)
972 return n
972 return n
973
973
974 def __unicode__(self):
974 def __unicode__(self):
975 return u'<userGroup:%s => %s >' % (self.users_group, self.repository)
975 return u'<userGroup:%s => %s >' % (self.users_group, self.repository)
976
976
977
977
978 class UsersGroupToPerm(Base, BaseModel):
978 class UsersGroupToPerm(Base, BaseModel):
979 __tablename__ = 'users_group_to_perm'
979 __tablename__ = 'users_group_to_perm'
980 __table_args__ = (
980 __table_args__ = (
981 UniqueConstraint('users_group_id', 'permission_id',),
981 UniqueConstraint('users_group_id', 'permission_id',),
982 {'extend_existing': True, 'mysql_engine':'InnoDB',
982 {'extend_existing': True, 'mysql_engine':'InnoDB',
983 'mysql_charset': 'utf8'}
983 'mysql_charset': 'utf8'}
984 )
984 )
985 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
985 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
986 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
986 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
987 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
987 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
988
988
989 users_group = relationship('UsersGroup')
989 users_group = relationship('UsersGroup')
990 permission = relationship('Permission')
990 permission = relationship('Permission')
991
991
992
992
993 class UserRepoGroupToPerm(Base, BaseModel):
993 class UserRepoGroupToPerm(Base, BaseModel):
994 __tablename__ = 'user_repo_group_to_perm'
994 __tablename__ = 'user_repo_group_to_perm'
995 __table_args__ = (
995 __table_args__ = (
996 UniqueConstraint('user_id', 'group_id', 'permission_id'),
996 UniqueConstraint('user_id', 'group_id', 'permission_id'),
997 {'extend_existing': True, 'mysql_engine':'InnoDB',
997 {'extend_existing': True, 'mysql_engine':'InnoDB',
998 'mysql_charset': 'utf8'}
998 'mysql_charset': 'utf8'}
999 )
999 )
1000
1000
1001 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1001 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1002 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1002 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1003 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
1003 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
1004 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1004 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1005
1005
1006 user = relationship('User')
1006 user = relationship('User')
1007 group = relationship('RepoGroup')
1007 group = relationship('RepoGroup')
1008 permission = relationship('Permission')
1008 permission = relationship('Permission')
1009
1009
1010
1010
1011 class UsersGroupRepoGroupToPerm(Base, BaseModel):
1011 class UsersGroupRepoGroupToPerm(Base, BaseModel):
1012 __tablename__ = 'users_group_repo_group_to_perm'
1012 __tablename__ = 'users_group_repo_group_to_perm'
1013 __table_args__ = (
1013 __table_args__ = (
1014 UniqueConstraint('users_group_id', 'group_id'),
1014 UniqueConstraint('users_group_id', 'group_id'),
1015 {'extend_existing': True, 'mysql_engine':'InnoDB',
1015 {'extend_existing': True, 'mysql_engine':'InnoDB',
1016 'mysql_charset': 'utf8'}
1016 'mysql_charset': 'utf8'}
1017 )
1017 )
1018
1018
1019 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)
1019 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)
1020 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1020 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1021 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
1021 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
1022 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1022 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1023
1023
1024 users_group = relationship('UsersGroup')
1024 users_group = relationship('UsersGroup')
1025 permission = relationship('Permission')
1025 permission = relationship('Permission')
1026 group = relationship('RepoGroup')
1026 group = relationship('RepoGroup')
1027
1027
1028
1028
1029 class Statistics(Base, BaseModel):
1029 class Statistics(Base, BaseModel):
1030 __tablename__ = 'statistics'
1030 __tablename__ = 'statistics'
1031 __table_args__ = (
1031 __table_args__ = (
1032 UniqueConstraint('repository_id'),
1032 UniqueConstraint('repository_id'),
1033 {'extend_existing': True, 'mysql_engine':'InnoDB',
1033 {'extend_existing': True, 'mysql_engine':'InnoDB',
1034 'mysql_charset': 'utf8'}
1034 'mysql_charset': 'utf8'}
1035 )
1035 )
1036 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1036 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1037 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
1037 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
1038 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
1038 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
1039 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
1039 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
1040 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
1040 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
1041 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
1041 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
1042
1042
1043 repository = relationship('Repository', single_parent=True)
1043 repository = relationship('Repository', single_parent=True)
1044
1044
1045
1045
1046 class UserFollowing(Base, BaseModel):
1046 class UserFollowing(Base, BaseModel):
1047 __tablename__ = 'user_followings'
1047 __tablename__ = 'user_followings'
1048 __table_args__ = (
1048 __table_args__ = (
1049 UniqueConstraint('user_id', 'follows_repository_id'),
1049 UniqueConstraint('user_id', 'follows_repository_id'),
1050 UniqueConstraint('user_id', 'follows_user_id'),
1050 UniqueConstraint('user_id', 'follows_user_id'),
1051 {'extend_existing': True, 'mysql_engine':'InnoDB',
1051 {'extend_existing': True, 'mysql_engine':'InnoDB',
1052 'mysql_charset': 'utf8'}
1052 'mysql_charset': 'utf8'}
1053 )
1053 )
1054
1054
1055 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1055 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1056 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1056 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1057 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
1057 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
1058 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1058 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1059 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
1059 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
1060
1060
1061 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
1061 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
1062
1062
1063 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
1063 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
1064 follows_repository = relationship('Repository', order_by='Repository.repo_name')
1064 follows_repository = relationship('Repository', order_by='Repository.repo_name')
1065
1065
1066 @classmethod
1066 @classmethod
1067 def get_repo_followers(cls, repo_id):
1067 def get_repo_followers(cls, repo_id):
1068 return cls.query().filter(cls.follows_repo_id == repo_id)
1068 return cls.query().filter(cls.follows_repo_id == repo_id)
1069
1069
1070
1070
1071 class CacheInvalidation(Base, BaseModel):
1071 class CacheInvalidation(Base, BaseModel):
1072 __tablename__ = 'cache_invalidation'
1072 __tablename__ = 'cache_invalidation'
1073 __table_args__ = (
1073 __table_args__ = (
1074 UniqueConstraint('cache_key'),
1074 UniqueConstraint('cache_key'),
1075 {'extend_existing': True, 'mysql_engine':'InnoDB',
1075 {'extend_existing': True, 'mysql_engine':'InnoDB',
1076 'mysql_charset': 'utf8'},
1076 'mysql_charset': 'utf8'},
1077 )
1077 )
1078 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1078 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1079 cache_key = Column("cache_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1079 cache_key = Column("cache_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1080 cache_args = Column("cache_args", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1080 cache_args = Column("cache_args", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1081 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
1081 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
1082
1082
1083 def __init__(self, cache_key, cache_args=''):
1083 def __init__(self, cache_key, cache_args=''):
1084 self.cache_key = cache_key
1084 self.cache_key = cache_key
1085 self.cache_args = cache_args
1085 self.cache_args = cache_args
1086 self.cache_active = False
1086 self.cache_active = False
1087
1087
1088 def __unicode__(self):
1088 def __unicode__(self):
1089 return u"<%s('%s:%s')>" % (self.__class__.__name__,
1089 return u"<%s('%s:%s')>" % (self.__class__.__name__,
1090 self.cache_id, self.cache_key)
1090 self.cache_id, self.cache_key)
1091 @classmethod
1091 @classmethod
1092 def clear_cache(cls):
1092 def clear_cache(cls):
1093 cls.query().delete()
1093 cls.query().delete()
1094
1094
1095 @classmethod
1095 @classmethod
1096 def _get_key(cls, key):
1096 def _get_key(cls, key):
1097 """
1097 """
1098 Wrapper for generating a key, together with a prefix
1098 Wrapper for generating a key, together with a prefix
1099
1099
1100 :param key:
1100 :param key:
1101 """
1101 """
1102 import rhodecode
1102 import rhodecode
1103 prefix = ''
1103 prefix = ''
1104 iid = rhodecode.CONFIG.get('instance_id')
1104 iid = rhodecode.CONFIG.get('instance_id')
1105 if iid:
1105 if iid:
1106 prefix = iid
1106 prefix = iid
1107 return "%s%s" % (prefix, key), prefix, key.rstrip('_README')
1107 return "%s%s" % (prefix, key), prefix, key.rstrip('_README')
1108
1108
1109 @classmethod
1109 @classmethod
1110 def get_by_key(cls, key):
1110 def get_by_key(cls, key):
1111 return cls.query().filter(cls.cache_key == key).scalar()
1111 return cls.query().filter(cls.cache_key == key).scalar()
1112
1112
1113 @classmethod
1113 @classmethod
1114 def _get_or_create_key(cls, key, prefix, org_key):
1114 def _get_or_create_key(cls, key, prefix, org_key):
1115 inv_obj = Session.query(cls).filter(cls.cache_key == key).scalar()
1115 inv_obj = Session.query(cls).filter(cls.cache_key == key).scalar()
1116 if not inv_obj:
1116 if not inv_obj:
1117 try:
1117 try:
1118 inv_obj = CacheInvalidation(key, org_key)
1118 inv_obj = CacheInvalidation(key, org_key)
1119 Session.add(inv_obj)
1119 Session.add(inv_obj)
1120 Session.commit()
1120 Session.commit()
1121 except Exception:
1121 except Exception:
1122 log.error(traceback.format_exc())
1122 log.error(traceback.format_exc())
1123 Session.rollback()
1123 Session.rollback()
1124 return inv_obj
1124 return inv_obj
1125
1125
1126 @classmethod
1126 @classmethod
1127 def invalidate(cls, key):
1127 def invalidate(cls, key):
1128 """
1128 """
1129 Returns Invalidation object if this given key should be invalidated
1129 Returns Invalidation object if this given key should be invalidated
1130 None otherwise. `cache_active = False` means that this cache
1130 None otherwise. `cache_active = False` means that this cache
1131 state is not valid and needs to be invalidated
1131 state is not valid and needs to be invalidated
1132
1132
1133 :param key:
1133 :param key:
1134 """
1134 """
1135
1135
1136 key, _prefix, _org_key = cls._get_key(key)
1136 key, _prefix, _org_key = cls._get_key(key)
1137 inv = cls._get_or_create_key(key, _prefix, _org_key)
1137 inv = cls._get_or_create_key(key, _prefix, _org_key)
1138
1138
1139 if inv and inv.cache_active is False:
1139 if inv and inv.cache_active is False:
1140 return inv
1140 return inv
1141
1141
1142 @classmethod
1142 @classmethod
1143 def set_invalidate(cls, key):
1143 def set_invalidate(cls, key):
1144 """
1144 """
1145 Mark this Cache key for invalidation
1145 Mark this Cache key for invalidation
1146
1146
1147 :param key:
1147 :param key:
1148 """
1148 """
1149
1149
1150 key, _prefix, _org_key = cls._get_key(key)
1150 key, _prefix, _org_key = cls._get_key(key)
1151 inv_objs = Session.query(cls).filter(cls.cache_args == _org_key).all()
1151 inv_objs = Session.query(cls).filter(cls.cache_args == _org_key).all()
1152 log.debug('marking %s key[s] %s for invalidation' % (len(inv_objs),
1152 log.debug('marking %s key[s] %s for invalidation' % (len(inv_objs),
1153 _org_key))
1153 _org_key))
1154 try:
1154 try:
1155 for inv_obj in inv_objs:
1155 for inv_obj in inv_objs:
1156 if inv_obj:
1156 if inv_obj:
1157 inv_obj.cache_active = False
1157 inv_obj.cache_active = False
1158
1158
1159 Session.add(inv_obj)
1159 Session.add(inv_obj)
1160 Session.commit()
1160 Session.commit()
1161 except Exception:
1161 except Exception:
1162 log.error(traceback.format_exc())
1162 log.error(traceback.format_exc())
1163 Session.rollback()
1163 Session.rollback()
1164
1164
1165 @classmethod
1165 @classmethod
1166 def set_valid(cls, key):
1166 def set_valid(cls, key):
1167 """
1167 """
1168 Mark this cache key as active and currently cached
1168 Mark this cache key as active and currently cached
1169
1169
1170 :param key:
1170 :param key:
1171 """
1171 """
1172 inv_obj = cls.get_by_key(key)
1172 inv_obj = cls.get_by_key(key)
1173 inv_obj.cache_active = True
1173 inv_obj.cache_active = True
1174 Session.add(inv_obj)
1174 Session.add(inv_obj)
1175 Session.commit()
1175 Session.commit()
1176
1176
1177
1177
1178 class ChangesetComment(Base, BaseModel):
1178 class ChangesetComment(Base, BaseModel):
1179 __tablename__ = 'changeset_comments'
1179 __tablename__ = 'changeset_comments'
1180 __table_args__ = (
1180 __table_args__ = (
1181 {'extend_existing': True, 'mysql_engine':'InnoDB',
1181 {'extend_existing': True, 'mysql_engine':'InnoDB',
1182 'mysql_charset': 'utf8'},
1182 'mysql_charset': 'utf8'},
1183 )
1183 )
1184 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
1184 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
1185 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1185 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1186 revision = Column('revision', String(40), nullable=False)
1186 revision = Column('revision', String(40), nullable=False)
1187 line_no = Column('line_no', Unicode(10), nullable=True)
1187 line_no = Column('line_no', Unicode(10), nullable=True)
1188 f_path = Column('f_path', Unicode(1000), nullable=True)
1188 f_path = Column('f_path', Unicode(1000), nullable=True)
1189 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
1189 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
1190 text = Column('text', Unicode(25000), nullable=False)
1190 text = Column('text', Unicode(25000), nullable=False)
1191 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
1191 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
1192
1192
1193 author = relationship('User', lazy='joined')
1193 author = relationship('User', lazy='joined')
1194 repo = relationship('Repository')
1194 repo = relationship('Repository')
1195
1195
1196 @classmethod
1196 @classmethod
1197 def get_users(cls, revision):
1197 def get_users(cls, revision):
1198 """
1198 """
1199 Returns user associated with this changesetComment. ie those
1199 Returns user associated with this changesetComment. ie those
1200 who actually commented
1200 who actually commented
1201
1201
1202 :param cls:
1202 :param cls:
1203 :param revision:
1203 :param revision:
1204 """
1204 """
1205 return Session.query(User)\
1205 return Session.query(User)\
1206 .filter(cls.revision == revision)\
1206 .filter(cls.revision == revision)\
1207 .join(ChangesetComment.author).all()
1207 .join(ChangesetComment.author).all()
1208
1208
1209
1209
1210 class Notification(Base, BaseModel):
1210 class Notification(Base, BaseModel):
1211 __tablename__ = 'notifications'
1211 __tablename__ = 'notifications'
1212 __table_args__ = (
1212 __table_args__ = (
1213 {'extend_existing': True, 'mysql_engine':'InnoDB',
1213 {'extend_existing': True, 'mysql_engine':'InnoDB',
1214 'mysql_charset': 'utf8'},
1214 'mysql_charset': 'utf8'},
1215 )
1215 )
1216
1216
1217 TYPE_CHANGESET_COMMENT = u'cs_comment'
1217 TYPE_CHANGESET_COMMENT = u'cs_comment'
1218 TYPE_MESSAGE = u'message'
1218 TYPE_MESSAGE = u'message'
1219 TYPE_MENTION = u'mention'
1219 TYPE_MENTION = u'mention'
1220 TYPE_REGISTRATION = u'registration'
1220 TYPE_REGISTRATION = u'registration'
1221
1221
1222 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
1222 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
1223 subject = Column('subject', Unicode(512), nullable=True)
1223 subject = Column('subject', Unicode(512), nullable=True)
1224 body = Column('body', Unicode(50000), nullable=True)
1224 body = Column('body', Unicode(50000), nullable=True)
1225 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
1225 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
1226 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1226 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1227 type_ = Column('type', Unicode(256))
1227 type_ = Column('type', Unicode(256))
1228
1228
1229 created_by_user = relationship('User')
1229 created_by_user = relationship('User')
1230 notifications_to_users = relationship('UserNotification', lazy='joined',
1230 notifications_to_users = relationship('UserNotification', lazy='joined',
1231 cascade="all, delete, delete-orphan")
1231 cascade="all, delete, delete-orphan")
1232
1232
1233 @property
1233 @property
1234 def recipients(self):
1234 def recipients(self):
1235 return [x.user for x in UserNotification.query()\
1235 return [x.user for x in UserNotification.query()\
1236 .filter(UserNotification.notification == self).all()]
1236 .filter(UserNotification.notification == self)\
1237 .order_by(UserNotification.user).all()]
1237
1238
1238 @classmethod
1239 @classmethod
1239 def create(cls, created_by, subject, body, recipients, type_=None):
1240 def create(cls, created_by, subject, body, recipients, type_=None):
1240 if type_ is None:
1241 if type_ is None:
1241 type_ = Notification.TYPE_MESSAGE
1242 type_ = Notification.TYPE_MESSAGE
1242
1243
1243 notification = cls()
1244 notification = cls()
1244 notification.created_by_user = created_by
1245 notification.created_by_user = created_by
1245 notification.subject = subject
1246 notification.subject = subject
1246 notification.body = body
1247 notification.body = body
1247 notification.type_ = type_
1248 notification.type_ = type_
1248 notification.created_on = datetime.datetime.now()
1249 notification.created_on = datetime.datetime.now()
1249
1250
1250 for u in recipients:
1251 for u in recipients:
1251 assoc = UserNotification()
1252 assoc = UserNotification()
1252 assoc.notification = notification
1253 assoc.notification = notification
1253 u.notifications.append(assoc)
1254 u.notifications.append(assoc)
1254 Session.add(notification)
1255 Session.add(notification)
1255 return notification
1256 return notification
1256
1257
1257 @property
1258 @property
1258 def description(self):
1259 def description(self):
1259 from rhodecode.model.notification import NotificationModel
1260 from rhodecode.model.notification import NotificationModel
1260 return NotificationModel().make_description(self)
1261 return NotificationModel().make_description(self)
1261
1262
1262
1263
1263 class UserNotification(Base, BaseModel):
1264 class UserNotification(Base, BaseModel):
1264 __tablename__ = 'user_to_notification'
1265 __tablename__ = 'user_to_notification'
1265 __table_args__ = (
1266 __table_args__ = (
1266 UniqueConstraint('user_id', 'notification_id'),
1267 UniqueConstraint('user_id', 'notification_id'),
1267 {'extend_existing': True, 'mysql_engine':'InnoDB',
1268 {'extend_existing': True, 'mysql_engine':'InnoDB',
1268 'mysql_charset': 'utf8'}
1269 'mysql_charset': 'utf8'}
1269 )
1270 )
1270 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
1271 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
1271 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
1272 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
1272 read = Column('read', Boolean, default=False)
1273 read = Column('read', Boolean, default=False)
1273 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
1274 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
1274
1275
1275 user = relationship('User', lazy="joined")
1276 user = relationship('User', lazy="joined")
1276 notification = relationship('Notification', lazy="joined",
1277 notification = relationship('Notification', lazy="joined",
1277 order_by=lambda: Notification.created_on.desc(),)
1278 order_by=lambda: Notification.created_on.desc(),)
1278
1279
1279 def mark_as_read(self):
1280 def mark_as_read(self):
1280 self.read = True
1281 self.read = True
1281 Session.add(self)
1282 Session.add(self)
1282
1283
1283
1284
1284 class DbMigrateVersion(Base, BaseModel):
1285 class DbMigrateVersion(Base, BaseModel):
1285 __tablename__ = 'db_migrate_version'
1286 __tablename__ = 'db_migrate_version'
1286 __table_args__ = (
1287 __table_args__ = (
1287 {'extend_existing': True, 'mysql_engine':'InnoDB',
1288 {'extend_existing': True, 'mysql_engine':'InnoDB',
1288 'mysql_charset': 'utf8'},
1289 'mysql_charset': 'utf8'},
1289 )
1290 )
1290 repository_id = Column('repository_id', String(250), primary_key=True)
1291 repository_id = Column('repository_id', String(250), primary_key=True)
1291 repository_path = Column('repository_path', Text)
1292 repository_path = Column('repository_path', Text)
1292 version = Column('version', Integer)
1293 version = Column('version', Integer)
@@ -1,592 +1,590 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.model.user
3 rhodecode.model.user
4 ~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~
5
5
6 users model for RhodeCode
6 users model for RhodeCode
7
7
8 :created_on: Apr 9, 2010
8 :created_on: Apr 9, 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 pylons import url
29 from pylons import url
30 from pylons.i18n.translation import _
30 from pylons.i18n.translation import _
31
31
32 from rhodecode.lib.utils2 import safe_unicode, generate_api_key
32 from rhodecode.lib.utils2 import safe_unicode, generate_api_key
33 from rhodecode.lib.caching_query import FromCache
33 from rhodecode.lib.caching_query import FromCache
34
34
35 from rhodecode.model import BaseModel
35 from rhodecode.model import BaseModel
36 from rhodecode.model.db import User, UserRepoToPerm, Repository, Permission, \
36 from rhodecode.model.db import User, UserRepoToPerm, Repository, Permission, \
37 UserToPerm, UsersGroupRepoToPerm, UsersGroupToPerm, UsersGroupMember, \
37 UserToPerm, UsersGroupRepoToPerm, UsersGroupToPerm, UsersGroupMember, \
38 Notification, RepoGroup, UserRepoGroupToPerm, UsersGroup,\
38 Notification, RepoGroup, UserRepoGroupToPerm, UsersGroup,\
39 UsersGroupRepoGroupToPerm
39 UsersGroupRepoGroupToPerm
40 from rhodecode.lib.exceptions import DefaultUserException, \
40 from rhodecode.lib.exceptions import DefaultUserException, \
41 UserOwnsReposException
41 UserOwnsReposException
42
42
43 from sqlalchemy.exc import DatabaseError
43 from sqlalchemy.exc import DatabaseError
44
44
45 from sqlalchemy.orm import joinedload
45 from sqlalchemy.orm import joinedload
46
46
47 log = logging.getLogger(__name__)
47 log = logging.getLogger(__name__)
48
48
49
49
50 PERM_WEIGHTS = {
50 PERM_WEIGHTS = {
51 'repository.none': 0,
51 'repository.none': 0,
52 'repository.read': 1,
52 'repository.read': 1,
53 'repository.write': 3,
53 'repository.write': 3,
54 'repository.admin': 4,
54 'repository.admin': 4,
55 'group.none': 0,
55 'group.none': 0,
56 'group.read': 1,
56 'group.read': 1,
57 'group.write': 3,
57 'group.write': 3,
58 'group.admin': 4,
58 'group.admin': 4,
59 }
59 }
60
60
61
61
62 class UserModel(BaseModel):
62 class UserModel(BaseModel):
63
63
64 def __get_user(self, user):
64 def __get_user(self, user):
65 return self._get_instance(User, user, callback=User.get_by_username)
65 return self._get_instance(User, user, callback=User.get_by_username)
66
66
67 def __get_perm(self, permission):
67 def __get_perm(self, permission):
68 return self._get_instance(Permission, permission,
68 return self._get_instance(Permission, permission,
69 callback=Permission.get_by_key)
69 callback=Permission.get_by_key)
70
70
71 def get(self, user_id, cache=False):
71 def get(self, user_id, cache=False):
72 user = self.sa.query(User)
72 user = self.sa.query(User)
73 if cache:
73 if cache:
74 user = user.options(FromCache("sql_cache_short",
74 user = user.options(FromCache("sql_cache_short",
75 "get_user_%s" % user_id))
75 "get_user_%s" % user_id))
76 return user.get(user_id)
76 return user.get(user_id)
77
77
78 def get_user(self, user):
78 def get_user(self, user):
79 return self.__get_user(user)
79 return self.__get_user(user)
80
80
81 def get_by_username(self, username, cache=False, case_insensitive=False):
81 def get_by_username(self, username, cache=False, case_insensitive=False):
82
82
83 if case_insensitive:
83 if case_insensitive:
84 user = self.sa.query(User).filter(User.username.ilike(username))
84 user = self.sa.query(User).filter(User.username.ilike(username))
85 else:
85 else:
86 user = self.sa.query(User)\
86 user = self.sa.query(User)\
87 .filter(User.username == username)
87 .filter(User.username == username)
88 if cache:
88 if cache:
89 user = user.options(FromCache("sql_cache_short",
89 user = user.options(FromCache("sql_cache_short",
90 "get_user_%s" % username))
90 "get_user_%s" % username))
91 return user.scalar()
91 return user.scalar()
92
92
93 def get_by_api_key(self, api_key, cache=False):
93 def get_by_api_key(self, api_key, cache=False):
94 return User.get_by_api_key(api_key, cache)
94 return User.get_by_api_key(api_key, cache)
95
95
96 def create(self, form_data):
96 def create(self, form_data):
97 try:
97 try:
98 new_user = User()
98 new_user = User()
99 for k, v in form_data.items():
99 for k, v in form_data.items():
100 setattr(new_user, k, v)
100 setattr(new_user, k, v)
101
101
102 new_user.api_key = generate_api_key(form_data['username'])
102 new_user.api_key = generate_api_key(form_data['username'])
103 self.sa.add(new_user)
103 self.sa.add(new_user)
104 return new_user
104 return new_user
105 except:
105 except:
106 log.error(traceback.format_exc())
106 log.error(traceback.format_exc())
107 raise
107 raise
108
108
109 def create_or_update(self, username, password, email, name, lastname,
109 def create_or_update(self, username, password, email, name, lastname,
110 active=True, admin=False, ldap_dn=None):
110 active=True, admin=False, ldap_dn=None):
111 """
111 """
112 Creates a new instance if not found, or updates current one
112 Creates a new instance if not found, or updates current one
113
113
114 :param username:
114 :param username:
115 :param password:
115 :param password:
116 :param email:
116 :param email:
117 :param active:
117 :param active:
118 :param name:
118 :param name:
119 :param lastname:
119 :param lastname:
120 :param active:
120 :param active:
121 :param admin:
121 :param admin:
122 :param ldap_dn:
122 :param ldap_dn:
123 """
123 """
124
124
125 from rhodecode.lib.auth import get_crypt_password
125 from rhodecode.lib.auth import get_crypt_password
126
126
127 log.debug('Checking for %s account in RhodeCode database' % username)
127 log.debug('Checking for %s account in RhodeCode database' % username)
128 user = User.get_by_username(username, case_insensitive=True)
128 user = User.get_by_username(username, case_insensitive=True)
129 if user is None:
129 if user is None:
130 log.debug('creating new user %s' % username)
130 log.debug('creating new user %s' % username)
131 new_user = User()
131 new_user = User()
132 else:
132 else:
133 log.debug('updating user %s' % username)
133 log.debug('updating user %s' % username)
134 new_user = user
134 new_user = user
135
135
136 try:
136 try:
137 new_user.username = username
137 new_user.username = username
138 new_user.admin = admin
138 new_user.admin = admin
139 new_user.password = get_crypt_password(password)
139 new_user.password = get_crypt_password(password)
140 new_user.api_key = generate_api_key(username)
140 new_user.api_key = generate_api_key(username)
141 new_user.email = email
141 new_user.email = email
142 new_user.active = active
142 new_user.active = active
143 new_user.ldap_dn = safe_unicode(ldap_dn) if ldap_dn else None
143 new_user.ldap_dn = safe_unicode(ldap_dn) if ldap_dn else None
144 new_user.name = name
144 new_user.name = name
145 new_user.lastname = lastname
145 new_user.lastname = lastname
146 self.sa.add(new_user)
146 self.sa.add(new_user)
147 return new_user
147 return new_user
148 except (DatabaseError,):
148 except (DatabaseError,):
149 log.error(traceback.format_exc())
149 log.error(traceback.format_exc())
150 raise
150 raise
151
151
152 def create_for_container_auth(self, username, attrs):
152 def create_for_container_auth(self, username, attrs):
153 """
153 """
154 Creates the given user if it's not already in the database
154 Creates the given user if it's not already in the database
155
155
156 :param username:
156 :param username:
157 :param attrs:
157 :param attrs:
158 """
158 """
159 if self.get_by_username(username, case_insensitive=True) is None:
159 if self.get_by_username(username, case_insensitive=True) is None:
160
160
161 # autogenerate email for container account without one
161 # autogenerate email for container account without one
162 generate_email = lambda usr: '%s@container_auth.account' % usr
162 generate_email = lambda usr: '%s@container_auth.account' % usr
163
163
164 try:
164 try:
165 new_user = User()
165 new_user = User()
166 new_user.username = username
166 new_user.username = username
167 new_user.password = None
167 new_user.password = None
168 new_user.api_key = generate_api_key(username)
168 new_user.api_key = generate_api_key(username)
169 new_user.email = attrs['email']
169 new_user.email = attrs['email']
170 new_user.active = attrs.get('active', True)
170 new_user.active = attrs.get('active', True)
171 new_user.name = attrs['name'] or generate_email(username)
171 new_user.name = attrs['name'] or generate_email(username)
172 new_user.lastname = attrs['lastname']
172 new_user.lastname = attrs['lastname']
173
173
174 self.sa.add(new_user)
174 self.sa.add(new_user)
175 return new_user
175 return new_user
176 except (DatabaseError,):
176 except (DatabaseError,):
177 log.error(traceback.format_exc())
177 log.error(traceback.format_exc())
178 self.sa.rollback()
178 self.sa.rollback()
179 raise
179 raise
180 log.debug('User %s already exists. Skipping creation of account'
180 log.debug('User %s already exists. Skipping creation of account'
181 ' for container auth.', username)
181 ' for container auth.', username)
182 return None
182 return None
183
183
184 def create_ldap(self, username, password, user_dn, attrs):
184 def create_ldap(self, username, password, user_dn, attrs):
185 """
185 """
186 Checks if user is in database, if not creates this user marked
186 Checks if user is in database, if not creates this user marked
187 as ldap user
187 as ldap user
188
188
189 :param username:
189 :param username:
190 :param password:
190 :param password:
191 :param user_dn:
191 :param user_dn:
192 :param attrs:
192 :param attrs:
193 """
193 """
194 from rhodecode.lib.auth import get_crypt_password
194 from rhodecode.lib.auth import get_crypt_password
195 log.debug('Checking for such ldap account in RhodeCode database')
195 log.debug('Checking for such ldap account in RhodeCode database')
196 if self.get_by_username(username, case_insensitive=True) is None:
196 if self.get_by_username(username, case_insensitive=True) is None:
197
197
198 # autogenerate email for ldap account without one
198 # autogenerate email for ldap account without one
199 generate_email = lambda usr: '%s@ldap.account' % usr
199 generate_email = lambda usr: '%s@ldap.account' % usr
200
200
201 try:
201 try:
202 new_user = User()
202 new_user = User()
203 username = username.lower()
203 username = username.lower()
204 # add ldap account always lowercase
204 # add ldap account always lowercase
205 new_user.username = username
205 new_user.username = username
206 new_user.password = get_crypt_password(password)
206 new_user.password = get_crypt_password(password)
207 new_user.api_key = generate_api_key(username)
207 new_user.api_key = generate_api_key(username)
208 new_user.email = attrs['email'] or generate_email(username)
208 new_user.email = attrs['email'] or generate_email(username)
209 new_user.active = attrs.get('active', True)
209 new_user.active = attrs.get('active', True)
210 new_user.ldap_dn = safe_unicode(user_dn)
210 new_user.ldap_dn = safe_unicode(user_dn)
211 new_user.name = attrs['name']
211 new_user.name = attrs['name']
212 new_user.lastname = attrs['lastname']
212 new_user.lastname = attrs['lastname']
213
213
214 self.sa.add(new_user)
214 self.sa.add(new_user)
215 return new_user
215 return new_user
216 except (DatabaseError,):
216 except (DatabaseError,):
217 log.error(traceback.format_exc())
217 log.error(traceback.format_exc())
218 self.sa.rollback()
218 self.sa.rollback()
219 raise
219 raise
220 log.debug('this %s user exists skipping creation of ldap account',
220 log.debug('this %s user exists skipping creation of ldap account',
221 username)
221 username)
222 return None
222 return None
223
223
224 def create_registration(self, form_data):
224 def create_registration(self, form_data):
225 from rhodecode.model.notification import NotificationModel
225 from rhodecode.model.notification import NotificationModel
226
226
227 try:
227 try:
228 new_user = User()
228 form_data['admin'] = False
229 for k, v in form_data.items():
229 new_user = self.create(form_data)
230 if k != 'admin':
231 setattr(new_user, k, v)
232
230
233 self.sa.add(new_user)
231 self.sa.add(new_user)
234 self.sa.flush()
232 self.sa.flush()
235
233
236 # notification to admins
234 # notification to admins
237 subject = _('new user registration')
235 subject = _('new user registration')
238 body = ('New user registration\n'
236 body = ('New user registration\n'
239 '---------------------\n'
237 '---------------------\n'
240 '- Username: %s\n'
238 '- Username: %s\n'
241 '- Full Name: %s\n'
239 '- Full Name: %s\n'
242 '- Email: %s\n')
240 '- Email: %s\n')
243 body = body % (new_user.username, new_user.full_name,
241 body = body % (new_user.username, new_user.full_name,
244 new_user.email)
242 new_user.email)
245 edit_url = url('edit_user', id=new_user.user_id, qualified=True)
243 edit_url = url('edit_user', id=new_user.user_id, qualified=True)
246 kw = {'registered_user_url': edit_url}
244 kw = {'registered_user_url': edit_url}
247 NotificationModel().create(created_by=new_user, subject=subject,
245 NotificationModel().create(created_by=new_user, subject=subject,
248 body=body, recipients=None,
246 body=body, recipients=None,
249 type_=Notification.TYPE_REGISTRATION,
247 type_=Notification.TYPE_REGISTRATION,
250 email_kwargs=kw)
248 email_kwargs=kw)
251
249
252 except:
250 except:
253 log.error(traceback.format_exc())
251 log.error(traceback.format_exc())
254 raise
252 raise
255
253
256 def update(self, user_id, form_data):
254 def update(self, user_id, form_data):
257 try:
255 try:
258 user = self.get(user_id, cache=False)
256 user = self.get(user_id, cache=False)
259 if user.username == 'default':
257 if user.username == 'default':
260 raise DefaultUserException(
258 raise DefaultUserException(
261 _("You can't Edit this user since it's"
259 _("You can't Edit this user since it's"
262 " crucial for entire application"))
260 " crucial for entire application"))
263
261
264 for k, v in form_data.items():
262 for k, v in form_data.items():
265 if k == 'new_password' and v != '':
263 if k == 'new_password' and v != '':
266 user.password = v
264 user.password = v
267 user.api_key = generate_api_key(user.username)
265 user.api_key = generate_api_key(user.username)
268 else:
266 else:
269 setattr(user, k, v)
267 setattr(user, k, v)
270
268
271 self.sa.add(user)
269 self.sa.add(user)
272 except:
270 except:
273 log.error(traceback.format_exc())
271 log.error(traceback.format_exc())
274 raise
272 raise
275
273
276 def update_my_account(self, user_id, form_data):
274 def update_my_account(self, user_id, form_data):
277 try:
275 try:
278 user = self.get(user_id, cache=False)
276 user = self.get(user_id, cache=False)
279 if user.username == 'default':
277 if user.username == 'default':
280 raise DefaultUserException(
278 raise DefaultUserException(
281 _("You can't Edit this user since it's"
279 _("You can't Edit this user since it's"
282 " crucial for entire application"))
280 " crucial for entire application"))
283 for k, v in form_data.items():
281 for k, v in form_data.items():
284 if k == 'new_password' and v != '':
282 if k == 'new_password' and v != '':
285 user.password = v
283 user.password = v
286 user.api_key = generate_api_key(user.username)
284 user.api_key = generate_api_key(user.username)
287 else:
285 else:
288 if k not in ['admin', 'active']:
286 if k not in ['admin', 'active']:
289 setattr(user, k, v)
287 setattr(user, k, v)
290
288
291 self.sa.add(user)
289 self.sa.add(user)
292 except:
290 except:
293 log.error(traceback.format_exc())
291 log.error(traceback.format_exc())
294 raise
292 raise
295
293
296 def delete(self, user):
294 def delete(self, user):
297 user = self.__get_user(user)
295 user = self.__get_user(user)
298
296
299 try:
297 try:
300 if user.username == 'default':
298 if user.username == 'default':
301 raise DefaultUserException(
299 raise DefaultUserException(
302 _(u"You can't remove this user since it's"
300 _(u"You can't remove this user since it's"
303 " crucial for entire application")
301 " crucial for entire application")
304 )
302 )
305 if user.repositories:
303 if user.repositories:
306 repos = [x.repo_name for x in user.repositories]
304 repos = [x.repo_name for x in user.repositories]
307 raise UserOwnsReposException(
305 raise UserOwnsReposException(
308 _(u'user "%s" still owns %s repositories and cannot be '
306 _(u'user "%s" still owns %s repositories and cannot be '
309 'removed. Switch owners or remove those repositories. %s')
307 'removed. Switch owners or remove those repositories. %s')
310 % (user.username, len(repos), ', '.join(repos))
308 % (user.username, len(repos), ', '.join(repos))
311 )
309 )
312 self.sa.delete(user)
310 self.sa.delete(user)
313 except:
311 except:
314 log.error(traceback.format_exc())
312 log.error(traceback.format_exc())
315 raise
313 raise
316
314
317 def reset_password_link(self, data):
315 def reset_password_link(self, data):
318 from rhodecode.lib.celerylib import tasks, run_task
316 from rhodecode.lib.celerylib import tasks, run_task
319 run_task(tasks.send_password_link, data['email'])
317 run_task(tasks.send_password_link, data['email'])
320
318
321 def reset_password(self, data):
319 def reset_password(self, data):
322 from rhodecode.lib.celerylib import tasks, run_task
320 from rhodecode.lib.celerylib import tasks, run_task
323 run_task(tasks.reset_user_password, data['email'])
321 run_task(tasks.reset_user_password, data['email'])
324
322
325 def fill_data(self, auth_user, user_id=None, api_key=None):
323 def fill_data(self, auth_user, user_id=None, api_key=None):
326 """
324 """
327 Fetches auth_user by user_id,or api_key if present.
325 Fetches auth_user by user_id,or api_key if present.
328 Fills auth_user attributes with those taken from database.
326 Fills auth_user attributes with those taken from database.
329 Additionally set's is_authenitated if lookup fails
327 Additionally set's is_authenitated if lookup fails
330 present in database
328 present in database
331
329
332 :param auth_user: instance of user to set attributes
330 :param auth_user: instance of user to set attributes
333 :param user_id: user id to fetch by
331 :param user_id: user id to fetch by
334 :param api_key: api key to fetch by
332 :param api_key: api key to fetch by
335 """
333 """
336 if user_id is None and api_key is None:
334 if user_id is None and api_key is None:
337 raise Exception('You need to pass user_id or api_key')
335 raise Exception('You need to pass user_id or api_key')
338
336
339 try:
337 try:
340 if api_key:
338 if api_key:
341 dbuser = self.get_by_api_key(api_key)
339 dbuser = self.get_by_api_key(api_key)
342 else:
340 else:
343 dbuser = self.get(user_id)
341 dbuser = self.get(user_id)
344
342
345 if dbuser is not None and dbuser.active:
343 if dbuser is not None and dbuser.active:
346 log.debug('filling %s data' % dbuser)
344 log.debug('filling %s data' % dbuser)
347 for k, v in dbuser.get_dict().items():
345 for k, v in dbuser.get_dict().items():
348 setattr(auth_user, k, v)
346 setattr(auth_user, k, v)
349 else:
347 else:
350 return False
348 return False
351
349
352 except:
350 except:
353 log.error(traceback.format_exc())
351 log.error(traceback.format_exc())
354 auth_user.is_authenticated = False
352 auth_user.is_authenticated = False
355 return False
353 return False
356
354
357 return True
355 return True
358
356
359 def fill_perms(self, user):
357 def fill_perms(self, user):
360 """
358 """
361 Fills user permission attribute with permissions taken from database
359 Fills user permission attribute with permissions taken from database
362 works for permissions given for repositories, and for permissions that
360 works for permissions given for repositories, and for permissions that
363 are granted to groups
361 are granted to groups
364
362
365 :param user: user instance to fill his perms
363 :param user: user instance to fill his perms
366 """
364 """
367 RK = 'repositories'
365 RK = 'repositories'
368 GK = 'repositories_groups'
366 GK = 'repositories_groups'
369 GLOBAL = 'global'
367 GLOBAL = 'global'
370 user.permissions[RK] = {}
368 user.permissions[RK] = {}
371 user.permissions[GK] = {}
369 user.permissions[GK] = {}
372 user.permissions[GLOBAL] = set()
370 user.permissions[GLOBAL] = set()
373
371
374 #======================================================================
372 #======================================================================
375 # fetch default permissions
373 # fetch default permissions
376 #======================================================================
374 #======================================================================
377 default_user = User.get_by_username('default', cache=True)
375 default_user = User.get_by_username('default', cache=True)
378 default_user_id = default_user.user_id
376 default_user_id = default_user.user_id
379
377
380 default_repo_perms = Permission.get_default_perms(default_user_id)
378 default_repo_perms = Permission.get_default_perms(default_user_id)
381 default_repo_groups_perms = Permission.get_default_group_perms(default_user_id)
379 default_repo_groups_perms = Permission.get_default_group_perms(default_user_id)
382
380
383 if user.is_admin:
381 if user.is_admin:
384 #==================================================================
382 #==================================================================
385 # admin user have all default rights for repositories
383 # admin user have all default rights for repositories
386 # and groups set to admin
384 # and groups set to admin
387 #==================================================================
385 #==================================================================
388 user.permissions[GLOBAL].add('hg.admin')
386 user.permissions[GLOBAL].add('hg.admin')
389
387
390 # repositories
388 # repositories
391 for perm in default_repo_perms:
389 for perm in default_repo_perms:
392 r_k = perm.UserRepoToPerm.repository.repo_name
390 r_k = perm.UserRepoToPerm.repository.repo_name
393 p = 'repository.admin'
391 p = 'repository.admin'
394 user.permissions[RK][r_k] = p
392 user.permissions[RK][r_k] = p
395
393
396 # repositories groups
394 # repositories groups
397 for perm in default_repo_groups_perms:
395 for perm in default_repo_groups_perms:
398 rg_k = perm.UserRepoGroupToPerm.group.group_name
396 rg_k = perm.UserRepoGroupToPerm.group.group_name
399 p = 'group.admin'
397 p = 'group.admin'
400 user.permissions[GK][rg_k] = p
398 user.permissions[GK][rg_k] = p
401 return user
399 return user
402
400
403 #==================================================================
401 #==================================================================
404 # set default permissions first for repositories and groups
402 # set default permissions first for repositories and groups
405 #==================================================================
403 #==================================================================
406 uid = user.user_id
404 uid = user.user_id
407
405
408 # default global permissions
406 # default global permissions
409 default_global_perms = self.sa.query(UserToPerm)\
407 default_global_perms = self.sa.query(UserToPerm)\
410 .filter(UserToPerm.user_id == default_user_id)
408 .filter(UserToPerm.user_id == default_user_id)
411
409
412 for perm in default_global_perms:
410 for perm in default_global_perms:
413 user.permissions[GLOBAL].add(perm.permission.permission_name)
411 user.permissions[GLOBAL].add(perm.permission.permission_name)
414
412
415 # defaults for repositories, taken from default user
413 # defaults for repositories, taken from default user
416 for perm in default_repo_perms:
414 for perm in default_repo_perms:
417 r_k = perm.UserRepoToPerm.repository.repo_name
415 r_k = perm.UserRepoToPerm.repository.repo_name
418 if perm.Repository.private and not (perm.Repository.user_id == uid):
416 if perm.Repository.private and not (perm.Repository.user_id == uid):
419 # disable defaults for private repos,
417 # disable defaults for private repos,
420 p = 'repository.none'
418 p = 'repository.none'
421 elif perm.Repository.user_id == uid:
419 elif perm.Repository.user_id == uid:
422 # set admin if owner
420 # set admin if owner
423 p = 'repository.admin'
421 p = 'repository.admin'
424 else:
422 else:
425 p = perm.Permission.permission_name
423 p = perm.Permission.permission_name
426
424
427 user.permissions[RK][r_k] = p
425 user.permissions[RK][r_k] = p
428
426
429 # defaults for repositories groups taken from default user permission
427 # defaults for repositories groups taken from default user permission
430 # on given group
428 # on given group
431 for perm in default_repo_groups_perms:
429 for perm in default_repo_groups_perms:
432 rg_k = perm.UserRepoGroupToPerm.group.group_name
430 rg_k = perm.UserRepoGroupToPerm.group.group_name
433 p = perm.Permission.permission_name
431 p = perm.Permission.permission_name
434 user.permissions[GK][rg_k] = p
432 user.permissions[GK][rg_k] = p
435
433
436 #==================================================================
434 #==================================================================
437 # overwrite defaults with user permissions if any found
435 # overwrite defaults with user permissions if any found
438 #==================================================================
436 #==================================================================
439
437
440 # user global permissions
438 # user global permissions
441 user_perms = self.sa.query(UserToPerm)\
439 user_perms = self.sa.query(UserToPerm)\
442 .options(joinedload(UserToPerm.permission))\
440 .options(joinedload(UserToPerm.permission))\
443 .filter(UserToPerm.user_id == uid).all()
441 .filter(UserToPerm.user_id == uid).all()
444
442
445 for perm in user_perms:
443 for perm in user_perms:
446 user.permissions[GLOBAL].add(perm.permission.permission_name)
444 user.permissions[GLOBAL].add(perm.permission.permission_name)
447
445
448 # user explicit permissions for repositories
446 # user explicit permissions for repositories
449 user_repo_perms = \
447 user_repo_perms = \
450 self.sa.query(UserRepoToPerm, Permission, Repository)\
448 self.sa.query(UserRepoToPerm, Permission, Repository)\
451 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
449 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
452 .join((Permission, UserRepoToPerm.permission_id == Permission.permission_id))\
450 .join((Permission, UserRepoToPerm.permission_id == Permission.permission_id))\
453 .filter(UserRepoToPerm.user_id == uid)\
451 .filter(UserRepoToPerm.user_id == uid)\
454 .all()
452 .all()
455
453
456 for perm in user_repo_perms:
454 for perm in user_repo_perms:
457 # set admin if owner
455 # set admin if owner
458 r_k = perm.UserRepoToPerm.repository.repo_name
456 r_k = perm.UserRepoToPerm.repository.repo_name
459 if perm.Repository.user_id == uid:
457 if perm.Repository.user_id == uid:
460 p = 'repository.admin'
458 p = 'repository.admin'
461 else:
459 else:
462 p = perm.Permission.permission_name
460 p = perm.Permission.permission_name
463 user.permissions[RK][r_k] = p
461 user.permissions[RK][r_k] = p
464
462
465 # USER GROUP
463 # USER GROUP
466 #==================================================================
464 #==================================================================
467 # check if user is part of user groups for this repository and
465 # check if user is part of user groups for this repository and
468 # fill in (or replace with higher) permissions
466 # fill in (or replace with higher) permissions
469 #==================================================================
467 #==================================================================
470
468
471 # users group global
469 # users group global
472 user_perms_from_users_groups = self.sa.query(UsersGroupToPerm)\
470 user_perms_from_users_groups = self.sa.query(UsersGroupToPerm)\
473 .options(joinedload(UsersGroupToPerm.permission))\
471 .options(joinedload(UsersGroupToPerm.permission))\
474 .join((UsersGroupMember, UsersGroupToPerm.users_group_id ==
472 .join((UsersGroupMember, UsersGroupToPerm.users_group_id ==
475 UsersGroupMember.users_group_id))\
473 UsersGroupMember.users_group_id))\
476 .filter(UsersGroupMember.user_id == uid).all()
474 .filter(UsersGroupMember.user_id == uid).all()
477
475
478 for perm in user_perms_from_users_groups:
476 for perm in user_perms_from_users_groups:
479 user.permissions[GLOBAL].add(perm.permission.permission_name)
477 user.permissions[GLOBAL].add(perm.permission.permission_name)
480
478
481 # users group for repositories permissions
479 # users group for repositories permissions
482 user_repo_perms_from_users_groups = \
480 user_repo_perms_from_users_groups = \
483 self.sa.query(UsersGroupRepoToPerm, Permission, Repository,)\
481 self.sa.query(UsersGroupRepoToPerm, Permission, Repository,)\
484 .join((Repository, UsersGroupRepoToPerm.repository_id == Repository.repo_id))\
482 .join((Repository, UsersGroupRepoToPerm.repository_id == Repository.repo_id))\
485 .join((Permission, UsersGroupRepoToPerm.permission_id == Permission.permission_id))\
483 .join((Permission, UsersGroupRepoToPerm.permission_id == Permission.permission_id))\
486 .join((UsersGroupMember, UsersGroupRepoToPerm.users_group_id == UsersGroupMember.users_group_id))\
484 .join((UsersGroupMember, UsersGroupRepoToPerm.users_group_id == UsersGroupMember.users_group_id))\
487 .filter(UsersGroupMember.user_id == uid)\
485 .filter(UsersGroupMember.user_id == uid)\
488 .all()
486 .all()
489
487
490 for perm in user_repo_perms_from_users_groups:
488 for perm in user_repo_perms_from_users_groups:
491 r_k = perm.UsersGroupRepoToPerm.repository.repo_name
489 r_k = perm.UsersGroupRepoToPerm.repository.repo_name
492 p = perm.Permission.permission_name
490 p = perm.Permission.permission_name
493 cur_perm = user.permissions[RK][r_k]
491 cur_perm = user.permissions[RK][r_k]
494 # overwrite permission only if it's greater than permission
492 # overwrite permission only if it's greater than permission
495 # given from other sources
493 # given from other sources
496 if PERM_WEIGHTS[p] > PERM_WEIGHTS[cur_perm]:
494 if PERM_WEIGHTS[p] > PERM_WEIGHTS[cur_perm]:
497 user.permissions[RK][r_k] = p
495 user.permissions[RK][r_k] = p
498
496
499 # REPO GROUP
497 # REPO GROUP
500 #==================================================================
498 #==================================================================
501 # get access for this user for repos group and override defaults
499 # get access for this user for repos group and override defaults
502 #==================================================================
500 #==================================================================
503
501
504 # user explicit permissions for repository
502 # user explicit permissions for repository
505 user_repo_groups_perms = \
503 user_repo_groups_perms = \
506 self.sa.query(UserRepoGroupToPerm, Permission, RepoGroup)\
504 self.sa.query(UserRepoGroupToPerm, Permission, RepoGroup)\
507 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
505 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
508 .join((Permission, UserRepoGroupToPerm.permission_id == Permission.permission_id))\
506 .join((Permission, UserRepoGroupToPerm.permission_id == Permission.permission_id))\
509 .filter(UserRepoGroupToPerm.user_id == uid)\
507 .filter(UserRepoGroupToPerm.user_id == uid)\
510 .all()
508 .all()
511
509
512 for perm in user_repo_groups_perms:
510 for perm in user_repo_groups_perms:
513 rg_k = perm.UserRepoGroupToPerm.group.group_name
511 rg_k = perm.UserRepoGroupToPerm.group.group_name
514 p = perm.Permission.permission_name
512 p = perm.Permission.permission_name
515 cur_perm = user.permissions[GK][rg_k]
513 cur_perm = user.permissions[GK][rg_k]
516 if PERM_WEIGHTS[p] > PERM_WEIGHTS[cur_perm]:
514 if PERM_WEIGHTS[p] > PERM_WEIGHTS[cur_perm]:
517 user.permissions[GK][rg_k] = p
515 user.permissions[GK][rg_k] = p
518
516
519 # REPO GROUP + USER GROUP
517 # REPO GROUP + USER GROUP
520 #==================================================================
518 #==================================================================
521 # check if user is part of user groups for this repo group and
519 # check if user is part of user groups for this repo group and
522 # fill in (or replace with higher) permissions
520 # fill in (or replace with higher) permissions
523 #==================================================================
521 #==================================================================
524
522
525 # users group for repositories permissions
523 # users group for repositories permissions
526 user_repo_group_perms_from_users_groups = \
524 user_repo_group_perms_from_users_groups = \
527 self.sa.query(UsersGroupRepoGroupToPerm, Permission, RepoGroup)\
525 self.sa.query(UsersGroupRepoGroupToPerm, Permission, RepoGroup)\
528 .join((RepoGroup, UsersGroupRepoGroupToPerm.group_id == RepoGroup.group_id))\
526 .join((RepoGroup, UsersGroupRepoGroupToPerm.group_id == RepoGroup.group_id))\
529 .join((Permission, UsersGroupRepoGroupToPerm.permission_id == Permission.permission_id))\
527 .join((Permission, UsersGroupRepoGroupToPerm.permission_id == Permission.permission_id))\
530 .join((UsersGroupMember, UsersGroupRepoGroupToPerm.users_group_id == UsersGroupMember.users_group_id))\
528 .join((UsersGroupMember, UsersGroupRepoGroupToPerm.users_group_id == UsersGroupMember.users_group_id))\
531 .filter(UsersGroupMember.user_id == uid)\
529 .filter(UsersGroupMember.user_id == uid)\
532 .all()
530 .all()
533
531
534 for perm in user_repo_group_perms_from_users_groups:
532 for perm in user_repo_group_perms_from_users_groups:
535 g_k = perm.UsersGroupRepoGroupToPerm.group.group_name
533 g_k = perm.UsersGroupRepoGroupToPerm.group.group_name
536 print perm, g_k
534 print perm, g_k
537 p = perm.Permission.permission_name
535 p = perm.Permission.permission_name
538 cur_perm = user.permissions[GK][g_k]
536 cur_perm = user.permissions[GK][g_k]
539 # overwrite permission only if it's greater than permission
537 # overwrite permission only if it's greater than permission
540 # given from other sources
538 # given from other sources
541 if PERM_WEIGHTS[p] > PERM_WEIGHTS[cur_perm]:
539 if PERM_WEIGHTS[p] > PERM_WEIGHTS[cur_perm]:
542 user.permissions[GK][g_k] = p
540 user.permissions[GK][g_k] = p
543
541
544 return user
542 return user
545
543
546 def has_perm(self, user, perm):
544 def has_perm(self, user, perm):
547 if not isinstance(perm, Permission):
545 if not isinstance(perm, Permission):
548 raise Exception('perm needs to be an instance of Permission class '
546 raise Exception('perm needs to be an instance of Permission class '
549 'got %s instead' % type(perm))
547 'got %s instead' % type(perm))
550
548
551 user = self.__get_user(user)
549 user = self.__get_user(user)
552
550
553 return UserToPerm.query().filter(UserToPerm.user == user)\
551 return UserToPerm.query().filter(UserToPerm.user == user)\
554 .filter(UserToPerm.permission == perm).scalar() is not None
552 .filter(UserToPerm.permission == perm).scalar() is not None
555
553
556 def grant_perm(self, user, perm):
554 def grant_perm(self, user, perm):
557 """
555 """
558 Grant user global permissions
556 Grant user global permissions
559
557
560 :param user:
558 :param user:
561 :param perm:
559 :param perm:
562 """
560 """
563 user = self.__get_user(user)
561 user = self.__get_user(user)
564 perm = self.__get_perm(perm)
562 perm = self.__get_perm(perm)
565 # if this permission is already granted skip it
563 # if this permission is already granted skip it
566 _perm = UserToPerm.query()\
564 _perm = UserToPerm.query()\
567 .filter(UserToPerm.user == user)\
565 .filter(UserToPerm.user == user)\
568 .filter(UserToPerm.permission == perm)\
566 .filter(UserToPerm.permission == perm)\
569 .scalar()
567 .scalar()
570 if _perm:
568 if _perm:
571 return
569 return
572 new = UserToPerm()
570 new = UserToPerm()
573 new.user = user
571 new.user = user
574 new.permission = perm
572 new.permission = perm
575 self.sa.add(new)
573 self.sa.add(new)
576
574
577 def revoke_perm(self, user, perm):
575 def revoke_perm(self, user, perm):
578 """
576 """
579 Revoke users global permissions
577 Revoke users global permissions
580
578
581 :param user:
579 :param user:
582 :param perm:
580 :param perm:
583 """
581 """
584 user = self.__get_user(user)
582 user = self.__get_user(user)
585 perm = self.__get_perm(perm)
583 perm = self.__get_perm(perm)
586
584
587 obj = UserToPerm.query()\
585 obj = UserToPerm.query()\
588 .filter(UserToPerm.user == user)\
586 .filter(UserToPerm.user == user)\
589 .filter(UserToPerm.permission == perm)\
587 .filter(UserToPerm.permission == perm)\
590 .scalar()
588 .scalar()
591 if obj:
589 if obj:
592 self.sa.delete(obj)
590 self.sa.delete(obj)
@@ -1,268 +1,267 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 from rhodecode.tests import *
2 from rhodecode.tests import *
3 from rhodecode.model.db import User, Notification
3 from rhodecode.model.db import User, Notification
4 from rhodecode.lib.utils2 import generate_api_key
4 from rhodecode.lib.utils2 import generate_api_key
5 from rhodecode.lib.auth import check_password
5 from rhodecode.lib.auth import check_password
6 from rhodecode.model.meta import Session
6 from rhodecode.model.meta import Session
7
7
8
8
9 class TestLoginController(TestController):
9 class TestLoginController(TestController):
10
10
11 def tearDown(self):
11 def tearDown(self):
12 for n in Notification.query().all():
12 for n in Notification.query().all():
13 Session.delete(n)
13 Session.delete(n)
14
14
15 Session.commit()
15 Session.commit()
16 self.assertEqual(Notification.query().all(), [])
16 self.assertEqual(Notification.query().all(), [])
17
17
18 def test_index(self):
18 def test_index(self):
19 response = self.app.get(url(controller='login', action='index'))
19 response = self.app.get(url(controller='login', action='index'))
20 self.assertEqual(response.status, '200 OK')
20 self.assertEqual(response.status, '200 OK')
21 # Test response...
21 # Test response...
22
22
23 def test_login_admin_ok(self):
23 def test_login_admin_ok(self):
24 response = self.app.post(url(controller='login', action='index'),
24 response = self.app.post(url(controller='login', action='index'),
25 {'username':'test_admin',
25 {'username':'test_admin',
26 'password':'test12'})
26 'password':'test12'})
27 self.assertEqual(response.status, '302 Found')
27 self.assertEqual(response.status, '302 Found')
28 self.assertEqual(response.session['rhodecode_user'].get('username') ,
28 self.assertEqual(response.session['rhodecode_user'].get('username') ,
29 'test_admin')
29 'test_admin')
30 response = response.follow()
30 response = response.follow()
31 self.assertTrue('%s repository' % HG_REPO in response.body)
31 self.assertTrue('%s repository' % HG_REPO in response.body)
32
32
33 def test_login_regular_ok(self):
33 def test_login_regular_ok(self):
34 response = self.app.post(url(controller='login', action='index'),
34 response = self.app.post(url(controller='login', action='index'),
35 {'username':'test_regular',
35 {'username':'test_regular',
36 'password':'test12'})
36 'password':'test12'})
37
37
38 self.assertEqual(response.status, '302 Found')
38 self.assertEqual(response.status, '302 Found')
39 self.assertEqual(response.session['rhodecode_user'].get('username') ,
39 self.assertEqual(response.session['rhodecode_user'].get('username') ,
40 'test_regular')
40 'test_regular')
41 response = response.follow()
41 response = response.follow()
42 self.assertTrue('%s repository' % HG_REPO in response.body)
42 self.assertTrue('%s repository' % HG_REPO in response.body)
43 self.assertTrue('<a title="Admin" href="/_admin">' not in response.body)
43 self.assertTrue('<a title="Admin" href="/_admin">' not in response.body)
44
44
45 def test_login_ok_came_from(self):
45 def test_login_ok_came_from(self):
46 test_came_from = '/_admin/users'
46 test_came_from = '/_admin/users'
47 response = self.app.post(url(controller='login', action='index',
47 response = self.app.post(url(controller='login', action='index',
48 came_from=test_came_from),
48 came_from=test_came_from),
49 {'username':'test_admin',
49 {'username':'test_admin',
50 'password':'test12'})
50 'password':'test12'})
51 self.assertEqual(response.status, '302 Found')
51 self.assertEqual(response.status, '302 Found')
52 response = response.follow()
52 response = response.follow()
53
53
54 self.assertEqual(response.status, '200 OK')
54 self.assertEqual(response.status, '200 OK')
55 self.assertTrue('Users administration' in response.body)
55 self.assertTrue('Users administration' in response.body)
56
56
57
58 def test_login_short_password(self):
57 def test_login_short_password(self):
59 response = self.app.post(url(controller='login', action='index'),
58 response = self.app.post(url(controller='login', action='index'),
60 {'username':'test_admin',
59 {'username':'test_admin',
61 'password':'as'})
60 'password':'as'})
62 self.assertEqual(response.status, '200 OK')
61 self.assertEqual(response.status, '200 OK')
63
62
64 self.assertTrue('Enter 3 characters or more' in response.body)
63 self.assertTrue('Enter 3 characters or more' in response.body)
65
64
66 def test_login_wrong_username_password(self):
65 def test_login_wrong_username_password(self):
67 response = self.app.post(url(controller='login', action='index'),
66 response = self.app.post(url(controller='login', action='index'),
68 {'username':'error',
67 {'username':'error',
69 'password':'test12'})
68 'password':'test12'})
70 self.assertEqual(response.status , '200 OK')
69 self.assertEqual(response.status , '200 OK')
71
70
72 self.assertTrue('invalid user name' in response.body)
71 self.assertTrue('invalid user name' in response.body)
73 self.assertTrue('invalid password' in response.body)
72 self.assertTrue('invalid password' in response.body)
74
73
75 #==========================================================================
74 #==========================================================================
76 # REGISTRATIONS
75 # REGISTRATIONS
77 #==========================================================================
76 #==========================================================================
78 def test_register(self):
77 def test_register(self):
79 response = self.app.get(url(controller='login', action='register'))
78 response = self.app.get(url(controller='login', action='register'))
80 self.assertTrue('Sign Up to RhodeCode' in response.body)
79 self.assertTrue('Sign Up to RhodeCode' in response.body)
81
80
82 def test_register_err_same_username(self):
81 def test_register_err_same_username(self):
83 response = self.app.post(url(controller='login', action='register'),
82 response = self.app.post(url(controller='login', action='register'),
84 {'username':'test_admin',
83 {'username':'test_admin',
85 'password':'test12',
84 'password':'test12',
86 'password_confirmation':'test12',
85 'password_confirmation':'test12',
87 'email':'goodmail@domain.com',
86 'email':'goodmail@domain.com',
88 'name':'test',
87 'name':'test',
89 'lastname':'test'})
88 'lastname':'test'})
90
89
91 self.assertEqual(response.status , '200 OK')
90 self.assertEqual(response.status , '200 OK')
92 self.assertTrue('This username already exists' in response.body)
91 self.assertTrue('This username already exists' in response.body)
93
92
94 def test_register_err_same_email(self):
93 def test_register_err_same_email(self):
95 response = self.app.post(url(controller='login', action='register'),
94 response = self.app.post(url(controller='login', action='register'),
96 {'username':'test_admin_0',
95 {'username':'test_admin_0',
97 'password':'test12',
96 'password':'test12',
98 'password_confirmation':'test12',
97 'password_confirmation':'test12',
99 'email':'test_admin@mail.com',
98 'email':'test_admin@mail.com',
100 'name':'test',
99 'name':'test',
101 'lastname':'test'})
100 'lastname':'test'})
102
101
103 self.assertEqual(response.status , '200 OK')
102 self.assertEqual(response.status , '200 OK')
104 assert 'This e-mail address is already taken' in response.body
103 response.mustcontain('This e-mail address is already taken')
105
104
106 def test_register_err_same_email_case_sensitive(self):
105 def test_register_err_same_email_case_sensitive(self):
107 response = self.app.post(url(controller='login', action='register'),
106 response = self.app.post(url(controller='login', action='register'),
108 {'username':'test_admin_1',
107 {'username':'test_admin_1',
109 'password':'test12',
108 'password':'test12',
110 'password_confirmation':'test12',
109 'password_confirmation':'test12',
111 'email':'TesT_Admin@mail.COM',
110 'email':'TesT_Admin@mail.COM',
112 'name':'test',
111 'name':'test',
113 'lastname':'test'})
112 'lastname':'test'})
114 self.assertEqual(response.status , '200 OK')
113 self.assertEqual(response.status , '200 OK')
115 assert 'This e-mail address is already taken' in response.body
114 response.mustcontain('This e-mail address is already taken')
116
115
117 def test_register_err_wrong_data(self):
116 def test_register_err_wrong_data(self):
118 response = self.app.post(url(controller='login', action='register'),
117 response = self.app.post(url(controller='login', action='register'),
119 {'username':'xs',
118 {'username':'xs',
120 'password':'test',
119 'password':'test',
121 'password_confirmation':'test',
120 'password_confirmation':'test',
122 'email':'goodmailm',
121 'email':'goodmailm',
123 'name':'test',
122 'name':'test',
124 'lastname':'test'})
123 'lastname':'test'})
125 self.assertEqual(response.status , '200 OK')
124 self.assertEqual(response.status , '200 OK')
126 assert 'An email address must contain a single @' in response.body
125 response.mustcontain('An email address must contain a single @')
127 assert 'Enter a value 6 characters long or more' in response.body
126 response.mustcontain('Enter a value 6 characters long or more')
128
129
127
130 def test_register_err_username(self):
128 def test_register_err_username(self):
131 response = self.app.post(url(controller='login', action='register'),
129 response = self.app.post(url(controller='login', action='register'),
132 {'username':'error user',
130 {'username':'error user',
133 'password':'test12',
131 'password':'test12',
134 'password_confirmation':'test12',
132 'password_confirmation':'test12',
135 'email':'goodmailm',
133 'email':'goodmailm',
136 'name':'test',
134 'name':'test',
137 'lastname':'test'})
135 'lastname':'test'})
138
136
139 self.assertEqual(response.status , '200 OK')
137 self.assertEqual(response.status , '200 OK')
140 assert 'An email address must contain a single @' in response.body
138 response.mustcontain('An email address must contain a single @')
141 assert ('Username may only contain '
139 response.mustcontain('Username may only contain '
142 'alphanumeric characters underscores, '
140 'alphanumeric characters underscores, '
143 'periods or dashes and must begin with '
141 'periods or dashes and must begin with '
144 'alphanumeric character') in response.body
142 'alphanumeric character')
145
143
146 def test_register_err_case_sensitive(self):
144 def test_register_err_case_sensitive(self):
147 response = self.app.post(url(controller='login', action='register'),
145 response = self.app.post(url(controller='login', action='register'),
148 {'username':'Test_Admin',
146 {'username':'Test_Admin',
149 'password':'test12',
147 'password':'test12',
150 'password_confirmation':'test12',
148 'password_confirmation':'test12',
151 'email':'goodmailm',
149 'email':'goodmailm',
152 'name':'test',
150 'name':'test',
153 'lastname':'test'})
151 'lastname':'test'})
154
152
155 self.assertEqual(response.status , '200 OK')
153 self.assertEqual(response.status , '200 OK')
156 self.assertTrue('An email address must contain a single @' in response.body)
154 self.assertTrue('An email address must contain a single @' in response.body)
157 self.assertTrue('This username already exists' in response.body)
155 self.assertTrue('This username already exists' in response.body)
158
156
159
160
161 def test_register_special_chars(self):
157 def test_register_special_chars(self):
162 response = self.app.post(url(controller='login', action='register'),
158 response = self.app.post(url(controller='login', action='register'),
163 {'username':'xxxaxn',
159 {'username':'xxxaxn',
164 'password':'Δ…Δ‡ΕΊΕΌΔ…Ε›Ε›Ε›Ε›',
160 'password':'Δ…Δ‡ΕΊΕΌΔ…Ε›Ε›Ε›Ε›',
165 'password_confirmation':'Δ…Δ‡ΕΊΕΌΔ…Ε›Ε›Ε›Ε›',
161 'password_confirmation':'Δ…Δ‡ΕΊΕΌΔ…Ε›Ε›Ε›Ε›',
166 'email':'goodmailm@test.plx',
162 'email':'goodmailm@test.plx',
167 'name':'test',
163 'name':'test',
168 'lastname':'test'})
164 'lastname':'test'})
169
165
170 self.assertEqual(response.status , '200 OK')
166 self.assertEqual(response.status , '200 OK')
171 self.assertTrue('Invalid characters in password' in response.body)
167 self.assertTrue('Invalid characters in password' in response.body)
172
168
173
174 def test_register_password_mismatch(self):
169 def test_register_password_mismatch(self):
175 response = self.app.post(url(controller='login', action='register'),
170 response = self.app.post(url(controller='login', action='register'),
176 {'username':'xs',
171 {'username':'xs',
177 'password':'123qwe',
172 'password':'123qwe',
178 'password_confirmation':'qwe123',
173 'password_confirmation':'qwe123',
179 'email':'goodmailm@test.plxa',
174 'email':'goodmailm@test.plxa',
180 'name':'test',
175 'name':'test',
181 'lastname':'test'})
176 'lastname':'test'})
182
177
183 self.assertEqual(response.status , '200 OK')
178 self.assertEqual(response.status, '200 OK')
184 assert 'Passwords do not match' in response.body
179 response.mustcontain('Passwords do not match')
185
180
186 def test_register_ok(self):
181 def test_register_ok(self):
187 username = 'test_regular4'
182 username = 'test_regular4'
188 password = 'qweqwe'
183 password = 'qweqwe'
189 email = 'marcin@test.com'
184 email = 'marcin@test.com'
190 name = 'testname'
185 name = 'testname'
191 lastname = 'testlastname'
186 lastname = 'testlastname'
192
187
193 response = self.app.post(url(controller='login', action='register'),
188 response = self.app.post(url(controller='login', action='register'),
194 {'username':username,
189 {'username':username,
195 'password':password,
190 'password':password,
196 'password_confirmation':password,
191 'password_confirmation':password,
197 'email':email,
192 'email':email,
198 'name':name,
193 'name':name,
199 'lastname':lastname})
194 'lastname':lastname,
200 self.assertEqual(response.status , '302 Found')
195 'admin':True}) # This should be overriden
201 assert 'You have successfully registered into rhodecode' in response.session['flash'][0], 'No flash message about user registration'
196 self.assertEqual(response.status, '302 Found')
197 self.checkSessionFlash(response, 'You have successfully registered into rhodecode')
202
198
203 ret = self.Session.query(User).filter(User.username == 'test_regular4').one()
199 ret = self.Session.query(User).filter(User.username == 'test_regular4').one()
204 assert ret.username == username , 'field mismatch %s %s' % (ret.username, username)
200 self.assertEqual(ret.username, username)
205 assert check_password(password, ret.password) == True , 'password mismatch'
201 self.assertEqual(check_password(password, ret.password), True)
206 assert ret.email == email , 'field mismatch %s %s' % (ret.email, email)
202 self.assertEqual(ret.email, email)
207 assert ret.name == name , 'field mismatch %s %s' % (ret.name, name)
203 self.assertEqual(ret.name, name)
208 assert ret.lastname == lastname , 'field mismatch %s %s' % (ret.lastname, lastname)
204 self.assertEqual(ret.lastname, lastname)
209
205 self.assertNotEqual(ret.api_key, None)
206 self.assertEqual(ret.admin, False)
210
207
211 def test_forgot_password_wrong_mail(self):
208 def test_forgot_password_wrong_mail(self):
212 response = self.app.post(url(controller='login', action='password_reset'),
209 response = self.app.post(
213 {'email':'marcin@wrongmail.org', })
210 url(controller='login', action='password_reset'),
211 {'email': 'marcin@wrongmail.org',}
212 )
214
213
215 assert "This e-mail address doesn't exist" in response.body, 'Missing error message about wrong email'
214 response.mustcontain("This e-mail address doesn't exist")
216
215
217 def test_forgot_password(self):
216 def test_forgot_password(self):
218 response = self.app.get(url(controller='login',
217 response = self.app.get(url(controller='login',
219 action='password_reset'))
218 action='password_reset'))
220 self.assertEqual(response.status , '200 OK')
219 self.assertEqual(response.status, '200 OK')
221
220
222 username = 'test_password_reset_1'
221 username = 'test_password_reset_1'
223 password = 'qweqwe'
222 password = 'qweqwe'
224 email = 'marcin@python-works.com'
223 email = 'marcin@python-works.com'
225 name = 'passwd'
224 name = 'passwd'
226 lastname = 'reset'
225 lastname = 'reset'
227
226
228 new = User()
227 new = User()
229 new.username = username
228 new.username = username
230 new.password = password
229 new.password = password
231 new.email = email
230 new.email = email
232 new.name = name
231 new.name = name
233 new.lastname = lastname
232 new.lastname = lastname
234 new.api_key = generate_api_key(username)
233 new.api_key = generate_api_key(username)
235 self.Session.add(new)
234 self.Session.add(new)
236 self.Session.commit()
235 self.Session.commit()
237
236
238 response = self.app.post(url(controller='login',
237 response = self.app.post(url(controller='login',
239 action='password_reset'),
238 action='password_reset'),
240 {'email':email, })
239 {'email':email, })
241
240
242 self.checkSessionFlash(response, 'Your password reset link was sent')
241 self.checkSessionFlash(response, 'Your password reset link was sent')
243
242
244 response = response.follow()
243 response = response.follow()
245
244
246 # BAD KEY
245 # BAD KEY
247
246
248 key = "bad"
247 key = "bad"
249 response = self.app.get(url(controller='login',
248 response = self.app.get(url(controller='login',
250 action='password_reset_confirmation',
249 action='password_reset_confirmation',
251 key=key))
250 key=key))
252 self.assertEqual(response.status, '302 Found')
251 self.assertEqual(response.status, '302 Found')
253 self.assertTrue(response.location.endswith(url('reset_password')))
252 self.assertTrue(response.location.endswith(url('reset_password')))
254
253
255 # GOOD KEY
254 # GOOD KEY
256
255
257 key = User.get_by_username(username).api_key
256 key = User.get_by_username(username).api_key
258 response = self.app.get(url(controller='login',
257 response = self.app.get(url(controller='login',
259 action='password_reset_confirmation',
258 action='password_reset_confirmation',
260 key=key))
259 key=key))
261 self.assertEqual(response.status, '302 Found')
260 self.assertEqual(response.status, '302 Found')
262 self.assertTrue(response.location.endswith(url('login_home')))
261 self.assertTrue(response.location.endswith(url('login_home')))
263
262
264 self.checkSessionFlash(response,
263 self.checkSessionFlash(response,
265 ('Your password reset was successful, '
264 ('Your password reset was successful, '
266 'new password has been sent to your email'))
265 'new password has been sent to your email'))
267
266
268 response = response.follow()
267 response = response.follow()
General Comments 0
You need to be logged in to leave comments. Login now