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