##// END OF EJS Templates
updated CONTRIBUTORS...
marcink -
r2058:fb51a6fc beta
parent child Browse files
Show More
@@ -1,18 +1,19 b''
1 List of contributors to RhodeCode project:
1 List of contributors to RhodeCode project:
2 Marcin Kuźmiński <marcin@python-works.com>
2 Marcin Kuźmiński <marcin@python-works.com>
3 Lukasz Balcerzak <lukaszbalcerzak@gmail.com>
3 Lukasz Balcerzak <lukaszbalcerzak@gmail.com>
4 Jason Harris <jason@jasonfharris.com>
4 Jason Harris <jason@jasonfharris.com>
5 Thayne Harbaugh <thayne@fusionio.com>
5 Thayne Harbaugh <thayne@fusionio.com>
6 cejones
6 cejones
7 Thomas Waldmann <tw-public@gmx.de>
7 Thomas Waldmann <tw-public@gmx.de>
8 Lorenzo M. Catucci <lorenzo@sancho.ccd.uniroma2.it>
8 Lorenzo M. Catucci <lorenzo@sancho.ccd.uniroma2.it>
9 Dmitri Kuznetsov
9 Dmitri Kuznetsov
10 Jared Bunting <jared.bunting@peachjean.com>
10 Jared Bunting <jared.bunting@peachjean.com>
11 Steve Romanow <slestak989@gmail.com>
11 Steve Romanow <slestak989@gmail.com>
12 Augosto Hermann <augusto.herrmann@planejamento.gov.br>
12 Augosto Hermann <augusto.herrmann@planejamento.gov.br>
13 Ankit Solanki <ankit.solanki@gmail.com>
13 Ankit Solanki <ankit.solanki@gmail.com>
14 Liad Shani <liadff@gmail.com>
14 Liad Shani <liadff@gmail.com>
15 Les Peabody <lpeabody@gmail.com>
15 Les Peabody <lpeabody@gmail.com>
16 Jonas Oberschweiber <jonas.oberschweiber@d-velop.de>
16 Jonas Oberschweiber <jonas.oberschweiber@d-velop.de>
17 Matt Zuba <matt.zuba@goodwillaz.org>
17 Matt Zuba <matt.zuba@goodwillaz.org>
18 Aras Pranckevicius <aras@unity3d.com> No newline at end of file
18 Aras Pranckevicius <aras@unity3d.com>
19 Tony Bussieres <t.bussieres@gmail.com>
@@ -1,533 +1,534 b''
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
24
24 1.3.1 (**2012-02-27**)
25 1.3.1 (**2012-02-27**)
25 ----------------------
26 ----------------------
26
27
27 news
28 news
28 ++++
29 ++++
29
30
30
31
31 fixes
32 fixes
32 +++++
33 +++++
33
34
34 - redirection loop occurs when remember-me wasn't checked during login
35 - redirection loop occurs when remember-me wasn't checked during login
35 - fixes issues with git blob history generation
36 - fixes issues with git blob history generation
36 - don't fetch branch for git in file history dropdown. Causes unneeded slowness
37 - don't fetch branch for git in file history dropdown. Causes unneeded slowness
37
38
38 1.3.0 (**2012-02-26**)
39 1.3.0 (**2012-02-26**)
39 ----------------------
40 ----------------------
40
41
41 news
42 news
42 ++++
43 ++++
43
44
44 - code review, inspired by github code-comments
45 - code review, inspired by github code-comments
45 - #215 rst and markdown README files support
46 - #215 rst and markdown README files support
46 - #252 Container-based and proxy pass-through authentication support
47 - #252 Container-based and proxy pass-through authentication support
47 - #44 branch browser. Filtering of changelog by branches
48 - #44 branch browser. Filtering of changelog by branches
48 - mercurial bookmarks support
49 - mercurial bookmarks support
49 - new hover top menu, optimized to add maximum size for important views
50 - new hover top menu, optimized to add maximum size for important views
50 - configurable clone url template with possibility to specify protocol like
51 - configurable clone url template with possibility to specify protocol like
51 ssh:// or http:// and also manually alter other parts of clone_url.
52 ssh:// or http:// and also manually alter other parts of clone_url.
52 - enabled largefiles extension by default
53 - enabled largefiles extension by default
53 - optimized summary file pages and saved a lot of unused space in them
54 - optimized summary file pages and saved a lot of unused space in them
54 - #239 option to manually mark repository as fork
55 - #239 option to manually mark repository as fork
55 - #320 mapping of commit authors to RhodeCode users
56 - #320 mapping of commit authors to RhodeCode users
56 - #304 hashes are displayed using monospace font
57 - #304 hashes are displayed using monospace font
57 - diff configuration, toggle white lines and context lines
58 - diff configuration, toggle white lines and context lines
58 - #307 configurable diffs, whitespace toggle, increasing context lines
59 - #307 configurable diffs, whitespace toggle, increasing context lines
59 - sorting on branches, tags and bookmarks using YUI datatable
60 - sorting on branches, tags and bookmarks using YUI datatable
60 - improved file filter on files page
61 - improved file filter on files page
61 - implements #330 api method for listing nodes ar particular revision
62 - implements #330 api method for listing nodes ar particular revision
62 - #73 added linking issues in commit messages to chosen issue tracker url
63 - #73 added linking issues in commit messages to chosen issue tracker url
63 based on user defined regular expression
64 based on user defined regular expression
64 - added linking of changesets in commit messages
65 - added linking of changesets in commit messages
65 - new compact changelog with expandable commit messages
66 - new compact changelog with expandable commit messages
66 - firstname and lastname are optional in user creation
67 - firstname and lastname are optional in user creation
67 - #348 added post-create repository hook
68 - #348 added post-create repository hook
68 - #212 global encoding settings is now configurable from .ini files
69 - #212 global encoding settings is now configurable from .ini files
69 - #227 added repository groups permissions
70 - #227 added repository groups permissions
70 - markdown gets codehilite extensions
71 - markdown gets codehilite extensions
71 - new API methods, delete_repositories, grante/revoke permissions for groups
72 - new API methods, delete_repositories, grante/revoke permissions for groups
72 and repos
73 and repos
73
74
74
75
75 fixes
76 fixes
76 +++++
77 +++++
77
78
78 - rewrote dbsession management for atomic operations, and better error handling
79 - rewrote dbsession management for atomic operations, and better error handling
79 - fixed sorting of repo tables
80 - fixed sorting of repo tables
80 - #326 escape of special html entities in diffs
81 - #326 escape of special html entities in diffs
81 - normalized user_name => username in api attributes
82 - normalized user_name => username in api attributes
82 - fixes #298 ldap created users with mixed case emails created conflicts
83 - fixes #298 ldap created users with mixed case emails created conflicts
83 on saving a form
84 on saving a form
84 - fixes issue when owner of a repo couldn't revoke permissions for users
85 - fixes issue when owner of a repo couldn't revoke permissions for users
85 and groups
86 and groups
86 - fixes #271 rare JSON serialization problem with statistics
87 - fixes #271 rare JSON serialization problem with statistics
87 - fixes #337 missing validation check for conflicting names of a group with a
88 - fixes #337 missing validation check for conflicting names of a group with a
88 repositories group
89 repositories group
89 - #340 fixed session problem for mysql and celery tasks
90 - #340 fixed session problem for mysql and celery tasks
90 - fixed #331 RhodeCode mangles repository names if the a repository group
91 - fixed #331 RhodeCode mangles repository names if the a repository group
91 contains the "full path" to the repositories
92 contains the "full path" to the repositories
92 - #355 RhodeCode doesn't store encrypted LDAP passwords
93 - #355 RhodeCode doesn't store encrypted LDAP passwords
93
94
94 1.2.5 (**2012-01-28**)
95 1.2.5 (**2012-01-28**)
95 ----------------------
96 ----------------------
96
97
97 news
98 news
98 ++++
99 ++++
99
100
100 fixes
101 fixes
101 +++++
102 +++++
102
103
103 - #340 Celery complains about MySQL server gone away, added session cleanup
104 - #340 Celery complains about MySQL server gone away, added session cleanup
104 for celery tasks
105 for celery tasks
105 - #341 "scanning for repositories in None" log message during Rescan was missing
106 - #341 "scanning for repositories in None" log message during Rescan was missing
106 a parameter
107 a parameter
107 - fixed creating archives with subrepos. Some hooks were triggered during that
108 - fixed creating archives with subrepos. Some hooks were triggered during that
108 operation leading to crash.
109 operation leading to crash.
109 - fixed missing email in account page.
110 - fixed missing email in account page.
110 - Reverted Mercurial to 2.0.1 for windows due to bug in Mercurial that makes
111 - Reverted Mercurial to 2.0.1 for windows due to bug in Mercurial that makes
111 forking on windows impossible
112 forking on windows impossible
112
113
113 1.2.4 (**2012-01-19**)
114 1.2.4 (**2012-01-19**)
114 ----------------------
115 ----------------------
115
116
116 news
117 news
117 ++++
118 ++++
118
119
119 - RhodeCode is bundled with mercurial series 2.0.X by default, with
120 - RhodeCode is bundled with mercurial series 2.0.X by default, with
120 full support to largefiles extension. Enabled by default in new installations
121 full support to largefiles extension. Enabled by default in new installations
121 - #329 Ability to Add/Remove Groups to/from a Repository via AP
122 - #329 Ability to Add/Remove Groups to/from a Repository via AP
122 - added requires.txt file with requirements
123 - added requires.txt file with requirements
123
124
124 fixes
125 fixes
125 +++++
126 +++++
126
127
127 - fixes db session issues with celery when emailing admins
128 - fixes db session issues with celery when emailing admins
128 - #331 RhodeCode mangles repository names if the a repository group
129 - #331 RhodeCode mangles repository names if the a repository group
129 contains the "full path" to the repositories
130 contains the "full path" to the repositories
130 - #298 Conflicting e-mail addresses for LDAP and RhodeCode users
131 - #298 Conflicting e-mail addresses for LDAP and RhodeCode users
131 - DB session cleanup after hg protocol operations, fixes issues with
132 - DB session cleanup after hg protocol operations, fixes issues with
132 `mysql has gone away` errors
133 `mysql has gone away` errors
133 - #333 doc fixes for get_repo api function
134 - #333 doc fixes for get_repo api function
134 - #271 rare JSON serialization problem with statistics enabled
135 - #271 rare JSON serialization problem with statistics enabled
135 - #337 Fixes issues with validation of repository name conflicting with
136 - #337 Fixes issues with validation of repository name conflicting with
136 a group name. A proper message is now displayed.
137 a group name. A proper message is now displayed.
137 - #292 made ldap_dn in user edit readonly, to get rid of confusion that field
138 - #292 made ldap_dn in user edit readonly, to get rid of confusion that field
138 doesn't work
139 doesn't work
139 - #316 fixes issues with web description in hgrc files
140 - #316 fixes issues with web description in hgrc files
140
141
141 1.2.3 (**2011-11-02**)
142 1.2.3 (**2011-11-02**)
142 ----------------------
143 ----------------------
143
144
144 news
145 news
145 ++++
146 ++++
146
147
147 - added option to manage repos group for non admin users
148 - added option to manage repos group for non admin users
148 - added following API methods for get_users, create_user, get_users_groups,
149 - added following API methods for get_users, create_user, get_users_groups,
149 get_users_group, create_users_group, add_user_to_users_groups, get_repos,
150 get_users_group, create_users_group, add_user_to_users_groups, get_repos,
150 get_repo, create_repo, add_user_to_repo
151 get_repo, create_repo, add_user_to_repo
151 - implements #237 added password confirmation for my account
152 - implements #237 added password confirmation for my account
152 and admin edit user.
153 and admin edit user.
153 - implements #291 email notification for global events are now sent to all
154 - implements #291 email notification for global events are now sent to all
154 administrator users, and global config email.
155 administrator users, and global config email.
155
156
156 fixes
157 fixes
157 +++++
158 +++++
158
159
159 - added option for passing auth method for smtp mailer
160 - added option for passing auth method for smtp mailer
160 - #276 issue with adding a single user with id>10 to usergroups
161 - #276 issue with adding a single user with id>10 to usergroups
161 - #277 fixes windows LDAP settings in which missing values breaks the ldap auth
162 - #277 fixes windows LDAP settings in which missing values breaks the ldap auth
162 - #288 fixes managing of repos in a group for non admin user
163 - #288 fixes managing of repos in a group for non admin user
163
164
164 1.2.2 (**2011-10-17**)
165 1.2.2 (**2011-10-17**)
165 ----------------------
166 ----------------------
166
167
167 news
168 news
168 ++++
169 ++++
169
170
170 - #226 repo groups are available by path instead of numerical id
171 - #226 repo groups are available by path instead of numerical id
171
172
172 fixes
173 fixes
173 +++++
174 +++++
174
175
175 - #259 Groups with the same name but with different parent group
176 - #259 Groups with the same name but with different parent group
176 - #260 Put repo in group, then move group to another group -> repo becomes unavailable
177 - #260 Put repo in group, then move group to another group -> repo becomes unavailable
177 - #258 RhodeCode 1.2 assumes egg folder is writable (lockfiles problems)
178 - #258 RhodeCode 1.2 assumes egg folder is writable (lockfiles problems)
178 - #265 ldap save fails sometimes on converting attributes to booleans,
179 - #265 ldap save fails sometimes on converting attributes to booleans,
179 added getter and setter into model that will prevent from this on db model level
180 added getter and setter into model that will prevent from this on db model level
180 - fixed problems with timestamps issues #251 and #213
181 - fixed problems with timestamps issues #251 and #213
181 - fixes #266 RhodeCode allows to create repo with the same name and in
182 - fixes #266 RhodeCode allows to create repo with the same name and in
182 the same parent as group
183 the same parent as group
183 - fixes #245 Rescan of the repositories on Windows
184 - fixes #245 Rescan of the repositories on Windows
184 - fixes #248 cannot edit repos inside a group on windows
185 - fixes #248 cannot edit repos inside a group on windows
185 - fixes #219 forking problems on windows
186 - fixes #219 forking problems on windows
186
187
187 1.2.1 (**2011-10-08**)
188 1.2.1 (**2011-10-08**)
188 ----------------------
189 ----------------------
189
190
190 news
191 news
191 ++++
192 ++++
192
193
193
194
194 fixes
195 fixes
195 +++++
196 +++++
196
197
197 - fixed problems with basic auth and push problems
198 - fixed problems with basic auth and push problems
198 - gui fixes
199 - gui fixes
199 - fixed logger
200 - fixed logger
200
201
201 1.2.0 (**2011-10-07**)
202 1.2.0 (**2011-10-07**)
202 ----------------------
203 ----------------------
203
204
204 news
205 news
205 ++++
206 ++++
206
207
207 - implemented #47 repository groups
208 - implemented #47 repository groups
208 - implemented #89 Can setup google analytics code from settings menu
209 - implemented #89 Can setup google analytics code from settings menu
209 - implemented #91 added nicer looking archive urls with more download options
210 - implemented #91 added nicer looking archive urls with more download options
210 like tags, branches
211 like tags, branches
211 - implemented #44 into file browsing, and added follow branch option
212 - implemented #44 into file browsing, and added follow branch option
212 - implemented #84 downloads can be enabled/disabled for each repository
213 - implemented #84 downloads can be enabled/disabled for each repository
213 - anonymous repository can be cloned without having to pass default:default
214 - anonymous repository can be cloned without having to pass default:default
214 into clone url
215 into clone url
215 - fixed #90 whoosh indexer can index chooses repositories passed in command
216 - fixed #90 whoosh indexer can index chooses repositories passed in command
216 line
217 line
217 - extended journal with day aggregates and paging
218 - extended journal with day aggregates and paging
218 - implemented #107 source code lines highlight ranges
219 - implemented #107 source code lines highlight ranges
219 - implemented #93 customizable changelog on combined revision ranges -
220 - implemented #93 customizable changelog on combined revision ranges -
220 equivalent of githubs compare view
221 equivalent of githubs compare view
221 - implemented #108 extended and more powerful LDAP configuration
222 - implemented #108 extended and more powerful LDAP configuration
222 - implemented #56 users groups
223 - implemented #56 users groups
223 - major code rewrites optimized codes for speed and memory usage
224 - major code rewrites optimized codes for speed and memory usage
224 - raw and diff downloads are now in git format
225 - raw and diff downloads are now in git format
225 - setup command checks for write access to given path
226 - setup command checks for write access to given path
226 - fixed many issues with international characters and unicode. It uses utf8
227 - fixed many issues with international characters and unicode. It uses utf8
227 decode with replace to provide less errors even with non utf8 encoded strings
228 decode with replace to provide less errors even with non utf8 encoded strings
228 - #125 added API KEY access to feeds
229 - #125 added API KEY access to feeds
229 - #109 Repository can be created from external Mercurial link (aka. remote
230 - #109 Repository can be created from external Mercurial link (aka. remote
230 repository, and manually updated (via pull) from admin panel
231 repository, and manually updated (via pull) from admin panel
231 - beta git support - push/pull server + basic view for git repos
232 - beta git support - push/pull server + basic view for git repos
232 - added followers page and forks page
233 - added followers page and forks page
233 - server side file creation (with binary file upload interface)
234 - server side file creation (with binary file upload interface)
234 and edition with commits powered by codemirror
235 and edition with commits powered by codemirror
235 - #111 file browser file finder, quick lookup files on whole file tree
236 - #111 file browser file finder, quick lookup files on whole file tree
236 - added quick login sliding menu into main page
237 - added quick login sliding menu into main page
237 - changelog uses lazy loading of affected files details, in some scenarios
238 - changelog uses lazy loading of affected files details, in some scenarios
238 this can improve speed of changelog page dramatically especially for
239 this can improve speed of changelog page dramatically especially for
239 larger repositories.
240 larger repositories.
240 - implements #214 added support for downloading subrepos in download menu.
241 - implements #214 added support for downloading subrepos in download menu.
241 - Added basic API for direct operations on rhodecode via JSON
242 - Added basic API for direct operations on rhodecode via JSON
242 - Implemented advanced hook management
243 - Implemented advanced hook management
243
244
244 fixes
245 fixes
245 +++++
246 +++++
246
247
247 - fixed file browser bug, when switching into given form revision the url was
248 - fixed file browser bug, when switching into given form revision the url was
248 not changing
249 not changing
249 - fixed propagation to error controller on simplehg and simplegit middlewares
250 - fixed propagation to error controller on simplehg and simplegit middlewares
250 - fixed error when trying to make a download on empty repository
251 - fixed error when trying to make a download on empty repository
251 - fixed problem with '[' chars in commit messages in journal
252 - fixed problem with '[' chars in commit messages in journal
252 - fixed #99 Unicode errors, on file node paths with non utf-8 characters
253 - fixed #99 Unicode errors, on file node paths with non utf-8 characters
253 - journal fork fixes
254 - journal fork fixes
254 - removed issue with space inside renamed repository after deletion
255 - removed issue with space inside renamed repository after deletion
255 - fixed strange issue on formencode imports
256 - fixed strange issue on formencode imports
256 - fixed #126 Deleting repository on Windows, rename used incompatible chars.
257 - fixed #126 Deleting repository on Windows, rename used incompatible chars.
257 - #150 fixes for errors on repositories mapped in db but corrupted in
258 - #150 fixes for errors on repositories mapped in db but corrupted in
258 filesystem
259 filesystem
259 - fixed problem with ascendant characters in realm #181
260 - fixed problem with ascendant characters in realm #181
260 - fixed problem with sqlite file based database connection pool
261 - fixed problem with sqlite file based database connection pool
261 - whoosh indexer and code stats share the same dynamic extensions map
262 - whoosh indexer and code stats share the same dynamic extensions map
262 - fixes #188 - relationship delete of repo_to_perm entry on user removal
263 - fixes #188 - relationship delete of repo_to_perm entry on user removal
263 - fixes issue #189 Trending source files shows "show more" when no more exist
264 - fixes issue #189 Trending source files shows "show more" when no more exist
264 - fixes issue #197 Relative paths for pidlocks
265 - fixes issue #197 Relative paths for pidlocks
265 - fixes issue #198 password will require only 3 chars now for login form
266 - fixes issue #198 password will require only 3 chars now for login form
266 - fixes issue #199 wrong redirection for non admin users after creating a repository
267 - fixes issue #199 wrong redirection for non admin users after creating a repository
267 - fixes issues #202, bad db constraint made impossible to attach same group
268 - fixes issues #202, bad db constraint made impossible to attach same group
268 more than one time. Affects only mysql/postgres
269 more than one time. Affects only mysql/postgres
269 - fixes #218 os.kill patch for windows was missing sig param
270 - fixes #218 os.kill patch for windows was missing sig param
270 - improved rendering of dag (they are not trimmed anymore when number of
271 - improved rendering of dag (they are not trimmed anymore when number of
271 heads exceeds 5)
272 heads exceeds 5)
272
273
273 1.1.8 (**2011-04-12**)
274 1.1.8 (**2011-04-12**)
274 ----------------------
275 ----------------------
275
276
276 news
277 news
277 ++++
278 ++++
278
279
279 - improved windows support
280 - improved windows support
280
281
281 fixes
282 fixes
282 +++++
283 +++++
283
284
284 - fixed #140 freeze of python dateutil library, since new version is python2.x
285 - fixed #140 freeze of python dateutil library, since new version is python2.x
285 incompatible
286 incompatible
286 - setup-app will check for write permission in given path
287 - setup-app will check for write permission in given path
287 - cleaned up license info issue #149
288 - cleaned up license info issue #149
288 - fixes for issues #137,#116 and problems with unicode and accented characters.
289 - fixes for issues #137,#116 and problems with unicode and accented characters.
289 - fixes crashes on gravatar, when passed in email as unicode
290 - fixes crashes on gravatar, when passed in email as unicode
290 - fixed tooltip flickering problems
291 - fixed tooltip flickering problems
291 - fixed came_from redirection on windows
292 - fixed came_from redirection on windows
292 - fixed logging modules, and sql formatters
293 - fixed logging modules, and sql formatters
293 - windows fixes for os.kill issue #133
294 - windows fixes for os.kill issue #133
294 - fixes path splitting for windows issues #148
295 - fixes path splitting for windows issues #148
295 - fixed issue #143 wrong import on migration to 1.1.X
296 - fixed issue #143 wrong import on migration to 1.1.X
296 - fixed problems with displaying binary files, thanks to Thomas Waldmann
297 - fixed problems with displaying binary files, thanks to Thomas Waldmann
297 - removed name from archive files since it's breaking ui for long repo names
298 - removed name from archive files since it's breaking ui for long repo names
298 - fixed issue with archive headers sent to browser, thanks to Thomas Waldmann
299 - fixed issue with archive headers sent to browser, thanks to Thomas Waldmann
299 - fixed compatibility for 1024px displays, and larger dpi settings, thanks to
300 - fixed compatibility for 1024px displays, and larger dpi settings, thanks to
300 Thomas Waldmann
301 Thomas Waldmann
301 - fixed issue #166 summary pager was skipping 10 revisions on second page
302 - fixed issue #166 summary pager was skipping 10 revisions on second page
302
303
303
304
304 1.1.7 (**2011-03-23**)
305 1.1.7 (**2011-03-23**)
305 ----------------------
306 ----------------------
306
307
307 news
308 news
308 ++++
309 ++++
309
310
310 fixes
311 fixes
311 +++++
312 +++++
312
313
313 - fixed (again) #136 installation support for FreeBSD
314 - fixed (again) #136 installation support for FreeBSD
314
315
315
316
316 1.1.6 (**2011-03-21**)
317 1.1.6 (**2011-03-21**)
317 ----------------------
318 ----------------------
318
319
319 news
320 news
320 ++++
321 ++++
321
322
322 fixes
323 fixes
323 +++++
324 +++++
324
325
325 - fixed #136 installation support for FreeBSD
326 - fixed #136 installation support for FreeBSD
326 - RhodeCode will check for python version during installation
327 - RhodeCode will check for python version during installation
327
328
328 1.1.5 (**2011-03-17**)
329 1.1.5 (**2011-03-17**)
329 ----------------------
330 ----------------------
330
331
331 news
332 news
332 ++++
333 ++++
333
334
334 - basic windows support, by exchanging pybcrypt into sha256 for windows only
335 - basic windows support, by exchanging pybcrypt into sha256 for windows only
335 highly inspired by idea of mantis406
336 highly inspired by idea of mantis406
336
337
337 fixes
338 fixes
338 +++++
339 +++++
339
340
340 - fixed sorting by author in main page
341 - fixed sorting by author in main page
341 - fixed crashes with diffs on binary files
342 - fixed crashes with diffs on binary files
342 - fixed #131 problem with boolean values for LDAP
343 - fixed #131 problem with boolean values for LDAP
343 - fixed #122 mysql problems thanks to striker69
344 - fixed #122 mysql problems thanks to striker69
344 - fixed problem with errors on calling raw/raw_files/annotate functions
345 - fixed problem with errors on calling raw/raw_files/annotate functions
345 with unknown revisions
346 with unknown revisions
346 - fixed returned rawfiles attachment names with international character
347 - fixed returned rawfiles attachment names with international character
347 - cleaned out docs, big thanks to Jason Harris
348 - cleaned out docs, big thanks to Jason Harris
348
349
349 1.1.4 (**2011-02-19**)
350 1.1.4 (**2011-02-19**)
350 ----------------------
351 ----------------------
351
352
352 news
353 news
353 ++++
354 ++++
354
355
355 fixes
356 fixes
356 +++++
357 +++++
357
358
358 - fixed formencode import problem on settings page, that caused server crash
359 - fixed formencode import problem on settings page, that caused server crash
359 when that page was accessed as first after server start
360 when that page was accessed as first after server start
360 - journal fixes
361 - journal fixes
361 - fixed option to access repository just by entering http://server/<repo_name>
362 - fixed option to access repository just by entering http://server/<repo_name>
362
363
363 1.1.3 (**2011-02-16**)
364 1.1.3 (**2011-02-16**)
364 ----------------------
365 ----------------------
365
366
366 news
367 news
367 ++++
368 ++++
368
369
369 - implemented #102 allowing the '.' character in username
370 - implemented #102 allowing the '.' character in username
370 - added option to access repository just by entering http://server/<repo_name>
371 - added option to access repository just by entering http://server/<repo_name>
371 - celery task ignores result for better performance
372 - celery task ignores result for better performance
372
373
373 fixes
374 fixes
374 +++++
375 +++++
375
376
376 - fixed ehlo command and non auth mail servers on smtp_lib. Thanks to
377 - fixed ehlo command and non auth mail servers on smtp_lib. Thanks to
377 apollo13 and Johan Walles
378 apollo13 and Johan Walles
378 - small fixes in journal
379 - small fixes in journal
379 - fixed problems with getting setting for celery from .ini files
380 - fixed problems with getting setting for celery from .ini files
380 - registration, password reset and login boxes share the same title as main
381 - registration, password reset and login boxes share the same title as main
381 application now
382 application now
382 - fixed #113: to high permissions to fork repository
383 - fixed #113: to high permissions to fork repository
383 - fixed problem with '[' chars in commit messages in journal
384 - fixed problem with '[' chars in commit messages in journal
384 - removed issue with space inside renamed repository after deletion
385 - removed issue with space inside renamed repository after deletion
385 - db transaction fixes when filesystem repository creation failed
386 - db transaction fixes when filesystem repository creation failed
386 - fixed #106 relation issues on databases different than sqlite
387 - fixed #106 relation issues on databases different than sqlite
387 - fixed static files paths links to use of url() method
388 - fixed static files paths links to use of url() method
388
389
389 1.1.2 (**2011-01-12**)
390 1.1.2 (**2011-01-12**)
390 ----------------------
391 ----------------------
391
392
392 news
393 news
393 ++++
394 ++++
394
395
395
396
396 fixes
397 fixes
397 +++++
398 +++++
398
399
399 - fixes #98 protection against float division of percentage stats
400 - fixes #98 protection against float division of percentage stats
400 - fixed graph bug
401 - fixed graph bug
401 - forced webhelpers version since it was making troubles during installation
402 - forced webhelpers version since it was making troubles during installation
402
403
403 1.1.1 (**2011-01-06**)
404 1.1.1 (**2011-01-06**)
404 ----------------------
405 ----------------------
405
406
406 news
407 news
407 ++++
408 ++++
408
409
409 - added force https option into ini files for easier https usage (no need to
410 - added force https option into ini files for easier https usage (no need to
410 set server headers with this options)
411 set server headers with this options)
411 - small css updates
412 - small css updates
412
413
413 fixes
414 fixes
414 +++++
415 +++++
415
416
416 - fixed #96 redirect loop on files view on repositories without changesets
417 - fixed #96 redirect loop on files view on repositories without changesets
417 - fixed #97 unicode string passed into server header in special cases (mod_wsgi)
418 - fixed #97 unicode string passed into server header in special cases (mod_wsgi)
418 and server crashed with errors
419 and server crashed with errors
419 - fixed large tooltips problems on main page
420 - fixed large tooltips problems on main page
420 - fixed #92 whoosh indexer is more error proof
421 - fixed #92 whoosh indexer is more error proof
421
422
422 1.1.0 (**2010-12-18**)
423 1.1.0 (**2010-12-18**)
423 ----------------------
424 ----------------------
424
425
425 news
426 news
426 ++++
427 ++++
427
428
428 - rewrite of internals for vcs >=0.1.10
429 - rewrite of internals for vcs >=0.1.10
429 - uses mercurial 1.7 with dotencode disabled for maintaining compatibility
430 - uses mercurial 1.7 with dotencode disabled for maintaining compatibility
430 with older clients
431 with older clients
431 - anonymous access, authentication via ldap
432 - anonymous access, authentication via ldap
432 - performance upgrade for cached repos list - each repository has its own
433 - performance upgrade for cached repos list - each repository has its own
433 cache that's invalidated when needed.
434 cache that's invalidated when needed.
434 - performance upgrades on repositories with large amount of commits (20K+)
435 - performance upgrades on repositories with large amount of commits (20K+)
435 - main page quick filter for filtering repositories
436 - main page quick filter for filtering repositories
436 - user dashboards with ability to follow chosen repositories actions
437 - user dashboards with ability to follow chosen repositories actions
437 - sends email to admin on new user registration
438 - sends email to admin on new user registration
438 - added cache/statistics reset options into repository settings
439 - added cache/statistics reset options into repository settings
439 - more detailed action logger (based on hooks) with pushed changesets lists
440 - more detailed action logger (based on hooks) with pushed changesets lists
440 and options to disable those hooks from admin panel
441 and options to disable those hooks from admin panel
441 - introduced new enhanced changelog for merges that shows more accurate results
442 - introduced new enhanced changelog for merges that shows more accurate results
442 - new improved and faster code stats (based on pygments lexers mapping tables,
443 - new improved and faster code stats (based on pygments lexers mapping tables,
443 showing up to 10 trending sources for each repository. Additionally stats
444 showing up to 10 trending sources for each repository. Additionally stats
444 can be disabled in repository settings.
445 can be disabled in repository settings.
445 - gui optimizations, fixed application width to 1024px
446 - gui optimizations, fixed application width to 1024px
446 - added cut off (for large files/changesets) limit into config files
447 - added cut off (for large files/changesets) limit into config files
447 - whoosh, celeryd, upgrade moved to paster command
448 - whoosh, celeryd, upgrade moved to paster command
448 - other than sqlite database backends can be used
449 - other than sqlite database backends can be used
449
450
450 fixes
451 fixes
451 +++++
452 +++++
452
453
453 - fixes #61 forked repo was showing only after cache expired
454 - fixes #61 forked repo was showing only after cache expired
454 - fixes #76 no confirmation on user deletes
455 - fixes #76 no confirmation on user deletes
455 - fixes #66 Name field misspelled
456 - fixes #66 Name field misspelled
456 - fixes #72 block user removal when he owns repositories
457 - fixes #72 block user removal when he owns repositories
457 - fixes #69 added password confirmation fields
458 - fixes #69 added password confirmation fields
458 - fixes #87 RhodeCode crashes occasionally on updating repository owner
459 - fixes #87 RhodeCode crashes occasionally on updating repository owner
459 - fixes #82 broken annotations on files with more than 1 blank line at the end
460 - fixes #82 broken annotations on files with more than 1 blank line at the end
460 - a lot of fixes and tweaks for file browser
461 - a lot of fixes and tweaks for file browser
461 - fixed detached session issues
462 - fixed detached session issues
462 - fixed when user had no repos he would see all repos listed in my account
463 - fixed when user had no repos he would see all repos listed in my account
463 - fixed ui() instance bug when global hgrc settings was loaded for server
464 - fixed ui() instance bug when global hgrc settings was loaded for server
464 instance and all hgrc options were merged with our db ui() object
465 instance and all hgrc options were merged with our db ui() object
465 - numerous small bugfixes
466 - numerous small bugfixes
466
467
467 (special thanks for TkSoh for detailed feedback)
468 (special thanks for TkSoh for detailed feedback)
468
469
469
470
470 1.0.2 (**2010-11-12**)
471 1.0.2 (**2010-11-12**)
471 ----------------------
472 ----------------------
472
473
473 news
474 news
474 ++++
475 ++++
475
476
476 - tested under python2.7
477 - tested under python2.7
477 - bumped sqlalchemy and celery versions
478 - bumped sqlalchemy and celery versions
478
479
479 fixes
480 fixes
480 +++++
481 +++++
481
482
482 - fixed #59 missing graph.js
483 - fixed #59 missing graph.js
483 - fixed repo_size crash when repository had broken symlinks
484 - fixed repo_size crash when repository had broken symlinks
484 - fixed python2.5 crashes.
485 - fixed python2.5 crashes.
485
486
486
487
487 1.0.1 (**2010-11-10**)
488 1.0.1 (**2010-11-10**)
488 ----------------------
489 ----------------------
489
490
490 news
491 news
491 ++++
492 ++++
492
493
493 - small css updated
494 - small css updated
494
495
495 fixes
496 fixes
496 +++++
497 +++++
497
498
498 - fixed #53 python2.5 incompatible enumerate calls
499 - fixed #53 python2.5 incompatible enumerate calls
499 - fixed #52 disable mercurial extension for web
500 - fixed #52 disable mercurial extension for web
500 - fixed #51 deleting repositories don't delete it's dependent objects
501 - fixed #51 deleting repositories don't delete it's dependent objects
501
502
502
503
503 1.0.0 (**2010-11-02**)
504 1.0.0 (**2010-11-02**)
504 ----------------------
505 ----------------------
505
506
506 - security bugfix simplehg wasn't checking for permissions on commands
507 - security bugfix simplehg wasn't checking for permissions on commands
507 other than pull or push.
508 other than pull or push.
508 - fixed doubled messages after push or pull in admin journal
509 - fixed doubled messages after push or pull in admin journal
509 - templating and css corrections, fixed repo switcher on chrome, updated titles
510 - templating and css corrections, fixed repo switcher on chrome, updated titles
510 - admin menu accessible from options menu on repository view
511 - admin menu accessible from options menu on repository view
511 - permissions cached queries
512 - permissions cached queries
512
513
513 1.0.0rc4 (**2010-10-12**)
514 1.0.0rc4 (**2010-10-12**)
514 --------------------------
515 --------------------------
515
516
516 - fixed python2.5 missing simplejson imports (thanks to Jens Bäckman)
517 - fixed python2.5 missing simplejson imports (thanks to Jens Bäckman)
517 - removed cache_manager settings from sqlalchemy meta
518 - removed cache_manager settings from sqlalchemy meta
518 - added sqlalchemy cache settings to ini files
519 - added sqlalchemy cache settings to ini files
519 - validated password length and added second try of failure on paster setup-app
520 - validated password length and added second try of failure on paster setup-app
520 - fixed setup database destroy prompt even when there was no db
521 - fixed setup database destroy prompt even when there was no db
521
522
522
523
523 1.0.0rc3 (**2010-10-11**)
524 1.0.0rc3 (**2010-10-11**)
524 -------------------------
525 -------------------------
525
526
526 - fixed i18n during installation.
527 - fixed i18n during installation.
527
528
528 1.0.0rc2 (**2010-10-11**)
529 1.0.0rc2 (**2010-10-11**)
529 -------------------------
530 -------------------------
530
531
531 - Disabled dirsize in file browser, it's causing nasty bug when dir renames
532 - Disabled dirsize in file browser, it's causing nasty bug when dir renames
532 occure. After vcs is fixed it'll be put back again.
533 occure. After vcs is fixed it'll be put back again.
533 - templating/css rewrites, optimized css. No newline at end of file
534 - templating/css rewrites, optimized css.
@@ -1,18 +1,18 b''
1 {% extends "basic/layout.html" %}
1 {% extends "basic/layout.html" %}
2
2
3 {% block sidebarlogo %}
3 {% block sidebarlogo %}
4 <h3>Support RhodeCode development.</h3>
4 <h3>Support RhodeCode development.</h3>
5 <div style="text-align:center">
5 <div style="text-align:center">
6 <form action="https://www.paypal.com/cgi-bin/webscr" method="post">
6 <form action="https://www.paypal.com/cgi-bin/webscr" method="post">
7 <input type="hidden" name="cmd" value="_s-xclick">
7 <input type="hidden" name="cmd" value="_s-xclick">
8 <input type="hidden" name="hosted_button_id" value="8U2LLRPLBKWDU">
8 <input type="hidden" name="hosted_button_id" value="8U2LLRPLBKWDU">
9 <input style="border:0px !important" type="image" src="https://www.paypal.com/en_US/i/btn/btn_donate_SM.gif"
9 <input style="border:0px !important" type="image" src="https://www.paypal.com/en_US/i/btn/btn_donate_SM.gif"
10 border="0" name="submit" alt="PayPal - The safer, easier way to pay online!">
10 border="0" name="submit" alt="PayPal - The safer, easier way to pay online!">
11 <img alt="" border="0" src="https://www.paypal.com/en_US/i/scr/pixel.gif" width="1" height="1">
11 <img alt="" border="0" src="https://www.paypal.com/en_US/i/scr/pixel.gif" width="1" height="1">
12 </form>
12 </form>
13 <div style="padding:5px">
13 <div style="padding:5px">
14 <a href="http://flattr.com/thing/167489/RhodeCode" target="_blank">
14 <a href="http://flattr.com/thing/167489/RhodeCode" target="_blank">
15 <img src="http://api.flattr.com/button/flattr-badge-large.png" alt="Flattr this" title="Flattr this" border="0" /></a>
15 <img src="http://api.flattr.com/button/flattr-badge-large.png" alt="Flattr this" title="Flattr this" border="0" /></a>
16 </div>
16 </div>
17 </div>
17 </div>
18 {% endblock %}}
18 {% endblock %}}
@@ -1,465 +1,465 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.lib.__init__
3 rhodecode.lib.__init__
4 ~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 Some simple helper functions
6 Some simple helper functions
7
7
8 :created_on: Jan 5, 2011
8 :created_on: Jan 5, 2011
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2011-2012 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2011-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 re
27 import re
28 from rhodecode.lib.vcs.utils.lazy import LazyProperty
28 from rhodecode.lib.vcs.utils.lazy import LazyProperty
29
29
30
30
31 def __get_lem():
31 def __get_lem():
32 from pygments import lexers
32 from pygments import lexers
33 from string import lower
33 from string import lower
34 from collections import defaultdict
34 from collections import defaultdict
35
35
36 d = defaultdict(lambda: [])
36 d = defaultdict(lambda: [])
37
37
38 def __clean(s):
38 def __clean(s):
39 s = s.lstrip('*')
39 s = s.lstrip('*')
40 s = s.lstrip('.')
40 s = s.lstrip('.')
41
41
42 if s.find('[') != -1:
42 if s.find('[') != -1:
43 exts = []
43 exts = []
44 start, stop = s.find('['), s.find(']')
44 start, stop = s.find('['), s.find(']')
45
45
46 for suffix in s[start + 1:stop]:
46 for suffix in s[start + 1:stop]:
47 exts.append(s[:s.find('[')] + suffix)
47 exts.append(s[:s.find('[')] + suffix)
48 return map(lower, exts)
48 return map(lower, exts)
49 else:
49 else:
50 return map(lower, [s])
50 return map(lower, [s])
51
51
52 for lx, t in sorted(lexers.LEXERS.items()):
52 for lx, t in sorted(lexers.LEXERS.items()):
53 m = map(__clean, t[-2])
53 m = map(__clean, t[-2])
54 if m:
54 if m:
55 m = reduce(lambda x, y: x + y, m)
55 m = reduce(lambda x, y: x + y, m)
56 for ext in m:
56 for ext in m:
57 desc = lx.replace('Lexer', '')
57 desc = lx.replace('Lexer', '')
58 d[ext].append(desc)
58 d[ext].append(desc)
59
59
60 return dict(d)
60 return dict(d)
61
61
62 # language map is also used by whoosh indexer, which for those specified
62 # language map is also used by whoosh indexer, which for those specified
63 # extensions will index it's content
63 # extensions will index it's content
64 LANGUAGES_EXTENSIONS_MAP = __get_lem()
64 LANGUAGES_EXTENSIONS_MAP = __get_lem()
65
65
66 # Additional mappings that are not present in the pygments lexers
66 # Additional mappings that are not present in the pygments lexers
67 # NOTE: that this will overide any mappings in LANGUAGES_EXTENSIONS_MAP
67 # NOTE: that this will overide any mappings in LANGUAGES_EXTENSIONS_MAP
68 ADDITIONAL_MAPPINGS = {'xaml': 'XAML'}
68 ADDITIONAL_MAPPINGS = {'xaml': 'XAML'}
69
69
70 LANGUAGES_EXTENSIONS_MAP.update(ADDITIONAL_MAPPINGS)
70 LANGUAGES_EXTENSIONS_MAP.update(ADDITIONAL_MAPPINGS)
71
71
72 # list of readme files to search in file tree and display in summary
72 # list of readme files to search in file tree and display in summary
73 # attached weights defines the search order lower is first
73 # attached weights defines the search order lower is first
74 ALL_READMES = [
74 ALL_READMES = [
75 ('readme', 0), ('README', 0), ('Readme', 0),
75 ('readme', 0), ('README', 0), ('Readme', 0),
76 ('doc/readme', 1), ('doc/README', 1), ('doc/Readme', 1),
76 ('doc/readme', 1), ('doc/README', 1), ('doc/Readme', 1),
77 ('Docs/readme', 2), ('Docs/README', 2), ('Docs/Readme', 2),
77 ('Docs/readme', 2), ('Docs/README', 2), ('Docs/Readme', 2),
78 ('DOCS/readme', 2), ('DOCS/README', 2), ('DOCS/Readme', 2),
78 ('DOCS/readme', 2), ('DOCS/README', 2), ('DOCS/Readme', 2),
79 ('docs/readme', 2), ('docs/README', 2), ('docs/Readme', 2),
79 ('docs/readme', 2), ('docs/README', 2), ('docs/Readme', 2),
80 ]
80 ]
81
81
82 # extension together with weights to search lower is first
82 # extension together with weights to search lower is first
83 RST_EXTS = [
83 RST_EXTS = [
84 ('', 0), ('.rst', 1), ('.rest', 1),
84 ('', 0), ('.rst', 1), ('.rest', 1),
85 ('.RST', 2), ('.REST', 2),
85 ('.RST', 2), ('.REST', 2),
86 ('.txt', 3), ('.TXT', 3)
86 ('.txt', 3), ('.TXT', 3)
87 ]
87 ]
88
88
89 MARKDOWN_EXTS = [
89 MARKDOWN_EXTS = [
90 ('.md', 1), ('.MD', 1),
90 ('.md', 1), ('.MD', 1),
91 ('.mkdn', 2), ('.MKDN', 2),
91 ('.mkdn', 2), ('.MKDN', 2),
92 ('.mdown', 3), ('.MDOWN', 3),
92 ('.mdown', 3), ('.MDOWN', 3),
93 ('.markdown', 4), ('.MARKDOWN', 4)
93 ('.markdown', 4), ('.MARKDOWN', 4)
94 ]
94 ]
95
95
96 PLAIN_EXTS = [('.text', 2), ('.TEXT', 2)]
96 PLAIN_EXTS = [('.text', 2), ('.TEXT', 2)]
97
97
98 ALL_EXTS = MARKDOWN_EXTS + RST_EXTS + PLAIN_EXTS
98 ALL_EXTS = MARKDOWN_EXTS + RST_EXTS + PLAIN_EXTS
99
99
100
100
101 def str2bool(_str):
101 def str2bool(_str):
102 """
102 """
103 returs True/False value from given string, it tries to translate the
103 returs True/False value from given string, it tries to translate the
104 string into boolean
104 string into boolean
105
105
106 :param _str: string value to translate into boolean
106 :param _str: string value to translate into boolean
107 :rtype: boolean
107 :rtype: boolean
108 :returns: boolean from given string
108 :returns: boolean from given string
109 """
109 """
110 if _str is None:
110 if _str is None:
111 return False
111 return False
112 if _str in (True, False):
112 if _str in (True, False):
113 return _str
113 return _str
114 _str = str(_str).strip().lower()
114 _str = str(_str).strip().lower()
115 return _str in ('t', 'true', 'y', 'yes', 'on', '1')
115 return _str in ('t', 'true', 'y', 'yes', 'on', '1')
116
116
117
117
118 def convert_line_endings(line, mode):
118 def convert_line_endings(line, mode):
119 """
119 """
120 Converts a given line "line end" accordingly to given mode
120 Converts a given line "line end" accordingly to given mode
121
121
122 Available modes are::
122 Available modes are::
123 0 - Unix
123 0 - Unix
124 1 - Mac
124 1 - Mac
125 2 - DOS
125 2 - DOS
126
126
127 :param line: given line to convert
127 :param line: given line to convert
128 :param mode: mode to convert to
128 :param mode: mode to convert to
129 :rtype: str
129 :rtype: str
130 :return: converted line according to mode
130 :return: converted line according to mode
131 """
131 """
132 from string import replace
132 from string import replace
133
133
134 if mode == 0:
134 if mode == 0:
135 line = replace(line, '\r\n', '\n')
135 line = replace(line, '\r\n', '\n')
136 line = replace(line, '\r', '\n')
136 line = replace(line, '\r', '\n')
137 elif mode == 1:
137 elif mode == 1:
138 line = replace(line, '\r\n', '\r')
138 line = replace(line, '\r\n', '\r')
139 line = replace(line, '\n', '\r')
139 line = replace(line, '\n', '\r')
140 elif mode == 2:
140 elif mode == 2:
141 line = re.sub("\r(?!\n)|(?<!\r)\n", "\r\n", line)
141 line = re.sub("\r(?!\n)|(?<!\r)\n", "\r\n", line)
142 return line
142 return line
143
143
144
144
145 def detect_mode(line, default):
145 def detect_mode(line, default):
146 """
146 """
147 Detects line break for given line, if line break couldn't be found
147 Detects line break for given line, if line break couldn't be found
148 given default value is returned
148 given default value is returned
149
149
150 :param line: str line
150 :param line: str line
151 :param default: default
151 :param default: default
152 :rtype: int
152 :rtype: int
153 :return: value of line end on of 0 - Unix, 1 - Mac, 2 - DOS
153 :return: value of line end on of 0 - Unix, 1 - Mac, 2 - DOS
154 """
154 """
155 if line.endswith('\r\n'):
155 if line.endswith('\r\n'):
156 return 2
156 return 2
157 elif line.endswith('\n'):
157 elif line.endswith('\n'):
158 return 0
158 return 0
159 elif line.endswith('\r'):
159 elif line.endswith('\r'):
160 return 1
160 return 1
161 else:
161 else:
162 return default
162 return default
163
163
164
164
165 def generate_api_key(username, salt=None):
165 def generate_api_key(username, salt=None):
166 """
166 """
167 Generates unique API key for given username, if salt is not given
167 Generates unique API key for given username, if salt is not given
168 it'll be generated from some random string
168 it'll be generated from some random string
169
169
170 :param username: username as string
170 :param username: username as string
171 :param salt: salt to hash generate KEY
171 :param salt: salt to hash generate KEY
172 :rtype: str
172 :rtype: str
173 :returns: sha1 hash from username+salt
173 :returns: sha1 hash from username+salt
174 """
174 """
175 from tempfile import _RandomNameSequence
175 from tempfile import _RandomNameSequence
176 import hashlib
176 import hashlib
177
177
178 if salt is None:
178 if salt is None:
179 salt = _RandomNameSequence().next()
179 salt = _RandomNameSequence().next()
180
180
181 return hashlib.sha1(username + salt).hexdigest()
181 return hashlib.sha1(username + salt).hexdigest()
182
182
183
183
184 def safe_unicode(str_, from_encoding=None):
184 def safe_unicode(str_, from_encoding=None):
185 """
185 """
186 safe unicode function. Does few trick to turn str_ into unicode
186 safe unicode function. Does few trick to turn str_ into unicode
187
187
188 In case of UnicodeDecode error we try to return it with encoding detected
188 In case of UnicodeDecode error we try to return it with encoding detected
189 by chardet library if it fails fallback to unicode with errors replaced
189 by chardet library if it fails fallback to unicode with errors replaced
190
190
191 :param str_: string to decode
191 :param str_: string to decode
192 :rtype: unicode
192 :rtype: unicode
193 :returns: unicode object
193 :returns: unicode object
194 """
194 """
195 if isinstance(str_, unicode):
195 if isinstance(str_, unicode):
196 return str_
196 return str_
197
197
198 if not from_encoding:
198 if not from_encoding:
199 import rhodecode
199 import rhodecode
200 DEFAULT_ENCODING = rhodecode.CONFIG.get('default_encoding','utf8')
200 DEFAULT_ENCODING = rhodecode.CONFIG.get('default_encoding','utf8')
201 from_encoding = DEFAULT_ENCODING
201 from_encoding = DEFAULT_ENCODING
202
202
203 try:
203 try:
204 return unicode(str_)
204 return unicode(str_)
205 except UnicodeDecodeError:
205 except UnicodeDecodeError:
206 pass
206 pass
207
207
208 try:
208 try:
209 return unicode(str_, from_encoding)
209 return unicode(str_, from_encoding)
210 except UnicodeDecodeError:
210 except UnicodeDecodeError:
211 pass
211 pass
212
212
213 try:
213 try:
214 import chardet
214 import chardet
215 encoding = chardet.detect(str_)['encoding']
215 encoding = chardet.detect(str_)['encoding']
216 if encoding is None:
216 if encoding is None:
217 raise Exception()
217 raise Exception()
218 return str_.decode(encoding)
218 return str_.decode(encoding)
219 except (ImportError, UnicodeDecodeError, Exception):
219 except (ImportError, UnicodeDecodeError, Exception):
220 return unicode(str_, from_encoding, 'replace')
220 return unicode(str_, from_encoding, 'replace')
221
221
222
222
223 def safe_str(unicode_, to_encoding=None):
223 def safe_str(unicode_, to_encoding=None):
224 """
224 """
225 safe str function. Does few trick to turn unicode_ into string
225 safe str function. Does few trick to turn unicode_ into string
226
226
227 In case of UnicodeEncodeError we try to return it with encoding detected
227 In case of UnicodeEncodeError we try to return it with encoding detected
228 by chardet library if it fails fallback to string with errors replaced
228 by chardet library if it fails fallback to string with errors replaced
229
229
230 :param unicode_: unicode to encode
230 :param unicode_: unicode to encode
231 :rtype: str
231 :rtype: str
232 :returns: str object
232 :returns: str object
233 """
233 """
234
234
235 # if it's not basestr cast to str
235 # if it's not basestr cast to str
236 if not isinstance(unicode_, basestring):
236 if not isinstance(unicode_, basestring):
237 return str(unicode_)
237 return str(unicode_)
238
238
239 if isinstance(unicode_, str):
239 if isinstance(unicode_, str):
240 return unicode_
240 return unicode_
241
241
242 if not to_encoding:
242 if not to_encoding:
243 import rhodecode
243 import rhodecode
244 DEFAULT_ENCODING = rhodecode.CONFIG.get('default_encoding','utf8')
244 DEFAULT_ENCODING = rhodecode.CONFIG.get('default_encoding','utf8')
245 to_encoding = DEFAULT_ENCODING
245 to_encoding = DEFAULT_ENCODING
246
246
247 try:
247 try:
248 return unicode_.encode(to_encoding)
248 return unicode_.encode(to_encoding)
249 except UnicodeEncodeError:
249 except UnicodeEncodeError:
250 pass
250 pass
251
251
252 try:
252 try:
253 import chardet
253 import chardet
254 encoding = chardet.detect(unicode_)['encoding']
254 encoding = chardet.detect(unicode_)['encoding']
255 print encoding
255 print encoding
256 if encoding is None:
256 if encoding is None:
257 raise UnicodeEncodeError()
257 raise UnicodeEncodeError()
258
258
259 return unicode_.encode(encoding)
259 return unicode_.encode(encoding)
260 except (ImportError, UnicodeEncodeError):
260 except (ImportError, UnicodeEncodeError):
261 return unicode_.encode(to_encoding, 'replace')
261 return unicode_.encode(to_encoding, 'replace')
262
262
263 return safe_str
263 return safe_str
264
264
265
265
266 def engine_from_config(configuration, prefix='sqlalchemy.', **kwargs):
266 def engine_from_config(configuration, prefix='sqlalchemy.', **kwargs):
267 """
267 """
268 Custom engine_from_config functions that makes sure we use NullPool for
268 Custom engine_from_config functions that makes sure we use NullPool for
269 file based sqlite databases. This prevents errors on sqlite. This only
269 file based sqlite databases. This prevents errors on sqlite. This only
270 applies to sqlalchemy versions < 0.7.0
270 applies to sqlalchemy versions < 0.7.0
271
271
272 """
272 """
273 import sqlalchemy
273 import sqlalchemy
274 from sqlalchemy import engine_from_config as efc
274 from sqlalchemy import engine_from_config as efc
275 import logging
275 import logging
276
276
277 if int(sqlalchemy.__version__.split('.')[1]) < 7:
277 if int(sqlalchemy.__version__.split('.')[1]) < 7:
278
278
279 # This solution should work for sqlalchemy < 0.7.0, and should use
279 # This solution should work for sqlalchemy < 0.7.0, and should use
280 # proxy=TimerProxy() for execution time profiling
280 # proxy=TimerProxy() for execution time profiling
281
281
282 from sqlalchemy.pool import NullPool
282 from sqlalchemy.pool import NullPool
283 url = configuration[prefix + 'url']
283 url = configuration[prefix + 'url']
284
284
285 if url.startswith('sqlite'):
285 if url.startswith('sqlite'):
286 kwargs.update({'poolclass': NullPool})
286 kwargs.update({'poolclass': NullPool})
287 return efc(configuration, prefix, **kwargs)
287 return efc(configuration, prefix, **kwargs)
288 else:
288 else:
289 import time
289 import time
290 from sqlalchemy import event
290 from sqlalchemy import event
291 from sqlalchemy.engine import Engine
291 from sqlalchemy.engine import Engine
292
292
293 log = logging.getLogger('sqlalchemy.engine')
293 log = logging.getLogger('sqlalchemy.engine')
294 BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE = xrange(30, 38)
294 BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE = xrange(30, 38)
295 engine = efc(configuration, prefix, **kwargs)
295 engine = efc(configuration, prefix, **kwargs)
296
296
297 def color_sql(sql):
297 def color_sql(sql):
298 COLOR_SEQ = "\033[1;%dm"
298 COLOR_SEQ = "\033[1;%dm"
299 COLOR_SQL = YELLOW
299 COLOR_SQL = YELLOW
300 normal = '\x1b[0m'
300 normal = '\x1b[0m'
301 return ''.join([COLOR_SEQ % COLOR_SQL, sql, normal])
301 return ''.join([COLOR_SEQ % COLOR_SQL, sql, normal])
302
302
303 if configuration['debug']:
303 if configuration['debug']:
304 #attach events only for debug configuration
304 #attach events only for debug configuration
305
305
306 def before_cursor_execute(conn, cursor, statement,
306 def before_cursor_execute(conn, cursor, statement,
307 parameters, context, executemany):
307 parameters, context, executemany):
308 context._query_start_time = time.time()
308 context._query_start_time = time.time()
309 log.info(color_sql(">>>>> STARTING QUERY >>>>>"))
309 log.info(color_sql(">>>>> STARTING QUERY >>>>>"))
310
310
311
311
312 def after_cursor_execute(conn, cursor, statement,
312 def after_cursor_execute(conn, cursor, statement,
313 parameters, context, executemany):
313 parameters, context, executemany):
314 total = time.time() - context._query_start_time
314 total = time.time() - context._query_start_time
315 log.info(color_sql("<<<<< TOTAL TIME: %f <<<<<" % total))
315 log.info(color_sql("<<<<< TOTAL TIME: %f <<<<<" % total))
316
316
317 event.listen(engine, "before_cursor_execute",
317 event.listen(engine, "before_cursor_execute",
318 before_cursor_execute)
318 before_cursor_execute)
319 event.listen(engine, "after_cursor_execute",
319 event.listen(engine, "after_cursor_execute",
320 after_cursor_execute)
320 after_cursor_execute)
321
321
322 return engine
322 return engine
323
323
324
324
325 def age(curdate):
325 def age(curdate):
326 """
326 """
327 turns a datetime into an age string.
327 turns a datetime into an age string.
328
328
329 :param curdate: datetime object
329 :param curdate: datetime object
330 :rtype: unicode
330 :rtype: unicode
331 :returns: unicode words describing age
331 :returns: unicode words describing age
332 """
332 """
333
333
334 from datetime import datetime
334 from datetime import datetime
335 from webhelpers.date import time_ago_in_words
335 from webhelpers.date import time_ago_in_words
336
336
337 _ = lambda s: s
337 _ = lambda s: s
338
338
339 if not curdate:
339 if not curdate:
340 return ''
340 return ''
341
341
342 agescales = [(_(u"year"), 3600 * 24 * 365),
342 agescales = [(_(u"year"), 3600 * 24 * 365),
343 (_(u"month"), 3600 * 24 * 30),
343 (_(u"month"), 3600 * 24 * 30),
344 (_(u"day"), 3600 * 24),
344 (_(u"day"), 3600 * 24),
345 (_(u"hour"), 3600),
345 (_(u"hour"), 3600),
346 (_(u"minute"), 60),
346 (_(u"minute"), 60),
347 (_(u"second"), 1), ]
347 (_(u"second"), 1), ]
348
348
349 age = datetime.now() - curdate
349 age = datetime.now() - curdate
350 age_seconds = (age.days * agescales[2][1]) + age.seconds
350 age_seconds = (age.days * agescales[2][1]) + age.seconds
351 pos = 1
351 pos = 1
352 for scale in agescales:
352 for scale in agescales:
353 if scale[1] <= age_seconds:
353 if scale[1] <= age_seconds:
354 if pos == 6:
354 if pos == 6:
355 pos = 5
355 pos = 5
356 return '%s %s' % (time_ago_in_words(curdate,
356 return '%s %s' % (time_ago_in_words(curdate,
357 agescales[pos][0]), _('ago'))
357 agescales[pos][0]), _('ago'))
358 pos += 1
358 pos += 1
359
359
360 return _(u'just now')
360 return _(u'just now')
361
361
362
362
363 def uri_filter(uri):
363 def uri_filter(uri):
364 """
364 """
365 Removes user:password from given url string
365 Removes user:password from given url string
366
366
367 :param uri:
367 :param uri:
368 :rtype: unicode
368 :rtype: unicode
369 :returns: filtered list of strings
369 :returns: filtered list of strings
370 """
370 """
371 if not uri:
371 if not uri:
372 return ''
372 return ''
373
373
374 proto = ''
374 proto = ''
375
375
376 for pat in ('https://', 'http://'):
376 for pat in ('https://', 'http://'):
377 if uri.startswith(pat):
377 if uri.startswith(pat):
378 uri = uri[len(pat):]
378 uri = uri[len(pat):]
379 proto = pat
379 proto = pat
380 break
380 break
381
381
382 # remove passwords and username
382 # remove passwords and username
383 uri = uri[uri.find('@') + 1:]
383 uri = uri[uri.find('@') + 1:]
384
384
385 # get the port
385 # get the port
386 cred_pos = uri.find(':')
386 cred_pos = uri.find(':')
387 if cred_pos == -1:
387 if cred_pos == -1:
388 host, port = uri, None
388 host, port = uri, None
389 else:
389 else:
390 host, port = uri[:cred_pos], uri[cred_pos + 1:]
390 host, port = uri[:cred_pos], uri[cred_pos + 1:]
391
391
392 return filter(None, [proto, host, port])
392 return filter(None, [proto, host, port])
393
393
394
394
395 def credentials_filter(uri):
395 def credentials_filter(uri):
396 """
396 """
397 Returns a url with removed credentials
397 Returns a url with removed credentials
398
398
399 :param uri:
399 :param uri:
400 """
400 """
401
401
402 uri = uri_filter(uri)
402 uri = uri_filter(uri)
403 #check if we have port
403 #check if we have port
404 if len(uri) > 2 and uri[2]:
404 if len(uri) > 2 and uri[2]:
405 uri[2] = ':' + uri[2]
405 uri[2] = ':' + uri[2]
406
406
407 return ''.join(uri)
407 return ''.join(uri)
408
408
409
409
410 def get_changeset_safe(repo, rev):
410 def get_changeset_safe(repo, rev):
411 """
411 """
412 Safe version of get_changeset if this changeset doesn't exists for a
412 Safe version of get_changeset if this changeset doesn't exists for a
413 repo it returns a Dummy one instead
413 repo it returns a Dummy one instead
414
414
415 :param repo:
415 :param repo:
416 :param rev:
416 :param rev:
417 """
417 """
418 from rhodecode.lib.vcs.backends.base import BaseRepository
418 from rhodecode.lib.vcs.backends.base import BaseRepository
419 from rhodecode.lib.vcs.exceptions import RepositoryError
419 from rhodecode.lib.vcs.exceptions import RepositoryError
420 if not isinstance(repo, BaseRepository):
420 if not isinstance(repo, BaseRepository):
421 raise Exception('You must pass an Repository '
421 raise Exception('You must pass an Repository '
422 'object as first argument got %s', type(repo))
422 'object as first argument got %s', type(repo))
423
423
424 try:
424 try:
425 cs = repo.get_changeset(rev)
425 cs = repo.get_changeset(rev)
426 except RepositoryError:
426 except RepositoryError:
427 from rhodecode.lib.utils import EmptyChangeset
427 from rhodecode.lib.utils import EmptyChangeset
428 cs = EmptyChangeset(requested_revision=rev)
428 cs = EmptyChangeset(requested_revision=rev)
429 return cs
429 return cs
430
430
431
431
432 def get_current_revision(quiet=False):
432 def get_current_revision(quiet=False):
433 """
433 """
434 Returns tuple of (number, id) from repository containing this package
434 Returns tuple of (number, id) from repository containing this package
435 or None if repository could not be found.
435 or None if repository could not be found.
436
436
437 :param quiet: prints error for fetching revision if True
437 :param quiet: prints error for fetching revision if True
438 """
438 """
439
439
440 try:
440 try:
441 from rhodecode.lib.vcs import get_repo
441 from rhodecode.lib.vcs import get_repo
442 from rhodecode.lib.vcs.utils.helpers import get_scm
442 from rhodecode.lib.vcs.utils.helpers import get_scm
443 repopath = os.path.join(os.path.dirname(__file__), '..', '..')
443 repopath = os.path.join(os.path.dirname(__file__), '..', '..')
444 scm = get_scm(repopath)[0]
444 scm = get_scm(repopath)[0]
445 repo = get_repo(path=repopath, alias=scm)
445 repo = get_repo(path=repopath, alias=scm)
446 tip = repo.get_changeset()
446 tip = repo.get_changeset()
447 return (tip.revision, tip.short_id)
447 return (tip.revision, tip.short_id)
448 except Exception, err:
448 except Exception, err:
449 if not quiet:
449 if not quiet:
450 print ("Cannot retrieve rhodecode's revision. Original error "
450 print ("Cannot retrieve rhodecode's revision. Original error "
451 "was: %s" % err)
451 "was: %s" % err)
452 return None
452 return None
453
453
454
454
455 def extract_mentioned_users(s):
455 def extract_mentioned_users(s):
456 """
456 """
457 Returns unique usernames from given string s that have @mention
457 Returns unique usernames from given string s that have @mention
458
458
459 :param s: string to get mentions
459 :param s: string to get mentions
460 """
460 """
461 usrs = {}
461 usrs = {}
462 for username in re.findall(r'(?:^@|\s@)(\w+)', s):
462 for username in re.findall(r'(?:^@|\s@)(\w+)', s):
463 usrs[username] = username
463 usrs[username] = username
464
464
465 return sorted(usrs.keys())
465 return sorted(usrs.keys())
@@ -1,247 +1,251 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.lib.middleware.simplegit
3 rhodecode.lib.middleware.simplegit
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 SimpleGit middleware for handling git protocol request (push/clone etc.)
6 SimpleGit middleware for handling git protocol request (push/clone etc.)
7 It's implemented with basic auth function
7 It's implemented with basic auth function
8
8
9 :created_on: Apr 28, 2010
9 :created_on: Apr 28, 2010
10 :author: marcink
10 :author: marcink
11 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
12 :license: GPLv3, see COPYING for more details.
12 :license: GPLv3, see COPYING for more details.
13 """
13 """
14 # This program is free software: you can redistribute it and/or modify
14 # This program is free software: you can redistribute it and/or modify
15 # it under the terms of the GNU General Public License as published by
15 # it under the terms of the GNU General Public License as published by
16 # the Free Software Foundation, either version 3 of the License, or
16 # the Free Software Foundation, either version 3 of the License, or
17 # (at your option) any later version.
17 # (at your option) any later version.
18 #
18 #
19 # This program is distributed in the hope that it will be useful,
19 # This program is distributed in the hope that it will be useful,
20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 # GNU General Public License for more details.
22 # GNU General Public License for more details.
23 #
23 #
24 # You should have received a copy of the GNU General Public License
24 # You should have received a copy of the GNU General Public License
25 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 # along with this program. If not, see <http://www.gnu.org/licenses/>.
26
26
27 import os
27 import os
28 import re
28 import re
29 import logging
29 import logging
30 import traceback
30 import traceback
31
31
32 from dulwich import server as dulserver
32 from dulwich import server as dulserver
33
33
34
34
35 class SimpleGitUploadPackHandler(dulserver.UploadPackHandler):
35 class SimpleGitUploadPackHandler(dulserver.UploadPackHandler):
36
36
37 def handle(self):
37 def handle(self):
38 write = lambda x: self.proto.write_sideband(1, x)
38 write = lambda x: self.proto.write_sideband(1, x)
39
39
40 graph_walker = dulserver.ProtocolGraphWalker(self,
40 graph_walker = dulserver.ProtocolGraphWalker(self,
41 self.repo.object_store,
41 self.repo.object_store,
42 self.repo.get_peeled)
42 self.repo.get_peeled)
43 objects_iter = self.repo.fetch_objects(
43 objects_iter = self.repo.fetch_objects(
44 graph_walker.determine_wants, graph_walker, self.progress,
44 graph_walker.determine_wants, graph_walker, self.progress,
45 get_tagged=self.get_tagged)
45 get_tagged=self.get_tagged)
46
46
47 # Do they want any objects?
47 # Do they want any objects?
48 if objects_iter is None or len(objects_iter) == 0:
48 if objects_iter is None or len(objects_iter) == 0:
49 return
49 return
50
50
51 self.progress("counting objects: %d, done.\n" % len(objects_iter))
51 self.progress("counting objects: %d, done.\n" % len(objects_iter))
52 dulserver.write_pack_objects(dulserver.ProtocolFile(None, write),
52 dulserver.write_pack_objects(dulserver.ProtocolFile(None, write),
53 objects_iter, len(objects_iter))
53 objects_iter, len(objects_iter))
54 messages = []
54 messages = []
55 messages.append('thank you for using rhodecode')
55 messages.append('thank you for using rhodecode')
56
56
57 for msg in messages:
57 for msg in messages:
58 self.progress(msg + "\n")
58 self.progress(msg + "\n")
59 # we are done
59 # we are done
60 self.proto.write("0000")
60 self.proto.write("0000")
61
61
62 dulserver.DEFAULT_HANDLERS = {
62 dulserver.DEFAULT_HANDLERS = {
63 'git-upload-pack': SimpleGitUploadPackHandler,
63 'git-upload-pack': SimpleGitUploadPackHandler,
64 'git-receive-pack': dulserver.ReceivePackHandler,
64 'git-receive-pack': dulserver.ReceivePackHandler,
65 }
65 }
66
66
67 from dulwich.repo import Repo
67 from dulwich.repo import Repo
68 from dulwich.web import HTTPGitApplication
68 from dulwich.web import HTTPGitApplication
69
69
70 from paste.httpheaders import REMOTE_USER, AUTH_TYPE
70 from paste.httpheaders import REMOTE_USER, AUTH_TYPE
71
71
72 from rhodecode.lib import safe_str
72 from rhodecode.lib import safe_str
73 from rhodecode.lib.base import BaseVCSController
73 from rhodecode.lib.base import BaseVCSController
74 from rhodecode.lib.auth import get_container_username
74 from rhodecode.lib.auth import get_container_username
75 from rhodecode.lib.utils import is_valid_repo
75 from rhodecode.lib.utils import is_valid_repo
76 from rhodecode.model.db import User
76 from rhodecode.model.db import User
77
77
78 from webob.exc import HTTPNotFound, HTTPForbidden, HTTPInternalServerError
78 from webob.exc import HTTPNotFound, HTTPForbidden, HTTPInternalServerError
79
79
80 log = logging.getLogger(__name__)
80 log = logging.getLogger(__name__)
81
81
82
82
83 GIT_PROTO_PAT = re.compile(r'git-upload-pack|git-receive-pack|info\/refs')
83 GIT_PROTO_PAT = re.compile(r'git-upload-pack|git-receive-pack|info\/refs')
84
84
85
85 def is_git(action):
86 def is_git(action):
86 return action in ['pull','push']
87 return action in ['pull','push']
87
88
89
88 class SimpleGit(BaseVCSController):
90 class SimpleGit(BaseVCSController):
89
91
90 def _handle_request(self, environ, start_response):
92 def _handle_request(self, environ, start_response):
91 #======================================================================
93 #======================================================================
92 # GET ACTION PULL or PUSH
94 # GET ACTION PULL or PUSH
93 #======================================================================
95 #======================================================================
94 action = self.__get_action(environ)
96 action = self.__get_action(environ)
95
97
96 if not is_git(action):
98 if not is_git(action):
97 return self.application(environ, start_response)
99 return self.application(environ, start_response)
98
100
99 proxy_key = 'HTTP_X_REAL_IP'
101 proxy_key = 'HTTP_X_REAL_IP'
100 def_key = 'REMOTE_ADDR'
102 def_key = 'REMOTE_ADDR'
101 ipaddr = environ.get(proxy_key, environ.get(def_key, '0.0.0.0'))
103 ipaddr = environ.get(proxy_key, environ.get(def_key, '0.0.0.0'))
102 username = None
104 username = None
103 # skip passing error to error controller
105 # skip passing error to error controller
104 environ['pylons.status_code_redirect'] = True
106 environ['pylons.status_code_redirect'] = True
105
107
106 #======================================================================
108 #======================================================================
107 # EXTRACT REPOSITORY NAME FROM ENV
109 # EXTRACT REPOSITORY NAME FROM ENV
108 #======================================================================
110 #======================================================================
109 try:
111 try:
110 repo_name = self.__get_repository(environ)
112 repo_name = self.__get_repository(environ)
111 log.debug('Extracted repo name is %s' % repo_name)
113 log.debug('Extracted repo name is %s' % repo_name)
112 except:
114 except:
113 return HTTPInternalServerError()(environ, start_response)
115 return HTTPInternalServerError()(environ, start_response)
114
116
115
117
116 #======================================================================
118 #======================================================================
117 # CHECK ANONYMOUS PERMISSION
119 # CHECK ANONYMOUS PERMISSION
118 #======================================================================
120 #======================================================================
119 if action in ['pull', 'push']:
121 if action in ['pull', 'push']:
120 anonymous_user = self.__get_user('default')
122 anonymous_user = self.__get_user('default')
121 username = anonymous_user.username
123 username = anonymous_user.username
122 anonymous_perm = self._check_permission(action, anonymous_user,
124 anonymous_perm = self._check_permission(action, anonymous_user,
123 repo_name)
125 repo_name)
124
126
125 if anonymous_perm is not True or anonymous_user.active is False:
127 if anonymous_perm is not True or anonymous_user.active is False:
126 if anonymous_perm is not True:
128 if anonymous_perm is not True:
127 log.debug('Not enough credentials to access this '
129 log.debug('Not enough credentials to access this '
128 'repository as anonymous user')
130 'repository as anonymous user')
129 if anonymous_user.active is False:
131 if anonymous_user.active is False:
130 log.debug('Anonymous access is disabled, running '
132 log.debug('Anonymous access is disabled, running '
131 'authentication')
133 'authentication')
132 #==============================================================
134 #==============================================================
133 # DEFAULT PERM FAILED OR ANONYMOUS ACCESS IS DISABLED SO WE
135 # DEFAULT PERM FAILED OR ANONYMOUS ACCESS IS DISABLED SO WE
134 # NEED TO AUTHENTICATE AND ASK FOR AUTH USER PERMISSIONS
136 # NEED TO AUTHENTICATE AND ASK FOR AUTH USER PERMISSIONS
135 #==============================================================
137 #==============================================================
136
138
137 # Attempting to retrieve username from the container
139 # Attempting to retrieve username from the container
138 username = get_container_username(environ, self.config)
140 username = get_container_username(environ, self.config)
139
141
140 # If not authenticated by the container, running basic auth
142 # If not authenticated by the container, running basic auth
141 if not username:
143 if not username:
142 self.authenticate.realm = \
144 self.authenticate.realm = \
143 safe_str(self.config['rhodecode_realm'])
145 safe_str(self.config['rhodecode_realm'])
144 result = self.authenticate(environ)
146 result = self.authenticate(environ)
145 if isinstance(result, str):
147 if isinstance(result, str):
146 AUTH_TYPE.update(environ, 'basic')
148 AUTH_TYPE.update(environ, 'basic')
147 REMOTE_USER.update(environ, result)
149 REMOTE_USER.update(environ, result)
148 username = result
150 username = result
149 else:
151 else:
150 return result.wsgi_application(environ, start_response)
152 return result.wsgi_application(environ, start_response)
151
153
152 #==============================================================
154 #==============================================================
153 # CHECK PERMISSIONS FOR THIS REQUEST USING GIVEN USERNAME
155 # CHECK PERMISSIONS FOR THIS REQUEST USING GIVEN USERNAME
154 #==============================================================
156 #==============================================================
155 if action in ['pull', 'push']:
157 if action in ['pull', 'push']:
156 try:
158 try:
157 user = self.__get_user(username)
159 user = self.__get_user(username)
158 if user is None or not user.active:
160 if user is None or not user.active:
159 return HTTPForbidden()(environ, start_response)
161 return HTTPForbidden()(environ, start_response)
160 username = user.username
162 username = user.username
161 except:
163 except:
162 log.error(traceback.format_exc())
164 log.error(traceback.format_exc())
163 return HTTPInternalServerError()(environ,
165 return HTTPInternalServerError()(environ,
164 start_response)
166 start_response)
165
167
166 #check permissions for this repository
168 #check permissions for this repository
167 perm = self._check_permission(action, user,
169 perm = self._check_permission(action, user,
168 repo_name)
170 repo_name)
169 if perm is not True:
171 if perm is not True:
170 return HTTPForbidden()(environ, start_response)
172 return HTTPForbidden()(environ, start_response)
171
173
172 #===================================================================
174 #===================================================================
173 # GIT REQUEST HANDLING
175 # GIT REQUEST HANDLING
174 #===================================================================
176 #===================================================================
175
177
176 repo_path = safe_str(os.path.join(self.basepath, repo_name))
178 repo_path = safe_str(os.path.join(self.basepath, repo_name))
177 log.debug('Repository path is %s' % repo_path)
179 log.debug('Repository path is %s' % repo_path)
178
180
179 # quick check if that dir exists...
181 # quick check if that dir exists...
180 if is_valid_repo(repo_name, self.basepath) is False:
182 if is_valid_repo(repo_name, self.basepath) is False:
181 return HTTPNotFound()(environ, start_response)
183 return HTTPNotFound()(environ, start_response)
182
184
183 try:
185 try:
184 #invalidate cache on push
186 #invalidate cache on push
185 if action == 'push':
187 if action == 'push':
186 self._invalidate_cache(repo_name)
188 self._invalidate_cache(repo_name)
187 log.info('%s action on GIT repo "%s"' % (action, repo_name))
189 log.info('%s action on GIT repo "%s"' % (action, repo_name))
188 app = self.__make_app(repo_name, repo_path)
190 app = self.__make_app(repo_name, repo_path)
189 return app(environ, start_response)
191 return app(environ, start_response)
190 except Exception:
192 except Exception:
191 log.error(traceback.format_exc())
193 log.error(traceback.format_exc())
192 return HTTPInternalServerError()(environ, start_response)
194 return HTTPInternalServerError()(environ, start_response)
193
195
194 def __make_app(self, repo_name, repo_path):
196 def __make_app(self, repo_name, repo_path):
195 """
197 """
196 Make an wsgi application using dulserver
198 Make an wsgi application using dulserver
197
199
198 :param repo_name: name of the repository
200 :param repo_name: name of the repository
199 :param repo_path: full path to the repository
201 :param repo_path: full path to the repository
200 """
202 """
201
203
202 _d = {'/' + repo_name: Repo(repo_path)}
204 _d = {'/' + repo_name: Repo(repo_path)}
203 backend = dulserver.DictBackend(_d)
205 backend = dulserver.DictBackend(_d)
204 gitserve = HTTPGitApplication(backend)
206 gitserve = HTTPGitApplication(backend)
205
207
206 return gitserve
208 return gitserve
207
209
208 def __get_repository(self, environ):
210 def __get_repository(self, environ):
209 """
211 """
210 Get's repository name out of PATH_INFO header
212 Get's repository name out of PATH_INFO header
211
213
212 :param environ: environ where PATH_INFO is stored
214 :param environ: environ where PATH_INFO is stored
213 """
215 """
214 try:
216 try:
215 environ['PATH_INFO'] = self._get_by_id(environ['PATH_INFO'])
217 environ['PATH_INFO'] = self._get_by_id(environ['PATH_INFO'])
216 repo_name = '/'.join(environ['PATH_INFO'].split('/')[1:])
218 repo_name = '/'.join(environ['PATH_INFO'].split('/')[1:])
217 repo_name = GIT_PROTO_PAT.split(repo_name)
219 repo_name = GIT_PROTO_PAT.split(repo_name)
218 if repo_name:
220 if repo_name:
219 repo_name = repo_name[0]
221 repo_name = repo_name[0]
220
222
221 if repo_name.endswith('/'):
223 if repo_name.endswith('/'):
222 repo_name = repo_name.rstrip('/')
224 repo_name = repo_name.rstrip('/')
223 except:
225 except:
224 log.error(traceback.format_exc())
226 log.error(traceback.format_exc())
225 raise
227 raise
226
228
227 return repo_name
229 return repo_name
228
230
229 def __get_user(self, username):
231 def __get_user(self, username):
230 return User.get_by_username(username)
232 return User.get_by_username(username)
231
233
232 def __get_action(self, environ):
234 def __get_action(self, environ):
233 """Maps git request commands into a pull or push command.
235 """
236 Maps git request commands into a pull or push command.
234
237
235 :param environ:
238 :param environ:
236 """
239 """
237 service = environ['QUERY_STRING'].split('=')
240 service = environ['QUERY_STRING'].split('=')
238 if len(service) > 1:
241 if len(service) > 1:
239 service_cmd = service[1]
242 service_cmd = service[1]
240 mapping = {'git-receive-pack': 'push',
243 mapping = {
241 'git-upload-pack': 'pull',
244 'git-receive-pack': 'push',
242 }
245 'git-upload-pack': 'pull',
246 }
243
247
244 return mapping.get(service_cmd,
248 return mapping.get(service_cmd,
245 service_cmd if service_cmd else 'other')
249 service_cmd if service_cmd else 'other')
246 else:
250 else:
247 return 'other'
251 return 'other'
@@ -1,1203 +1,1203 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.model.db
3 rhodecode.model.db
4 ~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~
5
5
6 Database Models for RhodeCode
6 Database Models for RhodeCode
7
7
8 :created_on: Apr 08, 2010
8 :created_on: Apr 08, 2010
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software: you can redistribute it and/or modify
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
16 # (at your option) any later version.
17 #
17 #
18 # This program is distributed in the hope that it will be useful,
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # GNU General Public License for more details.
21 # GNU General Public License for more details.
22 #
22 #
23 # You should have received a copy of the GNU General Public License
23 # You should have received a copy of the GNU General Public License
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25
25
26 import os
26 import os
27 import logging
27 import logging
28 import datetime
28 import datetime
29 import traceback
29 import traceback
30 from collections import defaultdict
30 from collections import defaultdict
31
31
32 from sqlalchemy import *
32 from sqlalchemy import *
33 from sqlalchemy.ext.hybrid import hybrid_property
33 from sqlalchemy.ext.hybrid import hybrid_property
34 from sqlalchemy.orm import relationship, joinedload, class_mapper, validates
34 from sqlalchemy.orm import relationship, joinedload, class_mapper, validates
35 from beaker.cache import cache_region, region_invalidate
35 from beaker.cache import cache_region, region_invalidate
36
36
37 from rhodecode.lib.vcs import get_backend
37 from rhodecode.lib.vcs import get_backend
38 from rhodecode.lib.vcs.utils.helpers import get_scm
38 from rhodecode.lib.vcs.utils.helpers import get_scm
39 from rhodecode.lib.vcs.exceptions import VCSError
39 from rhodecode.lib.vcs.exceptions import VCSError
40 from rhodecode.lib.vcs.utils.lazy import LazyProperty
40 from rhodecode.lib.vcs.utils.lazy import LazyProperty
41
41
42 from rhodecode.lib 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
47
48
48
49 log = logging.getLogger(__name__)
49 log = logging.getLogger(__name__)
50
50
51 #==============================================================================
51 #==============================================================================
52 # BASE CLASSES
52 # BASE CLASSES
53 #==============================================================================
53 #==============================================================================
54
54
55
55
56 class ModelSerializer(json.JSONEncoder):
56 class ModelSerializer(json.JSONEncoder):
57 """
57 """
58 Simple Serializer for JSON,
58 Simple Serializer for JSON,
59
59
60 usage::
60 usage::
61
61
62 to make object customized for serialization implement a __json__
62 to make object customized for serialization implement a __json__
63 method that will return a dict for serialization into json
63 method that will return a dict for serialization into json
64
64
65 example::
65 example::
66
66
67 class Task(object):
67 class Task(object):
68
68
69 def __init__(self, name, value):
69 def __init__(self, name, value):
70 self.name = name
70 self.name = name
71 self.value = value
71 self.value = value
72
72
73 def __json__(self):
73 def __json__(self):
74 return dict(name=self.name,
74 return dict(name=self.name,
75 value=self.value)
75 value=self.value)
76
76
77 """
77 """
78
78
79 def default(self, obj):
79 def default(self, obj):
80
80
81 if hasattr(obj, '__json__'):
81 if hasattr(obj, '__json__'):
82 return obj.__json__()
82 return obj.__json__()
83 else:
83 else:
84 return json.JSONEncoder.default(self, obj)
84 return json.JSONEncoder.default(self, obj)
85
85
86
86
87 class BaseModel(object):
87 class BaseModel(object):
88 """
88 """
89 Base Model for all classess
89 Base Model for all classess
90 """
90 """
91
91
92 @classmethod
92 @classmethod
93 def _get_keys(cls):
93 def _get_keys(cls):
94 """return column names for this model """
94 """return column names for this model """
95 return class_mapper(cls).c.keys()
95 return class_mapper(cls).c.keys()
96
96
97 def get_dict(self):
97 def get_dict(self):
98 """
98 """
99 return dict with keys and values corresponding
99 return dict with keys and values corresponding
100 to this model data """
100 to this model data """
101
101
102 d = {}
102 d = {}
103 for k in self._get_keys():
103 for k in self._get_keys():
104 d[k] = getattr(self, k)
104 d[k] = getattr(self, k)
105
105
106 # also use __json__() if present to get additional fields
106 # also use __json__() if present to get additional fields
107 for k, val in getattr(self, '__json__', lambda: {})().iteritems():
107 for k, val in getattr(self, '__json__', lambda: {})().iteritems():
108 d[k] = val
108 d[k] = val
109 return d
109 return d
110
110
111 def get_appstruct(self):
111 def get_appstruct(self):
112 """return list with keys and values tupples corresponding
112 """return list with keys and values tupples corresponding
113 to this model data """
113 to this model data """
114
114
115 l = []
115 l = []
116 for k in self._get_keys():
116 for k in self._get_keys():
117 l.append((k, getattr(self, k),))
117 l.append((k, getattr(self, k),))
118 return l
118 return l
119
119
120 def populate_obj(self, populate_dict):
120 def populate_obj(self, populate_dict):
121 """populate model with data from given populate_dict"""
121 """populate model with data from given populate_dict"""
122
122
123 for k in self._get_keys():
123 for k in self._get_keys():
124 if k in populate_dict:
124 if k in populate_dict:
125 setattr(self, k, populate_dict[k])
125 setattr(self, k, populate_dict[k])
126
126
127 @classmethod
127 @classmethod
128 def query(cls):
128 def query(cls):
129 return Session.query(cls)
129 return Session.query(cls)
130
130
131 @classmethod
131 @classmethod
132 def get(cls, id_):
132 def get(cls, id_):
133 if id_:
133 if id_:
134 return cls.query().get(id_)
134 return cls.query().get(id_)
135
135
136 @classmethod
136 @classmethod
137 def getAll(cls):
137 def getAll(cls):
138 return cls.query().all()
138 return cls.query().all()
139
139
140 @classmethod
140 @classmethod
141 def delete(cls, id_):
141 def delete(cls, id_):
142 obj = cls.query().get(id_)
142 obj = cls.query().get(id_)
143 Session.delete(obj)
143 Session.delete(obj)
144
144
145
145
146 class RhodeCodeSetting(Base, BaseModel):
146 class RhodeCodeSetting(Base, BaseModel):
147 __tablename__ = 'rhodecode_settings'
147 __tablename__ = 'rhodecode_settings'
148 __table_args__ = (
148 __table_args__ = (
149 UniqueConstraint('app_settings_name'),
149 UniqueConstraint('app_settings_name'),
150 {'extend_existing': True}
150 {'extend_existing': True}
151 )
151 )
152 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
152 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)
153 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)
154 _app_settings_value = Column("app_settings_value", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
155
155
156 def __init__(self, k='', v=''):
156 def __init__(self, k='', v=''):
157 self.app_settings_name = k
157 self.app_settings_name = k
158 self.app_settings_value = v
158 self.app_settings_value = v
159
159
160 @validates('_app_settings_value')
160 @validates('_app_settings_value')
161 def validate_settings_value(self, key, val):
161 def validate_settings_value(self, key, val):
162 assert type(val) == unicode
162 assert type(val) == unicode
163 return val
163 return val
164
164
165 @hybrid_property
165 @hybrid_property
166 def app_settings_value(self):
166 def app_settings_value(self):
167 v = self._app_settings_value
167 v = self._app_settings_value
168 if self.app_settings_name == 'ldap_active':
168 if self.app_settings_name == 'ldap_active':
169 v = str2bool(v)
169 v = str2bool(v)
170 return v
170 return v
171
171
172 @app_settings_value.setter
172 @app_settings_value.setter
173 def app_settings_value(self, val):
173 def app_settings_value(self, val):
174 """
174 """
175 Setter that will always make sure we use unicode in app_settings_value
175 Setter that will always make sure we use unicode in app_settings_value
176
176
177 :param val:
177 :param val:
178 """
178 """
179 self._app_settings_value = safe_unicode(val)
179 self._app_settings_value = safe_unicode(val)
180
180
181 def __repr__(self):
181 def __repr__(self):
182 return "<%s('%s:%s')>" % (
182 return "<%s('%s:%s')>" % (
183 self.__class__.__name__,
183 self.__class__.__name__,
184 self.app_settings_name, self.app_settings_value
184 self.app_settings_name, self.app_settings_value
185 )
185 )
186
186
187 @classmethod
187 @classmethod
188 def get_by_name(cls, ldap_key):
188 def get_by_name(cls, ldap_key):
189 return cls.query()\
189 return cls.query()\
190 .filter(cls.app_settings_name == ldap_key).scalar()
190 .filter(cls.app_settings_name == ldap_key).scalar()
191
191
192 @classmethod
192 @classmethod
193 def get_app_settings(cls, cache=False):
193 def get_app_settings(cls, cache=False):
194
194
195 ret = cls.query()
195 ret = cls.query()
196
196
197 if cache:
197 if cache:
198 ret = ret.options(FromCache("sql_cache_short", "get_hg_settings"))
198 ret = ret.options(FromCache("sql_cache_short", "get_hg_settings"))
199
199
200 if not ret:
200 if not ret:
201 raise Exception('Could not get application settings !')
201 raise Exception('Could not get application settings !')
202 settings = {}
202 settings = {}
203 for each in ret:
203 for each in ret:
204 settings['rhodecode_' + each.app_settings_name] = \
204 settings['rhodecode_' + each.app_settings_name] = \
205 each.app_settings_value
205 each.app_settings_value
206
206
207 return settings
207 return settings
208
208
209 @classmethod
209 @classmethod
210 def get_ldap_settings(cls, cache=False):
210 def get_ldap_settings(cls, cache=False):
211 ret = cls.query()\
211 ret = cls.query()\
212 .filter(cls.app_settings_name.startswith('ldap_')).all()
212 .filter(cls.app_settings_name.startswith('ldap_')).all()
213 fd = {}
213 fd = {}
214 for row in ret:
214 for row in ret:
215 fd.update({row.app_settings_name:row.app_settings_value})
215 fd.update({row.app_settings_name:row.app_settings_value})
216
216
217 return fd
217 return fd
218
218
219
219
220 class RhodeCodeUi(Base, BaseModel):
220 class RhodeCodeUi(Base, BaseModel):
221 __tablename__ = 'rhodecode_ui'
221 __tablename__ = 'rhodecode_ui'
222 __table_args__ = (
222 __table_args__ = (
223 UniqueConstraint('ui_key'),
223 UniqueConstraint('ui_key'),
224 {'extend_existing': True}
224 {'extend_existing': True}
225 )
225 )
226
226
227 HOOK_UPDATE = 'changegroup.update'
227 HOOK_UPDATE = 'changegroup.update'
228 HOOK_REPO_SIZE = 'changegroup.repo_size'
228 HOOK_REPO_SIZE = 'changegroup.repo_size'
229 HOOK_PUSH = 'pretxnchangegroup.push_logger'
229 HOOK_PUSH = 'pretxnchangegroup.push_logger'
230 HOOK_PULL = 'preoutgoing.pull_logger'
230 HOOK_PULL = 'preoutgoing.pull_logger'
231
231
232 ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
232 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)
233 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)
234 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)
235 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)
236 ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True)
237
237
238 @classmethod
238 @classmethod
239 def get_by_key(cls, key):
239 def get_by_key(cls, key):
240 return cls.query().filter(cls.ui_key == key)
240 return cls.query().filter(cls.ui_key == key)
241
241
242 @classmethod
242 @classmethod
243 def get_builtin_hooks(cls):
243 def get_builtin_hooks(cls):
244 q = cls.query()
244 q = cls.query()
245 q = q.filter(cls.ui_key.in_([cls.HOOK_UPDATE,
245 q = q.filter(cls.ui_key.in_([cls.HOOK_UPDATE,
246 cls.HOOK_REPO_SIZE,
246 cls.HOOK_REPO_SIZE,
247 cls.HOOK_PUSH, cls.HOOK_PULL]))
247 cls.HOOK_PUSH, cls.HOOK_PULL]))
248 return q.all()
248 return q.all()
249
249
250 @classmethod
250 @classmethod
251 def get_custom_hooks(cls):
251 def get_custom_hooks(cls):
252 q = cls.query()
252 q = cls.query()
253 q = q.filter(~cls.ui_key.in_([cls.HOOK_UPDATE,
253 q = q.filter(~cls.ui_key.in_([cls.HOOK_UPDATE,
254 cls.HOOK_REPO_SIZE,
254 cls.HOOK_REPO_SIZE,
255 cls.HOOK_PUSH, cls.HOOK_PULL]))
255 cls.HOOK_PUSH, cls.HOOK_PULL]))
256 q = q.filter(cls.ui_section == 'hooks')
256 q = q.filter(cls.ui_section == 'hooks')
257 return q.all()
257 return q.all()
258
258
259 @classmethod
259 @classmethod
260 def create_or_update_hook(cls, key, val):
260 def create_or_update_hook(cls, key, val):
261 new_ui = cls.get_by_key(key).scalar() or cls()
261 new_ui = cls.get_by_key(key).scalar() or cls()
262 new_ui.ui_section = 'hooks'
262 new_ui.ui_section = 'hooks'
263 new_ui.ui_active = True
263 new_ui.ui_active = True
264 new_ui.ui_key = key
264 new_ui.ui_key = key
265 new_ui.ui_value = val
265 new_ui.ui_value = val
266
266
267 Session.add(new_ui)
267 Session.add(new_ui)
268
268
269
269
270 class User(Base, BaseModel):
270 class User(Base, BaseModel):
271 __tablename__ = 'users'
271 __tablename__ = 'users'
272 __table_args__ = (
272 __table_args__ = (
273 UniqueConstraint('username'), UniqueConstraint('email'),
273 UniqueConstraint('username'), UniqueConstraint('email'),
274 {'extend_existing': True}
274 {'extend_existing': True}
275 )
275 )
276 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
276 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)
277 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)
278 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)
279 active = Column("active", Boolean(), nullable=True, unique=None, default=None)
280 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
280 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)
281 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)
282 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)
283 _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)
284 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)
285 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)
286 api_key = Column("api_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
287
287
288 user_log = relationship('UserLog', cascade='all')
288 user_log = relationship('UserLog', cascade='all')
289 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
289 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
290
290
291 repositories = relationship('Repository')
291 repositories = relationship('Repository')
292 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
292 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')
293 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
294
294
295 group_member = relationship('UsersGroupMember', cascade='all')
295 group_member = relationship('UsersGroupMember', cascade='all')
296
296
297 notifications = relationship('UserNotification',)
297 notifications = relationship('UserNotification',)
298
298
299 @hybrid_property
299 @hybrid_property
300 def email(self):
300 def email(self):
301 return self._email
301 return self._email
302
302
303 @email.setter
303 @email.setter
304 def email(self, val):
304 def email(self, val):
305 self._email = val.lower() if val else None
305 self._email = val.lower() if val else None
306
306
307 @property
307 @property
308 def full_name(self):
308 def full_name(self):
309 return '%s %s' % (self.name, self.lastname)
309 return '%s %s' % (self.name, self.lastname)
310
310
311 @property
311 @property
312 def full_name_or_username(self):
312 def full_name_or_username(self):
313 return ('%s %s' % (self.name, self.lastname)
313 return ('%s %s' % (self.name, self.lastname)
314 if (self.name and self.lastname) else self.username)
314 if (self.name and self.lastname) else self.username)
315
315
316 @property
316 @property
317 def full_contact(self):
317 def full_contact(self):
318 return '%s %s <%s>' % (self.name, self.lastname, self.email)
318 return '%s %s <%s>' % (self.name, self.lastname, self.email)
319
319
320 @property
320 @property
321 def short_contact(self):
321 def short_contact(self):
322 return '%s %s' % (self.name, self.lastname)
322 return '%s %s' % (self.name, self.lastname)
323
323
324 @property
324 @property
325 def is_admin(self):
325 def is_admin(self):
326 return self.admin
326 return self.admin
327
327
328 def __repr__(self):
328 def __repr__(self):
329 return "<%s('id:%s:%s')>" % (self.__class__.__name__,
329 return "<%s('id:%s:%s')>" % (self.__class__.__name__,
330 self.user_id, self.username)
330 self.user_id, self.username)
331
331
332 @classmethod
332 @classmethod
333 def get_by_username(cls, username, case_insensitive=False, cache=False):
333 def get_by_username(cls, username, case_insensitive=False, cache=False):
334 if case_insensitive:
334 if case_insensitive:
335 q = cls.query().filter(cls.username.ilike(username))
335 q = cls.query().filter(cls.username.ilike(username))
336 else:
336 else:
337 q = cls.query().filter(cls.username == username)
337 q = cls.query().filter(cls.username == username)
338
338
339 if cache:
339 if cache:
340 q = q.options(FromCache("sql_cache_short",
340 q = q.options(FromCache("sql_cache_short",
341 "get_user_%s" % username))
341 "get_user_%s" % username))
342 return q.scalar()
342 return q.scalar()
343
343
344 @classmethod
344 @classmethod
345 def get_by_api_key(cls, api_key, cache=False):
345 def get_by_api_key(cls, api_key, cache=False):
346 q = cls.query().filter(cls.api_key == api_key)
346 q = cls.query().filter(cls.api_key == api_key)
347
347
348 if cache:
348 if cache:
349 q = q.options(FromCache("sql_cache_short",
349 q = q.options(FromCache("sql_cache_short",
350 "get_api_key_%s" % api_key))
350 "get_api_key_%s" % api_key))
351 return q.scalar()
351 return q.scalar()
352
352
353 @classmethod
353 @classmethod
354 def get_by_email(cls, email, case_insensitive=False, cache=False):
354 def get_by_email(cls, email, case_insensitive=False, cache=False):
355 if case_insensitive:
355 if case_insensitive:
356 q = cls.query().filter(cls.email.ilike(email))
356 q = cls.query().filter(cls.email.ilike(email))
357 else:
357 else:
358 q = cls.query().filter(cls.email == email)
358 q = cls.query().filter(cls.email == email)
359
359
360 if cache:
360 if cache:
361 q = q.options(FromCache("sql_cache_short",
361 q = q.options(FromCache("sql_cache_short",
362 "get_api_key_%s" % email))
362 "get_api_key_%s" % email))
363 return q.scalar()
363 return q.scalar()
364
364
365 def update_lastlogin(self):
365 def update_lastlogin(self):
366 """Update user lastlogin"""
366 """Update user lastlogin"""
367 self.last_login = datetime.datetime.now()
367 self.last_login = datetime.datetime.now()
368 Session.add(self)
368 Session.add(self)
369 log.debug('updated user %s lastlogin' % self.username)
369 log.debug('updated user %s lastlogin' % self.username)
370
370
371 def __json__(self):
371 def __json__(self):
372 return dict(
372 return dict(
373 email=self.email,
373 email=self.email,
374 full_name=self.full_name,
374 full_name=self.full_name,
375 full_name_or_username=self.full_name_or_username,
375 full_name_or_username=self.full_name_or_username,
376 short_contact=self.short_contact,
376 short_contact=self.short_contact,
377 full_contact=self.full_contact
377 full_contact=self.full_contact
378 )
378 )
379
379
380
380
381 class UserLog(Base, BaseModel):
381 class UserLog(Base, BaseModel):
382 __tablename__ = 'user_logs'
382 __tablename__ = 'user_logs'
383 __table_args__ = {'extend_existing': True}
383 __table_args__ = {'extend_existing': True}
384 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
384 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)
385 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)
386 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)
387 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)
388 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)
389 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)
390 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
391
391
392 @property
392 @property
393 def action_as_day(self):
393 def action_as_day(self):
394 return datetime.date(*self.action_date.timetuple()[:3])
394 return datetime.date(*self.action_date.timetuple()[:3])
395
395
396 user = relationship('User')
396 user = relationship('User')
397 repository = relationship('Repository',cascade='')
397 repository = relationship('Repository',cascade='')
398
398
399
399
400 class UsersGroup(Base, BaseModel):
400 class UsersGroup(Base, BaseModel):
401 __tablename__ = 'users_groups'
401 __tablename__ = 'users_groups'
402 __table_args__ = {'extend_existing': True}
402 __table_args__ = {'extend_existing': True}
403
403
404 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
404 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)
405 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)
406 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
407
407
408 members = relationship('UsersGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
408 members = relationship('UsersGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
409
409
410 def __repr__(self):
410 def __repr__(self):
411 return '<userGroup(%s)>' % (self.users_group_name)
411 return '<userGroup(%s)>' % (self.users_group_name)
412
412
413 @classmethod
413 @classmethod
414 def get_by_group_name(cls, group_name, cache=False,
414 def get_by_group_name(cls, group_name, cache=False,
415 case_insensitive=False):
415 case_insensitive=False):
416 if case_insensitive:
416 if case_insensitive:
417 q = cls.query().filter(cls.users_group_name.ilike(group_name))
417 q = cls.query().filter(cls.users_group_name.ilike(group_name))
418 else:
418 else:
419 q = cls.query().filter(cls.users_group_name == group_name)
419 q = cls.query().filter(cls.users_group_name == group_name)
420 if cache:
420 if cache:
421 q = q.options(FromCache("sql_cache_short",
421 q = q.options(FromCache("sql_cache_short",
422 "get_user_%s" % group_name))
422 "get_user_%s" % group_name))
423 return q.scalar()
423 return q.scalar()
424
424
425 @classmethod
425 @classmethod
426 def get(cls, users_group_id, cache=False):
426 def get(cls, users_group_id, cache=False):
427 users_group = cls.query()
427 users_group = cls.query()
428 if cache:
428 if cache:
429 users_group = users_group.options(FromCache("sql_cache_short",
429 users_group = users_group.options(FromCache("sql_cache_short",
430 "get_users_group_%s" % users_group_id))
430 "get_users_group_%s" % users_group_id))
431 return users_group.get(users_group_id)
431 return users_group.get(users_group_id)
432
432
433
433
434 class UsersGroupMember(Base, BaseModel):
434 class UsersGroupMember(Base, BaseModel):
435 __tablename__ = 'users_groups_members'
435 __tablename__ = 'users_groups_members'
436 __table_args__ = {'extend_existing': True}
436 __table_args__ = {'extend_existing': True}
437
437
438 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
438 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)
439 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)
440 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
441
441
442 user = relationship('User', lazy='joined')
442 user = relationship('User', lazy='joined')
443 users_group = relationship('UsersGroup')
443 users_group = relationship('UsersGroup')
444
444
445 def __init__(self, gr_id='', u_id=''):
445 def __init__(self, gr_id='', u_id=''):
446 self.users_group_id = gr_id
446 self.users_group_id = gr_id
447 self.user_id = u_id
447 self.user_id = u_id
448
448
449
449
450 class Repository(Base, BaseModel):
450 class Repository(Base, BaseModel):
451 __tablename__ = 'repositories'
451 __tablename__ = 'repositories'
452 __table_args__ = (
452 __table_args__ = (
453 UniqueConstraint('repo_name'),
453 UniqueConstraint('repo_name'),
454 {'extend_existing': True},
454 {'extend_existing': True},
455 )
455 )
456
456
457 repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
457 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)
458 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)
459 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')
460 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)
461 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)
462 private = Column("private", Boolean(), nullable=True, unique=None, default=None)
463 enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True)
463 enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True)
464 enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True)
464 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)
465 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)
466 created_on = Column('created_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
467
467
468 fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None)
468 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)
469 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None)
470
470
471 user = relationship('User')
471 user = relationship('User')
472 fork = relationship('Repository', remote_side=repo_id)
472 fork = relationship('Repository', remote_side=repo_id)
473 group = relationship('RepoGroup')
473 group = relationship('RepoGroup')
474 repo_to_perm = relationship('UserRepoToPerm', cascade='all', order_by='UserRepoToPerm.repo_to_perm_id')
474 repo_to_perm = relationship('UserRepoToPerm', cascade='all', order_by='UserRepoToPerm.repo_to_perm_id')
475 users_group_to_perm = relationship('UsersGroupRepoToPerm', cascade='all')
475 users_group_to_perm = relationship('UsersGroupRepoToPerm', cascade='all')
476 stats = relationship('Statistics', cascade='all', uselist=False)
476 stats = relationship('Statistics', cascade='all', uselist=False)
477
477
478 followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id', cascade='all')
478 followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id', cascade='all')
479
479
480 logs = relationship('UserLog')
480 logs = relationship('UserLog')
481
481
482 def __repr__(self):
482 def __repr__(self):
483 return "<%s('%s:%s')>" % (self.__class__.__name__,
483 return "<%s('%s:%s')>" % (self.__class__.__name__,
484 self.repo_id, self.repo_name)
484 self.repo_id, self.repo_name)
485
485
486 @classmethod
486 @classmethod
487 def url_sep(cls):
487 def url_sep(cls):
488 return '/'
488 return '/'
489
489
490 @classmethod
490 @classmethod
491 def get_by_repo_name(cls, repo_name):
491 def get_by_repo_name(cls, repo_name):
492 q = Session.query(cls).filter(cls.repo_name == repo_name)
492 q = Session.query(cls).filter(cls.repo_name == repo_name)
493 q = q.options(joinedload(Repository.fork))\
493 q = q.options(joinedload(Repository.fork))\
494 .options(joinedload(Repository.user))\
494 .options(joinedload(Repository.user))\
495 .options(joinedload(Repository.group))
495 .options(joinedload(Repository.group))
496 return q.scalar()
496 return q.scalar()
497
497
498 @classmethod
498 @classmethod
499 def get_repo_forks(cls, repo_id):
499 def get_repo_forks(cls, repo_id):
500 return cls.query().filter(Repository.fork_id == repo_id)
500 return cls.query().filter(Repository.fork_id == repo_id)
501
501
502 @classmethod
502 @classmethod
503 def base_path(cls):
503 def base_path(cls):
504 """
504 """
505 Returns base path when all repos are stored
505 Returns base path when all repos are stored
506
506
507 :param cls:
507 :param cls:
508 """
508 """
509 q = Session.query(RhodeCodeUi)\
509 q = Session.query(RhodeCodeUi)\
510 .filter(RhodeCodeUi.ui_key == cls.url_sep())
510 .filter(RhodeCodeUi.ui_key == cls.url_sep())
511 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
511 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
512 return q.one().ui_value
512 return q.one().ui_value
513
513
514 @property
514 @property
515 def just_name(self):
515 def just_name(self):
516 return self.repo_name.split(Repository.url_sep())[-1]
516 return self.repo_name.split(Repository.url_sep())[-1]
517
517
518 @property
518 @property
519 def groups_with_parents(self):
519 def groups_with_parents(self):
520 groups = []
520 groups = []
521 if self.group is None:
521 if self.group is None:
522 return groups
522 return groups
523
523
524 cur_gr = self.group
524 cur_gr = self.group
525 groups.insert(0, cur_gr)
525 groups.insert(0, cur_gr)
526 while 1:
526 while 1:
527 gr = getattr(cur_gr, 'parent_group', None)
527 gr = getattr(cur_gr, 'parent_group', None)
528 cur_gr = cur_gr.parent_group
528 cur_gr = cur_gr.parent_group
529 if gr is None:
529 if gr is None:
530 break
530 break
531 groups.insert(0, gr)
531 groups.insert(0, gr)
532
532
533 return groups
533 return groups
534
534
535 @property
535 @property
536 def groups_and_repo(self):
536 def groups_and_repo(self):
537 return self.groups_with_parents, self.just_name
537 return self.groups_with_parents, self.just_name
538
538
539 @LazyProperty
539 @LazyProperty
540 def repo_path(self):
540 def repo_path(self):
541 """
541 """
542 Returns base full path for that repository means where it actually
542 Returns base full path for that repository means where it actually
543 exists on a filesystem
543 exists on a filesystem
544 """
544 """
545 q = Session.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key ==
545 q = Session.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key ==
546 Repository.url_sep())
546 Repository.url_sep())
547 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
547 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
548 return q.one().ui_value
548 return q.one().ui_value
549
549
550 @property
550 @property
551 def repo_full_path(self):
551 def repo_full_path(self):
552 p = [self.repo_path]
552 p = [self.repo_path]
553 # we need to split the name by / since this is how we store the
553 # 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
554 # names in the database, but that eventually needs to be converted
555 # into a valid system path
555 # into a valid system path
556 p += self.repo_name.split(Repository.url_sep())
556 p += self.repo_name.split(Repository.url_sep())
557 return os.path.join(*p)
557 return os.path.join(*p)
558
558
559 def get_new_name(self, repo_name):
559 def get_new_name(self, repo_name):
560 """
560 """
561 returns new full repository name based on assigned group and new new
561 returns new full repository name based on assigned group and new new
562
562
563 :param group_name:
563 :param group_name:
564 """
564 """
565 path_prefix = self.group.full_path_splitted if self.group else []
565 path_prefix = self.group.full_path_splitted if self.group else []
566 return Repository.url_sep().join(path_prefix + [repo_name])
566 return Repository.url_sep().join(path_prefix + [repo_name])
567
567
568 @property
568 @property
569 def _ui(self):
569 def _ui(self):
570 """
570 """
571 Creates an db based ui object for this repository
571 Creates an db based ui object for this repository
572 """
572 """
573 from mercurial import ui
573 from mercurial import ui
574 from mercurial import config
574 from mercurial import config
575 baseui = ui.ui()
575 baseui = ui.ui()
576
576
577 #clean the baseui object
577 #clean the baseui object
578 baseui._ocfg = config.config()
578 baseui._ocfg = config.config()
579 baseui._ucfg = config.config()
579 baseui._ucfg = config.config()
580 baseui._tcfg = config.config()
580 baseui._tcfg = config.config()
581
581
582 ret = RhodeCodeUi.query()\
582 ret = RhodeCodeUi.query()\
583 .options(FromCache("sql_cache_short", "repository_repo_ui")).all()
583 .options(FromCache("sql_cache_short", "repository_repo_ui")).all()
584
584
585 hg_ui = ret
585 hg_ui = ret
586 for ui_ in hg_ui:
586 for ui_ in hg_ui:
587 if ui_.ui_active:
587 if ui_.ui_active:
588 log.debug('settings ui from db[%s]%s:%s', ui_.ui_section,
588 log.debug('settings ui from db[%s]%s:%s', ui_.ui_section,
589 ui_.ui_key, ui_.ui_value)
589 ui_.ui_key, ui_.ui_value)
590 baseui.setconfig(ui_.ui_section, ui_.ui_key, ui_.ui_value)
590 baseui.setconfig(ui_.ui_section, ui_.ui_key, ui_.ui_value)
591
591
592 return baseui
592 return baseui
593
593
594 @classmethod
594 @classmethod
595 def is_valid(cls, repo_name):
595 def is_valid(cls, repo_name):
596 """
596 """
597 returns True if given repo name is a valid filesystem repository
597 returns True if given repo name is a valid filesystem repository
598
598
599 :param cls:
599 :param cls:
600 :param repo_name:
600 :param repo_name:
601 """
601 """
602 from rhodecode.lib.utils import is_valid_repo
602 from rhodecode.lib.utils import is_valid_repo
603
603
604 return is_valid_repo(repo_name, cls.base_path())
604 return is_valid_repo(repo_name, cls.base_path())
605
605
606 #==========================================================================
606 #==========================================================================
607 # SCM PROPERTIES
607 # SCM PROPERTIES
608 #==========================================================================
608 #==========================================================================
609
609
610 def get_changeset(self, rev):
610 def get_changeset(self, rev):
611 return get_changeset_safe(self.scm_instance, rev)
611 return get_changeset_safe(self.scm_instance, rev)
612
612
613 @property
613 @property
614 def tip(self):
614 def tip(self):
615 return self.get_changeset('tip')
615 return self.get_changeset('tip')
616
616
617 @property
617 @property
618 def author(self):
618 def author(self):
619 return self.tip.author
619 return self.tip.author
620
620
621 @property
621 @property
622 def last_change(self):
622 def last_change(self):
623 return self.scm_instance.last_change
623 return self.scm_instance.last_change
624
624
625 def comments(self, revisions=None):
625 def comments(self, revisions=None):
626 """
626 """
627 Returns comments for this repository grouped by revisions
627 Returns comments for this repository grouped by revisions
628
628
629 :param revisions: filter query by revisions only
629 :param revisions: filter query by revisions only
630 """
630 """
631 cmts = ChangesetComment.query()\
631 cmts = ChangesetComment.query()\
632 .filter(ChangesetComment.repo == self)
632 .filter(ChangesetComment.repo == self)
633 if revisions:
633 if revisions:
634 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
634 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
635 grouped = defaultdict(list)
635 grouped = defaultdict(list)
636 for cmt in cmts.all():
636 for cmt in cmts.all():
637 grouped[cmt.revision].append(cmt)
637 grouped[cmt.revision].append(cmt)
638 return grouped
638 return grouped
639
639
640 #==========================================================================
640 #==========================================================================
641 # SCM CACHE INSTANCE
641 # SCM CACHE INSTANCE
642 #==========================================================================
642 #==========================================================================
643
643
644 @property
644 @property
645 def invalidate(self):
645 def invalidate(self):
646 return CacheInvalidation.invalidate(self.repo_name)
646 return CacheInvalidation.invalidate(self.repo_name)
647
647
648 def set_invalidate(self):
648 def set_invalidate(self):
649 """
649 """
650 set a cache for invalidation for this instance
650 set a cache for invalidation for this instance
651 """
651 """
652 CacheInvalidation.set_invalidate(self.repo_name)
652 CacheInvalidation.set_invalidate(self.repo_name)
653
653
654 @LazyProperty
654 @LazyProperty
655 def scm_instance(self):
655 def scm_instance(self):
656 return self.__get_instance()
656 return self.__get_instance()
657
657
658 @property
658 @property
659 def scm_instance_cached(self):
659 def scm_instance_cached(self):
660 @cache_region('long_term')
660 @cache_region('long_term')
661 def _c(repo_name):
661 def _c(repo_name):
662 return self.__get_instance()
662 return self.__get_instance()
663 rn = self.repo_name
663 rn = self.repo_name
664 log.debug('Getting cached instance of repo')
664 log.debug('Getting cached instance of repo')
665 inv = self.invalidate
665 inv = self.invalidate
666 if inv is not None:
666 if inv is not None:
667 region_invalidate(_c, None, rn)
667 region_invalidate(_c, None, rn)
668 # update our cache
668 # update our cache
669 CacheInvalidation.set_valid(inv.cache_key)
669 CacheInvalidation.set_valid(inv.cache_key)
670 return _c(rn)
670 return _c(rn)
671
671
672 def __get_instance(self):
672 def __get_instance(self):
673 repo_full_path = self.repo_full_path
673 repo_full_path = self.repo_full_path
674 try:
674 try:
675 alias = get_scm(repo_full_path)[0]
675 alias = get_scm(repo_full_path)[0]
676 log.debug('Creating instance of %s repository' % alias)
676 log.debug('Creating instance of %s repository' % alias)
677 backend = get_backend(alias)
677 backend = get_backend(alias)
678 except VCSError:
678 except VCSError:
679 log.error(traceback.format_exc())
679 log.error(traceback.format_exc())
680 log.error('Perhaps this repository is in db and not in '
680 log.error('Perhaps this repository is in db and not in '
681 'filesystem run rescan repositories with '
681 'filesystem run rescan repositories with '
682 '"destroy old data " option from admin panel')
682 '"destroy old data " option from admin panel')
683 return
683 return
684
684
685 if alias == 'hg':
685 if alias == 'hg':
686
686
687 repo = backend(safe_str(repo_full_path), create=False,
687 repo = backend(safe_str(repo_full_path), create=False,
688 baseui=self._ui)
688 baseui=self._ui)
689 # skip hidden web repository
689 # skip hidden web repository
690 if repo._get_hidden():
690 if repo._get_hidden():
691 return
691 return
692 else:
692 else:
693 repo = backend(repo_full_path, create=False)
693 repo = backend(repo_full_path, create=False)
694
694
695 return repo
695 return repo
696
696
697
697
698 class RepoGroup(Base, BaseModel):
698 class RepoGroup(Base, BaseModel):
699 __tablename__ = 'groups'
699 __tablename__ = 'groups'
700 __table_args__ = (
700 __table_args__ = (
701 UniqueConstraint('group_name', 'group_parent_id'),
701 UniqueConstraint('group_name', 'group_parent_id'),
702 CheckConstraint('group_id != group_parent_id'),
702 CheckConstraint('group_id != group_parent_id'),
703 {'extend_existing': True},
703 {'extend_existing': True},
704 )
704 )
705 __mapper_args__ = {'order_by': 'group_name'}
705 __mapper_args__ = {'order_by': 'group_name'}
706
706
707 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
707 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)
708 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)
709 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)
710 group_description = Column("group_description", String(length=10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
711
711
712 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
712 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
713 users_group_to_perm = relationship('UsersGroupRepoGroupToPerm', cascade='all')
713 users_group_to_perm = relationship('UsersGroupRepoGroupToPerm', cascade='all')
714
714
715 parent_group = relationship('RepoGroup', remote_side=group_id)
715 parent_group = relationship('RepoGroup', remote_side=group_id)
716
716
717 def __init__(self, group_name='', parent_group=None):
717 def __init__(self, group_name='', parent_group=None):
718 self.group_name = group_name
718 self.group_name = group_name
719 self.parent_group = parent_group
719 self.parent_group = parent_group
720
720
721 def __repr__(self):
721 def __repr__(self):
722 return "<%s('%s:%s')>" % (self.__class__.__name__, self.group_id,
722 return "<%s('%s:%s')>" % (self.__class__.__name__, self.group_id,
723 self.group_name)
723 self.group_name)
724
724
725 @classmethod
725 @classmethod
726 def groups_choices(cls):
726 def groups_choices(cls):
727 from webhelpers.html import literal as _literal
727 from webhelpers.html import literal as _literal
728 repo_groups = [('', '')]
728 repo_groups = [('', '')]
729 sep = ' &raquo; '
729 sep = ' &raquo; '
730 _name = lambda k: _literal(sep.join(k))
730 _name = lambda k: _literal(sep.join(k))
731
731
732 repo_groups.extend([(x.group_id, _name(x.full_path_splitted))
732 repo_groups.extend([(x.group_id, _name(x.full_path_splitted))
733 for x in cls.query().all()])
733 for x in cls.query().all()])
734
734
735 repo_groups = sorted(repo_groups, key=lambda t: t[1].split(sep)[0])
735 repo_groups = sorted(repo_groups, key=lambda t: t[1].split(sep)[0])
736 return repo_groups
736 return repo_groups
737
737
738 @classmethod
738 @classmethod
739 def url_sep(cls):
739 def url_sep(cls):
740 return '/'
740 return '/'
741
741
742 @classmethod
742 @classmethod
743 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
743 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
744 if case_insensitive:
744 if case_insensitive:
745 gr = cls.query()\
745 gr = cls.query()\
746 .filter(cls.group_name.ilike(group_name))
746 .filter(cls.group_name.ilike(group_name))
747 else:
747 else:
748 gr = cls.query()\
748 gr = cls.query()\
749 .filter(cls.group_name == group_name)
749 .filter(cls.group_name == group_name)
750 if cache:
750 if cache:
751 gr = gr.options(FromCache("sql_cache_short",
751 gr = gr.options(FromCache("sql_cache_short",
752 "get_group_%s" % group_name))
752 "get_group_%s" % group_name))
753 return gr.scalar()
753 return gr.scalar()
754
754
755 @property
755 @property
756 def parents(self):
756 def parents(self):
757 parents_recursion_limit = 5
757 parents_recursion_limit = 5
758 groups = []
758 groups = []
759 if self.parent_group is None:
759 if self.parent_group is None:
760 return groups
760 return groups
761 cur_gr = self.parent_group
761 cur_gr = self.parent_group
762 groups.insert(0, cur_gr)
762 groups.insert(0, cur_gr)
763 cnt = 0
763 cnt = 0
764 while 1:
764 while 1:
765 cnt += 1
765 cnt += 1
766 gr = getattr(cur_gr, 'parent_group', None)
766 gr = getattr(cur_gr, 'parent_group', None)
767 cur_gr = cur_gr.parent_group
767 cur_gr = cur_gr.parent_group
768 if gr is None:
768 if gr is None:
769 break
769 break
770 if cnt == parents_recursion_limit:
770 if cnt == parents_recursion_limit:
771 # this will prevent accidental infinit loops
771 # this will prevent accidental infinit loops
772 log.error('group nested more than %s' %
772 log.error('group nested more than %s' %
773 parents_recursion_limit)
773 parents_recursion_limit)
774 break
774 break
775
775
776 groups.insert(0, gr)
776 groups.insert(0, gr)
777 return groups
777 return groups
778
778
779 @property
779 @property
780 def children(self):
780 def children(self):
781 return RepoGroup.query().filter(RepoGroup.parent_group == self)
781 return RepoGroup.query().filter(RepoGroup.parent_group == self)
782
782
783 @property
783 @property
784 def name(self):
784 def name(self):
785 return self.group_name.split(RepoGroup.url_sep())[-1]
785 return self.group_name.split(RepoGroup.url_sep())[-1]
786
786
787 @property
787 @property
788 def full_path(self):
788 def full_path(self):
789 return self.group_name
789 return self.group_name
790
790
791 @property
791 @property
792 def full_path_splitted(self):
792 def full_path_splitted(self):
793 return self.group_name.split(RepoGroup.url_sep())
793 return self.group_name.split(RepoGroup.url_sep())
794
794
795 @property
795 @property
796 def repositories(self):
796 def repositories(self):
797 return Repository.query().filter(Repository.group == self)
797 return Repository.query().filter(Repository.group == self)
798
798
799 @property
799 @property
800 def repositories_recursive_count(self):
800 def repositories_recursive_count(self):
801 cnt = self.repositories.count()
801 cnt = self.repositories.count()
802
802
803 def children_count(group):
803 def children_count(group):
804 cnt = 0
804 cnt = 0
805 for child in group.children:
805 for child in group.children:
806 cnt += child.repositories.count()
806 cnt += child.repositories.count()
807 cnt += children_count(child)
807 cnt += children_count(child)
808 return cnt
808 return cnt
809
809
810 return cnt + children_count(self)
810 return cnt + children_count(self)
811
811
812 def get_new_name(self, group_name):
812 def get_new_name(self, group_name):
813 """
813 """
814 returns new full group name based on parent and new name
814 returns new full group name based on parent and new name
815
815
816 :param group_name:
816 :param group_name:
817 """
817 """
818 path_prefix = (self.parent_group.full_path_splitted if
818 path_prefix = (self.parent_group.full_path_splitted if
819 self.parent_group else [])
819 self.parent_group else [])
820 return RepoGroup.url_sep().join(path_prefix + [group_name])
820 return RepoGroup.url_sep().join(path_prefix + [group_name])
821
821
822
822
823 class Permission(Base, BaseModel):
823 class Permission(Base, BaseModel):
824 __tablename__ = 'permissions'
824 __tablename__ = 'permissions'
825 __table_args__ = {'extend_existing': True}
825 __table_args__ = {'extend_existing': True}
826 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
826 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)
827 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)
828 permission_longname = Column("permission_longname", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
829
829
830 def __repr__(self):
830 def __repr__(self):
831 return "<%s('%s:%s')>" % (
831 return "<%s('%s:%s')>" % (
832 self.__class__.__name__, self.permission_id, self.permission_name
832 self.__class__.__name__, self.permission_id, self.permission_name
833 )
833 )
834
834
835 @classmethod
835 @classmethod
836 def get_by_key(cls, key):
836 def get_by_key(cls, key):
837 return cls.query().filter(cls.permission_name == key).scalar()
837 return cls.query().filter(cls.permission_name == key).scalar()
838
838
839 @classmethod
839 @classmethod
840 def get_default_perms(cls, default_user_id):
840 def get_default_perms(cls, default_user_id):
841 q = Session.query(UserRepoToPerm, Repository, cls)\
841 q = Session.query(UserRepoToPerm, Repository, cls)\
842 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
842 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
843 .join((cls, UserRepoToPerm.permission_id == cls.permission_id))\
843 .join((cls, UserRepoToPerm.permission_id == cls.permission_id))\
844 .filter(UserRepoToPerm.user_id == default_user_id)
844 .filter(UserRepoToPerm.user_id == default_user_id)
845
845
846 return q.all()
846 return q.all()
847
847
848 @classmethod
848 @classmethod
849 def get_default_group_perms(cls, default_user_id):
849 def get_default_group_perms(cls, default_user_id):
850 q = Session.query(UserRepoGroupToPerm, RepoGroup, cls)\
850 q = Session.query(UserRepoGroupToPerm, RepoGroup, cls)\
851 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
851 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
852 .join((cls, UserRepoGroupToPerm.permission_id == cls.permission_id))\
852 .join((cls, UserRepoGroupToPerm.permission_id == cls.permission_id))\
853 .filter(UserRepoGroupToPerm.user_id == default_user_id)
853 .filter(UserRepoGroupToPerm.user_id == default_user_id)
854
854
855 return q.all()
855 return q.all()
856
856
857
857
858 class UserRepoToPerm(Base, BaseModel):
858 class UserRepoToPerm(Base, BaseModel):
859 __tablename__ = 'repo_to_perm'
859 __tablename__ = 'repo_to_perm'
860 __table_args__ = (
860 __table_args__ = (
861 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
861 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
862 {'extend_existing': True}
862 {'extend_existing': True}
863 )
863 )
864 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
864 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)
865 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)
866 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)
867 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
868
868
869 user = relationship('User')
869 user = relationship('User')
870 repository = relationship('Repository')
870 repository = relationship('Repository')
871 permission = relationship('Permission')
871 permission = relationship('Permission')
872
872
873 @classmethod
873 @classmethod
874 def create(cls, user, repository, permission):
874 def create(cls, user, repository, permission):
875 n = cls()
875 n = cls()
876 n.user = user
876 n.user = user
877 n.repository = repository
877 n.repository = repository
878 n.permission = permission
878 n.permission = permission
879 Session.add(n)
879 Session.add(n)
880 return n
880 return n
881
881
882 def __repr__(self):
882 def __repr__(self):
883 return '<user:%s => %s >' % (self.user, self.repository)
883 return '<user:%s => %s >' % (self.user, self.repository)
884
884
885
885
886 class UserToPerm(Base, BaseModel):
886 class UserToPerm(Base, BaseModel):
887 __tablename__ = 'user_to_perm'
887 __tablename__ = 'user_to_perm'
888 __table_args__ = (
888 __table_args__ = (
889 UniqueConstraint('user_id', 'permission_id'),
889 UniqueConstraint('user_id', 'permission_id'),
890 {'extend_existing': True}
890 {'extend_existing': True}
891 )
891 )
892 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
892 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)
893 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)
894 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
895
895
896 user = relationship('User')
896 user = relationship('User')
897 permission = relationship('Permission', lazy='joined')
897 permission = relationship('Permission', lazy='joined')
898
898
899
899
900 class UsersGroupRepoToPerm(Base, BaseModel):
900 class UsersGroupRepoToPerm(Base, BaseModel):
901 __tablename__ = 'users_group_repo_to_perm'
901 __tablename__ = 'users_group_repo_to_perm'
902 __table_args__ = (
902 __table_args__ = (
903 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
903 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
904 {'extend_existing': True}
904 {'extend_existing': True}
905 )
905 )
906 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
906 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)
907 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)
908 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)
909 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
910
910
911 users_group = relationship('UsersGroup')
911 users_group = relationship('UsersGroup')
912 permission = relationship('Permission')
912 permission = relationship('Permission')
913 repository = relationship('Repository')
913 repository = relationship('Repository')
914
914
915 @classmethod
915 @classmethod
916 def create(cls, users_group, repository, permission):
916 def create(cls, users_group, repository, permission):
917 n = cls()
917 n = cls()
918 n.users_group = users_group
918 n.users_group = users_group
919 n.repository = repository
919 n.repository = repository
920 n.permission = permission
920 n.permission = permission
921 Session.add(n)
921 Session.add(n)
922 return n
922 return n
923
923
924 def __repr__(self):
924 def __repr__(self):
925 return '<userGroup:%s => %s >' % (self.users_group, self.repository)
925 return '<userGroup:%s => %s >' % (self.users_group, self.repository)
926
926
927
927
928 class UsersGroupToPerm(Base, BaseModel):
928 class UsersGroupToPerm(Base, BaseModel):
929 __tablename__ = 'users_group_to_perm'
929 __tablename__ = 'users_group_to_perm'
930 __table_args__ = (
930 __table_args__ = (
931 UniqueConstraint('users_group_id', 'permission_id',),
931 UniqueConstraint('users_group_id', 'permission_id',),
932 {'extend_existing': True}
932 {'extend_existing': True}
933 )
933 )
934 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
934 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)
935 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)
936 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
937
937
938 users_group = relationship('UsersGroup')
938 users_group = relationship('UsersGroup')
939 permission = relationship('Permission')
939 permission = relationship('Permission')
940
940
941
941
942 class UserRepoGroupToPerm(Base, BaseModel):
942 class UserRepoGroupToPerm(Base, BaseModel):
943 __tablename__ = 'user_repo_group_to_perm'
943 __tablename__ = 'user_repo_group_to_perm'
944 __table_args__ = (
944 __table_args__ = (
945 UniqueConstraint('user_id', 'group_id', 'permission_id'),
945 UniqueConstraint('user_id', 'group_id', 'permission_id'),
946 {'extend_existing': True}
946 {'extend_existing': True}
947 )
947 )
948
948
949 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
949 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)
950 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)
951 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)
952 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
953
953
954 user = relationship('User')
954 user = relationship('User')
955 group = relationship('RepoGroup')
955 group = relationship('RepoGroup')
956 permission = relationship('Permission')
956 permission = relationship('Permission')
957
957
958
958
959 class UsersGroupRepoGroupToPerm(Base, BaseModel):
959 class UsersGroupRepoGroupToPerm(Base, BaseModel):
960 __tablename__ = 'users_group_repo_group_to_perm'
960 __tablename__ = 'users_group_repo_group_to_perm'
961 __table_args__ = (
961 __table_args__ = (
962 UniqueConstraint('users_group_id', 'group_id'),
962 UniqueConstraint('users_group_id', 'group_id'),
963 {'extend_existing': True}
963 {'extend_existing': True}
964 )
964 )
965
965
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)
966 users_group_repo_group_to_perm_id = Column("users_group_repo_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
967 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
967 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)
968 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)
969 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
970
970
971 users_group = relationship('UsersGroup')
971 users_group = relationship('UsersGroup')
972 permission = relationship('Permission')
972 permission = relationship('Permission')
973 group = relationship('RepoGroup')
973 group = relationship('RepoGroup')
974
974
975
975
976 class Statistics(Base, BaseModel):
976 class Statistics(Base, BaseModel):
977 __tablename__ = 'statistics'
977 __tablename__ = 'statistics'
978 __table_args__ = (UniqueConstraint('repository_id'), {'extend_existing': True})
978 __table_args__ = (UniqueConstraint('repository_id'), {'extend_existing': True})
979 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
979 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)
980 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)
981 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
982 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
982 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
983 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
983 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
984 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
984 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
985
985
986 repository = relationship('Repository', single_parent=True)
986 repository = relationship('Repository', single_parent=True)
987
987
988
988
989 class UserFollowing(Base, BaseModel):
989 class UserFollowing(Base, BaseModel):
990 __tablename__ = 'user_followings'
990 __tablename__ = 'user_followings'
991 __table_args__ = (
991 __table_args__ = (
992 UniqueConstraint('user_id', 'follows_repository_id'),
992 UniqueConstraint('user_id', 'follows_repository_id'),
993 UniqueConstraint('user_id', 'follows_user_id'),
993 UniqueConstraint('user_id', 'follows_user_id'),
994 {'extend_existing': True}
994 {'extend_existing': True}
995 )
995 )
996
996
997 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
997 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)
998 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)
999 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)
1000 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)
1001 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
1002
1002
1003 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
1003 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
1004
1004
1005 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
1005 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
1006 follows_repository = relationship('Repository', order_by='Repository.repo_name')
1006 follows_repository = relationship('Repository', order_by='Repository.repo_name')
1007
1007
1008 @classmethod
1008 @classmethod
1009 def get_repo_followers(cls, repo_id):
1009 def get_repo_followers(cls, repo_id):
1010 return cls.query().filter(cls.follows_repo_id == repo_id)
1010 return cls.query().filter(cls.follows_repo_id == repo_id)
1011
1011
1012
1012
1013 class CacheInvalidation(Base, BaseModel):
1013 class CacheInvalidation(Base, BaseModel):
1014 __tablename__ = 'cache_invalidation'
1014 __tablename__ = 'cache_invalidation'
1015 __table_args__ = (UniqueConstraint('cache_key'), {'extend_existing': True})
1015 __table_args__ = (UniqueConstraint('cache_key'), {'extend_existing': True})
1016 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1016 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)
1017 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)
1018 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)
1019 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
1020
1020
1021 def __init__(self, cache_key, cache_args=''):
1021 def __init__(self, cache_key, cache_args=''):
1022 self.cache_key = cache_key
1022 self.cache_key = cache_key
1023 self.cache_args = cache_args
1023 self.cache_args = cache_args
1024 self.cache_active = False
1024 self.cache_active = False
1025
1025
1026 def __repr__(self):
1026 def __repr__(self):
1027 return "<%s('%s:%s')>" % (self.__class__.__name__,
1027 return "<%s('%s:%s')>" % (self.__class__.__name__,
1028 self.cache_id, self.cache_key)
1028 self.cache_id, self.cache_key)
1029
1029
1030 @classmethod
1030 @classmethod
1031 def _get_key(cls, key):
1031 def _get_key(cls, key):
1032 """
1032 """
1033 Wrapper for generating a key
1033 Wrapper for generating a key
1034
1034
1035 :param key:
1035 :param key:
1036 """
1036 """
1037 import rhodecode
1037 import rhodecode
1038 prefix = ''
1038 prefix = ''
1039 iid = rhodecode.CONFIG.get('instance_id')
1039 iid = rhodecode.CONFIG.get('instance_id')
1040 if iid:
1040 if iid:
1041 prefix = iid
1041 prefix = iid
1042 return "%s%s" % (prefix, key)
1042 return "%s%s" % (prefix, key)
1043
1043
1044 @classmethod
1044 @classmethod
1045 def get_by_key(cls, key):
1045 def get_by_key(cls, key):
1046 return cls.query().filter(cls.cache_key == key).scalar()
1046 return cls.query().filter(cls.cache_key == key).scalar()
1047
1047
1048 @classmethod
1048 @classmethod
1049 def invalidate(cls, key):
1049 def invalidate(cls, key):
1050 """
1050 """
1051 Returns Invalidation object if this given key should be invalidated
1051 Returns Invalidation object if this given key should be invalidated
1052 None otherwise. `cache_active = False` means that this cache
1052 None otherwise. `cache_active = False` means that this cache
1053 state is not valid and needs to be invalidated
1053 state is not valid and needs to be invalidated
1054
1054
1055 :param key:
1055 :param key:
1056 """
1056 """
1057 return cls.query()\
1057 return cls.query()\
1058 .filter(CacheInvalidation.cache_key == key)\
1058 .filter(CacheInvalidation.cache_key == key)\
1059 .filter(CacheInvalidation.cache_active == False)\
1059 .filter(CacheInvalidation.cache_active == False)\
1060 .scalar()
1060 .scalar()
1061
1061
1062 @classmethod
1062 @classmethod
1063 def set_invalidate(cls, key):
1063 def set_invalidate(cls, key):
1064 """
1064 """
1065 Mark this Cache key for invalidation
1065 Mark this Cache key for invalidation
1066
1066
1067 :param key:
1067 :param key:
1068 """
1068 """
1069
1069
1070 log.debug('marking %s for invalidation' % key)
1070 log.debug('marking %s for invalidation' % key)
1071 inv_obj = Session.query(cls)\
1071 inv_obj = Session.query(cls)\
1072 .filter(cls.cache_key == key).scalar()
1072 .filter(cls.cache_key == key).scalar()
1073 if inv_obj:
1073 if inv_obj:
1074 inv_obj.cache_active = False
1074 inv_obj.cache_active = False
1075 else:
1075 else:
1076 log.debug('cache key not found in invalidation db -> creating one')
1076 log.debug('cache key not found in invalidation db -> creating one')
1077 inv_obj = CacheInvalidation(key)
1077 inv_obj = CacheInvalidation(key)
1078
1078
1079 try:
1079 try:
1080 Session.add(inv_obj)
1080 Session.add(inv_obj)
1081 Session.commit()
1081 Session.commit()
1082 except Exception:
1082 except Exception:
1083 log.error(traceback.format_exc())
1083 log.error(traceback.format_exc())
1084 Session.rollback()
1084 Session.rollback()
1085
1085
1086 @classmethod
1086 @classmethod
1087 def set_valid(cls, key):
1087 def set_valid(cls, key):
1088 """
1088 """
1089 Mark this cache key as active and currently cached
1089 Mark this cache key as active and currently cached
1090
1090
1091 :param key:
1091 :param key:
1092 """
1092 """
1093 inv_obj = cls.get_by_key(key)
1093 inv_obj = cls.get_by_key(key)
1094 inv_obj.cache_active = True
1094 inv_obj.cache_active = True
1095 Session.add(inv_obj)
1095 Session.add(inv_obj)
1096 Session.commit()
1096 Session.commit()
1097
1097
1098
1098
1099 class ChangesetComment(Base, BaseModel):
1099 class ChangesetComment(Base, BaseModel):
1100 __tablename__ = 'changeset_comments'
1100 __tablename__ = 'changeset_comments'
1101 __table_args__ = ({'extend_existing': True},)
1101 __table_args__ = ({'extend_existing': True},)
1102 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
1102 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
1103 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1103 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1104 revision = Column('revision', String(40), nullable=False)
1104 revision = Column('revision', String(40), nullable=False)
1105 line_no = Column('line_no', Unicode(10), nullable=True)
1105 line_no = Column('line_no', Unicode(10), nullable=True)
1106 f_path = Column('f_path', Unicode(1000), nullable=True)
1106 f_path = Column('f_path', Unicode(1000), nullable=True)
1107 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
1107 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
1108 text = Column('text', Unicode(25000), nullable=False)
1108 text = Column('text', Unicode(25000), nullable=False)
1109 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
1109 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
1110
1110
1111 author = relationship('User', lazy='joined')
1111 author = relationship('User', lazy='joined')
1112 repo = relationship('Repository')
1112 repo = relationship('Repository')
1113
1113
1114 @classmethod
1114 @classmethod
1115 def get_users(cls, revision):
1115 def get_users(cls, revision):
1116 """
1116 """
1117 Returns user associated with this changesetComment. ie those
1117 Returns user associated with this changesetComment. ie those
1118 who actually commented
1118 who actually commented
1119
1119
1120 :param cls:
1120 :param cls:
1121 :param revision:
1121 :param revision:
1122 """
1122 """
1123 return Session.query(User)\
1123 return Session.query(User)\
1124 .filter(cls.revision == revision)\
1124 .filter(cls.revision == revision)\
1125 .join(ChangesetComment.author).all()
1125 .join(ChangesetComment.author).all()
1126
1126
1127
1127
1128 class Notification(Base, BaseModel):
1128 class Notification(Base, BaseModel):
1129 __tablename__ = 'notifications'
1129 __tablename__ = 'notifications'
1130 __table_args__ = ({'extend_existing': True},)
1130 __table_args__ = ({'extend_existing': True},)
1131
1131
1132 TYPE_CHANGESET_COMMENT = u'cs_comment'
1132 TYPE_CHANGESET_COMMENT = u'cs_comment'
1133 TYPE_MESSAGE = u'message'
1133 TYPE_MESSAGE = u'message'
1134 TYPE_MENTION = u'mention'
1134 TYPE_MENTION = u'mention'
1135 TYPE_REGISTRATION = u'registration'
1135 TYPE_REGISTRATION = u'registration'
1136
1136
1137 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
1137 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
1138 subject = Column('subject', Unicode(512), nullable=True)
1138 subject = Column('subject', Unicode(512), nullable=True)
1139 body = Column('body', Unicode(50000), nullable=True)
1139 body = Column('body', Unicode(50000), nullable=True)
1140 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
1140 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)
1141 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1142 type_ = Column('type', Unicode(256))
1142 type_ = Column('type', Unicode(256))
1143
1143
1144 created_by_user = relationship('User')
1144 created_by_user = relationship('User')
1145 notifications_to_users = relationship('UserNotification', lazy='joined',
1145 notifications_to_users = relationship('UserNotification', lazy='joined',
1146 cascade="all, delete, delete-orphan")
1146 cascade="all, delete, delete-orphan")
1147
1147
1148 @property
1148 @property
1149 def recipients(self):
1149 def recipients(self):
1150 return [x.user for x in UserNotification.query()\
1150 return [x.user for x in UserNotification.query()\
1151 .filter(UserNotification.notification == self).all()]
1151 .filter(UserNotification.notification == self).all()]
1152
1152
1153 @classmethod
1153 @classmethod
1154 def create(cls, created_by, subject, body, recipients, type_=None):
1154 def create(cls, created_by, subject, body, recipients, type_=None):
1155 if type_ is None:
1155 if type_ is None:
1156 type_ = Notification.TYPE_MESSAGE
1156 type_ = Notification.TYPE_MESSAGE
1157
1157
1158 notification = cls()
1158 notification = cls()
1159 notification.created_by_user = created_by
1159 notification.created_by_user = created_by
1160 notification.subject = subject
1160 notification.subject = subject
1161 notification.body = body
1161 notification.body = body
1162 notification.type_ = type_
1162 notification.type_ = type_
1163 notification.created_on = datetime.datetime.now()
1163 notification.created_on = datetime.datetime.now()
1164
1164
1165 for u in recipients:
1165 for u in recipients:
1166 assoc = UserNotification()
1166 assoc = UserNotification()
1167 assoc.notification = notification
1167 assoc.notification = notification
1168 u.notifications.append(assoc)
1168 u.notifications.append(assoc)
1169 Session.add(notification)
1169 Session.add(notification)
1170 return notification
1170 return notification
1171
1171
1172 @property
1172 @property
1173 def description(self):
1173 def description(self):
1174 from rhodecode.model.notification import NotificationModel
1174 from rhodecode.model.notification import NotificationModel
1175 return NotificationModel().make_description(self)
1175 return NotificationModel().make_description(self)
1176
1176
1177
1177
1178 class UserNotification(Base, BaseModel):
1178 class UserNotification(Base, BaseModel):
1179 __tablename__ = 'user_to_notification'
1179 __tablename__ = 'user_to_notification'
1180 __table_args__ = (
1180 __table_args__ = (
1181 UniqueConstraint('user_id', 'notification_id'),
1181 UniqueConstraint('user_id', 'notification_id'),
1182 {'extend_existing': True}
1182 {'extend_existing': True}
1183 )
1183 )
1184 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
1184 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)
1185 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
1186 read = Column('read', Boolean, default=False)
1186 read = Column('read', Boolean, default=False)
1187 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
1187 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
1188
1188
1189 user = relationship('User', lazy="joined")
1189 user = relationship('User', lazy="joined")
1190 notification = relationship('Notification', lazy="joined",
1190 notification = relationship('Notification', lazy="joined",
1191 order_by=lambda: Notification.created_on.desc(),)
1191 order_by=lambda: Notification.created_on.desc(),)
1192
1192
1193 def mark_as_read(self):
1193 def mark_as_read(self):
1194 self.read = True
1194 self.read = True
1195 Session.add(self)
1195 Session.add(self)
1196
1196
1197
1197
1198 class DbMigrateVersion(Base, BaseModel):
1198 class DbMigrateVersion(Base, BaseModel):
1199 __tablename__ = 'db_migrate_version'
1199 __tablename__ = 'db_migrate_version'
1200 __table_args__ = {'extend_existing': True}
1200 __table_args__ = {'extend_existing': True}
1201 repository_id = Column('repository_id', String(250), primary_key=True)
1201 repository_id = Column('repository_id', String(250), primary_key=True)
1202 repository_path = Column('repository_path', Text)
1202 repository_path = Column('repository_path', Text)
1203 version = Column('version', Integer)
1203 version = Column('version', Integer)
@@ -1,773 +1,773 b''
1 """ this is forms validation classes
1 """ this is forms validation classes
2 http://formencode.org/module-formencode.validators.html
2 http://formencode.org/module-formencode.validators.html
3 for list off all availible validators
3 for list off all availible validators
4
4
5 we can create our own validators
5 we can create our own validators
6
6
7 The table below outlines the options which can be used in a schema in addition to the validators themselves
7 The table below outlines the options which can be used in a schema in addition to the validators themselves
8 pre_validators [] These validators will be applied before the schema
8 pre_validators [] These validators will be applied before the schema
9 chained_validators [] These validators will be applied after the schema
9 chained_validators [] These validators will be applied after the schema
10 allow_extra_fields False If True, then it is not an error when keys that aren't associated with a validator are present
10 allow_extra_fields False If True, then it is not an error when keys that aren't associated with a validator are present
11 filter_extra_fields False If True, then keys that aren't associated with a validator are removed
11 filter_extra_fields False If True, then keys that aren't associated with a validator are removed
12 if_key_missing NoDefault If this is given, then any keys that aren't available but are expected will be replaced with this value (and then validated). This does not override a present .if_missing attribute on validators. NoDefault is a special FormEncode class to mean that no default values has been specified and therefore missing keys shouldn't take a default value.
12 if_key_missing NoDefault If this is given, then any keys that aren't available but are expected will be replaced with this value (and then validated). This does not override a present .if_missing attribute on validators. NoDefault is a special FormEncode class to mean that no default values has been specified and therefore missing keys shouldn't take a default value.
13 ignore_key_missing False If True, then missing keys will be missing in the result, if the validator doesn't have .if_missing on it already
13 ignore_key_missing False If True, then missing keys will be missing in the result, if the validator doesn't have .if_missing on it already
14
14
15
15
16 <name> = formencode.validators.<name of validator>
16 <name> = formencode.validators.<name of validator>
17 <name> must equal form name
17 <name> must equal form name
18 list=[1,2,3,4,5]
18 list=[1,2,3,4,5]
19 for SELECT use formencode.All(OneOf(list), Int())
19 for SELECT use formencode.All(OneOf(list), Int())
20
20
21 """
21 """
22 import os
22 import os
23 import re
23 import re
24 import logging
24 import logging
25 import traceback
25 import traceback
26
26
27 import formencode
27 import formencode
28 from formencode import All
28 from formencode import All
29 from formencode.validators import UnicodeString, OneOf, Int, Number, Regex, \
29 from formencode.validators import UnicodeString, OneOf, Int, Number, Regex, \
30 Email, Bool, StringBoolean, Set
30 Email, Bool, StringBoolean, Set
31
31
32 from pylons.i18n.translation import _
32 from pylons.i18n.translation import _
33 from webhelpers.pylonslib.secure_form import authentication_token
33 from webhelpers.pylonslib.secure_form import authentication_token
34
34
35 from rhodecode.config.routing import ADMIN_PREFIX
35 from rhodecode.config.routing import ADMIN_PREFIX
36 from rhodecode.lib.utils import repo_name_slug
36 from rhodecode.lib.utils import repo_name_slug
37 from rhodecode.lib.auth import authenticate, get_crypt_password
37 from rhodecode.lib.auth import authenticate, get_crypt_password
38 from rhodecode.lib.exceptions import LdapImportError
38 from rhodecode.lib.exceptions import LdapImportError
39 from rhodecode.model.db import User, UsersGroup, RepoGroup, Repository
39 from rhodecode.model.db import User, UsersGroup, RepoGroup, Repository
40 from rhodecode import BACKENDS
40 from rhodecode import BACKENDS
41
41
42 log = logging.getLogger(__name__)
42 log = logging.getLogger(__name__)
43
43
44
44
45 #this is needed to translate the messages using _() in validators
45 #this is needed to translate the messages using _() in validators
46 class State_obj(object):
46 class State_obj(object):
47 _ = staticmethod(_)
47 _ = staticmethod(_)
48
48
49
49
50 #==============================================================================
50 #==============================================================================
51 # VALIDATORS
51 # VALIDATORS
52 #==============================================================================
52 #==============================================================================
53 class ValidAuthToken(formencode.validators.FancyValidator):
53 class ValidAuthToken(formencode.validators.FancyValidator):
54 messages = {'invalid_token': _('Token mismatch')}
54 messages = {'invalid_token': _('Token mismatch')}
55
55
56 def validate_python(self, value, state):
56 def validate_python(self, value, state):
57
57
58 if value != authentication_token():
58 if value != authentication_token():
59 raise formencode.Invalid(
59 raise formencode.Invalid(
60 self.message('invalid_token',
60 self.message('invalid_token',
61 state, search_number=value),
61 state, search_number=value),
62 value,
62 value,
63 state
63 state
64 )
64 )
65
65
66
66
67 def ValidUsername(edit, old_data):
67 def ValidUsername(edit, old_data):
68 class _ValidUsername(formencode.validators.FancyValidator):
68 class _ValidUsername(formencode.validators.FancyValidator):
69
69
70 def validate_python(self, value, state):
70 def validate_python(self, value, state):
71 if value in ['default', 'new_user']:
71 if value in ['default', 'new_user']:
72 raise formencode.Invalid(_('Invalid username'), value, state)
72 raise formencode.Invalid(_('Invalid username'), value, state)
73 #check if user is unique
73 #check if user is unique
74 old_un = None
74 old_un = None
75 if edit:
75 if edit:
76 old_un = User.get(old_data.get('user_id')).username
76 old_un = User.get(old_data.get('user_id')).username
77
77
78 if old_un != value or not edit:
78 if old_un != value or not edit:
79 if User.get_by_username(value, case_insensitive=True):
79 if User.get_by_username(value, case_insensitive=True):
80 raise formencode.Invalid(_('This username already '
80 raise formencode.Invalid(_('This username already '
81 'exists') , value, state)
81 'exists') , value, state)
82
82
83 if re.match(r'^[a-zA-Z0-9]{1}[a-zA-Z0-9\-\_\.]+$', value) is None:
83 if re.match(r'^[a-zA-Z0-9]{1}[a-zA-Z0-9\-\_\.]+$', value) is None:
84 raise formencode.Invalid(
84 raise formencode.Invalid(
85 _('Username may only contain alphanumeric characters '
85 _('Username may only contain alphanumeric characters '
86 'underscores, periods or dashes and must begin with '
86 'underscores, periods or dashes and must begin with '
87 'alphanumeric character'),
87 'alphanumeric character'),
88 value,
88 value,
89 state
89 state
90 )
90 )
91
91
92 return _ValidUsername
92 return _ValidUsername
93
93
94
94
95 def ValidUsersGroup(edit, old_data):
95 def ValidUsersGroup(edit, old_data):
96
96
97 class _ValidUsersGroup(formencode.validators.FancyValidator):
97 class _ValidUsersGroup(formencode.validators.FancyValidator):
98
98
99 def validate_python(self, value, state):
99 def validate_python(self, value, state):
100 if value in ['default']:
100 if value in ['default']:
101 raise formencode.Invalid(_('Invalid group name'), value, state)
101 raise formencode.Invalid(_('Invalid group name'), value, state)
102 #check if group is unique
102 #check if group is unique
103 old_ugname = None
103 old_ugname = None
104 if edit:
104 if edit:
105 old_ugname = UsersGroup.get(
105 old_ugname = UsersGroup.get(
106 old_data.get('users_group_id')).users_group_name
106 old_data.get('users_group_id')).users_group_name
107
107
108 if old_ugname != value or not edit:
108 if old_ugname != value or not edit:
109 if UsersGroup.get_by_group_name(value, cache=False,
109 if UsersGroup.get_by_group_name(value, cache=False,
110 case_insensitive=True):
110 case_insensitive=True):
111 raise formencode.Invalid(_('This users group '
111 raise formencode.Invalid(_('This users group '
112 'already exists'), value,
112 'already exists'), value,
113 state)
113 state)
114
114
115 if re.match(r'^[a-zA-Z0-9]{1}[a-zA-Z0-9\-\_\.]+$', value) is None:
115 if re.match(r'^[a-zA-Z0-9]{1}[a-zA-Z0-9\-\_\.]+$', value) is None:
116 raise formencode.Invalid(
116 raise formencode.Invalid(
117 _('RepoGroup name may only contain alphanumeric characters '
117 _('RepoGroup name may only contain alphanumeric characters '
118 'underscores, periods or dashes and must begin with '
118 'underscores, periods or dashes and must begin with '
119 'alphanumeric character'),
119 'alphanumeric character'),
120 value,
120 value,
121 state
121 state
122 )
122 )
123
123
124 return _ValidUsersGroup
124 return _ValidUsersGroup
125
125
126
126
127 def ValidReposGroup(edit, old_data):
127 def ValidReposGroup(edit, old_data):
128 class _ValidReposGroup(formencode.validators.FancyValidator):
128 class _ValidReposGroup(formencode.validators.FancyValidator):
129
129
130 def validate_python(self, value, state):
130 def validate_python(self, value, state):
131 # TODO WRITE VALIDATIONS
131 # TODO WRITE VALIDATIONS
132 group_name = value.get('group_name')
132 group_name = value.get('group_name')
133 group_parent_id = value.get('group_parent_id')
133 group_parent_id = value.get('group_parent_id')
134
134
135 # slugify repo group just in case :)
135 # slugify repo group just in case :)
136 slug = repo_name_slug(group_name)
136 slug = repo_name_slug(group_name)
137
137
138 # check for parent of self
138 # check for parent of self
139 parent_of_self = lambda: (
139 parent_of_self = lambda: (
140 old_data['group_id'] == int(group_parent_id)
140 old_data['group_id'] == int(group_parent_id)
141 if group_parent_id else False
141 if group_parent_id else False
142 )
142 )
143 if edit and parent_of_self():
143 if edit and parent_of_self():
144 e_dict = {
144 e_dict = {
145 'group_parent_id': _('Cannot assign this group as parent')
145 'group_parent_id': _('Cannot assign this group as parent')
146 }
146 }
147 raise formencode.Invalid('', value, state,
147 raise formencode.Invalid('', value, state,
148 error_dict=e_dict)
148 error_dict=e_dict)
149
149
150 old_gname = None
150 old_gname = None
151 if edit:
151 if edit:
152 old_gname = RepoGroup.get(old_data.get('group_id')).group_name
152 old_gname = RepoGroup.get(old_data.get('group_id')).group_name
153
153
154 if old_gname != group_name or not edit:
154 if old_gname != group_name or not edit:
155
155
156 # check group
156 # check group
157 gr = RepoGroup.query()\
157 gr = RepoGroup.query()\
158 .filter(RepoGroup.group_name == slug)\
158 .filter(RepoGroup.group_name == slug)\
159 .filter(RepoGroup.group_parent_id == group_parent_id)\
159 .filter(RepoGroup.group_parent_id == group_parent_id)\
160 .scalar()
160 .scalar()
161
161
162 if gr:
162 if gr:
163 e_dict = {
163 e_dict = {
164 'group_name': _('This group already exists')
164 'group_name': _('This group already exists')
165 }
165 }
166 raise formencode.Invalid('', value, state,
166 raise formencode.Invalid('', value, state,
167 error_dict=e_dict)
167 error_dict=e_dict)
168
168
169 # check for same repo
169 # check for same repo
170 repo = Repository.query()\
170 repo = Repository.query()\
171 .filter(Repository.repo_name == slug)\
171 .filter(Repository.repo_name == slug)\
172 .scalar()
172 .scalar()
173
173
174 if repo:
174 if repo:
175 e_dict = {
175 e_dict = {
176 'group_name': _('Repository with this name already exists')
176 'group_name': _('Repository with this name already exists')
177 }
177 }
178 raise formencode.Invalid('', value, state,
178 raise formencode.Invalid('', value, state,
179 error_dict=e_dict)
179 error_dict=e_dict)
180
180
181 return _ValidReposGroup
181 return _ValidReposGroup
182
182
183
183
184 class ValidPassword(formencode.validators.FancyValidator):
184 class ValidPassword(formencode.validators.FancyValidator):
185
185
186 def to_python(self, value, state):
186 def to_python(self, value, state):
187
187
188 if not value:
188 if not value:
189 return
189 return
190
190
191 if value.get('password'):
191 if value.get('password'):
192 try:
192 try:
193 value['password'] = get_crypt_password(value['password'])
193 value['password'] = get_crypt_password(value['password'])
194 except UnicodeEncodeError:
194 except UnicodeEncodeError:
195 e_dict = {'password': _('Invalid characters in password')}
195 e_dict = {'password': _('Invalid characters in password')}
196 raise formencode.Invalid('', value, state, error_dict=e_dict)
196 raise formencode.Invalid('', value, state, error_dict=e_dict)
197
197
198 if value.get('password_confirmation'):
198 if value.get('password_confirmation'):
199 try:
199 try:
200 value['password_confirmation'] = \
200 value['password_confirmation'] = \
201 get_crypt_password(value['password_confirmation'])
201 get_crypt_password(value['password_confirmation'])
202 except UnicodeEncodeError:
202 except UnicodeEncodeError:
203 e_dict = {
203 e_dict = {
204 'password_confirmation': _('Invalid characters in password')
204 'password_confirmation': _('Invalid characters in password')
205 }
205 }
206 raise formencode.Invalid('', value, state, error_dict=e_dict)
206 raise formencode.Invalid('', value, state, error_dict=e_dict)
207
207
208 if value.get('new_password'):
208 if value.get('new_password'):
209 try:
209 try:
210 value['new_password'] = \
210 value['new_password'] = \
211 get_crypt_password(value['new_password'])
211 get_crypt_password(value['new_password'])
212 except UnicodeEncodeError:
212 except UnicodeEncodeError:
213 e_dict = {'new_password': _('Invalid characters in password')}
213 e_dict = {'new_password': _('Invalid characters in password')}
214 raise formencode.Invalid('', value, state, error_dict=e_dict)
214 raise formencode.Invalid('', value, state, error_dict=e_dict)
215
215
216 return value
216 return value
217
217
218
218
219 class ValidPasswordsMatch(formencode.validators.FancyValidator):
219 class ValidPasswordsMatch(formencode.validators.FancyValidator):
220
220
221 def validate_python(self, value, state):
221 def validate_python(self, value, state):
222
222
223 pass_val = value.get('password') or value.get('new_password')
223 pass_val = value.get('password') or value.get('new_password')
224 if pass_val != value['password_confirmation']:
224 if pass_val != value['password_confirmation']:
225 e_dict = {'password_confirmation':
225 e_dict = {'password_confirmation':
226 _('Passwords do not match')}
226 _('Passwords do not match')}
227 raise formencode.Invalid('', value, state, error_dict=e_dict)
227 raise formencode.Invalid('', value, state, error_dict=e_dict)
228
228
229
229
230 class ValidAuth(formencode.validators.FancyValidator):
230 class ValidAuth(formencode.validators.FancyValidator):
231 messages = {
231 messages = {
232 'invalid_password':_('invalid password'),
232 'invalid_password':_('invalid password'),
233 'invalid_login':_('invalid user name'),
233 'invalid_login':_('invalid user name'),
234 'disabled_account':_('Your account is disabled')
234 'disabled_account':_('Your account is disabled')
235 }
235 }
236
236
237 # error mapping
237 # error mapping
238 e_dict = {'username': messages['invalid_login'],
238 e_dict = {'username': messages['invalid_login'],
239 'password': messages['invalid_password']}
239 'password': messages['invalid_password']}
240 e_dict_disable = {'username': messages['disabled_account']}
240 e_dict_disable = {'username': messages['disabled_account']}
241
241
242 def validate_python(self, value, state):
242 def validate_python(self, value, state):
243 password = value['password']
243 password = value['password']
244 username = value['username']
244 username = value['username']
245 user = User.get_by_username(username)
245 user = User.get_by_username(username)
246
246
247 if authenticate(username, password):
247 if authenticate(username, password):
248 return value
248 return value
249 else:
249 else:
250 if user and user.active is False:
250 if user and user.active is False:
251 log.warning('user %s is disabled' % username)
251 log.warning('user %s is disabled' % username)
252 raise formencode.Invalid(
252 raise formencode.Invalid(
253 self.message('disabled_account',
253 self.message('disabled_account',
254 state=State_obj),
254 state=State_obj),
255 value, state,
255 value, state,
256 error_dict=self.e_dict_disable
256 error_dict=self.e_dict_disable
257 )
257 )
258 else:
258 else:
259 log.warning('user %s failed to authenticate' % username)
259 log.warning('user %s failed to authenticate' % username)
260 raise formencode.Invalid(
260 raise formencode.Invalid(
261 self.message('invalid_password',
261 self.message('invalid_password',
262 state=State_obj), value, state,
262 state=State_obj), value, state,
263 error_dict=self.e_dict
263 error_dict=self.e_dict
264 )
264 )
265
265
266
266
267 class ValidRepoUser(formencode.validators.FancyValidator):
267 class ValidRepoUser(formencode.validators.FancyValidator):
268
268
269 def to_python(self, value, state):
269 def to_python(self, value, state):
270 try:
270 try:
271 User.query().filter(User.active == True)\
271 User.query().filter(User.active == True)\
272 .filter(User.username == value).one()
272 .filter(User.username == value).one()
273 except Exception:
273 except Exception:
274 raise formencode.Invalid(_('This username is not valid'),
274 raise formencode.Invalid(_('This username is not valid'),
275 value, state)
275 value, state)
276 return value
276 return value
277
277
278
278
279 def ValidRepoName(edit, old_data):
279 def ValidRepoName(edit, old_data):
280 class _ValidRepoName(formencode.validators.FancyValidator):
280 class _ValidRepoName(formencode.validators.FancyValidator):
281 def to_python(self, value, state):
281 def to_python(self, value, state):
282
282
283 repo_name = value.get('repo_name')
283 repo_name = value.get('repo_name')
284
284
285 slug = repo_name_slug(repo_name)
285 slug = repo_name_slug(repo_name)
286 if slug in [ADMIN_PREFIX, '']:
286 if slug in [ADMIN_PREFIX, '']:
287 e_dict = {'repo_name': _('This repository name is disallowed')}
287 e_dict = {'repo_name': _('This repository name is disallowed')}
288 raise formencode.Invalid('', value, state, error_dict=e_dict)
288 raise formencode.Invalid('', value, state, error_dict=e_dict)
289
289
290 if value.get('repo_group'):
290 if value.get('repo_group'):
291 gr = RepoGroup.get(value.get('repo_group'))
291 gr = RepoGroup.get(value.get('repo_group'))
292 group_path = gr.full_path
292 group_path = gr.full_path
293 # value needs to be aware of group name in order to check
293 # value needs to be aware of group name in order to check
294 # db key This is an actual just the name to store in the
294 # db key This is an actual just the name to store in the
295 # database
295 # database
296 repo_name_full = group_path + RepoGroup.url_sep() + repo_name
296 repo_name_full = group_path + RepoGroup.url_sep() + repo_name
297
297
298 else:
298 else:
299 group_path = ''
299 group_path = ''
300 repo_name_full = repo_name
300 repo_name_full = repo_name
301
301
302 value['repo_name_full'] = repo_name_full
302 value['repo_name_full'] = repo_name_full
303 rename = old_data.get('repo_name') != repo_name_full
303 rename = old_data.get('repo_name') != repo_name_full
304 create = not edit
304 create = not edit
305 if rename or create:
305 if rename or create:
306
306
307 if group_path != '':
307 if group_path != '':
308 if Repository.get_by_repo_name(repo_name_full):
308 if Repository.get_by_repo_name(repo_name_full):
309 e_dict = {
309 e_dict = {
310 'repo_name': _('This repository already exists in '
310 'repo_name': _('This repository already exists in '
311 'a group "%s"') % gr.group_name
311 'a group "%s"') % gr.group_name
312 }
312 }
313 raise formencode.Invalid('', value, state,
313 raise formencode.Invalid('', value, state,
314 error_dict=e_dict)
314 error_dict=e_dict)
315 elif RepoGroup.get_by_group_name(repo_name_full):
315 elif RepoGroup.get_by_group_name(repo_name_full):
316 e_dict = {
316 e_dict = {
317 'repo_name': _('There is a group with this name '
317 'repo_name': _('There is a group with this name '
318 'already "%s"') % repo_name_full
318 'already "%s"') % repo_name_full
319 }
319 }
320 raise formencode.Invalid('', value, state,
320 raise formencode.Invalid('', value, state,
321 error_dict=e_dict)
321 error_dict=e_dict)
322
322
323 elif Repository.get_by_repo_name(repo_name_full):
323 elif Repository.get_by_repo_name(repo_name_full):
324 e_dict = {'repo_name': _('This repository '
324 e_dict = {'repo_name': _('This repository '
325 'already exists')}
325 'already exists')}
326 raise formencode.Invalid('', value, state,
326 raise formencode.Invalid('', value, state,
327 error_dict=e_dict)
327 error_dict=e_dict)
328
328
329 return value
329 return value
330
330
331 return _ValidRepoName
331 return _ValidRepoName
332
332
333
333
334 def ValidForkName(*args, **kwargs):
334 def ValidForkName(*args, **kwargs):
335 return ValidRepoName(*args, **kwargs)
335 return ValidRepoName(*args, **kwargs)
336
336
337
337
338 def SlugifyName():
338 def SlugifyName():
339 class _SlugifyName(formencode.validators.FancyValidator):
339 class _SlugifyName(formencode.validators.FancyValidator):
340
340
341 def to_python(self, value, state):
341 def to_python(self, value, state):
342 return repo_name_slug(value)
342 return repo_name_slug(value)
343
343
344 return _SlugifyName
344 return _SlugifyName
345
345
346
346
347 def ValidCloneUri():
347 def ValidCloneUri():
348 from rhodecode.lib.utils import make_ui
348 from rhodecode.lib.utils import make_ui
349
349
350 def url_handler(repo_type, url, proto, ui=None):
350 def url_handler(repo_type, url, proto, ui=None):
351 if repo_type == 'hg':
351 if repo_type == 'hg':
352 from mercurial.httprepo import httprepository, httpsrepository
352 from mercurial.httprepo import httprepository, httpsrepository
353 if proto == 'https':
353 if proto == 'https':
354 httpsrepository(make_ui('db'), url).capabilities
354 httpsrepository(make_ui('db'), url).capabilities
355 elif proto == 'http':
355 elif proto == 'http':
356 httprepository(make_ui('db'), url).capabilities
356 httprepository(make_ui('db'), url).capabilities
357 elif repo_type == 'git':
357 elif repo_type == 'git':
358 #TODO: write a git url validator
358 #TODO: write a git url validator
359 pass
359 pass
360
360
361 class _ValidCloneUri(formencode.validators.FancyValidator):
361 class _ValidCloneUri(formencode.validators.FancyValidator):
362
362
363 def to_python(self, value, state):
363 def to_python(self, value, state):
364
364
365 repo_type = value.get('repo_type')
365 repo_type = value.get('repo_type')
366 url = value.get('clone_uri')
366 url = value.get('clone_uri')
367 e_dict = {'clone_uri': _('invalid clone url')}
367 e_dict = {'clone_uri': _('invalid clone url')}
368
368
369 if not url:
369 if not url:
370 pass
370 pass
371 elif url.startswith('https'):
371 elif url.startswith('https'):
372 try:
372 try:
373 url_handler(repo_type, url, 'https', make_ui('db'))
373 url_handler(repo_type, url, 'https', make_ui('db'))
374 except Exception:
374 except Exception:
375 log.error(traceback.format_exc())
375 log.error(traceback.format_exc())
376 raise formencode.Invalid('', value, state, error_dict=e_dict)
376 raise formencode.Invalid('', value, state, error_dict=e_dict)
377 elif url.startswith('http'):
377 elif url.startswith('http'):
378 try:
378 try:
379 url_handler(repo_type, url, 'http', make_ui('db'))
379 url_handler(repo_type, url, 'http', make_ui('db'))
380 except Exception:
380 except Exception:
381 log.error(traceback.format_exc())
381 log.error(traceback.format_exc())
382 raise formencode.Invalid('', value, state, error_dict=e_dict)
382 raise formencode.Invalid('', value, state, error_dict=e_dict)
383 else:
383 else:
384 e_dict = {'clone_uri': _('Invalid clone url, provide a '
384 e_dict = {'clone_uri': _('Invalid clone url, provide a '
385 'valid clone http\s url')}
385 'valid clone http\s url')}
386 raise formencode.Invalid('', value, state, error_dict=e_dict)
386 raise formencode.Invalid('', value, state, error_dict=e_dict)
387
387
388 return value
388 return value
389
389
390 return _ValidCloneUri
390 return _ValidCloneUri
391
391
392
392
393 def ValidForkType(old_data):
393 def ValidForkType(old_data):
394 class _ValidForkType(formencode.validators.FancyValidator):
394 class _ValidForkType(formencode.validators.FancyValidator):
395
395
396 def to_python(self, value, state):
396 def to_python(self, value, state):
397 if old_data['repo_type'] != value:
397 if old_data['repo_type'] != value:
398 raise formencode.Invalid(_('Fork have to be the same '
398 raise formencode.Invalid(_('Fork have to be the same '
399 'type as original'), value, state)
399 'type as original'), value, state)
400
400
401 return value
401 return value
402 return _ValidForkType
402 return _ValidForkType
403
403
404
404
405 def ValidPerms(type_='repo'):
405 def ValidPerms(type_='repo'):
406 if type_ == 'group':
406 if type_ == 'group':
407 EMPTY_PERM = 'group.none'
407 EMPTY_PERM = 'group.none'
408 elif type_ == 'repo':
408 elif type_ == 'repo':
409 EMPTY_PERM = 'repository.none'
409 EMPTY_PERM = 'repository.none'
410
410
411 class _ValidPerms(formencode.validators.FancyValidator):
411 class _ValidPerms(formencode.validators.FancyValidator):
412 messages = {
412 messages = {
413 'perm_new_member_name':
413 'perm_new_member_name':
414 _('This username or users group name is not valid')
414 _('This username or users group name is not valid')
415 }
415 }
416
416
417 def to_python(self, value, state):
417 def to_python(self, value, state):
418 perms_update = []
418 perms_update = []
419 perms_new = []
419 perms_new = []
420 # build a list of permission to update and new permission to create
420 # build a list of permission to update and new permission to create
421 for k, v in value.items():
421 for k, v in value.items():
422 # means new added member to permissions
422 # means new added member to permissions
423 if k.startswith('perm_new_member'):
423 if k.startswith('perm_new_member'):
424 new_perm = value.get('perm_new_member', False)
424 new_perm = value.get('perm_new_member', False)
425 new_member = value.get('perm_new_member_name', False)
425 new_member = value.get('perm_new_member_name', False)
426 new_type = value.get('perm_new_member_type')
426 new_type = value.get('perm_new_member_type')
427
427
428 if new_member and new_perm:
428 if new_member and new_perm:
429 if (new_member, new_perm, new_type) not in perms_new:
429 if (new_member, new_perm, new_type) not in perms_new:
430 perms_new.append((new_member, new_perm, new_type))
430 perms_new.append((new_member, new_perm, new_type))
431 elif k.startswith('u_perm_') or k.startswith('g_perm_'):
431 elif k.startswith('u_perm_') or k.startswith('g_perm_'):
432 member = k[7:]
432 member = k[7:]
433 t = {'u': 'user',
433 t = {'u': 'user',
434 'g': 'users_group'
434 'g': 'users_group'
435 }[k[0]]
435 }[k[0]]
436 if member == 'default':
436 if member == 'default':
437 if value.get('private'):
437 if value.get('private'):
438 # set none for default when updating to private repo
438 # set none for default when updating to private repo
439 v = EMPTY_PERM
439 v = EMPTY_PERM
440 perms_update.append((member, v, t))
440 perms_update.append((member, v, t))
441
441
442 value['perms_updates'] = perms_update
442 value['perms_updates'] = perms_update
443 value['perms_new'] = perms_new
443 value['perms_new'] = perms_new
444
444
445 # update permissions
445 # update permissions
446 for k, v, t in perms_new:
446 for k, v, t in perms_new:
447 try:
447 try:
448 if t is 'user':
448 if t is 'user':
449 self.user_db = User.query()\
449 self.user_db = User.query()\
450 .filter(User.active == True)\
450 .filter(User.active == True)\
451 .filter(User.username == k).one()
451 .filter(User.username == k).one()
452 if t is 'users_group':
452 if t is 'users_group':
453 self.user_db = UsersGroup.query()\
453 self.user_db = UsersGroup.query()\
454 .filter(UsersGroup.users_group_active == True)\
454 .filter(UsersGroup.users_group_active == True)\
455 .filter(UsersGroup.users_group_name == k).one()
455 .filter(UsersGroup.users_group_name == k).one()
456
456
457 except Exception:
457 except Exception:
458 msg = self.message('perm_new_member_name',
458 msg = self.message('perm_new_member_name',
459 state=State_obj)
459 state=State_obj)
460 raise formencode.Invalid(
460 raise formencode.Invalid(
461 msg, value, state, error_dict={'perm_new_member_name': msg}
461 msg, value, state, error_dict={'perm_new_member_name': msg}
462 )
462 )
463 return value
463 return value
464 return _ValidPerms
464 return _ValidPerms
465
465
466
466
467 class ValidSettings(formencode.validators.FancyValidator):
467 class ValidSettings(formencode.validators.FancyValidator):
468
468
469 def to_python(self, value, state):
469 def to_python(self, value, state):
470 # settings form can't edit user
470 # settings form can't edit user
471 if 'user' in value:
471 if 'user' in value:
472 del['value']['user']
472 del['value']['user']
473 return value
473 return value
474
474
475
475
476 class ValidPath(formencode.validators.FancyValidator):
476 class ValidPath(formencode.validators.FancyValidator):
477 def to_python(self, value, state):
477 def to_python(self, value, state):
478
478
479 if not os.path.isdir(value):
479 if not os.path.isdir(value):
480 msg = _('This is not a valid path')
480 msg = _('This is not a valid path')
481 raise formencode.Invalid(msg, value, state,
481 raise formencode.Invalid(msg, value, state,
482 error_dict={'paths_root_path': msg})
482 error_dict={'paths_root_path': msg})
483 return value
483 return value
484
484
485
485
486 def UniqSystemEmail(old_data):
486 def UniqSystemEmail(old_data):
487 class _UniqSystemEmail(formencode.validators.FancyValidator):
487 class _UniqSystemEmail(formencode.validators.FancyValidator):
488 def to_python(self, value, state):
488 def to_python(self, value, state):
489 value = value.lower()
489 value = value.lower()
490 if old_data.get('email', '').lower() != value:
490 if old_data.get('email', '').lower() != value:
491 user = User.get_by_email(value, case_insensitive=True)
491 user = User.get_by_email(value, case_insensitive=True)
492 if user:
492 if user:
493 raise formencode.Invalid(
493 raise formencode.Invalid(
494 _("This e-mail address is already taken"), value, state
494 _("This e-mail address is already taken"), value, state
495 )
495 )
496 return value
496 return value
497
497
498 return _UniqSystemEmail
498 return _UniqSystemEmail
499
499
500
500
501 class ValidSystemEmail(formencode.validators.FancyValidator):
501 class ValidSystemEmail(formencode.validators.FancyValidator):
502 def to_python(self, value, state):
502 def to_python(self, value, state):
503 value = value.lower()
503 value = value.lower()
504 user = User.get_by_email(value, case_insensitive=True)
504 user = User.get_by_email(value, case_insensitive=True)
505 if user is None:
505 if user is None:
506 raise formencode.Invalid(
506 raise formencode.Invalid(
507 _("This e-mail address doesn't exist."), value, state
507 _("This e-mail address doesn't exist."), value, state
508 )
508 )
509
509
510 return value
510 return value
511
511
512
512
513 class LdapLibValidator(formencode.validators.FancyValidator):
513 class LdapLibValidator(formencode.validators.FancyValidator):
514
514
515 def to_python(self, value, state):
515 def to_python(self, value, state):
516
516
517 try:
517 try:
518 import ldap
518 import ldap
519 except ImportError:
519 except ImportError:
520 raise LdapImportError
520 raise LdapImportError
521 return value
521 return value
522
522
523
523
524 class AttrLoginValidator(formencode.validators.FancyValidator):
524 class AttrLoginValidator(formencode.validators.FancyValidator):
525
525
526 def to_python(self, value, state):
526 def to_python(self, value, state):
527
527
528 if not value or not isinstance(value, (str, unicode)):
528 if not value or not isinstance(value, (str, unicode)):
529 raise formencode.Invalid(
529 raise formencode.Invalid(
530 _("The LDAP Login attribute of the CN must be specified - "
530 _("The LDAP Login attribute of the CN must be specified - "
531 "this is the name of the attribute that is equivalent "
531 "this is the name of the attribute that is equivalent "
532 "to 'username'"), value, state
532 "to 'username'"), value, state
533 )
533 )
534
534
535 return value
535 return value
536
536
537
537
538 #==============================================================================
538 #==============================================================================
539 # FORMS
539 # FORMS
540 #==============================================================================
540 #==============================================================================
541 class LoginForm(formencode.Schema):
541 class LoginForm(formencode.Schema):
542 allow_extra_fields = True
542 allow_extra_fields = True
543 filter_extra_fields = True
543 filter_extra_fields = True
544 username = UnicodeString(
544 username = UnicodeString(
545 strip=True,
545 strip=True,
546 min=1,
546 min=1,
547 not_empty=True,
547 not_empty=True,
548 messages={
548 messages={
549 'empty': _('Please enter a login'),
549 'empty': _('Please enter a login'),
550 'tooShort': _('Enter a value %(min)i characters long or more')}
550 'tooShort': _('Enter a value %(min)i characters long or more')}
551 )
551 )
552
552
553 password = UnicodeString(
553 password = UnicodeString(
554 strip=True,
554 strip=True,
555 min=3,
555 min=3,
556 not_empty=True,
556 not_empty=True,
557 messages={
557 messages={
558 'empty': _('Please enter a password'),
558 'empty': _('Please enter a password'),
559 'tooShort': _('Enter %(min)i characters or more')}
559 'tooShort': _('Enter %(min)i characters or more')}
560 )
560 )
561
561
562 remember = StringBoolean(if_missing=False)
562 remember = StringBoolean(if_missing=False)
563
563
564 chained_validators = [ValidAuth]
564 chained_validators = [ValidAuth]
565
565
566
566
567 def UserForm(edit=False, old_data={}):
567 def UserForm(edit=False, old_data={}):
568 class _UserForm(formencode.Schema):
568 class _UserForm(formencode.Schema):
569 allow_extra_fields = True
569 allow_extra_fields = True
570 filter_extra_fields = True
570 filter_extra_fields = True
571 username = All(UnicodeString(strip=True, min=1, not_empty=True),
571 username = All(UnicodeString(strip=True, min=1, not_empty=True),
572 ValidUsername(edit, old_data))
572 ValidUsername(edit, old_data))
573 if edit:
573 if edit:
574 new_password = All(UnicodeString(strip=True, min=6, not_empty=False))
574 new_password = All(UnicodeString(strip=True, min=6, not_empty=False))
575 password_confirmation = All(UnicodeString(strip=True, min=6,
575 password_confirmation = All(UnicodeString(strip=True, min=6,
576 not_empty=False))
576 not_empty=False))
577 admin = StringBoolean(if_missing=False)
577 admin = StringBoolean(if_missing=False)
578 else:
578 else:
579 password = All(UnicodeString(strip=True, min=6, not_empty=True))
579 password = All(UnicodeString(strip=True, min=6, not_empty=True))
580 password_confirmation = All(UnicodeString(strip=True, min=6,
580 password_confirmation = All(UnicodeString(strip=True, min=6,
581 not_empty=False))
581 not_empty=False))
582
582
583 active = StringBoolean(if_missing=False)
583 active = StringBoolean(if_missing=False)
584 name = UnicodeString(strip=True, min=1, not_empty=False)
584 name = UnicodeString(strip=True, min=1, not_empty=False)
585 lastname = UnicodeString(strip=True, min=1, not_empty=False)
585 lastname = UnicodeString(strip=True, min=1, not_empty=False)
586 email = All(Email(not_empty=True), UniqSystemEmail(old_data))
586 email = All(Email(not_empty=True), UniqSystemEmail(old_data))
587
587
588 chained_validators = [ValidPasswordsMatch, ValidPassword]
588 chained_validators = [ValidPasswordsMatch, ValidPassword]
589
589
590 return _UserForm
590 return _UserForm
591
591
592
592
593 def UsersGroupForm(edit=False, old_data={}, available_members=[]):
593 def UsersGroupForm(edit=False, old_data={}, available_members=[]):
594 class _UsersGroupForm(formencode.Schema):
594 class _UsersGroupForm(formencode.Schema):
595 allow_extra_fields = True
595 allow_extra_fields = True
596 filter_extra_fields = True
596 filter_extra_fields = True
597
597
598 users_group_name = All(UnicodeString(strip=True, min=1, not_empty=True),
598 users_group_name = All(UnicodeString(strip=True, min=1, not_empty=True),
599 ValidUsersGroup(edit, old_data))
599 ValidUsersGroup(edit, old_data))
600
600
601 users_group_active = StringBoolean(if_missing=False)
601 users_group_active = StringBoolean(if_missing=False)
602
602
603 if edit:
603 if edit:
604 users_group_members = OneOf(available_members, hideList=False,
604 users_group_members = OneOf(available_members, hideList=False,
605 testValueList=True,
605 testValueList=True,
606 if_missing=None, not_empty=False)
606 if_missing=None, not_empty=False)
607
607
608 return _UsersGroupForm
608 return _UsersGroupForm
609
609
610
610
611 def ReposGroupForm(edit=False, old_data={}, available_groups=[]):
611 def ReposGroupForm(edit=False, old_data={}, available_groups=[]):
612 class _ReposGroupForm(formencode.Schema):
612 class _ReposGroupForm(formencode.Schema):
613 allow_extra_fields = True
613 allow_extra_fields = True
614 filter_extra_fields = False
614 filter_extra_fields = False
615
615
616 group_name = All(UnicodeString(strip=True, min=1, not_empty=True),
616 group_name = All(UnicodeString(strip=True, min=1, not_empty=True),
617 SlugifyName())
617 SlugifyName())
618 group_description = UnicodeString(strip=True, min=1,
618 group_description = UnicodeString(strip=True, min=1,
619 not_empty=True)
619 not_empty=True)
620 group_parent_id = OneOf(available_groups, hideList=False,
620 group_parent_id = OneOf(available_groups, hideList=False,
621 testValueList=True,
621 testValueList=True,
622 if_missing=None, not_empty=False)
622 if_missing=None, not_empty=False)
623
623
624 chained_validators = [ValidReposGroup(edit, old_data), ValidPerms('group')]
624 chained_validators = [ValidReposGroup(edit, old_data), ValidPerms('group')]
625
625
626 return _ReposGroupForm
626 return _ReposGroupForm
627
627
628
628
629 def RegisterForm(edit=False, old_data={}):
629 def RegisterForm(edit=False, old_data={}):
630 class _RegisterForm(formencode.Schema):
630 class _RegisterForm(formencode.Schema):
631 allow_extra_fields = True
631 allow_extra_fields = True
632 filter_extra_fields = True
632 filter_extra_fields = True
633 username = All(ValidUsername(edit, old_data),
633 username = All(ValidUsername(edit, old_data),
634 UnicodeString(strip=True, min=1, not_empty=True))
634 UnicodeString(strip=True, min=1, not_empty=True))
635 password = All(UnicodeString(strip=True, min=6, not_empty=True))
635 password = All(UnicodeString(strip=True, min=6, not_empty=True))
636 password_confirmation = All(UnicodeString(strip=True, min=6, not_empty=True))
636 password_confirmation = All(UnicodeString(strip=True, min=6, not_empty=True))
637 active = StringBoolean(if_missing=False)
637 active = StringBoolean(if_missing=False)
638 name = UnicodeString(strip=True, min=1, not_empty=False)
638 name = UnicodeString(strip=True, min=1, not_empty=False)
639 lastname = UnicodeString(strip=True, min=1, not_empty=False)
639 lastname = UnicodeString(strip=True, min=1, not_empty=False)
640 email = All(Email(not_empty=True), UniqSystemEmail(old_data))
640 email = All(Email(not_empty=True), UniqSystemEmail(old_data))
641
641
642 chained_validators = [ValidPasswordsMatch, ValidPassword]
642 chained_validators = [ValidPasswordsMatch, ValidPassword]
643
643
644 return _RegisterForm
644 return _RegisterForm
645
645
646
646
647 def PasswordResetForm():
647 def PasswordResetForm():
648 class _PasswordResetForm(formencode.Schema):
648 class _PasswordResetForm(formencode.Schema):
649 allow_extra_fields = True
649 allow_extra_fields = True
650 filter_extra_fields = True
650 filter_extra_fields = True
651 email = All(ValidSystemEmail(), Email(not_empty=True))
651 email = All(ValidSystemEmail(), Email(not_empty=True))
652 return _PasswordResetForm
652 return _PasswordResetForm
653
653
654
654
655 def RepoForm(edit=False, old_data={}, supported_backends=BACKENDS.keys(),
655 def RepoForm(edit=False, old_data={}, supported_backends=BACKENDS.keys(),
656 repo_groups=[]):
656 repo_groups=[]):
657 class _RepoForm(formencode.Schema):
657 class _RepoForm(formencode.Schema):
658 allow_extra_fields = True
658 allow_extra_fields = True
659 filter_extra_fields = False
659 filter_extra_fields = False
660 repo_name = All(UnicodeString(strip=True, min=1, not_empty=True),
660 repo_name = All(UnicodeString(strip=True, min=1, not_empty=True),
661 SlugifyName())
661 SlugifyName())
662 clone_uri = All(UnicodeString(strip=True, min=1, not_empty=False))
662 clone_uri = All(UnicodeString(strip=True, min=1, not_empty=False))
663 repo_group = OneOf(repo_groups, hideList=True)
663 repo_group = OneOf(repo_groups, hideList=True)
664 repo_type = OneOf(supported_backends)
664 repo_type = OneOf(supported_backends)
665 description = UnicodeString(strip=True, min=1, not_empty=True)
665 description = UnicodeString(strip=True, min=1, not_empty=True)
666 private = StringBoolean(if_missing=False)
666 private = StringBoolean(if_missing=False)
667 enable_statistics = StringBoolean(if_missing=False)
667 enable_statistics = StringBoolean(if_missing=False)
668 enable_downloads = StringBoolean(if_missing=False)
668 enable_downloads = StringBoolean(if_missing=False)
669
669
670 if edit:
670 if edit:
671 #this is repo owner
671 #this is repo owner
672 user = All(UnicodeString(not_empty=True), ValidRepoUser)
672 user = All(UnicodeString(not_empty=True), ValidRepoUser)
673
673
674 chained_validators = [ValidCloneUri()(),
674 chained_validators = [ValidCloneUri()(),
675 ValidRepoName(edit, old_data),
675 ValidRepoName(edit, old_data),
676 ValidPerms()]
676 ValidPerms()]
677 return _RepoForm
677 return _RepoForm
678
678
679
679
680 def RepoForkForm(edit=False, old_data={}, supported_backends=BACKENDS.keys(),
680 def RepoForkForm(edit=False, old_data={}, supported_backends=BACKENDS.keys(),
681 repo_groups=[]):
681 repo_groups=[]):
682 class _RepoForkForm(formencode.Schema):
682 class _RepoForkForm(formencode.Schema):
683 allow_extra_fields = True
683 allow_extra_fields = True
684 filter_extra_fields = False
684 filter_extra_fields = False
685 repo_name = All(UnicodeString(strip=True, min=1, not_empty=True),
685 repo_name = All(UnicodeString(strip=True, min=1, not_empty=True),
686 SlugifyName())
686 SlugifyName())
687 repo_group = OneOf(repo_groups, hideList=True)
687 repo_group = OneOf(repo_groups, hideList=True)
688 repo_type = All(ValidForkType(old_data), OneOf(supported_backends))
688 repo_type = All(ValidForkType(old_data), OneOf(supported_backends))
689 description = UnicodeString(strip=True, min=1, not_empty=True)
689 description = UnicodeString(strip=True, min=1, not_empty=True)
690 private = StringBoolean(if_missing=False)
690 private = StringBoolean(if_missing=False)
691 copy_permissions = StringBoolean(if_missing=False)
691 copy_permissions = StringBoolean(if_missing=False)
692 update_after_clone = StringBoolean(if_missing=False)
692 update_after_clone = StringBoolean(if_missing=False)
693 fork_parent_id = UnicodeString()
693 fork_parent_id = UnicodeString()
694 chained_validators = [ValidForkName(edit, old_data)]
694 chained_validators = [ValidForkName(edit, old_data)]
695
695
696 return _RepoForkForm
696 return _RepoForkForm
697
697
698
698
699 def RepoSettingsForm(edit=False, old_data={}, supported_backends=BACKENDS.keys(),
699 def RepoSettingsForm(edit=False, old_data={}, supported_backends=BACKENDS.keys(),
700 repo_groups=[]):
700 repo_groups=[]):
701 class _RepoForm(formencode.Schema):
701 class _RepoForm(formencode.Schema):
702 allow_extra_fields = True
702 allow_extra_fields = True
703 filter_extra_fields = False
703 filter_extra_fields = False
704 repo_name = All(UnicodeString(strip=True, min=1, not_empty=True),
704 repo_name = All(UnicodeString(strip=True, min=1, not_empty=True),
705 SlugifyName())
705 SlugifyName())
706 description = UnicodeString(strip=True, min=1, not_empty=True)
706 description = UnicodeString(strip=True, min=1, not_empty=True)
707 repo_group = OneOf(repo_groups, hideList=True)
707 repo_group = OneOf(repo_groups, hideList=True)
708 private = StringBoolean(if_missing=False)
708 private = StringBoolean(if_missing=False)
709
709
710 chained_validators = [ValidRepoName(edit, old_data), ValidPerms(),
710 chained_validators = [ValidRepoName(edit, old_data), ValidPerms(),
711 ValidSettings]
711 ValidSettings]
712 return _RepoForm
712 return _RepoForm
713
713
714
714
715 def ApplicationSettingsForm():
715 def ApplicationSettingsForm():
716 class _ApplicationSettingsForm(formencode.Schema):
716 class _ApplicationSettingsForm(formencode.Schema):
717 allow_extra_fields = True
717 allow_extra_fields = True
718 filter_extra_fields = False
718 filter_extra_fields = False
719 rhodecode_title = UnicodeString(strip=True, min=1, not_empty=True)
719 rhodecode_title = UnicodeString(strip=True, min=1, not_empty=True)
720 rhodecode_realm = UnicodeString(strip=True, min=1, not_empty=True)
720 rhodecode_realm = UnicodeString(strip=True, min=1, not_empty=True)
721 rhodecode_ga_code = UnicodeString(strip=True, min=1, not_empty=False)
721 rhodecode_ga_code = UnicodeString(strip=True, min=1, not_empty=False)
722
722
723 return _ApplicationSettingsForm
723 return _ApplicationSettingsForm
724
724
725
725
726 def ApplicationUiSettingsForm():
726 def ApplicationUiSettingsForm():
727 class _ApplicationUiSettingsForm(formencode.Schema):
727 class _ApplicationUiSettingsForm(formencode.Schema):
728 allow_extra_fields = True
728 allow_extra_fields = True
729 filter_extra_fields = False
729 filter_extra_fields = False
730 web_push_ssl = OneOf(['true', 'false'], if_missing='false')
730 web_push_ssl = OneOf(['true', 'false'], if_missing='false')
731 paths_root_path = All(ValidPath(), UnicodeString(strip=True, min=1, not_empty=True))
731 paths_root_path = All(ValidPath(), UnicodeString(strip=True, min=1, not_empty=True))
732 hooks_changegroup_update = OneOf(['True', 'False'], if_missing=False)
732 hooks_changegroup_update = OneOf(['True', 'False'], if_missing=False)
733 hooks_changegroup_repo_size = OneOf(['True', 'False'], if_missing=False)
733 hooks_changegroup_repo_size = OneOf(['True', 'False'], if_missing=False)
734 hooks_pretxnchangegroup_push_logger = OneOf(['True', 'False'], if_missing=False)
734 hooks_pretxnchangegroup_push_logger = OneOf(['True', 'False'], if_missing=False)
735 hooks_preoutgoing_pull_logger = OneOf(['True', 'False'], if_missing=False)
735 hooks_preoutgoing_pull_logger = OneOf(['True', 'False'], if_missing=False)
736
736
737 return _ApplicationUiSettingsForm
737 return _ApplicationUiSettingsForm
738
738
739
739
740 def DefaultPermissionsForm(perms_choices, register_choices, create_choices):
740 def DefaultPermissionsForm(perms_choices, register_choices, create_choices):
741 class _DefaultPermissionsForm(formencode.Schema):
741 class _DefaultPermissionsForm(formencode.Schema):
742 allow_extra_fields = True
742 allow_extra_fields = True
743 filter_extra_fields = True
743 filter_extra_fields = True
744 overwrite_default = StringBoolean(if_missing=False)
744 overwrite_default = StringBoolean(if_missing=False)
745 anonymous = OneOf(['True', 'False'], if_missing=False)
745 anonymous = OneOf(['True', 'False'], if_missing=False)
746 default_perm = OneOf(perms_choices)
746 default_perm = OneOf(perms_choices)
747 default_register = OneOf(register_choices)
747 default_register = OneOf(register_choices)
748 default_create = OneOf(create_choices)
748 default_create = OneOf(create_choices)
749
749
750 return _DefaultPermissionsForm
750 return _DefaultPermissionsForm
751
751
752
752
753 def LdapSettingsForm(tls_reqcert_choices, search_scope_choices, tls_kind_choices):
753 def LdapSettingsForm(tls_reqcert_choices, search_scope_choices, tls_kind_choices):
754 class _LdapSettingsForm(formencode.Schema):
754 class _LdapSettingsForm(formencode.Schema):
755 allow_extra_fields = True
755 allow_extra_fields = True
756 filter_extra_fields = True
756 filter_extra_fields = True
757 pre_validators = [LdapLibValidator]
757 pre_validators = [LdapLibValidator]
758 ldap_active = StringBoolean(if_missing=False)
758 ldap_active = StringBoolean(if_missing=False)
759 ldap_host = UnicodeString(strip=True,)
759 ldap_host = UnicodeString(strip=True,)
760 ldap_port = Number(strip=True,)
760 ldap_port = Number(strip=True,)
761 ldap_tls_kind = OneOf(tls_kind_choices)
761 ldap_tls_kind = OneOf(tls_kind_choices)
762 ldap_tls_reqcert = OneOf(tls_reqcert_choices)
762 ldap_tls_reqcert = OneOf(tls_reqcert_choices)
763 ldap_dn_user = UnicodeString(strip=True,)
763 ldap_dn_user = UnicodeString(strip=True,)
764 ldap_dn_pass = UnicodeString(strip=True,)
764 ldap_dn_pass = UnicodeString(strip=True,)
765 ldap_base_dn = UnicodeString(strip=True,)
765 ldap_base_dn = UnicodeString(strip=True,)
766 ldap_filter = UnicodeString(strip=True,)
766 ldap_filter = UnicodeString(strip=True,)
767 ldap_search_scope = OneOf(search_scope_choices)
767 ldap_search_scope = OneOf(search_scope_choices)
768 ldap_attr_login = All(AttrLoginValidator, UnicodeString(strip=True,))
768 ldap_attr_login = All(AttrLoginValidator, UnicodeString(strip=True,))
769 ldap_attr_firstname = UnicodeString(strip=True,)
769 ldap_attr_firstname = UnicodeString(strip=True,)
770 ldap_attr_lastname = UnicodeString(strip=True,)
770 ldap_attr_lastname = UnicodeString(strip=True,)
771 ldap_attr_email = UnicodeString(strip=True,)
771 ldap_attr_email = UnicodeString(strip=True,)
772
772
773 return _LdapSettingsForm
773 return _LdapSettingsForm
General Comments 0
You need to be logged in to leave comments. Login now