##// END OF EJS Templates
changes for release 1.1.5
marcink -
r1136:93b980eb default
parent child Browse files
Show More
@@ -1,131 +1,132 b''
1
1
2 =================================================
2 =================================================
3 Welcome to RhodeCode (RhodiumCode) documentation!
3 Welcome to RhodeCode (RhodiumCode) documentation!
4 =================================================
4 =================================================
5
5
6 ``RhodeCode`` (formerly hg-app) is a Pylons framework based Mercurial repository
6 ``RhodeCode`` (formerly hg-app) is a Pylons framework based Mercurial repository
7 browser/management tool with a built in push/pull server and full text search.
7 browser/management tool with a built in push/pull server and full text search.
8 It works on http/https and has a built in permission/authentication system with
8 It works on http/https and has a built in permission/authentication system with
9 the ability to authenticate via LDAP.
9 the ability to authenticate via LDAP.
10
10
11 RhodeCode is similar in some respects to github or bitbucket,
11 RhodeCode is similar in some respects to github or bitbucket_,
12 however RhodeCode can be run as standalone hosted application on your own server. It is open source
12 however RhodeCode can be run as standalone hosted application on your own server.
13 and donation ware and focuses more on providing a customized, self administered
13 It is open source and donation ware and focuses more on providing a customized,
14 interface for Mercurial(and soon GIT) repositories. RhodeCode is powered by a vcs_
14 self administered interface for Mercurial(and soon GIT) repositories.
15 library that Lukasz Balcerzak and I created to handle multiple different version
15 RhodeCode is powered by a vcs_ library that Lukasz Balcerzak and I created to
16 control systems.
16 handle multiple different version control systems.
17
17
18 RhodeCode uses `Semantic Versioning <http://semver.org/>`_
18 RhodeCode uses `Semantic Versioning <http://semver.org/>`_
19
19
20 RhodeCode demo
20 RhodeCode demo
21 --------------
21 --------------
22
22
23 http://hg.python-works.com
23 http://demo.rhodecode.org
24
24
25 The default access is anonymous but you can login to an administrative account
25 The default access is anonymous but you can login to an administrative account
26 using the following credentials:
26 using the following credentials:
27
27
28 - username: demo
28 - username: demo
29 - password: demo
29 - password: demo
30
30
31 Source code
31 Source code
32 -----------
32 -----------
33
33
34 The latest source for RhodeCode can be obtained from my own RhodeCode instance
34 The latest source for RhodeCode can be obtained from official RhodeCode instance
35 https://rhodecode.org
35 https://hg.rhodecode.org
36
36
37 Rarely updated source code and issue tracker is available at bitbcuket
37 Rarely updated source code and issue tracker is available at bitbcuket
38 http://bitbucket.org/marcinkuzminski/rhodecode
38 http://bitbucket.org/marcinkuzminski/rhodecode
39
39
40 Installation
40 Installation
41 ------------
41 ------------
42
42
43 Please visit http://packages.python.org/RhodeCode/installation.html
43 Please visit http://packages.python.org/RhodeCode/installation.html
44
44
45
45
46 RhodeCode Features
46 RhodeCode Features
47 ------------------
47 ------------------
48
48
49 - Has it's own middleware to handle mercurial_ protocol requests.
49 - Has it's own middleware to handle mercurial_ protocol requests.
50 Each request can be logged and authenticated.
50 Each request can be logged and authenticated.
51 - Runs on threads unlike hgweb. You can make multiple pulls/pushes simultaneous. Supports http/https
51 - Runs on threads unlike hgweb. You can make multiple pulls/pushes simultaneous. Supports http/https
52 and LDAP
52 and LDAP
53 - Full permissions (private/read/write/admin) and authentication per project.
53 - Full permissions (private/read/write/admin) and authentication per project.
54 One account for web interface and mercurial_ push/pull/clone operations.
54 One account for web interface and mercurial_ push/pull/clone operations.
55 - Mako templates let's you customize the look and feel of the application.
55 - Mako templates let's you customize the look and feel of the application.
56 - Beautiful diffs, annotations and source code browsing all colored by pygments.
56 - Beautiful diffs, annotations and source code browsing all colored by pygments.
57 - Mercurial_ branch graph and yui-flot powered graphs with zooming and statistics
57 - Mercurial_ branch graph and yui-flot powered graphs with zooming and statistics
58 - Admin interface with user/permission management. Admin activity journal, logs
58 - Admin interface with user/permission management. Admin activity journal, logs
59 pulls, pushes, forks, registrations and other actions made by all users.
59 pulls, pushes, forks, registrations and other actions made by all users.
60 - Server side forks. It is possible to fork a project and modify it freely without
60 - Server side forks. It is possible to fork a project and modify it freely without
61 breaking the main repository.
61 breaking the main repository.
62 - Full text search powered by Whoosh on the source files, and file names.
62 - Full text search powered by Whoosh on the source files, and file names.
63 Build in indexing daemons, with optional incremental index build
63 Build in indexing daemons, with optional incremental index build
64 (no external search servers required all in one application)
64 (no external search servers required all in one application)
65 - Setup project descriptions and info inside built in db for easy, non
65 - Setup project descriptions and info inside built in db for easy, non
66 file-system operations
66 file-system operations
67 - Intelligent cache with invalidation after push or project change, provides high
67 - Intelligent cache with invalidation after push or project change, provides high
68 performance and always up to date data.
68 performance and always up to date data.
69 - Rss / atom feeds, gravatar support, download sources as zip/tar/gz
69 - Rss / atom feeds, gravatar support, download sources as zip/tar/gz
70 - Async tasks for speed and performance using celery_ (works without them too)
70 - Async tasks for speed and performance using celery_ (works without them too)
71 - Backup scripts can do backup of whole app and send it over scp to desired
71 - Backup scripts can do backup of whole app and send it over scp to desired
72 location
72 location
73 - Based on pylons / sqlalchemy / sqlite / whoosh / vcs
73 - Based on pylons / sqlalchemy / sqlite / whoosh / vcs
74
74
75
75
76 .. include:: ./docs/screenshots.rst
76 .. include:: ./docs/screenshots.rst
77
77
78
78
79 Incoming / Plans
79 Incoming / Plans
80 ----------------
80 ----------------
81
81
82 - Project grouping
82 - Project grouping
83 - User groups/teams
83 - User groups/teams
84 - SSH based authentication with server side key management
84 - SSH based authentication with server side key management
85 - Code review (probably based on hg-review)
85 - Code review (probably based on hg-review)
86 - Full git_ support, with push/pull server (currently in beta tests)
86 - Full git_ support, with push/pull server (currently in beta tests)
87 - Redmine integration
87 - Redmine integration
88 - Public accessible activity feeds
88 - Public accessible activity feeds
89 - Commit based built in wiki system
89 - Commit based built in wiki system
90 - Clone points and cloning from remote repositories into RhodeCode
90 - Clone points and cloning from remote repositories into RhodeCode
91 - More statistics and graph (global annotation + some more statistics)
91 - More statistics and graph (global annotation + some more statistics)
92 - Other advancements as development continues (or you can of course make additions and or requests)
92 - Other advancements as development continues (or you can of course make additions and or requests)
93
93
94 License
94 License
95 -------
95 -------
96
96
97 ``RhodeCode`` is released under the GPL_ license.
97 ``RhodeCode`` is released under the GPL_ license.
98
98
99
99
100 Mailing group Q&A
100 Mailing group Q&A
101 -----------------
101 -----------------
102
102
103 Join the `Google group <http://groups.google.com/group/rhodecode>`_
103 Join the `Google group <http://groups.google.com/group/rhodecode>`_
104
104
105 Open an issue at `issue tracker <http://bitbucket.org/marcinkuzminski/rhodecode/issues>`_
105 Open an issue at `issue tracker <http://bitbucket.org/marcinkuzminski/rhodecode/issues>`_
106
106
107 Join #rhodecode on FreeNode (irc.freenode.net)
107 Join #rhodecode on FreeNode (irc.freenode.net)
108 or use http://webchat.freenode.net/?channels=rhodecode for web access to irc.
108 or use http://webchat.freenode.net/?channels=rhodecode for web access to irc.
109
109
110 Online documentation
110 Online documentation
111 --------------------
111 --------------------
112
112
113 Online documentation for the current version of RhodeCode is available at
113 Online documentation for the current version of RhodeCode is available at
114 http://packages.python.org/RhodeCode/.
114 http://packages.python.org/RhodeCode/.
115 You may also build the documentation for yourself - go into ``docs/`` and run::
115 You may also build the documentation for yourself - go into ``docs/`` and run::
116
116
117 make html
117 make html
118
118
119 (You need to have sphinx installed to build the documentation. If you don't
119 (You need to have sphinx installed to build the documentation. If you don't
120 have sphinx installed you can install it via the command: ``easy_install sphinx``)
120 have sphinx installed you can install it via the command: ``easy_install sphinx``)
121
121
122 .. _virtualenv: http://pypi.python.org/pypi/virtualenv
122 .. _virtualenv: http://pypi.python.org/pypi/virtualenv
123 .. _python: http://www.python.org/
123 .. _python: http://www.python.org/
124 .. _django: http://www.djangoproject.com/
124 .. _django: http://www.djangoproject.com/
125 .. _mercurial: http://mercurial.selenic.com/
125 .. _mercurial: http://mercurial.selenic.com/
126 .. _bitbucket: http://bitbucket.org/
126 .. _subversion: http://subversion.tigris.org/
127 .. _subversion: http://subversion.tigris.org/
127 .. _git: http://git-scm.com/
128 .. _git: http://git-scm.com/
128 .. _celery: http://celeryproject.org/
129 .. _celery: http://celeryproject.org/
129 .. _Sphinx: http://sphinx.pocoo.org/
130 .. _Sphinx: http://sphinx.pocoo.org/
130 .. _GPL: http://www.gnu.org/licenses/gpl.html
131 .. _GPL: http://www.gnu.org/licenses/gpl.html
131 .. _vcs: http://pypi.python.org/pypi/vcs No newline at end of file
132 .. _vcs: http://pypi.python.org/pypi/vcs
@@ -1,191 +1,213 b''
1 .. _changelog:
1 .. _changelog:
2
2
3 Changelog
3 Changelog
4 =========
4 =========
5
5
6
7 1.1.5 (**2011-03-1X**)
8 ======================
9
10 news
11 ----
12
13 - basic windows support, by exchanging pybcrypt into sha256 for windows only
14 highly inspired by idea of mantis406
15
16 fixes
17 -----
18
19 - fixed sorting by author in main page
20 - fixed crashes with diffs on binary files
21 - fixed #131 problem with boolean values for LDAP
22 - fixed #122 mysql problems thanks to striker69
23 - fixed problem with errors on calling raw/raw_files/annotate functions
24 with unknown revisions
25 - fixed returned rawfiles attachment names with international character
26 - cleaned out docs, big thanks to Jason Harris
27
6 1.1.4 (**2011-02-19**)
28 1.1.4 (**2011-02-19**)
7 ======================
29 ======================
8
30
9 news
31 news
10 ----
32 ----
11
33
12 fixes
34 fixes
13 -----
35 -----
14
36
15 - fixed formencode import problem on settings page, that caused server crash
37 - fixed formencode import problem on settings page, that caused server crash
16 when that page was accessed as first after server start
38 when that page was accessed as first after server start
17 - journal fixes
39 - journal fixes
18 - fixed option to access repository just by entering http://server/<repo_name>
40 - fixed option to access repository just by entering http://server/<repo_name>
19
41
20
42
21 1.1.3 (**2011-02-16**)
43 1.1.3 (**2011-02-16**)
22 ======================
44 ======================
23
45
24 news
46 news
25 ----
47 ----
26
48
27 - implemented #102 allowing the '.' character in username
49 - implemented #102 allowing the '.' character in username
28 - added option to access repository just by entering http://server/<repo_name>
50 - added option to access repository just by entering http://server/<repo_name>
29 - celery task ignores result for better performance
51 - celery task ignores result for better performance
30
52
31 fixes
53 fixes
32 -----
54 -----
33
55
34 - fixed ehlo command and non auth mail servers on smtp_lib. Thanks to
56 - fixed ehlo command and non auth mail servers on smtp_lib. Thanks to
35 apollo13 and Johan Walles
57 apollo13 and Johan Walles
36 - small fixes in journal
58 - small fixes in journal
37 - fixed problems with getting setting for celery from .ini files
59 - fixed problems with getting setting for celery from .ini files
38 - registration, password reset and login boxes share the same title as main
60 - registration, password reset and login boxes share the same title as main
39 application now
61 application now
40 - fixed #113: to high permissions to fork repository
62 - fixed #113: to high permissions to fork repository
41 - fixed problem with '[' chars in commit messages in journal
63 - fixed problem with '[' chars in commit messages in journal
42 - removed issue with space inside renamed repository after deletion
64 - removed issue with space inside renamed repository after deletion
43 - db transaction fixes when filesystem repository creation failed
65 - db transaction fixes when filesystem repository creation failed
44 - fixed #106 relation issues on databases different than sqlite
66 - fixed #106 relation issues on databases different than sqlite
45 - fixed static files paths links to use of url() method
67 - fixed static files paths links to use of url() method
46
68
47 1.1.2 (**2011-01-12**)
69 1.1.2 (**2011-01-12**)
48 ======================
70 ======================
49
71
50 news
72 news
51 ----
73 ----
52
74
53
75
54 fixes
76 fixes
55 -----
77 -----
56
78
57 - fixes #98 protection against float division of percentage stats
79 - fixes #98 protection against float division of percentage stats
58 - fixed graph bug
80 - fixed graph bug
59 - forced webhelpers version since it was making troubles during installation
81 - forced webhelpers version since it was making troubles during installation
60
82
61 1.1.1 (**2011-01-06**)
83 1.1.1 (**2011-01-06**)
62 ======================
84 ======================
63
85
64 news
86 news
65 ----
87 ----
66
88
67 - added force https option into ini files for easier https usage (no need to
89 - added force https option into ini files for easier https usage (no need to
68 set server headers with this options)
90 set server headers with this options)
69 - small css updates
91 - small css updates
70
92
71 fixes
93 fixes
72 -----
94 -----
73
95
74 - fixed #96 redirect loop on files view on repositories without changesets
96 - fixed #96 redirect loop on files view on repositories without changesets
75 - fixed #97 unicode string passed into server header in special cases (mod_wsgi)
97 - fixed #97 unicode string passed into server header in special cases (mod_wsgi)
76 and server crashed with errors
98 and server crashed with errors
77 - fixed large tooltips problems on main page
99 - fixed large tooltips problems on main page
78 - fixed #92 whoosh indexer is more error proof
100 - fixed #92 whoosh indexer is more error proof
79
101
80 1.1.0 (**2010-12-18**)
102 1.1.0 (**2010-12-18**)
81 ======================
103 ======================
82
104
83 news
105 news
84 ----
106 ----
85
107
86 - rewrite of internals for vcs >=0.1.10
108 - rewrite of internals for vcs >=0.1.10
87 - uses mercurial 1.7 with dotencode disabled for maintaining compatibility
109 - uses mercurial 1.7 with dotencode disabled for maintaining compatibility
88 with older clients
110 with older clients
89 - anonymous access, authentication via ldap
111 - anonymous access, authentication via ldap
90 - performance upgrade for cached repos list - each repository has it's own
112 - performance upgrade for cached repos list - each repository has it's own
91 cache that's invalidated when needed.
113 cache that's invalidated when needed.
92 - performance upgrades on repositories with large amount of commits (20K+)
114 - performance upgrades on repositories with large amount of commits (20K+)
93 - main page quick filter for filtering repositories
115 - main page quick filter for filtering repositories
94 - user dashboards with ability to follow chosen repositories actions
116 - user dashboards with ability to follow chosen repositories actions
95 - sends email to admin on new user registration
117 - sends email to admin on new user registration
96 - added cache/statistics reset options into repository settings
118 - added cache/statistics reset options into repository settings
97 - more detailed action logger (based on hooks) with pushed changesets lists
119 - more detailed action logger (based on hooks) with pushed changesets lists
98 and options to disable those hooks from admin panel
120 and options to disable those hooks from admin panel
99 - introduced new enhanced changelog for merges that shows more accurate results
121 - introduced new enhanced changelog for merges that shows more accurate results
100 - new improved and faster code stats (based on pygments lexers mapping tables,
122 - new improved and faster code stats (based on pygments lexers mapping tables,
101 showing up to 10 trending sources for each repository. Additionally stats
123 showing up to 10 trending sources for each repository. Additionally stats
102 can be disabled in repository settings.
124 can be disabled in repository settings.
103 - gui optimizations, fixed application width to 1024px
125 - gui optimizations, fixed application width to 1024px
104 - added cut off (for large files/changesets) limit into config files
126 - added cut off (for large files/changesets) limit into config files
105 - whoosh, celeryd, upgrade moved to paster command
127 - whoosh, celeryd, upgrade moved to paster command
106 - other than sqlite database backends can be used
128 - other than sqlite database backends can be used
107
129
108 fixes
130 fixes
109 -----
131 -----
110
132
111 - fixes #61 forked repo was showing only after cache expired
133 - fixes #61 forked repo was showing only after cache expired
112 - fixes #76 no confirmation on user deletes
134 - fixes #76 no confirmation on user deletes
113 - fixes #66 Name field misspelled
135 - fixes #66 Name field misspelled
114 - fixes #72 block user removal when he owns repositories
136 - fixes #72 block user removal when he owns repositories
115 - fixes #69 added password confirmation fields
137 - fixes #69 added password confirmation fields
116 - fixes #87 RhodeCode crashes occasionally on updating repository owner
138 - fixes #87 RhodeCode crashes occasionally on updating repository owner
117 - fixes #82 broken annotations on files with more than 1 blank line at the end
139 - fixes #82 broken annotations on files with more than 1 blank line at the end
118 - a lot of fixes and tweaks for file browser
140 - a lot of fixes and tweaks for file browser
119 - fixed detached session issues
141 - fixed detached session issues
120 - fixed when user had no repos he would see all repos listed in my account
142 - fixed when user had no repos he would see all repos listed in my account
121 - fixed ui() instance bug when global hgrc settings was loaded for server
143 - fixed ui() instance bug when global hgrc settings was loaded for server
122 instance and all hgrc options were merged with our db ui() object
144 instance and all hgrc options were merged with our db ui() object
123 - numerous small bugfixes
145 - numerous small bugfixes
124
146
125 (special thanks for TkSoh for detailed feedback)
147 (special thanks for TkSoh for detailed feedback)
126
148
127
149
128 1.0.2 (**2010-11-12**)
150 1.0.2 (**2010-11-12**)
129 ======================
151 ======================
130
152
131 news
153 news
132 ----
154 ----
133
155
134 - tested under python2.7
156 - tested under python2.7
135 - bumped sqlalchemy and celery versions
157 - bumped sqlalchemy and celery versions
136
158
137 fixes
159 fixes
138 -----
160 -----
139
161
140 - fixed #59 missing graph.js
162 - fixed #59 missing graph.js
141 - fixed repo_size crash when repository had broken symlinks
163 - fixed repo_size crash when repository had broken symlinks
142 - fixed python2.5 crashes.
164 - fixed python2.5 crashes.
143
165
144
166
145 1.0.1 (**2010-11-10**)
167 1.0.1 (**2010-11-10**)
146 ======================
168 ======================
147
169
148 news
170 news
149 ----
171 ----
150
172
151 - small css updated
173 - small css updated
152
174
153 fixes
175 fixes
154 -----
176 -----
155
177
156 - fixed #53 python2.5 incompatible enumerate calls
178 - fixed #53 python2.5 incompatible enumerate calls
157 - fixed #52 disable mercurial extension for web
179 - fixed #52 disable mercurial extension for web
158 - fixed #51 deleting repositories don't delete it's dependent objects
180 - fixed #51 deleting repositories don't delete it's dependent objects
159
181
160
182
161 1.0.0 (**2010-11-02**)
183 1.0.0 (**2010-11-02**)
162 ======================
184 ======================
163
185
164 - security bugfix simplehg wasn't checking for permissions on commands
186 - security bugfix simplehg wasn't checking for permissions on commands
165 other than pull or push.
187 other than pull or push.
166 - fixed doubled messages after push or pull in admin journal
188 - fixed doubled messages after push or pull in admin journal
167 - templating and css corrections, fixed repo switcher on chrome, updated titles
189 - templating and css corrections, fixed repo switcher on chrome, updated titles
168 - admin menu accessible from options menu on repository view
190 - admin menu accessible from options menu on repository view
169 - permissions cached queries
191 - permissions cached queries
170
192
171 1.0.0rc4 (**2010-10-12**)
193 1.0.0rc4 (**2010-10-12**)
172 ==========================
194 ==========================
173
195
174 - fixed python2.5 missing simplejson imports (thanks to Jens BΓ€ckman)
196 - fixed python2.5 missing simplejson imports (thanks to Jens BΓ€ckman)
175 - removed cache_manager settings from sqlalchemy meta
197 - removed cache_manager settings from sqlalchemy meta
176 - added sqlalchemy cache settings to ini files
198 - added sqlalchemy cache settings to ini files
177 - validated password length and added second try of failure on paster setup-app
199 - validated password length and added second try of failure on paster setup-app
178 - fixed setup database destroy prompt even when there was no db
200 - fixed setup database destroy prompt even when there was no db
179
201
180
202
181 1.0.0rc3 (**2010-10-11**)
203 1.0.0rc3 (**2010-10-11**)
182 =========================
204 =========================
183
205
184 - fixed i18n during installation.
206 - fixed i18n during installation.
185
207
186 1.0.0rc2 (**2010-10-11**)
208 1.0.0rc2 (**2010-10-11**)
187 =========================
209 =========================
188
210
189 - Disabled dirsize in file browser, it's causing nasty bug when dir renames
211 - Disabled dirsize in file browser, it's causing nasty bug when dir renames
190 occure. After vcs is fixed it'll be put back again.
212 occure. After vcs is fixed it'll be put back again.
191 - templating/css rewrites, optimized css. No newline at end of file
213 - templating/css rewrites, optimized css.
@@ -1,19 +1,25 b''
1 .. _contributing:
1 .. _contributing:
2
2
3 Contributing to RhodeCode
3 Contributing to RhodeCode
4 =========================
4 =========================
5
5
6 If you would like to contribute to RhodeCode, please contact me, any help is
6 If you would like to contribute to RhodeCode, please contact me, any help is
7 greatly appreciated!
7 greatly appreciated!
8
8
9 Could I request that you make your source contributions by first forking the
9 Could I request that you make your source contributions by first forking the
10 RhodeCode repository on bitbucket
10 RhodeCode repository on bitbucket_
11 https://bitbucket.org/marcinkuzminski/rhodecode and then make your changes to
11 https://bitbucket.org/marcinkuzminski/rhodecode and then make your changes to
12 your forked repository. Finally, when you are finished making a change, please
12 your forked repository. Please post all fixes into **BETA** branch since your
13 send me a pull request.
13 fix might be already fixed there and i try to merge all fixes from beta into
14 stable, and not the other way. Finally, when you are finished making a change,
15 please send me a pull request.
14
16
15 To run RhodeCode in a development version you always need to install the tip
17 To run RhodeCode in a development version you always need to install the tip
16 version of RhodeCode and the VCS library.
18 version of RhodeCode and the VCS library.
17
19
18 | Thank you for any contributions!
20 | Thank you for any contributions!
19 | Marcin No newline at end of file
21 | Marcin
22
23
24
25 .. _bitbucket: http://bitbucket.org/
@@ -1,56 +1,57 b''
1 .. _index:
1 .. _index:
2
2
3 .. include:: ./../README.rst
3 .. include:: ./../README.rst
4
4
5 Documentation
5 Documentation
6 -------------
6 -------------
7
7
8 **Installation:**
8 **Installation:**
9
9
10 .. toctree::
10 .. toctree::
11 :maxdepth: 1
11 :maxdepth: 1
12
12
13 installation
13 installation
14 setup
14 setup
15 upgrade
15 upgrade
16
16
17 **Usage**
17 **Usage**
18
18
19 .. toctree::
19 .. toctree::
20 :maxdepth: 1
20 :maxdepth: 1
21
21
22 enable_git
22 enable_git
23 statistics
23 statistics
24
24
25 **Develop**
25 **Develop**
26
26
27 .. toctree::
27 .. toctree::
28 :maxdepth: 1
28 :maxdepth: 1
29
29
30 contributing
30 contributing
31 changelog
31 changelog
32
32
33 **API**
33 **API**
34
34
35 .. toctree::
35 .. toctree::
36 :maxdepth: 2
36 :maxdepth: 2
37
37
38 api/index
38 api/index
39
39
40
40
41 Other topics
41 Other topics
42 ------------
42 ------------
43
43
44 * :ref:`genindex`
44 * :ref:`genindex`
45 * :ref:`search`
45 * :ref:`search`
46
46
47 .. _virtualenv: http://pypi.python.org/pypi/virtualenv
47 .. _virtualenv: http://pypi.python.org/pypi/virtualenv
48 .. _python: http://www.python.org/
48 .. _python: http://www.python.org/
49 .. _django: http://www.djangoproject.com/
49 .. _django: http://www.djangoproject.com/
50 .. _mercurial: http://mercurial.selenic.com/
50 .. _mercurial: http://mercurial.selenic.com/
51 .. _bitbucket: http://bitbucket.org/
51 .. _subversion: http://subversion.tigris.org/
52 .. _subversion: http://subversion.tigris.org/
52 .. _git: http://git-scm.com/
53 .. _git: http://git-scm.com/
53 .. _celery: http://celeryproject.org/
54 .. _celery: http://celeryproject.org/
54 .. _Sphinx: http://sphinx.pocoo.org/
55 .. _Sphinx: http://sphinx.pocoo.org/
55 .. _GPL: http://www.gnu.org/licenses/gpl.html
56 .. _GPL: http://www.gnu.org/licenses/gpl.html
56 .. _vcs: http://pypi.python.org/pypi/vcs
57 .. _vcs: http://pypi.python.org/pypi/vcs
@@ -1,105 +1,106 b''
1 .. _installation:
1 .. _installation:
2
2
3 Installation
3 Installation
4 ============
4 ============
5
5
6 ``RhodeCode`` is written entirely in Python. In order to gain maximum performance
6 ``RhodeCode`` is written entirely in Python. In order to gain maximum performance
7 there are some third-party you must install. When RhodeCode is used
7 there are some third-party you must install. When RhodeCode is used
8 together with celery you have to install some kind of message broker,
8 together with celery you have to install some kind of message broker,
9 recommended one is rabbitmq_ to make the async tasks work.
9 recommended one is rabbitmq_ to make the async tasks work.
10
10
11 Of course RhodeCode works in sync mode also and then you do not have to install
11 Of course RhodeCode works in sync mode also and then you do not have to install
12 any third party applications. However, using Celery_ will give you a large speed improvement when using
12 any third party applications. However, using Celery_ will give you a large
13 many big repositories. If you plan to use RhodeCode for say 7 to 10 small repositories, RhodeCode
13 speed improvement when using many big repositories. If you plan to use
14 will perform perfectly well without celery running.
14 RhodeCode for say 7 to 10 small repositories, RhodeCode will perform perfectly
15 well without celery running.
15
16
16 If you make the decision to run RhodeCode with celery make sure you run celeryd using paster
17 If you make the decision to run RhodeCode with celery make sure you run
17 and message broker together with the application.
18 celeryd using paster and message broker together with the application.
18
19
19 Installing RhodeCode from Cheese Shop
20 Installing RhodeCode from Cheese Shop
20 -------------------------------------
21 -------------------------------------
21
22
22 Rhodecode requires python version 2.5 or higher.
23 Rhodecode requires python version 2.5 or higher.
23
24
24 The easiest way to install ``rhodecode`` is to run::
25 The easiest way to install ``rhodecode`` is to run::
25
26
26 easy_install rhodecode
27 easy_install rhodecode
27
28
28 Or::
29 Or::
29
30
30 pip install rhodecode
31 pip install rhodecode
31
32
32 If you prefer to install RhodeCode manually simply grab latest release from
33 If you prefer to install RhodeCode manually simply grab latest release from
33 http://pypi.python.org/pypi/rhodecode, decompress the archive and run::
34 http://pypi.python.org/pypi/rhodecode, decompress the archive and run::
34
35
35 python setup.py install
36 python setup.py install
36
37
37
38
38 Step by step installation example
39 Step by step installation example
39 ---------------------------------
40 ---------------------------------
40
41
41
42
42 - Assuming you have installed virtualenv_ create a new virtual environment using virtualenv::
43 - Assuming you have installed virtualenv_ create a new virtual environment using virtualenv::
43
44
44 virtualenv --no-site-packages /var/www/rhodecode-venv
45 virtualenv --no-site-packages /var/www/rhodecode-venv
45
46
46
47
47 .. note:: Using ``--no-site-packages`` when generating your
48 .. note:: Using ``--no-site-packages`` when generating your
48 virtualenv is **very important**. This flag provides the necessary
49 virtualenv is **very important**. This flag provides the necessary
49 isolation for running the set of packages required by
50 isolation for running the set of packages required by
50 RhodeCode. If you do not specify ``--no-site-packages``,
51 RhodeCode. If you do not specify ``--no-site-packages``,
51 it's possible that RhodeCode will not install properly into
52 it's possible that RhodeCode will not install properly into
52 the virtualenv, or, even if it does, may not run properly,
53 the virtualenv, or, even if it does, may not run properly,
53 depending on the packages you've already got installed into your
54 depending on the packages you've already got installed into your
54 Python's "main" site-packages dir.
55 Python's "main" site-packages dir.
55
56
56
57
57 - this will install new virtualenv_ into `/var/www/rhodecode-venv`.
58 - this will install new virtualenv_ into `/var/www/rhodecode-venv`.
58 - Activate the virtualenv_ by running::
59 - Activate the virtualenv_ by running::
59
60
60 source /var/www/rhodecode-venv/bin/activate
61 source /var/www/rhodecode-venv/bin/activate
61
62
62 .. note:: If you're using UNIX, *do not* use ``sudo`` to run the
63 .. note:: If you're using UNIX, *do not* use ``sudo`` to run the
63 ``virtualenv`` script. It's perfectly acceptable (and desirable)
64 ``virtualenv`` script. It's perfectly acceptable (and desirable)
64 to create a virtualenv as a normal user.
65 to create a virtualenv as a normal user.
65
66
66 - Make a folder for rhodecode somewhere on the filesystem for example::
67 - Make a folder for rhodecode somewhere on the filesystem for example::
67
68
68 mkdir /var/www/rhodecode
69 mkdir /var/www/rhodecode
69
70
70
71
71 - Run this command to install rhodecode::
72 - Run this command to install rhodecode::
72
73
73 easy_install rhodecode
74 easy_install rhodecode
74
75
75 - This will install rhodecode together with pylons and all other required python
76 - This will install rhodecode together with pylons and all other required python
76 libraries
77 libraries
77
78
78 Requirements for Celery (optional)
79 Requirements for Celery (optional)
79 ----------------------------------
80 ----------------------------------
80
81
81 .. note::
82 .. note::
82 Installing message broker and using celery is optional, RhodeCode will
83 Installing message broker and using celery is optional, RhodeCode will
83 work perfectly fine without them.
84 work perfectly fine without them.
84
85
85
86
86 **Message Broker**
87 **Message Broker**
87
88
88 - preferred is `RabbitMq <http://www.rabbitmq.com/>`_
89 - preferred is `RabbitMq <http://www.rabbitmq.com/>`_
89 - A possible alternative is `Redis <http://code.google.com/p/redis/>`_
90 - A possible alternative is `Redis <http://code.google.com/p/redis/>`_
90
91
91 For installation instructions you can visit:
92 For installation instructions you can visit:
92 http://ask.github.com/celery/getting-started/index.html.
93 http://ask.github.com/celery/getting-started/index.html.
93 This is a very nice tutorial on how to start using celery_ with rabbitmq_
94 This is a very nice tutorial on how to start using celery_ with rabbitmq_
94
95
95
96
96 You can now proceed to :ref:`setup`
97 You can now proceed to :ref:`setup`
97 -----------------------------------
98 -----------------------------------
98
99
99
100
100
101
101 .. _virtualenv: http://pypi.python.org/pypi/virtualenv
102 .. _virtualenv: http://pypi.python.org/pypi/virtualenv
102 .. _python: http://www.python.org/
103 .. _python: http://www.python.org/
103 .. _mercurial: http://mercurial.selenic.com/
104 .. _mercurial: http://mercurial.selenic.com/
104 .. _celery: http://celeryproject.org/
105 .. _celery: http://celeryproject.org/
105 .. _rabbitmq: http://www.rabbitmq.com/ No newline at end of file
106 .. _rabbitmq: http://www.rabbitmq.com/
@@ -1,51 +1,53 b''
1 .. _upgrade:
1 .. _upgrade:
2
2
3 Upgrade
3 Upgrade
4 =======
4 =======
5
5
6 Upgrading from Cheese Shop
6 Upgrading from Cheese Shop
7 --------------------------
7 --------------------------
8
8
9 .. note::
9 .. note::
10 Firstly, it is recommended that you **always** perform a database backup before doing an upgrade.
10 Firstly, it is recommended that you **always** perform a database backup
11 before doing an upgrade.
11
12
12 The easiest way to upgrade ``rhodecode`` is to run::
13 The easiest way to upgrade ``rhodecode`` is to run::
13
14
14 easy_install -U rhodecode
15 easy_install -U rhodecode
15
16
16 Or::
17 Or::
17
18
18 pip install --upgrade rhodecode
19 pip install --upgrade rhodecode
19
20
20
21
21 Then make sure you run the following command from the installation directory::
22 Then make sure you run the following command from the installation directory::
22
23
23 paster make-config RhodeCode production.ini
24 paster make-config RhodeCode production.ini
24
25
25 This will display any changes made by the new version of RhodeCode to your
26 This will display any changes made by the new version of RhodeCode to your
26 current configuration. It will try to perform an automerge. It's always better
27 current configuration. It will try to perform an automerge. It's always better
27 to make a backup of your configuration file before hand and recheck the content after the automerge.
28 to make a backup of your configuration file before hand and recheck the
29 content after the automerge.
28
30
29 .. note::
31 .. note::
30 The next steps only apply to upgrading from non bugfix releases eg. from
32 The next steps only apply to upgrading from non bugfix releases eg. from
31 any minor or major releases. Bugfix releases (eg. 1.1.2->1.1.3) will
33 any minor or major releases. Bugfix releases (eg. 1.1.2->1.1.3) will
32 not have any database schema changes or whoosh library updates.
34 not have any database schema changes or whoosh library updates.
33
35
34 It is also recommended that you rebuild the whoosh index after upgrading since the new whoosh
36 It is also recommended that you rebuild the whoosh index after upgrading since
35 version could introduce some incompatible index changes.
37 the new whoosh version could introduce some incompatible index changes.
36
38
37
39
38 The final step is to upgrade the database. To do this simply run::
40 The final step is to upgrade the database. To do this simply run::
39
41
40 paster upgrade-db production.ini
42 paster upgrade-db production.ini
41
43
42 This will upgrade the schema and update some of the defaults in the database,
44 This will upgrade the schema and update some of the defaults in the database,
43 and will always recheck the settings of the application, if there are no new options
45 and will always recheck the settings of the application, if there are no new
44 that need to be set.
46 options that need to be set.
45
47
46
48
47 .. _virtualenv: http://pypi.python.org/pypi/virtualenv
49 .. _virtualenv: http://pypi.python.org/pypi/virtualenv
48 .. _python: http://www.python.org/
50 .. _python: http://www.python.org/
49 .. _mercurial: http://mercurial.selenic.com/
51 .. _mercurial: http://mercurial.selenic.com/
50 .. _celery: http://celeryproject.org/
52 .. _celery: http://celeryproject.org/
51 .. _rabbitmq: http://www.rabbitmq.com/ No newline at end of file
53 .. _rabbitmq: http://www.rabbitmq.com/
@@ -1,52 +1,53 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.__init__
3 rhodecode.__init__
4 ~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~
5
5
6 RhodeCode, a web based repository management based on pylons
6 RhodeCode, a web based repository management based on pylons
7 versioning implementation: http://semver.org/
7 versioning implementation: http://semver.org/
8
8
9 :created_on: Apr 9, 2010
9 :created_on: Apr 9, 2010
10 :author: marcink
10 :author: marcink
11 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
11 :copyright: (C) 2009-2011 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
14 # This program is free software; you can redistribute it and/or
15 # modify it under the terms of the GNU General Public License
15 # modify it under the terms of the GNU General Public License
16 # as published by the Free Software Foundation; version 2
16 # as published by the Free Software Foundation; version 2
17 # of the License or (at your opinion) any later version of the license.
17 # of the License or (at your opinion) any later version of the license.
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, write to the Free Software
25 # along with this program; if not, write to the Free Software
26 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
26 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
27 # MA 02110-1301, USA.
27 # MA 02110-1301, USA.
28
28 import platform
29
29
30 VERSION = (1, 1, 4)
30 VERSION = (1, 1, 5)
31 __version__ = '.'.join((str(each) for each in VERSION[:4]))
31 __version__ = '.'.join((str(each) for each in VERSION[:4]))
32 __dbversion__ = 2 #defines current db version for migrations
32 __dbversion__ = 2 #defines current db version for migrations
33 __platform__ = platform.system()
33
34
34 try:
35 try:
35 from rhodecode.lib.utils import get_current_revision
36 from rhodecode.lib.utils import get_current_revision
36 _rev = get_current_revision()
37 _rev = get_current_revision()
37 except ImportError:
38 except ImportError:
38 #this is needed when doing some setup.py operations
39 #this is needed when doing some setup.py operations
39 _rev = False
40 _rev = False
40
41
41 if len(VERSION) > 3 and _rev:
42 if len(VERSION) > 3 and _rev:
42 __version__ += ' [rev:%s]' % _rev[0]
43 __version__ += ' [rev:%s]' % _rev[0]
43
44
44 def get_version():
45 def get_version():
45 """Returns shorter version (digit parts only) as string."""
46 """Returns shorter version (digit parts only) as string."""
46
47
47 return '.'.join((str(each) for each in VERSION[:3]))
48 return '.'.join((str(each) for each in VERSION[:3]))
48
49
49 BACKENDS = {
50 BACKENDS = {
50 'hg': 'Mercurial repository',
51 'hg': 'Mercurial repository',
51 #'git': 'Git repository',
52 #'git': 'Git repository',
52 }
53 }
@@ -1,218 +1,218 b''
1 ################################################################################
1 ################################################################################
2 ################################################################################
2 ################################################################################
3 # RhodeCode - Pylons environment configuration #
3 # RhodeCode - Pylons environment configuration #
4 # #
4 # #
5 # The %(here)s variable will be replaced with the parent directory of this file#
5 # The %(here)s variable will be replaced with the parent directory of this file#
6 ################################################################################
6 ################################################################################
7
7
8 [DEFAULT]
8 [DEFAULT]
9 debug = true
9 debug = true
10 ################################################################################
10 ################################################################################
11 ## Uncomment and replace with the address which should receive ##
11 ## Uncomment and replace with the address which should receive ##
12 ## any error reports after application crash ##
12 ## any error reports after application crash ##
13 ## Additionally those settings will be used by RhodeCode mailing system ##
13 ## Additionally those settings will be used by RhodeCode mailing system ##
14 ################################################################################
14 ################################################################################
15 #email_to = admin@localhost
15 #email_to = admin@localhost
16 #error_email_from = paste_error@localhost
16 #error_email_from = paste_error@localhost
17 #app_email_from = rhodecode-noreply@localhost
17 #app_email_from = rhodecode-noreply@localhost
18 #error_message =
18 #error_message =
19
19
20 #smtp_server = mail.server.com
20 #smtp_server = mail.server.com
21 #smtp_username =
21 #smtp_username =
22 #smtp_password =
22 #smtp_password =
23 #smtp_port =
23 #smtp_port =
24 #smtp_use_tls = false
24 #smtp_use_tls = false
25 #smtp_use_ssl = true
25 #smtp_use_ssl = true
26
26
27 [server:main]
27 [server:main]
28 ##nr of threads to spawn
28 ##nr of threads to spawn
29 threadpool_workers = 5
29 threadpool_workers = 5
30
30
31 ##max request before thread respawn
31 ##max request before thread respawn
32 threadpool_max_requests = 10
32 threadpool_max_requests = 10
33
33
34 ##option to use threads of process
34 ##option to use threads of process
35 use_threadpool = true
35 use_threadpool = true
36
36
37 use = egg:Paste#http
37 use = egg:Paste#http
38 host = 127.0.0.1
38 host = 127.0.0.1
39 port = 5000
39 port = 5000
40
40
41 [app:main]
41 [app:main]
42 use = egg:rhodecode
42 use = egg:rhodecode
43 full_stack = true
43 full_stack = true
44 static_files = true
44 static_files = true
45 lang=en
45 lang=en
46 cache_dir = %(here)s/data
46 cache_dir = %(here)s/data
47 index_dir = %(here)s/data/index
47 index_dir = %(here)s/data/index
48 app_instance_uuid = ${app_instance_uuid}
48 app_instance_uuid = ${app_instance_uuid}
49 cut_off_limit = 256000
49 cut_off_limit = 256000
50 force_https = false
50 force_https = false
51
51
52 ####################################
52 ####################################
53 ### CELERY CONFIG ####
53 ### CELERY CONFIG ####
54 ####################################
54 ####################################
55 use_celery = false
55 use_celery = false
56 broker.host = localhost
56 broker.host = localhost
57 broker.vhost = rabbitmqhost
57 broker.vhost = rabbitmqhost
58 broker.port = 5672
58 broker.port = 5672
59 broker.user = rabbitmq
59 broker.user = rabbitmq
60 broker.password = qweqwe
60 broker.password = qweqwe
61
61
62 celery.imports = rhodecode.lib.celerylib.tasks
62 celery.imports = rhodecode.lib.celerylib.tasks
63
63
64 celery.result.backend = amqp
64 celery.result.backend = amqp
65 celery.result.dburi = amqp://
65 celery.result.dburi = amqp://
66 celery.result.serialier = json
66 celery.result.serialier = json
67
67
68 #celery.send.task.error.emails = true
68 #celery.send.task.error.emails = true
69 #celery.amqp.task.result.expires = 18000
69 #celery.amqp.task.result.expires = 18000
70
70
71 celeryd.concurrency = 2
71 celeryd.concurrency = 2
72 #celeryd.log.file = celeryd.log
72 #celeryd.log.file = celeryd.log
73 celeryd.log.level = debug
73 celeryd.log.level = debug
74 celeryd.max.tasks.per.child = 3
74 celeryd.max.tasks.per.child = 1
75
75
76 #tasks will never be sent to the queue, but executed locally instead.
76 #tasks will never be sent to the queue, but executed locally instead.
77 celery.always.eager = false
77 celery.always.eager = false
78
78
79 ####################################
79 ####################################
80 ### BEAKER CACHE ####
80 ### BEAKER CACHE ####
81 ####################################
81 ####################################
82 beaker.cache.data_dir=%(here)s/data/cache/data
82 beaker.cache.data_dir=%(here)s/data/cache/data
83 beaker.cache.lock_dir=%(here)s/data/cache/lock
83 beaker.cache.lock_dir=%(here)s/data/cache/lock
84
84
85 beaker.cache.regions=super_short_term,short_term,long_term,sql_cache_short,sql_cache_med,sql_cache_long
85 beaker.cache.regions=super_short_term,short_term,long_term,sql_cache_short,sql_cache_med,sql_cache_long
86
86
87 beaker.cache.super_short_term.type=memory
87 beaker.cache.super_short_term.type=memory
88 beaker.cache.super_short_term.expire=10
88 beaker.cache.super_short_term.expire=10
89
89
90 beaker.cache.short_term.type=memory
90 beaker.cache.short_term.type=memory
91 beaker.cache.short_term.expire=60
91 beaker.cache.short_term.expire=60
92
92
93 beaker.cache.long_term.type=memory
93 beaker.cache.long_term.type=memory
94 beaker.cache.long_term.expire=36000
94 beaker.cache.long_term.expire=36000
95
95
96 beaker.cache.sql_cache_short.type=memory
96 beaker.cache.sql_cache_short.type=memory
97 beaker.cache.sql_cache_short.expire=10
97 beaker.cache.sql_cache_short.expire=10
98
98
99 beaker.cache.sql_cache_med.type=memory
99 beaker.cache.sql_cache_med.type=memory
100 beaker.cache.sql_cache_med.expire=360
100 beaker.cache.sql_cache_med.expire=360
101
101
102 beaker.cache.sql_cache_long.type=file
102 beaker.cache.sql_cache_long.type=file
103 beaker.cache.sql_cache_long.expire=3600
103 beaker.cache.sql_cache_long.expire=3600
104
104
105 ####################################
105 ####################################
106 ### BEAKER SESSION ####
106 ### BEAKER SESSION ####
107 ####################################
107 ####################################
108 ## Type of storage used for the session, current types are
108 ## Type of storage used for the session, current types are
109 ## dbm, file, memcached, database, and memory.
109 ## dbm, file, memcached, database, and memory.
110 ## The storage uses the Container API
110 ## The storage uses the Container API
111 ##that is also used by the cache system.
111 ##that is also used by the cache system.
112 beaker.session.type = file
112 beaker.session.type = file
113
113
114 beaker.session.key = rhodecode
114 beaker.session.key = rhodecode
115 beaker.session.secret = ${app_instance_secret}
115 beaker.session.secret = ${app_instance_secret}
116 beaker.session.timeout = 36000
116 beaker.session.timeout = 36000
117
117
118 ##auto save the session to not to use .save()
118 ##auto save the session to not to use .save()
119 beaker.session.auto = False
119 beaker.session.auto = False
120
120
121 ##true exire at browser close
121 ##true exire at browser close
122 #beaker.session.cookie_expires = 3600
122 #beaker.session.cookie_expires = 3600
123
123
124
124
125 ################################################################################
125 ################################################################################
126 ## WARNING: *THE LINE BELOW MUST BE UNCOMMENTED ON A PRODUCTION ENVIRONMENT* ##
126 ## WARNING: *THE LINE BELOW MUST BE UNCOMMENTED ON A PRODUCTION ENVIRONMENT* ##
127 ## Debug mode will enable the interactive debugging tool, allowing ANYONE to ##
127 ## Debug mode will enable the interactive debugging tool, allowing ANYONE to ##
128 ## execute malicious code after an exception is raised. ##
128 ## execute malicious code after an exception is raised. ##
129 ################################################################################
129 ################################################################################
130 set debug = false
130 set debug = false
131
131
132 ##################################
132 ##################################
133 ### LOGVIEW CONFIG ###
133 ### LOGVIEW CONFIG ###
134 ##################################
134 ##################################
135 logview.sqlalchemy = #faa
135 logview.sqlalchemy = #faa
136 logview.pylons.templating = #bfb
136 logview.pylons.templating = #bfb
137 logview.pylons.util = #eee
137 logview.pylons.util = #eee
138
138
139 #########################################################
139 #########################################################
140 ### DB CONFIGS - EACH DB WILL HAVE IT'S OWN CONFIG ###
140 ### DB CONFIGS - EACH DB WILL HAVE IT'S OWN CONFIG ###
141 #########################################################
141 #########################################################
142 sqlalchemy.db1.url = sqlite:///%(here)s/rhodecode.db
142 sqlalchemy.db1.url = sqlite:///%(here)s/rhodecode.db
143 #sqlalchemy.db1.echo = False
143 #sqlalchemy.db1.echo = False
144 #sqlalchemy.db1.pool_recycle = 3600
144 #sqlalchemy.db1.pool_recycle = 3600
145 sqlalchemy.convert_unicode = true
145 sqlalchemy.convert_unicode = true
146
146
147 ################################
147 ################################
148 ### LOGGING CONFIGURATION ####
148 ### LOGGING CONFIGURATION ####
149 ################################
149 ################################
150 [loggers]
150 [loggers]
151 keys = root, routes, rhodecode, sqlalchemy,beaker,templates
151 keys = root, routes, rhodecode, sqlalchemy,beaker,templates
152
152
153 [handlers]
153 [handlers]
154 keys = console
154 keys = console
155
155
156 [formatters]
156 [formatters]
157 keys = generic,color_formatter
157 keys = generic,color_formatter
158
158
159 #############
159 #############
160 ## LOGGERS ##
160 ## LOGGERS ##
161 #############
161 #############
162 [logger_root]
162 [logger_root]
163 level = INFO
163 level = INFO
164 handlers = console
164 handlers = console
165
165
166 [logger_routes]
166 [logger_routes]
167 level = INFO
167 level = INFO
168 handlers = console
168 handlers = console
169 qualname = routes.middleware
169 qualname = routes.middleware
170 # "level = DEBUG" logs the route matched and routing variables.
170 # "level = DEBUG" logs the route matched and routing variables.
171 propagate = 0
171 propagate = 0
172
172
173 [logger_beaker]
173 [logger_beaker]
174 level = ERROR
174 level = ERROR
175 handlers = console
175 handlers = console
176 qualname = beaker.container
176 qualname = beaker.container
177 propagate = 0
177 propagate = 0
178
178
179 [logger_templates]
179 [logger_templates]
180 level = INFO
180 level = INFO
181 handlers = console
181 handlers = console
182 qualname = pylons.templating
182 qualname = pylons.templating
183 propagate = 0
183 propagate = 0
184
184
185 [logger_rhodecode]
185 [logger_rhodecode]
186 level = DEBUG
186 level = DEBUG
187 handlers = console
187 handlers = console
188 qualname = rhodecode
188 qualname = rhodecode
189 propagate = 0
189 propagate = 0
190
190
191 [logger_sqlalchemy]
191 [logger_sqlalchemy]
192 level = ERROR
192 level = ERROR
193 handlers = console
193 handlers = console
194 qualname = sqlalchemy.engine
194 qualname = sqlalchemy.engine
195 propagate = 0
195 propagate = 0
196
196
197 ##############
197 ##############
198 ## HANDLERS ##
198 ## HANDLERS ##
199 ##############
199 ##############
200
200
201 [handler_console]
201 [handler_console]
202 class = StreamHandler
202 class = StreamHandler
203 args = (sys.stderr,)
203 args = (sys.stderr,)
204 level = NOTSET
204 level = NOTSET
205 formatter = color_formatter
205 formatter = color_formatter
206
206
207 ################
207 ################
208 ## FORMATTERS ##
208 ## FORMATTERS ##
209 ################
209 ################
210
210
211 [formatter_generic]
211 [formatter_generic]
212 format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
212 format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
213 datefmt = %Y-%m-%d %H:%M:%S
213 datefmt = %Y-%m-%d %H:%M:%S
214
214
215 [formatter_color_formatter]
215 [formatter_color_formatter]
216 class=rhodecode.lib.colored_formatter.ColorFormatter
216 class=rhodecode.lib.colored_formatter.ColorFormatter
217 format= %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
217 format= %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
218 datefmt = %Y-%m-%d %H:%M:%S No newline at end of file
218 datefmt = %Y-%m-%d %H:%M:%S
@@ -1,340 +1,340 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.controllers.admin.settings
3 rhodecode.controllers.admin.settings
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 settings controller for rhodecode admin
6 settings controller for rhodecode admin
7
7
8 :created_on: Jul 14, 2010
8 :created_on: Jul 14, 2010
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2009-2010 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
13 # This program is free software; you can redistribute it and/or
14 # modify it under the terms of the GNU General Public License
14 # modify it under the terms of the GNU General Public License
15 # as published by the Free Software Foundation; version 2
15 # as published by the Free Software Foundation; version 2
16 # of the License or (at your opinion) any later version of the license.
16 # of the License or (at your opinion) any later version of the license.
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, write to the Free Software
24 # along with this program; if not, write to the Free Software
25 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
25 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
26 # MA 02110-1301, USA.
26 # MA 02110-1301, USA.
27
27
28 from formencode import htmlfill
28 from formencode import htmlfill
29 from pylons import request, session, tmpl_context as c, url, app_globals as g, \
29 from pylons import request, session, tmpl_context as c, url, app_globals as g, \
30 config
30 config
31 from pylons.controllers.util import abort, redirect
31 from pylons.controllers.util import abort, redirect
32 from pylons.i18n.translation import _
32 from pylons.i18n.translation import _
33 from rhodecode.lib import helpers as h
33 from rhodecode.lib import helpers as h
34 from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator, \
34 from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator, \
35 HasPermissionAnyDecorator, NotAnonymous
35 HasPermissionAnyDecorator, NotAnonymous
36 from rhodecode.lib.base import BaseController, render
36 from rhodecode.lib.base import BaseController, render
37 from rhodecode.lib.celerylib import tasks, run_task
37 from rhodecode.lib.celerylib import tasks, run_task
38 from rhodecode.lib.utils import repo2db_mapper, invalidate_cache, \
38 from rhodecode.lib.utils import repo2db_mapper, invalidate_cache, \
39 set_rhodecode_config
39 set_rhodecode_config
40 from rhodecode.model.db import RhodeCodeUi, Repository
40 from rhodecode.model.db import RhodeCodeUi, Repository
41 from rhodecode.model.forms import UserForm, ApplicationSettingsForm, \
41 from rhodecode.model.forms import UserForm, ApplicationSettingsForm, \
42 ApplicationUiSettingsForm
42 ApplicationUiSettingsForm
43 from rhodecode.model.scm import ScmModel
43 from rhodecode.model.scm import ScmModel
44 from rhodecode.model.settings import SettingsModel
44 from rhodecode.model.settings import SettingsModel
45 from rhodecode.model.user import UserModel
45 from rhodecode.model.user import UserModel
46 from sqlalchemy import func
46 from sqlalchemy import func
47 import formencode
47 import formencode
48 import logging
48 import logging
49 import traceback
49 import traceback
50
50
51 log = logging.getLogger(__name__)
51 log = logging.getLogger(__name__)
52
52
53
53
54 class SettingsController(BaseController):
54 class SettingsController(BaseController):
55 """REST Controller styled on the Atom Publishing Protocol"""
55 """REST Controller styled on the Atom Publishing Protocol"""
56 # To properly map this controller, ensure your config/routing.py
56 # To properly map this controller, ensure your config/routing.py
57 # file has a resource setup:
57 # file has a resource setup:
58 # map.resource('setting', 'settings', controller='admin/settings',
58 # map.resource('setting', 'settings', controller='admin/settings',
59 # path_prefix='/admin', name_prefix='admin_')
59 # path_prefix='/admin', name_prefix='admin_')
60
60
61
61
62 @LoginRequired()
62 @LoginRequired()
63 def __before__(self):
63 def __before__(self):
64 c.admin_user = session.get('admin_user')
64 c.admin_user = session.get('admin_user')
65 c.admin_username = session.get('admin_username')
65 c.admin_username = session.get('admin_username')
66 super(SettingsController, self).__before__()
66 super(SettingsController, self).__before__()
67
67
68
68
69 @HasPermissionAllDecorator('hg.admin')
69 @HasPermissionAllDecorator('hg.admin')
70 def index(self, format='html'):
70 def index(self, format='html'):
71 """GET /admin/settings: All items in the collection"""
71 """GET /admin/settings: All items in the collection"""
72 # url('admin_settings')
72 # url('admin_settings')
73
73
74 defaults = SettingsModel().get_app_settings()
74 defaults = SettingsModel().get_app_settings()
75 defaults.update(self.get_hg_ui_settings())
75 defaults.update(self.get_hg_ui_settings())
76 return htmlfill.render(
76 return htmlfill.render(
77 render('admin/settings/settings.html'),
77 render('admin/settings/settings.html'),
78 defaults=defaults,
78 defaults=defaults,
79 encoding="UTF-8",
79 encoding="UTF-8",
80 force_defaults=False
80 force_defaults=False
81 )
81 )
82
82
83 @HasPermissionAllDecorator('hg.admin')
83 @HasPermissionAllDecorator('hg.admin')
84 def create(self):
84 def create(self):
85 """POST /admin/settings: Create a new item"""
85 """POST /admin/settings: Create a new item"""
86 # url('admin_settings')
86 # url('admin_settings')
87
87
88 @HasPermissionAllDecorator('hg.admin')
88 @HasPermissionAllDecorator('hg.admin')
89 def new(self, format='html'):
89 def new(self, format='html'):
90 """GET /admin/settings/new: Form to create a new item"""
90 """GET /admin/settings/new: Form to create a new item"""
91 # url('admin_new_setting')
91 # url('admin_new_setting')
92
92
93 @HasPermissionAllDecorator('hg.admin')
93 @HasPermissionAllDecorator('hg.admin')
94 def update(self, setting_id):
94 def update(self, setting_id):
95 """PUT /admin/settings/setting_id: Update an existing item"""
95 """PUT /admin/settings/setting_id: Update an existing item"""
96 # Forms posted to this method should contain a hidden field:
96 # Forms posted to this method should contain a hidden field:
97 # <input type="hidden" name="_method" value="PUT" />
97 # <input type="hidden" name="_method" value="PUT" />
98 # Or using helpers:
98 # Or using helpers:
99 # h.form(url('admin_setting', setting_id=ID),
99 # h.form(url('admin_setting', setting_id=ID),
100 # method='put')
100 # method='put')
101 # url('admin_setting', setting_id=ID)
101 # url('admin_setting', setting_id=ID)
102 if setting_id == 'mapping':
102 if setting_id == 'mapping':
103 rm_obsolete = request.POST.get('destroy', False)
103 rm_obsolete = request.POST.get('destroy', False)
104 log.debug('Rescanning directories with destroy=%s', rm_obsolete)
104 log.debug('Rescanning directories with destroy=%s', rm_obsolete)
105
105
106 initial = ScmModel().repo_scan(g.paths[0][1], g.baseui)
106 initial = ScmModel().repo_scan(g.paths[0][1], g.baseui)
107 for repo_name in initial.keys():
107 for repo_name in initial.keys():
108 invalidate_cache('get_repo_cached_%s' % repo_name)
108 invalidate_cache('get_repo_cached_%s' % repo_name)
109
109
110 repo2db_mapper(initial, rm_obsolete)
110 repo2db_mapper(initial, rm_obsolete)
111
111
112 h.flash(_('Repositories successfully rescanned'), category='success')
112 h.flash(_('Repositories successfully rescanned'), category='success')
113
113
114 if setting_id == 'whoosh':
114 if setting_id == 'whoosh':
115 repo_location = self.get_hg_ui_settings()['paths_root_path']
115 repo_location = self.get_hg_ui_settings()['paths_root_path']
116 full_index = request.POST.get('full_index', False)
116 full_index = request.POST.get('full_index', False)
117 task = run_task(tasks.whoosh_index, repo_location, full_index)
117 task = run_task(tasks.whoosh_index, repo_location, full_index)
118
118
119 h.flash(_('Whoosh reindex task scheduled'), category='success')
119 h.flash(_('Whoosh reindex task scheduled'), category='success')
120 if setting_id == 'global':
120 if setting_id == 'global':
121
121
122 application_form = ApplicationSettingsForm()()
122 application_form = ApplicationSettingsForm()()
123 try:
123 try:
124 form_result = application_form.to_python(dict(request.POST))
124 form_result = application_form.to_python(dict(request.POST))
125 settings_model = SettingsModel()
125 settings_model = SettingsModel()
126 try:
126 try:
127 hgsettings1 = settings_model.get('title')
127 hgsettings1 = settings_model.get('title')
128 hgsettings1.app_settings_value = form_result['rhodecode_title']
128 hgsettings1.app_settings_value = form_result['rhodecode_title']
129
129
130 hgsettings2 = settings_model.get('realm')
130 hgsettings2 = settings_model.get('realm')
131 hgsettings2.app_settings_value = form_result['rhodecode_realm']
131 hgsettings2.app_settings_value = form_result['rhodecode_realm']
132
132
133
133
134 self.sa.add(hgsettings1)
134 self.sa.add(hgsettings1)
135 self.sa.add(hgsettings2)
135 self.sa.add(hgsettings2)
136 self.sa.commit()
136 self.sa.commit()
137 set_rhodecode_config(config)
137 set_rhodecode_config(config)
138 h.flash(_('Updated application settings'),
138 h.flash(_('Updated application settings'),
139 category='success')
139 category='success')
140
140
141 except:
141 except:
142 log.error(traceback.format_exc())
142 log.error(traceback.format_exc())
143 h.flash(_('error occurred during updating application settings'),
143 h.flash(_('error occurred during updating'
144 category='error')
144 ' application settings'), category='error')
145
145
146 self.sa.rollback()
146 self.sa.rollback()
147
147
148
148
149 except formencode.Invalid, errors:
149 except formencode.Invalid, errors:
150 return htmlfill.render(
150 return htmlfill.render(
151 render('admin/settings/settings.html'),
151 render('admin/settings/settings.html'),
152 defaults=errors.value,
152 defaults=errors.value,
153 errors=errors.error_dict or {},
153 errors=errors.error_dict or {},
154 prefix_error=False,
154 prefix_error=False,
155 encoding="UTF-8")
155 encoding="UTF-8")
156
156
157 if setting_id == 'mercurial':
157 if setting_id == 'mercurial':
158 application_form = ApplicationUiSettingsForm()()
158 application_form = ApplicationUiSettingsForm()()
159 try:
159 try:
160 form_result = application_form.to_python(dict(request.POST))
160 form_result = application_form.to_python(dict(request.POST))
161
161
162 try:
162 try:
163
163
164 hgsettings1 = self.sa.query(RhodeCodeUi)\
164 hgsettings1 = self.sa.query(RhodeCodeUi)\
165 .filter(RhodeCodeUi.ui_key == 'push_ssl').one()
165 .filter(RhodeCodeUi.ui_key == 'push_ssl').one()
166 hgsettings1.ui_value = form_result['web_push_ssl']
166 hgsettings1.ui_value = form_result['web_push_ssl']
167
167
168 hgsettings2 = self.sa.query(RhodeCodeUi)\
168 hgsettings2 = self.sa.query(RhodeCodeUi)\
169 .filter(RhodeCodeUi.ui_key == '/').one()
169 .filter(RhodeCodeUi.ui_key == '/').one()
170 hgsettings2.ui_value = form_result['paths_root_path']
170 hgsettings2.ui_value = form_result['paths_root_path']
171
171
172
172
173 #HOOKS
173 #HOOKS
174 hgsettings3 = self.sa.query(RhodeCodeUi)\
174 hgsettings3 = self.sa.query(RhodeCodeUi)\
175 .filter(RhodeCodeUi.ui_key == 'changegroup.update').one()
175 .filter(RhodeCodeUi.ui_key == 'changegroup.update').one()
176 hgsettings3.ui_active = bool(form_result['hooks_changegroup_update'])
176 hgsettings3.ui_active = bool(form_result['hooks_changegroup_update'])
177
177
178 hgsettings4 = self.sa.query(RhodeCodeUi)\
178 hgsettings4 = self.sa.query(RhodeCodeUi)\
179 .filter(RhodeCodeUi.ui_key == 'changegroup.repo_size').one()
179 .filter(RhodeCodeUi.ui_key == 'changegroup.repo_size').one()
180 hgsettings4.ui_active = bool(form_result['hooks_changegroup_repo_size'])
180 hgsettings4.ui_active = bool(form_result['hooks_changegroup_repo_size'])
181
181
182 hgsettings5 = self.sa.query(RhodeCodeUi)\
182 hgsettings5 = self.sa.query(RhodeCodeUi)\
183 .filter(RhodeCodeUi.ui_key == 'pretxnchangegroup.push_logger').one()
183 .filter(RhodeCodeUi.ui_key == 'pretxnchangegroup.push_logger').one()
184 hgsettings5.ui_active = bool(form_result['hooks_pretxnchangegroup_push_logger'])
184 hgsettings5.ui_active = bool(form_result['hooks_pretxnchangegroup_push_logger'])
185
185
186 hgsettings6 = self.sa.query(RhodeCodeUi)\
186 hgsettings6 = self.sa.query(RhodeCodeUi)\
187 .filter(RhodeCodeUi.ui_key == 'preoutgoing.pull_logger').one()
187 .filter(RhodeCodeUi.ui_key == 'preoutgoing.pull_logger').one()
188 hgsettings6.ui_active = bool(form_result['hooks_preoutgoing_pull_logger'])
188 hgsettings6.ui_active = bool(form_result['hooks_preoutgoing_pull_logger'])
189
189
190
190
191 self.sa.add(hgsettings1)
191 self.sa.add(hgsettings1)
192 self.sa.add(hgsettings2)
192 self.sa.add(hgsettings2)
193 self.sa.add(hgsettings3)
193 self.sa.add(hgsettings3)
194 self.sa.add(hgsettings4)
194 self.sa.add(hgsettings4)
195 self.sa.add(hgsettings5)
195 self.sa.add(hgsettings5)
196 self.sa.add(hgsettings6)
196 self.sa.add(hgsettings6)
197 self.sa.commit()
197 self.sa.commit()
198
198
199 h.flash(_('Updated mercurial settings'),
199 h.flash(_('Updated mercurial settings'),
200 category='success')
200 category='success')
201
201
202 except:
202 except:
203 log.error(traceback.format_exc())
203 log.error(traceback.format_exc())
204 h.flash(_('error occurred during updating application settings'),
204 h.flash(_('error occurred during updating application settings'),
205 category='error')
205 category='error')
206
206
207 self.sa.rollback()
207 self.sa.rollback()
208
208
209
209
210 except formencode.Invalid, errors:
210 except formencode.Invalid, errors:
211 return htmlfill.render(
211 return htmlfill.render(
212 render('admin/settings/settings.html'),
212 render('admin/settings/settings.html'),
213 defaults=errors.value,
213 defaults=errors.value,
214 errors=errors.error_dict or {},
214 errors=errors.error_dict or {},
215 prefix_error=False,
215 prefix_error=False,
216 encoding="UTF-8")
216 encoding="UTF-8")
217
217
218
218
219
219
220 return redirect(url('admin_settings'))
220 return redirect(url('admin_settings'))
221
221
222 @HasPermissionAllDecorator('hg.admin')
222 @HasPermissionAllDecorator('hg.admin')
223 def delete(self, setting_id):
223 def delete(self, setting_id):
224 """DELETE /admin/settings/setting_id: Delete an existing item"""
224 """DELETE /admin/settings/setting_id: Delete an existing item"""
225 # Forms posted to this method should contain a hidden field:
225 # Forms posted to this method should contain a hidden field:
226 # <input type="hidden" name="_method" value="DELETE" />
226 # <input type="hidden" name="_method" value="DELETE" />
227 # Or using helpers:
227 # Or using helpers:
228 # h.form(url('admin_setting', setting_id=ID),
228 # h.form(url('admin_setting', setting_id=ID),
229 # method='delete')
229 # method='delete')
230 # url('admin_setting', setting_id=ID)
230 # url('admin_setting', setting_id=ID)
231
231
232 @HasPermissionAllDecorator('hg.admin')
232 @HasPermissionAllDecorator('hg.admin')
233 def show(self, setting_id, format='html'):
233 def show(self, setting_id, format='html'):
234 """GET /admin/settings/setting_id: Show a specific item"""
234 """GET /admin/settings/setting_id: Show a specific item"""
235 # url('admin_setting', setting_id=ID)
235 # url('admin_setting', setting_id=ID)
236
236
237 @HasPermissionAllDecorator('hg.admin')
237 @HasPermissionAllDecorator('hg.admin')
238 def edit(self, setting_id, format='html'):
238 def edit(self, setting_id, format='html'):
239 """GET /admin/settings/setting_id/edit: Form to edit an existing item"""
239 """GET /admin/settings/setting_id/edit: Form to edit an existing item"""
240 # url('admin_edit_setting', setting_id=ID)
240 # url('admin_edit_setting', setting_id=ID)
241
241
242 @NotAnonymous()
242 @NotAnonymous()
243 def my_account(self):
243 def my_account(self):
244 """
244 """
245 GET /_admin/my_account Displays info about my account
245 GET /_admin/my_account Displays info about my account
246 """
246 """
247 # url('admin_settings_my_account')
247 # url('admin_settings_my_account')
248
248
249 c.user = UserModel().get(c.rhodecode_user.user_id, cache=False)
249 c.user = UserModel().get(c.rhodecode_user.user_id, cache=False)
250 all_repos = self.sa.query(Repository)\
250 all_repos = self.sa.query(Repository)\
251 .filter(Repository.user_id == c.user.user_id)\
251 .filter(Repository.user_id == c.user.user_id)\
252 .order_by(func.lower(Repository.repo_name))\
252 .order_by(func.lower(Repository.repo_name))\
253 .all()
253 .all()
254
254
255 c.user_repos = ScmModel().get_repos(all_repos)
255 c.user_repos = ScmModel().get_repos(all_repos)
256
256
257 if c.user.username == 'default':
257 if c.user.username == 'default':
258 h.flash(_("You can't edit this user since it's"
258 h.flash(_("You can't edit this user since it's"
259 " crucial for entire application"), category='warning')
259 " crucial for entire application"), category='warning')
260 return redirect(url('users'))
260 return redirect(url('users'))
261
261
262 defaults = c.user.get_dict()
262 defaults = c.user.get_dict()
263 return htmlfill.render(
263 return htmlfill.render(
264 render('admin/users/user_edit_my_account.html'),
264 render('admin/users/user_edit_my_account.html'),
265 defaults=defaults,
265 defaults=defaults,
266 encoding="UTF-8",
266 encoding="UTF-8",
267 force_defaults=False
267 force_defaults=False
268 )
268 )
269
269
270 def my_account_update(self):
270 def my_account_update(self):
271 """PUT /_admin/my_account_update: Update an existing item"""
271 """PUT /_admin/my_account_update: Update an existing item"""
272 # Forms posted to this method should contain a hidden field:
272 # Forms posted to this method should contain a hidden field:
273 # <input type="hidden" name="_method" value="PUT" />
273 # <input type="hidden" name="_method" value="PUT" />
274 # Or using helpers:
274 # Or using helpers:
275 # h.form(url('admin_settings_my_account_update'),
275 # h.form(url('admin_settings_my_account_update'),
276 # method='put')
276 # method='put')
277 # url('admin_settings_my_account_update', id=ID)
277 # url('admin_settings_my_account_update', id=ID)
278 user_model = UserModel()
278 user_model = UserModel()
279 uid = c.rhodecode_user.user_id
279 uid = c.rhodecode_user.user_id
280 _form = UserForm(edit=True, old_data={'user_id':uid,
280 _form = UserForm(edit=True, old_data={'user_id':uid,
281 'email':c.rhodecode_user.email})()
281 'email':c.rhodecode_user.email})()
282 form_result = {}
282 form_result = {}
283 try:
283 try:
284 form_result = _form.to_python(dict(request.POST))
284 form_result = _form.to_python(dict(request.POST))
285 user_model.update_my_account(uid, form_result)
285 user_model.update_my_account(uid, form_result)
286 h.flash(_('Your account was updated successfully'),
286 h.flash(_('Your account was updated successfully'),
287 category='success')
287 category='success')
288
288
289 except formencode.Invalid, errors:
289 except formencode.Invalid, errors:
290 c.user = user_model.get(c.rhodecode_user.user_id, cache=False)
290 c.user = user_model.get(c.rhodecode_user.user_id, cache=False)
291 c.user = UserModel().get(c.rhodecode_user.user_id, cache=False)
291 c.user = UserModel().get(c.rhodecode_user.user_id, cache=False)
292 all_repos = self.sa.query(Repository)\
292 all_repos = self.sa.query(Repository)\
293 .filter(Repository.user_id == c.user.user_id)\
293 .filter(Repository.user_id == c.user.user_id)\
294 .order_by(func.lower(Repository.repo_name))\
294 .order_by(func.lower(Repository.repo_name))\
295 .all()
295 .all()
296 c.user_repos = ScmModel().get_repos(all_repos)
296 c.user_repos = ScmModel().get_repos(all_repos)
297
297
298 return htmlfill.render(
298 return htmlfill.render(
299 render('admin/users/user_edit_my_account.html'),
299 render('admin/users/user_edit_my_account.html'),
300 defaults=errors.value,
300 defaults=errors.value,
301 errors=errors.error_dict or {},
301 errors=errors.error_dict or {},
302 prefix_error=False,
302 prefix_error=False,
303 encoding="UTF-8")
303 encoding="UTF-8")
304 except Exception:
304 except Exception:
305 log.error(traceback.format_exc())
305 log.error(traceback.format_exc())
306 h.flash(_('error occurred during update of user %s') \
306 h.flash(_('error occurred during update of user %s') \
307 % form_result.get('username'), category='error')
307 % form_result.get('username'), category='error')
308
308
309 return redirect(url('my_account'))
309 return redirect(url('my_account'))
310
310
311 @NotAnonymous()
311 @NotAnonymous()
312 @HasPermissionAnyDecorator('hg.admin', 'hg.create.repository')
312 @HasPermissionAnyDecorator('hg.admin', 'hg.create.repository')
313 def create_repository(self):
313 def create_repository(self):
314 """GET /_admin/create_repository: Form to create a new item"""
314 """GET /_admin/create_repository: Form to create a new item"""
315 new_repo = request.GET.get('repo', '')
315 new_repo = request.GET.get('repo', '')
316 c.new_repo = h.repo_name_slug(new_repo)
316 c.new_repo = h.repo_name_slug(new_repo)
317
317
318 return render('admin/repos/repo_add_create_repository.html')
318 return render('admin/repos/repo_add_create_repository.html')
319
319
320 def get_hg_ui_settings(self):
320 def get_hg_ui_settings(self):
321 ret = self.sa.query(RhodeCodeUi).all()
321 ret = self.sa.query(RhodeCodeUi).all()
322
322
323 if not ret:
323 if not ret:
324 raise Exception('Could not get application ui settings !')
324 raise Exception('Could not get application ui settings !')
325 settings = {}
325 settings = {}
326 for each in ret:
326 for each in ret:
327 k = each.ui_key
327 k = each.ui_key
328 v = each.ui_value
328 v = each.ui_value
329 if k == '/':
329 if k == '/':
330 k = 'root_path'
330 k = 'root_path'
331
331
332 if k.find('.') != -1:
332 if k.find('.') != -1:
333 k = k.replace('.', '_')
333 k = k.replace('.', '_')
334
334
335 if each.ui_section == 'hooks':
335 if each.ui_section == 'hooks':
336 v = each.ui_active
336 v = each.ui_active
337
337
338 settings[each.ui_section + '_' + k] = v
338 settings[each.ui_section + '_' + k] = v
339
339
340 return settings
340 return settings
@@ -1,258 +1,278 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.controllers.files
3 rhodecode.controllers.files
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 Files controller for RhodeCode
6 Files controller for RhodeCode
7
7
8 :created_on: Apr 21, 2010
8 :created_on: Apr 21, 2010
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2009-2011 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
13 # This program is free software; you can redistribute it and/or
14 # modify it under the terms of the GNU General Public License
14 # modify it under the terms of the GNU General Public License
15 # as published by the Free Software Foundation; version 2
15 # as published by the Free Software Foundation; version 2
16 # of the License or (at your opinion) any later version of the license.
16 # of the License or (at your opinion) any later version of the license.
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, write to the Free Software
24 # along with this program; if not, write to the Free Software
25 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
25 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
26 # MA 02110-1301, USA.
26 # MA 02110-1301, USA.
27 import tempfile
27 import tempfile
28 import logging
28 import logging
29 import rhodecode.lib.helpers as h
29 import rhodecode.lib.helpers as h
30
30
31 from mercurial import archival
31 from mercurial import archival
32
32
33 from pylons import request, response, session, tmpl_context as c, url
33 from pylons import request, response, session, tmpl_context as c, url
34 from pylons.i18n.translation import _
34 from pylons.i18n.translation import _
35 from pylons.controllers.util import redirect
35 from pylons.controllers.util import redirect
36
36
37 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
37 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
38 from rhodecode.lib.base import BaseController, render
38 from rhodecode.lib.base import BaseController, render
39 from rhodecode.lib.utils import EmptyChangeset
39 from rhodecode.lib.utils import EmptyChangeset
40 from rhodecode.model.scm import ScmModel
40 from rhodecode.model.scm import ScmModel
41
41
42 from vcs.exceptions import RepositoryError, ChangesetError, \
42 from vcs.exceptions import RepositoryError, ChangesetError, \
43 ChangesetDoesNotExistError, EmptyRepositoryError
43 ChangesetDoesNotExistError, EmptyRepositoryError
44 from vcs.nodes import FileNode
44 from vcs.nodes import FileNode
45 from vcs.utils import diffs as differ
45 from vcs.utils import diffs as differ
46
46
47 log = logging.getLogger(__name__)
47 log = logging.getLogger(__name__)
48
48
49 class FilesController(BaseController):
49 class FilesController(BaseController):
50
50
51 @LoginRequired()
51 @LoginRequired()
52 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
52 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
53 'repository.admin')
53 'repository.admin')
54 def __before__(self):
54 def __before__(self):
55 super(FilesController, self).__before__()
55 super(FilesController, self).__before__()
56 c.cut_off_limit = self.cut_off_limit
56 c.cut_off_limit = self.cut_off_limit
57
57
58 def __get_cs_or_redirect(self, rev, repo_name):
59 """
60 Safe way to get changeset if error occur it redirects to tip with
61 proper message
62
63 :param rev: revision to fetch
64 :param repo_name: repo name to redirect after
65 """
66
67 _repo = ScmModel().get_repo(c.repo_name)
68 try:
69 return _repo.get_changeset(rev)
70 except EmptyRepositoryError, e:
71 h.flash(_('There are no files yet'), category='warning')
72 redirect(h.url('summary_home', repo_name=repo_name))
73
74 except RepositoryError, e:
75 h.flash(str(e), category='warning')
76 redirect(h.url('files_home', repo_name=repo_name, revision='tip'))
77
58 def index(self, repo_name, revision, f_path):
78 def index(self, repo_name, revision, f_path):
59 hg_model = ScmModel()
79 cs = self.__get_cs_or_redirect(revision, repo_name)
60 c.repo = hg_model.get_repo(c.repo_name)
80 c.repo = ScmModel().get_repo(c.repo_name)
81
61 revision = request.POST.get('at_rev', None) or revision
82 revision = request.POST.get('at_rev', None) or revision
62
83
63 def get_next_rev(cur):
84 def get_next_rev(cur):
64 max_rev = len(c.repo.revisions) - 1
85 max_rev = len(c.repo.revisions) - 1
65 r = cur + 1
86 r = cur + 1
66 if r > max_rev:
87 if r > max_rev:
67 r = max_rev
88 r = max_rev
68 return r
89 return r
69
90
70 def get_prev_rev(cur):
91 def get_prev_rev(cur):
71 r = cur - 1
92 r = cur - 1
72 return r
93 return r
73
94
74 c.f_path = f_path
95 c.f_path = f_path
75
96 c.changeset = cs
76
77 try:
78 c.changeset = c.repo.get_changeset(revision)
79 cur_rev = c.changeset.revision
97 cur_rev = c.changeset.revision
80 prev_rev = c.repo.get_changeset(get_prev_rev(cur_rev)).raw_id
98 prev_rev = c.repo.get_changeset(get_prev_rev(cur_rev)).raw_id
81 next_rev = c.repo.get_changeset(get_next_rev(cur_rev)).raw_id
99 next_rev = c.repo.get_changeset(get_next_rev(cur_rev)).raw_id
82
100
83 c.url_prev = url('files_home', repo_name=c.repo_name,
101 c.url_prev = url('files_home', repo_name=c.repo_name,
84 revision=prev_rev, f_path=f_path)
102 revision=prev_rev, f_path=f_path)
85 c.url_next = url('files_home', repo_name=c.repo_name,
103 c.url_next = url('files_home', repo_name=c.repo_name,
86 revision=next_rev, f_path=f_path)
104 revision=next_rev, f_path=f_path)
87
105
88 try:
106 try:
89 c.files_list = c.changeset.get_node(f_path)
107 c.files_list = c.changeset.get_node(f_path)
90 c.file_history = self._get_history(c.repo, c.files_list, f_path)
108 c.file_history = self._get_history(c.repo, c.files_list, f_path)
91 except RepositoryError, e:
109 except RepositoryError, e:
92 h.flash(str(e), category='warning')
110 h.flash(str(e), category='warning')
93 redirect(h.url('files_home', repo_name=repo_name, revision=revision))
111 redirect(h.url('files_home', repo_name=repo_name,
94
112 revision=revision))
95 except EmptyRepositoryError, e:
96 h.flash(_('There are no files yet'), category='warning')
97 redirect(h.url('summary_home', repo_name=repo_name))
98
99 except RepositoryError, e:
100 h.flash(str(e), category='warning')
101 redirect(h.url('files_home', repo_name=repo_name, revision='tip'))
102
103
113
104
114
105 return render('files/files.html')
115 return render('files/files.html')
106
116
107 def rawfile(self, repo_name, revision, f_path):
117 def rawfile(self, repo_name, revision, f_path):
108 hg_model = ScmModel()
118 cs = self.__get_cs_or_redirect(revision, repo_name)
109 c.repo = hg_model.get_repo(c.repo_name)
119 try:
110 file_node = c.repo.get_changeset(revision).get_node(f_path)
120 file_node = cs.get_node(f_path)
121 except RepositoryError, e:
122 h.flash(str(e), category='warning')
123 redirect(h.url('files_home', repo_name=repo_name,
124 revision=cs.raw_id))
125
126 fname = f_path.split('/')[-1].encode('utf8', 'replace')
127
128 response.content_disposition = 'attachment; filename=%s' % fname
111 response.content_type = file_node.mimetype
129 response.content_type = file_node.mimetype
112 response.content_disposition = 'attachment; filename=%s' \
113 % f_path.split('/')[-1]
114 return file_node.content
130 return file_node.content
115
131
116 def raw(self, repo_name, revision, f_path):
132 def raw(self, repo_name, revision, f_path):
117 hg_model = ScmModel()
133 cs = self.__get_cs_or_redirect(revision, repo_name)
118 c.repo = hg_model.get_repo(c.repo_name)
134 try:
119 file_node = c.repo.get_changeset(revision).get_node(f_path)
135 file_node = cs.get_node(f_path)
136 except RepositoryError, e:
137 h.flash(str(e), category='warning')
138 redirect(h.url('files_home', repo_name=repo_name,
139 revision=cs.raw_id))
140
120 response.content_type = 'text/plain'
141 response.content_type = 'text/plain'
121
122 return file_node.content
142 return file_node.content
123
143
124 def annotate(self, repo_name, revision, f_path):
144 def annotate(self, repo_name, revision, f_path):
125 hg_model = ScmModel()
145 cs = self.__get_cs_or_redirect(revision, repo_name)
126 c.repo = hg_model.get_repo(c.repo_name)
127
128 try:
146 try:
129 c.cs = c.repo.get_changeset(revision)
147 c.file = cs.get_node(f_path)
130 c.file = c.cs.get_node(f_path)
131 except RepositoryError, e:
148 except RepositoryError, e:
132 h.flash(str(e), category='warning')
149 h.flash(str(e), category='warning')
133 redirect(h.url('files_home', repo_name=repo_name, revision=revision))
150 redirect(h.url('files_home', repo_name=repo_name, revision=cs.raw_id))
134
151
135 c.file_history = self._get_history(c.repo, c.file, f_path)
152 c.file_history = self._get_history(ScmModel().get_repo(c.repo_name), c.file, f_path)
136
153 c.cs = cs
137 c.f_path = f_path
154 c.f_path = f_path
138
155
139 return render('files/files_annotate.html')
156 return render('files/files_annotate.html')
140
157
141 def archivefile(self, repo_name, revision, fileformat):
158 def archivefile(self, repo_name, revision, fileformat):
142 archive_specs = {
159 archive_specs = {
143 '.tar.bz2': ('application/x-tar', 'tbz2'),
160 '.tar.bz2': ('application/x-tar', 'tbz2'),
144 '.tar.gz': ('application/x-tar', 'tgz'),
161 '.tar.gz': ('application/x-tar', 'tgz'),
145 '.zip': ('application/zip', 'zip'),
162 '.zip': ('application/zip', 'zip'),
146 }
163 }
147 if not archive_specs.has_key(fileformat):
164 if not archive_specs.has_key(fileformat):
148 return 'Unknown archive type %s' % fileformat
165 return 'Unknown archive type %s' % fileformat
149
166
150 def read_in_chunks(file_object, chunk_size=1024 * 40):
167 def read_in_chunks(file_object, chunk_size=1024 * 40):
151 """Lazy function (generator) to read a file piece by piece.
168 """Lazy function (generator) to read a file piece by piece.
152 Default chunk size: 40k."""
169 Default chunk size: 40k."""
153 while True:
170 while True:
154 data = file_object.read(chunk_size)
171 data = file_object.read(chunk_size)
155 if not data:
172 if not data:
156 break
173 break
157 yield data
174 yield data
158
175
159 archive = tempfile.TemporaryFile()
176 archive = tempfile.TemporaryFile()
160 repo = ScmModel().get_repo(repo_name).repo
177 repo = ScmModel().get_repo(repo_name).repo
161 fname = '%s-%s%s' % (repo_name, revision, fileformat)
178 fname = '%s-%s%s' % (repo_name, revision, fileformat)
162 archival.archive(repo, archive, revision, archive_specs[fileformat][1],
179 archival.archive(repo, archive, revision, archive_specs[fileformat][1],
163 prefix='%s-%s' % (repo_name, revision))
180 prefix='%s-%s' % (repo_name, revision))
164 response.content_type = archive_specs[fileformat][0]
181 response.content_type = archive_specs[fileformat][0]
165 response.content_disposition = 'attachment; filename=%s' % fname
182 response.content_disposition = 'attachment; filename=%s' % fname
166 archive.seek(0)
183 archive.seek(0)
167 return read_in_chunks(archive)
184 return read_in_chunks(archive)
168
185
169 def diff(self, repo_name, f_path):
186 def diff(self, repo_name, f_path):
170 hg_model = ScmModel()
187 hg_model = ScmModel()
171 diff1 = request.GET.get('diff1')
188 diff1 = request.GET.get('diff1')
172 diff2 = request.GET.get('diff2')
189 diff2 = request.GET.get('diff2')
173 c.action = request.GET.get('diff')
190 c.action = request.GET.get('diff')
174 c.no_changes = diff1 == diff2
191 c.no_changes = diff1 == diff2
175 c.f_path = f_path
192 c.f_path = f_path
176 c.repo = hg_model.get_repo(c.repo_name)
193 c.repo = hg_model.get_repo(c.repo_name)
177
194
178 try:
195 try:
179 if diff1 not in ['', None, 'None', '0' * 12, '0' * 40]:
196 if diff1 not in ['', None, 'None', '0' * 12, '0' * 40]:
180 c.changeset_1 = c.repo.get_changeset(diff1)
197 c.changeset_1 = c.repo.get_changeset(diff1)
181 node1 = c.changeset_1.get_node(f_path)
198 node1 = c.changeset_1.get_node(f_path)
182 else:
199 else:
183 c.changeset_1 = EmptyChangeset()
200 c.changeset_1 = EmptyChangeset()
184 node1 = FileNode('.', '', changeset=c.changeset_1)
201 node1 = FileNode('.', '', changeset=c.changeset_1)
185
202
186 if diff2 not in ['', None, 'None', '0' * 12, '0' * 40]:
203 if diff2 not in ['', None, 'None', '0' * 12, '0' * 40]:
187 c.changeset_2 = c.repo.get_changeset(diff2)
204 c.changeset_2 = c.repo.get_changeset(diff2)
188 node2 = c.changeset_2.get_node(f_path)
205 node2 = c.changeset_2.get_node(f_path)
189 else:
206 else:
190 c.changeset_2 = EmptyChangeset()
207 c.changeset_2 = EmptyChangeset()
191 node2 = FileNode('.', '', changeset=c.changeset_2)
208 node2 = FileNode('.', '', changeset=c.changeset_2)
192 except RepositoryError:
209 except RepositoryError:
193 return redirect(url('files_home',
210 return redirect(url('files_home',
194 repo_name=c.repo_name, f_path=f_path))
211 repo_name=c.repo_name, f_path=f_path))
195
212
196 f_udiff = differ.get_udiff(node1, node2)
213 f_udiff = differ.get_udiff(node1, node2)
197 diff = differ.DiffProcessor(f_udiff)
214 diff = differ.DiffProcessor(f_udiff)
198
215
199 if c.action == 'download':
216 if c.action == 'download':
200 diff_name = '%s_vs_%s.diff' % (diff1, diff2)
217 diff_name = '%s_vs_%s.diff' % (diff1, diff2)
201 response.content_type = 'text/plain'
218 response.content_type = 'text/plain'
202 response.content_disposition = 'attachment; filename=%s' \
219 response.content_disposition = 'attachment; filename=%s' \
203 % diff_name
220 % diff_name
221 if node1.is_binary or node2.is_binary:
222 return _('binary file changed')
204 return diff.raw_diff()
223 return diff.raw_diff()
205
224
206 elif c.action == 'raw':
225 elif c.action == 'raw':
207 response.content_type = 'text/plain'
226 response.content_type = 'text/plain'
227 if node1.is_binary or node2.is_binary:
228 return _('binary file changed')
208 return diff.raw_diff()
229 return diff.raw_diff()
209
230
210 elif c.action == 'diff':
231 elif c.action == 'diff':
211 if node1.size > self.cut_off_limit or node2.size > self.cut_off_limit:
232 if node1.size > self.cut_off_limit or node2.size > self.cut_off_limit:
212 c.cur_diff = _('Diff is to big to display')
233 c.cur_diff = _('Diff is to big to display')
234 elif node1.is_binary or node2.is_binary:
235 c.cur_diff = _('Binary file')
213 else:
236 else:
214 c.cur_diff = diff.as_html()
237 c.cur_diff = diff.as_html()
215 else:
238 else:
216 #default option
239 #default option
217 if node1.size > self.cut_off_limit or node2.size > self.cut_off_limit:
240 if node1.size > self.cut_off_limit or node2.size > self.cut_off_limit:
218 c.cur_diff = _('Diff is to big to display')
241 c.cur_diff = _('Diff is to big to display')
242 elif node1.is_binary or node2.is_binary:
243 c.cur_diff = _('Binary file')
219 else:
244 else:
220 c.cur_diff = diff.as_html()
245 c.cur_diff = diff.as_html()
221
246
222 if not c.cur_diff: c.no_changes = True
247 if not c.cur_diff:
248 c.no_changes = True
223 return render('files/file_diff.html')
249 return render('files/file_diff.html')
224
250
225 def _get_history(self, repo, node, f_path):
251 def _get_history(self, repo, node, f_path):
226 from vcs.nodes import NodeKind
252 from vcs.nodes import NodeKind
227 if not node.kind is NodeKind.FILE:
253 if not node.kind is NodeKind.FILE:
228 return []
254 return []
229 changesets = node.history
255 changesets = node.history
230 hist_l = []
256 hist_l = []
231
257
232 changesets_group = ([], _("Changesets"))
258 changesets_group = ([], _("Changesets"))
233 branches_group = ([], _("Branches"))
259 branches_group = ([], _("Branches"))
234 tags_group = ([], _("Tags"))
260 tags_group = ([], _("Tags"))
235
261
236 for chs in changesets:
262 for chs in changesets:
237 n_desc = 'r%s:%s' % (chs.revision, chs.short_id)
263 n_desc = 'r%s:%s' % (chs.revision, chs.short_id)
238 changesets_group[0].append((chs.raw_id, n_desc,))
264 changesets_group[0].append((chs.raw_id, n_desc,))
239
265
240 hist_l.append(changesets_group)
266 hist_l.append(changesets_group)
241
267
242 for name, chs in c.repository_branches.items():
268 for name, chs in c.repository_branches.items():
243 #chs = chs.split(':')[-1]
269 #chs = chs.split(':')[-1]
244 branches_group[0].append((chs, name),)
270 branches_group[0].append((chs, name),)
245 hist_l.append(branches_group)
271 hist_l.append(branches_group)
246
272
247 for name, chs in c.repository_tags.items():
273 for name, chs in c.repository_tags.items():
248 #chs = chs.split(':')[-1]
274 #chs = chs.split(':')[-1]
249 tags_group[0].append((chs, name),)
275 tags_group[0].append((chs, name),)
250 hist_l.append(tags_group)
276 hist_l.append(tags_group)
251
277
252 return hist_l
278 return hist_l
253
254 # [
255 # ([("u1", "User1"), ("u2", "User2")], "Users"),
256 # ([("g1", "Group1"), ("g2", "Group2")], "Groups")
257 # ]
258
@@ -1,66 +1,66 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.controllers.home
3 rhodecode.controllers.home
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 Home controller for Rhodecode
6 Home controller for Rhodecode
7
7
8 :created_on: Feb 18, 2010
8 :created_on: Feb 18, 2010
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2009-2010 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
13 # This program is free software; you can redistribute it and/or
14 # modify it under the terms of the GNU General Public License
14 # modify it under the terms of the GNU General Public License
15 # as published by the Free Software Foundation; version 2
15 # as published by the Free Software Foundation; version 2
16 # of the License or (at your opinion) any later version of the license.
16 # of the License or (at your opinion) any later version of the license.
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, write to the Free Software
24 # along with this program; if not, write to the Free Software
25 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
25 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
26 # MA 02110-1301, USA.
26 # MA 02110-1301, USA.
27
27
28 import logging
28 import logging
29 from operator import itemgetter
29 from operator import itemgetter
30
30
31 from pylons import tmpl_context as c, request
31 from pylons import tmpl_context as c, request
32
32
33 from rhodecode.lib.auth import LoginRequired
33 from rhodecode.lib.auth import LoginRequired
34 from rhodecode.lib.base import BaseController, render
34 from rhodecode.lib.base import BaseController, render
35 from rhodecode.model.scm import ScmModel
35 from rhodecode.model.scm import ScmModel
36
36
37 log = logging.getLogger(__name__)
37 log = logging.getLogger(__name__)
38
38
39 class HomeController(BaseController):
39 class HomeController(BaseController):
40
40
41 @LoginRequired()
41 @LoginRequired()
42 def __before__(self):
42 def __before__(self):
43 super(HomeController, self).__before__()
43 super(HomeController, self).__before__()
44
44
45 def index(self):
45 def index(self):
46 sortables = ['name', 'description', 'last_change', 'tip', 'contact']
46 sortables = ['name', 'description', 'last_change', 'tip', 'owner']
47 current_sort = request.GET.get('sort', 'name')
47 current_sort = request.GET.get('sort', 'name')
48 current_sort_slug = current_sort.replace('-', '')
48 current_sort_slug = current_sort.replace('-', '')
49
49
50 if current_sort_slug not in sortables:
50 if current_sort_slug not in sortables:
51 c.sort_by = 'name'
51 c.sort_by = 'name'
52 current_sort_slug = c.sort_by
52 current_sort_slug = c.sort_by
53 else:
53 else:
54 c.sort_by = current_sort
54 c.sort_by = current_sort
55 c.sort_slug = current_sort_slug
55 c.sort_slug = current_sort_slug
56 cached_repo_list = ScmModel().get_repos()
56 cached_repo_list = ScmModel().get_repos()
57
57
58 sort_key = current_sort_slug + '_sort'
58 sort_key = current_sort_slug + '_sort'
59 if c.sort_by.startswith('-'):
59 if c.sort_by.startswith('-'):
60 c.repos_list = sorted(cached_repo_list, key=itemgetter(sort_key),
60 c.repos_list = sorted(cached_repo_list, key=itemgetter(sort_key),
61 reverse=True)
61 reverse=True)
62 else:
62 else:
63 c.repos_list = sorted(cached_repo_list, key=itemgetter(sort_key),
63 c.repos_list = sorted(cached_repo_list, key=itemgetter(sort_key),
64 reverse=False)
64 reverse=False)
65
65
66 return render('/index.html')
66 return render('/index.html')
@@ -1,97 +1,100 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.controllers.journal
3 rhodecode.controllers.journal
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 Journal controller for pylons
6 Journal controller for pylons
7
7
8 :created_on: Nov 21, 2010
8 :created_on: Nov 21, 2010
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2009-2010 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
13 # This program is free software; you can redistribute it and/or
14 # modify it under the terms of the GNU General Public License
14 # modify it under the terms of the GNU General Public License
15 # as published by the Free Software Foundation; version 2
15 # as published by the Free Software Foundation; version 2
16 # of the License or (at your opinion) any later version of the license.
16 # of the License or (at your opinion) any later version of the license.
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, write to the Free Software
24 # along with this program; if not, write to the Free Software
25 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
25 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
26 # MA 02110-1301, USA.
26 # MA 02110-1301, USA.
27
27
28 import logging
28 import logging
29 from sqlalchemy import or_
29 import traceback
30
30
31 from pylons import request, response, session, tmpl_context as c, url
31 from pylons import request, response, session, tmpl_context as c, url
32 from paste.httpexceptions import HTTPInternalServerError, HTTPBadRequest
33
34 from sqlalchemy import or_
32
35
33 from rhodecode.lib.auth import LoginRequired, NotAnonymous
36 from rhodecode.lib.auth import LoginRequired, NotAnonymous
34 from rhodecode.lib.base import BaseController, render
37 from rhodecode.lib.base import BaseController, render
35 from rhodecode.lib.helpers import get_token
38 from rhodecode.lib.helpers import get_token
36 from rhodecode.model.db import UserLog, UserFollowing
39 from rhodecode.model.db import UserLog, UserFollowing
37 from rhodecode.model.scm import ScmModel
40 from rhodecode.model.scm import ScmModel
38
41
39 from paste.httpexceptions import HTTPInternalServerError
40
41 log = logging.getLogger(__name__)
42 log = logging.getLogger(__name__)
42
43
43 class JournalController(BaseController):
44 class JournalController(BaseController):
44
45
45
46
46 @LoginRequired()
47 @LoginRequired()
47 @NotAnonymous()
48 @NotAnonymous()
48 def __before__(self):
49 def __before__(self):
49 super(JournalController, self).__before__()
50 super(JournalController, self).__before__()
50
51
51 def index(self):
52 def index(self):
52 # Return a rendered template
53 # Return a rendered template
53
54
54 c.following = self.sa.query(UserFollowing)\
55 c.following = self.sa.query(UserFollowing)\
55 .filter(UserFollowing.user_id == c.rhodecode_user.user_id).all()
56 .filter(UserFollowing.user_id == c.rhodecode_user.user_id).all()
56
57
57 repo_ids = [x.follows_repository.repo_id for x in c.following
58 repo_ids = [x.follows_repository.repo_id for x in c.following
58 if x.follows_repository is not None]
59 if x.follows_repository is not None]
59 user_ids = [x.follows_user.user_id for x in c.following
60 user_ids = [x.follows_user.user_id for x in c.following
60 if x.follows_user is not None]
61 if x.follows_user is not None]
61
62
62 c.journal = self.sa.query(UserLog)\
63 c.journal = self.sa.query(UserLog)\
63 .filter(or_(
64 .filter(or_(
64 UserLog.repository_id.in_(repo_ids),
65 UserLog.repository_id.in_(repo_ids),
65 UserLog.user_id.in_(user_ids),
66 UserLog.user_id.in_(user_ids),
66 ))\
67 ))\
67 .order_by(UserLog.action_date.desc())\
68 .order_by(UserLog.action_date.desc())\
68 .limit(20)\
69 .limit(20)\
69 .all()
70 .all()
70 return render('/journal.html')
71 return render('/journal.html')
71
72
72 def toggle_following(self):
73 def toggle_following(self):
73
74
74 if request.POST.get('auth_token') == get_token():
75 if request.POST.get('auth_token') == get_token():
75 scm_model = ScmModel()
76 scm_model = ScmModel()
76
77
77 user_id = request.POST.get('follows_user_id')
78 user_id = request.POST.get('follows_user_id')
78 if user_id:
79 if user_id:
79 try:
80 try:
80 scm_model.toggle_following_user(user_id,
81 scm_model.toggle_following_user(user_id,
81 c.rhodecode_user.user_id)
82 c.rhodecode_user.user_id)
82 return 'ok'
83 return 'ok'
83 except:
84 except:
85 log.error(traceback.format_exc())
84 raise HTTPInternalServerError()
86 raise HTTPInternalServerError()
85
87
86 repo_id = request.POST.get('follows_repo_id')
88 repo_id = request.POST.get('follows_repo_id')
87 if repo_id:
89 if repo_id:
88 try:
90 try:
89 scm_model.toggle_following_repo(repo_id,
91 scm_model.toggle_following_repo(repo_id,
90 c.rhodecode_user.user_id)
92 c.rhodecode_user.user_id)
91 return 'ok'
93 return 'ok'
92 except:
94 except:
95 log.error(traceback.format_exc())
93 raise HTTPInternalServerError()
96 raise HTTPInternalServerError()
94
97
95
98
96
99
97 raise HTTPInternalServerError()
100 raise HTTPBadRequest()
@@ -1,29 +1,46 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) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2009-2010 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
13 # This program is free software; you can redistribute it and/or
14 # modify it under the terms of the GNU General Public License
14 # modify it under the terms of the GNU General Public License
15 # as published by the Free Software Foundation; version 2
15 # as published by the Free Software Foundation; version 2
16 # of the License or (at your opinion) any later version of the license.
16 # of the License or (at your opinion) any later version of the license.
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, write to the Free Software
24 # along with this program; if not, write to the Free Software
25 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
25 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
26 # MA 02110-1301, USA.
26 # MA 02110-1301, USA.
27
27
28 def str2bool(v):
28 def str2bool(v):
29 return v.lower() in ["yes", "true", "t", "1"] if v else None
29 if isinstance(v, (str, unicode)):
30 obj = v.strip().lower()
31 if obj in ['true', 'yes', 'on', 'y', 't', '1']:
32 return True
33 elif obj in ['false', 'no', 'off', 'n', 'f', '0']:
34 return False
35 else:
36 raise ValueError("String is not true/false: %r" % obj)
37 return bool(obj)
38
39 def generate_api_key(username, salt=None):
40 from tempfile import _RandomNameSequence
41 import hashlib
42
43 if salt is None:
44 salt = _RandomNameSequence().next()
45
46 return hashlib.sha1(username + salt).hexdigest()
@@ -1,569 +1,614 b''
1 #!/usr/bin/env python
1 # -*- coding: utf-8 -*-
2 # encoding: utf-8
2 """
3 # authentication and permission libraries
3 rhodecode.lib.auth
4 # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
4 ~~~~~~~~~~~~~~~~~~
5 #
5
6 authentication and permission libraries
7
8 :created_on: Apr 4, 2010
9 :copyright: (c) 2010 by marcink.
10 :license: LICENSE_NAME, see LICENSE_FILE for more details.
11 """
6 # This program is free software; you can redistribute it and/or
12 # This program is free software; you can redistribute it and/or
7 # modify it under the terms of the GNU General Public License
13 # modify it under the terms of the GNU General Public License
8 # as published by the Free Software Foundation; version 2
14 # as published by the Free Software Foundation; version 2
9 # of the License or (at your opinion) any later version of the license.
15 # of the License or (at your opinion) any later version of the license.
10 #
16 #
11 # This program is distributed in the hope that it will be useful,
17 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
18 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
20 # GNU General Public License for more details.
15 #
21 #
16 # You should have received a copy of the GNU General Public License
22 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
23 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
24 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19 # MA 02110-1301, USA.
25 # MA 02110-1301, USA.
20 """
21 Created on April 4, 2010
22
26
23 @author: marcink
27 import random
24 """
28 import logging
29 import traceback
30
31 from decorator import decorator
32
25 from pylons import config, session, url, request
33 from pylons import config, session, url, request
26 from pylons.controllers.util import abort, redirect
34 from pylons.controllers.util import abort, redirect
27 from rhodecode.lib.exceptions import *
35 from pylons.i18n.translation import _
36
37 from rhodecode import __platform__
38
39 if __platform__ == 'Windows':
40 from hashlib import sha256
41 if __platform__ in ('Linux', 'Darwin'):
42 import bcrypt
43
44 from rhodecode.lib import str2bool
45 from rhodecode.lib.exceptions import LdapPasswordError, LdapUsernameError
28 from rhodecode.lib.utils import get_repo_slug
46 from rhodecode.lib.utils import get_repo_slug
29 from rhodecode.lib.auth_ldap import AuthLdap
47 from rhodecode.lib.auth_ldap import AuthLdap
48
30 from rhodecode.model import meta
49 from rhodecode.model import meta
31 from rhodecode.model.user import UserModel
50 from rhodecode.model.user import UserModel
32 from rhodecode.model.caching_query import FromCache
51 from rhodecode.model.db import Permission, RepoToPerm, Repository, \
33 from rhodecode.model.db import User, RepoToPerm, Repository, Permission, \
52 User, UserToPerm
34 UserToPerm
53
35 import bcrypt
36 from decorator import decorator
37 import logging
38 import random
39 import traceback
40
54
41 log = logging.getLogger(__name__)
55 log = logging.getLogger(__name__)
42
56
43 class PasswordGenerator(object):
57 class PasswordGenerator(object):
44 """This is a simple class for generating password from
58 """This is a simple class for generating password from
45 different sets of characters
59 different sets of characters
46 usage:
60 usage:
47 passwd_gen = PasswordGenerator()
61 passwd_gen = PasswordGenerator()
48 #print 8-letter password containing only big and small letters of alphabet
62 #print 8-letter password containing only big and small letters of alphabet
49 print passwd_gen.gen_password(8, passwd_gen.ALPHABETS_BIG_SMALL)
63 print passwd_gen.gen_password(8, passwd_gen.ALPHABETS_BIG_SMALL)
50 """
64 """
51 ALPHABETS_NUM = r'''1234567890'''#[0]
65 ALPHABETS_NUM = r'''1234567890'''#[0]
52 ALPHABETS_SMALL = r'''qwertyuiopasdfghjklzxcvbnm'''#[1]
66 ALPHABETS_SMALL = r'''qwertyuiopasdfghjklzxcvbnm'''#[1]
53 ALPHABETS_BIG = r'''QWERTYUIOPASDFGHJKLZXCVBNM'''#[2]
67 ALPHABETS_BIG = r'''QWERTYUIOPASDFGHJKLZXCVBNM'''#[2]
54 ALPHABETS_SPECIAL = r'''`-=[]\;',./~!@#$%^&*()_+{}|:"<>?''' #[3]
68 ALPHABETS_SPECIAL = r'''`-=[]\;',./~!@#$%^&*()_+{}|:"<>?''' #[3]
55 ALPHABETS_FULL = ALPHABETS_BIG + ALPHABETS_SMALL + ALPHABETS_NUM + ALPHABETS_SPECIAL#[4]
69 ALPHABETS_FULL = ALPHABETS_BIG + ALPHABETS_SMALL + ALPHABETS_NUM + ALPHABETS_SPECIAL#[4]
56 ALPHABETS_ALPHANUM = ALPHABETS_BIG + ALPHABETS_SMALL + ALPHABETS_NUM#[5]
70 ALPHABETS_ALPHANUM = ALPHABETS_BIG + ALPHABETS_SMALL + ALPHABETS_NUM#[5]
57 ALPHABETS_BIG_SMALL = ALPHABETS_BIG + ALPHABETS_SMALL
71 ALPHABETS_BIG_SMALL = ALPHABETS_BIG + ALPHABETS_SMALL
58 ALPHABETS_ALPHANUM_BIG = ALPHABETS_BIG + ALPHABETS_NUM#[6]
72 ALPHABETS_ALPHANUM_BIG = ALPHABETS_BIG + ALPHABETS_NUM#[6]
59 ALPHABETS_ALPHANUM_SMALL = ALPHABETS_SMALL + ALPHABETS_NUM#[7]
73 ALPHABETS_ALPHANUM_SMALL = ALPHABETS_SMALL + ALPHABETS_NUM#[7]
60
74
61 def __init__(self, passwd=''):
75 def __init__(self, passwd=''):
62 self.passwd = passwd
76 self.passwd = passwd
63
77
64 def gen_password(self, len, type):
78 def gen_password(self, len, type):
65 self.passwd = ''.join([random.choice(type) for _ in xrange(len)])
79 self.passwd = ''.join([random.choice(type) for _ in xrange(len)])
66 return self.passwd
80 return self.passwd
67
81
82 class RhodeCodeCrypto(object):
68
83
69 def get_crypt_password(password):
84 @classmethod
70 """Cryptographic function used for password hashing based on sha1
85 def hash_string(cls, str_):
86 """
87 Cryptographic function used for password hashing based on pybcrypt
88 or pycrypto in windows
89
71 :param password: password to hash
90 :param password: password to hash
72 """
91 """
73 return bcrypt.hashpw(password, bcrypt.gensalt(10))
92 if __platform__ == 'Windows':
93 return sha256(str_).hexdigest()
94 elif __platform__ in ('Linux', 'Darwin'):
95 return bcrypt.hashpw(str_, bcrypt.gensalt(10))
96 else:
97 raise Exception('Unknown or unsupported platform %s' % __platform__)
98
99 @classmethod
100 def hash_check(cls, password, hashed):
101 """
102 Checks matching password with it's hashed value, runs different
103 implementation based on platform it runs on
104
105 :param password: password
106 :param hashed: password in hashed form
107 """
108
109 if __platform__ == 'Windows':
110 return sha256(password).hexdigest() == hashed
111 elif __platform__ in ('Linux', 'Darwin'):
112 return bcrypt.hashpw(password, hashed) == hashed
113 else:
114 raise Exception('Unknown or unsupported platform %s' % __platform__)
115
116
117 def get_crypt_password(password):
118 return RhodeCodeCrypto.hash_string(password)
74
119
75 def check_password(password, hashed):
120 def check_password(password, hashed):
76 return bcrypt.hashpw(password, hashed) == hashed
121 return RhodeCodeCrypto.hash_check(password, hashed)
77
122
78 def authfunc(environ, username, password):
123 def authfunc(environ, username, password):
79 """
124 """
80 Dummy authentication function used in Mercurial/Git/ and access control,
125 Dummy authentication function used in Mercurial/Git/ and access control,
81
126
82 :param environ: needed only for using in Basic auth
127 :param environ: needed only for using in Basic auth
83 """
128 """
84 return authenticate(username, password)
129 return authenticate(username, password)
85
130
86
131
87 def authenticate(username, password):
132 def authenticate(username, password):
88 """
133 """
89 Authentication function used for access control,
134 Authentication function used for access control,
90 firstly checks for db authentication then if ldap is enabled for ldap
135 firstly checks for db authentication then if ldap is enabled for ldap
91 authentication, also creates ldap user if not in database
136 authentication, also creates ldap user if not in database
92
137
93 :param username: username
138 :param username: username
94 :param password: password
139 :param password: password
95 """
140 """
96 user_model = UserModel()
141 user_model = UserModel()
97 user = user_model.get_by_username(username, cache=False)
142 user = user_model.get_by_username(username, cache=False)
98
143
99 log.debug('Authenticating user using RhodeCode account')
144 log.debug('Authenticating user using RhodeCode account')
100 if user is not None and user.is_ldap is False:
145 if user is not None and user.is_ldap is False:
101 if user.active:
146 if user.active:
102
147
103 if user.username == 'default' and user.active:
148 if user.username == 'default' and user.active:
104 log.info('user %s authenticated correctly as anonymous user',
149 log.info('user %s authenticated correctly as anonymous user',
105 username)
150 username)
106 return True
151 return True
107
152
108 elif user.username == username and check_password(password, user.password):
153 elif user.username == username and check_password(password, user.password):
109 log.info('user %s authenticated correctly', username)
154 log.info('user %s authenticated correctly', username)
110 return True
155 return True
111 else:
156 else:
112 log.warning('user %s is disabled', username)
157 log.warning('user %s is disabled', username)
113
158
114 else:
159 else:
115 log.debug('Regular authentication failed')
160 log.debug('Regular authentication failed')
116 user_obj = user_model.get_by_username(username, cache=False,
161 user_obj = user_model.get_by_username(username, cache=False,
117 case_insensitive=True)
162 case_insensitive=True)
118
163
119 if user_obj is not None and user_obj.is_ldap is False:
164 if user_obj is not None and user_obj.is_ldap is False:
120 log.debug('this user already exists as non ldap')
165 log.debug('this user already exists as non ldap')
121 return False
166 return False
122
167
123 from rhodecode.model.settings import SettingsModel
168 from rhodecode.model.settings import SettingsModel
124 ldap_settings = SettingsModel().get_ldap_settings()
169 ldap_settings = SettingsModel().get_ldap_settings()
125
170
126 #======================================================================
171 #======================================================================
127 # FALLBACK TO LDAP AUTH IN ENABLE
172 # FALLBACK TO LDAP AUTH IN ENABLE
128 #======================================================================
173 #======================================================================
129 if ldap_settings.get('ldap_active', False):
174 if str2bool(ldap_settings.get('ldap_active')):
130 log.debug("Authenticating user using ldap")
175 log.debug("Authenticating user using ldap")
131 kwargs = {
176 kwargs = {
132 'server':ldap_settings.get('ldap_host', ''),
177 'server':ldap_settings.get('ldap_host', ''),
133 'base_dn':ldap_settings.get('ldap_base_dn', ''),
178 'base_dn':ldap_settings.get('ldap_base_dn', ''),
134 'port':ldap_settings.get('ldap_port'),
179 'port':ldap_settings.get('ldap_port'),
135 'bind_dn':ldap_settings.get('ldap_dn_user'),
180 'bind_dn':ldap_settings.get('ldap_dn_user'),
136 'bind_pass':ldap_settings.get('ldap_dn_pass'),
181 'bind_pass':ldap_settings.get('ldap_dn_pass'),
137 'use_ldaps':ldap_settings.get('ldap_ldaps'),
182 'use_ldaps':str2bool(ldap_settings.get('ldap_ldaps')),
138 'ldap_version':3,
183 'ldap_version':3,
139 }
184 }
140 log.debug('Checking for ldap authentication')
185 log.debug('Checking for ldap authentication')
141 try:
186 try:
142 aldap = AuthLdap(**kwargs)
187 aldap = AuthLdap(**kwargs)
143 res = aldap.authenticate_ldap(username, password)
188 res = aldap.authenticate_ldap(username, password)
144 log.debug('Got ldap response %s', res)
189 log.debug('Got ldap response %s', res)
145
190
146 if user_model.create_ldap(username, password):
191 if user_model.create_ldap(username, password):
147 log.info('created new ldap user')
192 log.info('created new ldap user')
148
193
149 return True
194 return True
150 except (LdapUsernameError, LdapPasswordError,):
195 except (LdapUsernameError, LdapPasswordError,):
151 pass
196 pass
152 except (Exception,):
197 except (Exception,):
153 log.error(traceback.format_exc())
198 log.error(traceback.format_exc())
154 pass
199 pass
155 return False
200 return False
156
201
157 class AuthUser(object):
202 class AuthUser(object):
158 """
203 """
159 A simple object that handles a mercurial username for authentication
204 A simple object that handles a mercurial username for authentication
160 """
205 """
161 def __init__(self):
206 def __init__(self):
162 self.username = 'None'
207 self.username = 'None'
163 self.name = ''
208 self.name = ''
164 self.lastname = ''
209 self.lastname = ''
165 self.email = ''
210 self.email = ''
166 self.user_id = None
211 self.user_id = None
167 self.is_authenticated = False
212 self.is_authenticated = False
168 self.is_admin = False
213 self.is_admin = False
169 self.permissions = {}
214 self.permissions = {}
170
215
171 def __repr__(self):
216 def __repr__(self):
172 return "<AuthUser('id:%s:%s')>" % (self.user_id, self.username)
217 return "<AuthUser('id:%s:%s')>" % (self.user_id, self.username)
173
218
174 def set_available_permissions(config):
219 def set_available_permissions(config):
175 """
220 """
176 This function will propagate pylons globals with all available defined
221 This function will propagate pylons globals with all available defined
177 permission given in db. We don't wannt to check each time from db for new
222 permission given in db. We don't wannt to check each time from db for new
178 permissions since adding a new permission also requires application restart
223 permissions since adding a new permission also requires application restart
179 ie. to decorate new views with the newly created permission
224 ie. to decorate new views with the newly created permission
180 :param config:
225 :param config:
181 """
226 """
182 log.info('getting information about all available permissions')
227 log.info('getting information about all available permissions')
183 try:
228 try:
184 sa = meta.Session()
229 sa = meta.Session()
185 all_perms = sa.query(Permission).all()
230 all_perms = sa.query(Permission).all()
186 except:
231 except:
187 pass
232 pass
188 finally:
233 finally:
189 meta.Session.remove()
234 meta.Session.remove()
190
235
191 config['available_permissions'] = [x.permission_name for x in all_perms]
236 config['available_permissions'] = [x.permission_name for x in all_perms]
192
237
193 def set_base_path(config):
238 def set_base_path(config):
194 config['base_path'] = config['pylons.app_globals'].base_path
239 config['base_path'] = config['pylons.app_globals'].base_path
195
240
196
241
197 def fill_perms(user):
242 def fill_perms(user):
198 """
243 """
199 Fills user permission attribute with permissions taken from database
244 Fills user permission attribute with permissions taken from database
200 :param user:
245 :param user:
201 """
246 """
202
247
203 sa = meta.Session()
248 sa = meta.Session()
204 user.permissions['repositories'] = {}
249 user.permissions['repositories'] = {}
205 user.permissions['global'] = set()
250 user.permissions['global'] = set()
206
251
207 #===========================================================================
252 #===========================================================================
208 # fetch default permissions
253 # fetch default permissions
209 #===========================================================================
254 #===========================================================================
210 default_user = UserModel().get_by_username('default', cache=True)
255 default_user = UserModel().get_by_username('default', cache=True)
211
256
212 default_perms = sa.query(RepoToPerm, Repository, Permission)\
257 default_perms = sa.query(RepoToPerm, Repository, Permission)\
213 .join((Repository, RepoToPerm.repository_id == Repository.repo_id))\
258 .join((Repository, RepoToPerm.repository_id == Repository.repo_id))\
214 .join((Permission, RepoToPerm.permission_id == Permission.permission_id))\
259 .join((Permission, RepoToPerm.permission_id == Permission.permission_id))\
215 .filter(RepoToPerm.user == default_user).all()
260 .filter(RepoToPerm.user == default_user).all()
216
261
217 if user.is_admin:
262 if user.is_admin:
218 #=======================================================================
263 #=======================================================================
219 # #admin have all default rights set to admin
264 # #admin have all default rights set to admin
220 #=======================================================================
265 #=======================================================================
221 user.permissions['global'].add('hg.admin')
266 user.permissions['global'].add('hg.admin')
222
267
223 for perm in default_perms:
268 for perm in default_perms:
224 p = 'repository.admin'
269 p = 'repository.admin'
225 user.permissions['repositories'][perm.RepoToPerm.repository.repo_name] = p
270 user.permissions['repositories'][perm.RepoToPerm.repository.repo_name] = p
226
271
227 else:
272 else:
228 #=======================================================================
273 #=======================================================================
229 # set default permissions
274 # set default permissions
230 #=======================================================================
275 #=======================================================================
231
276
232 #default global
277 #default global
233 default_global_perms = sa.query(UserToPerm)\
278 default_global_perms = sa.query(UserToPerm)\
234 .filter(UserToPerm.user == sa.query(User)\
279 .filter(UserToPerm.user == sa.query(User)\
235 .filter(User.username == 'default').one())
280 .filter(User.username == 'default').one())
236
281
237 for perm in default_global_perms:
282 for perm in default_global_perms:
238 user.permissions['global'].add(perm.permission.permission_name)
283 user.permissions['global'].add(perm.permission.permission_name)
239
284
240 #default repositories
285 #default repositories
241 for perm in default_perms:
286 for perm in default_perms:
242 if perm.Repository.private and not perm.Repository.user_id == user.user_id:
287 if perm.Repository.private and not perm.Repository.user_id == user.user_id:
243 #disable defaults for private repos,
288 #disable defaults for private repos,
244 p = 'repository.none'
289 p = 'repository.none'
245 elif perm.Repository.user_id == user.user_id:
290 elif perm.Repository.user_id == user.user_id:
246 #set admin if owner
291 #set admin if owner
247 p = 'repository.admin'
292 p = 'repository.admin'
248 else:
293 else:
249 p = perm.Permission.permission_name
294 p = perm.Permission.permission_name
250
295
251 user.permissions['repositories'][perm.RepoToPerm.repository.repo_name] = p
296 user.permissions['repositories'][perm.RepoToPerm.repository.repo_name] = p
252
297
253 #=======================================================================
298 #=======================================================================
254 # #overwrite default with user permissions if any
299 # #overwrite default with user permissions if any
255 #=======================================================================
300 #=======================================================================
256 user_perms = sa.query(RepoToPerm, Permission, Repository)\
301 user_perms = sa.query(RepoToPerm, Permission, Repository)\
257 .join((Repository, RepoToPerm.repository_id == Repository.repo_id))\
302 .join((Repository, RepoToPerm.repository_id == Repository.repo_id))\
258 .join((Permission, RepoToPerm.permission_id == Permission.permission_id))\
303 .join((Permission, RepoToPerm.permission_id == Permission.permission_id))\
259 .filter(RepoToPerm.user_id == user.user_id).all()
304 .filter(RepoToPerm.user_id == user.user_id).all()
260
305
261 for perm in user_perms:
306 for perm in user_perms:
262 if perm.Repository.user_id == user.user_id:#set admin if owner
307 if perm.Repository.user_id == user.user_id:#set admin if owner
263 p = 'repository.admin'
308 p = 'repository.admin'
264 else:
309 else:
265 p = perm.Permission.permission_name
310 p = perm.Permission.permission_name
266 user.permissions['repositories'][perm.RepoToPerm.repository.repo_name] = p
311 user.permissions['repositories'][perm.RepoToPerm.repository.repo_name] = p
267 meta.Session.remove()
312 meta.Session.remove()
268 return user
313 return user
269
314
270 def get_user(session):
315 def get_user(session):
271 """
316 """
272 Gets user from session, and wraps permissions into user
317 Gets user from session, and wraps permissions into user
273 :param session:
318 :param session:
274 """
319 """
275 user = session.get('rhodecode_user', AuthUser())
320 user = session.get('rhodecode_user', AuthUser())
276 #if the user is not logged in we check for anonymous access
321 #if the user is not logged in we check for anonymous access
277 #if user is logged and it's a default user check if we still have anonymous
322 #if user is logged and it's a default user check if we still have anonymous
278 #access enabled
323 #access enabled
279 if user.user_id is None or user.username == 'default':
324 if user.user_id is None or user.username == 'default':
280 anonymous_user = UserModel().get_by_username('default', cache=True)
325 anonymous_user = UserModel().get_by_username('default', cache=True)
281 if anonymous_user.active is True:
326 if anonymous_user.active is True:
282 #then we set this user is logged in
327 #then we set this user is logged in
283 user.is_authenticated = True
328 user.is_authenticated = True
284 user.user_id = anonymous_user.user_id
329 user.user_id = anonymous_user.user_id
285 else:
330 else:
286 user.is_authenticated = False
331 user.is_authenticated = False
287
332
288 if user.is_authenticated:
333 if user.is_authenticated:
289 user = UserModel().fill_data(user)
334 user = UserModel().fill_data(user)
290
335
291 user = fill_perms(user)
336 user = fill_perms(user)
292 session['rhodecode_user'] = user
337 session['rhodecode_user'] = user
293 session.save()
338 session.save()
294 return user
339 return user
295
340
296 #===============================================================================
341 #===============================================================================
297 # CHECK DECORATORS
342 # CHECK DECORATORS
298 #===============================================================================
343 #===============================================================================
299 class LoginRequired(object):
344 class LoginRequired(object):
300 """Must be logged in to execute this function else
345 """Must be logged in to execute this function else
301 redirect to login page"""
346 redirect to login page"""
302
347
303 def __call__(self, func):
348 def __call__(self, func):
304 return decorator(self.__wrapper, func)
349 return decorator(self.__wrapper, func)
305
350
306 def __wrapper(self, func, *fargs, **fkwargs):
351 def __wrapper(self, func, *fargs, **fkwargs):
307 user = session.get('rhodecode_user', AuthUser())
352 user = session.get('rhodecode_user', AuthUser())
308 log.debug('Checking login required for user:%s', user.username)
353 log.debug('Checking login required for user:%s', user.username)
309 if user.is_authenticated:
354 if user.is_authenticated:
310 log.debug('user %s is authenticated', user.username)
355 log.debug('user %s is authenticated', user.username)
311 return func(*fargs, **fkwargs)
356 return func(*fargs, **fkwargs)
312 else:
357 else:
313 log.warn('user %s not authenticated', user.username)
358 log.warn('user %s not authenticated', user.username)
314
359
315 p = ''
360 p = ''
316 if request.environ.get('SCRIPT_NAME') != '/':
361 if request.environ.get('SCRIPT_NAME') != '/':
317 p += request.environ.get('SCRIPT_NAME')
362 p += request.environ.get('SCRIPT_NAME')
318
363
319 p += request.environ.get('PATH_INFO')
364 p += request.environ.get('PATH_INFO')
320 if request.environ.get('QUERY_STRING'):
365 if request.environ.get('QUERY_STRING'):
321 p += '?' + request.environ.get('QUERY_STRING')
366 p += '?' + request.environ.get('QUERY_STRING')
322
367
323 log.debug('redirecting to login page with %s', p)
368 log.debug('redirecting to login page with %s', p)
324 return redirect(url('login_home', came_from=p))
369 return redirect(url('login_home', came_from=p))
325
370
326 class NotAnonymous(object):
371 class NotAnonymous(object):
327 """Must be logged in to execute this function else
372 """Must be logged in to execute this function else
328 redirect to login page"""
373 redirect to login page"""
329
374
330 def __call__(self, func):
375 def __call__(self, func):
331 return decorator(self.__wrapper, func)
376 return decorator(self.__wrapper, func)
332
377
333 def __wrapper(self, func, *fargs, **fkwargs):
378 def __wrapper(self, func, *fargs, **fkwargs):
334 user = session.get('rhodecode_user', AuthUser())
379 user = session.get('rhodecode_user', AuthUser())
335 log.debug('Checking if user is not anonymous')
380 log.debug('Checking if user is not anonymous')
336
381
337 anonymous = user.username == 'default'
382 anonymous = user.username == 'default'
338
383
339 if anonymous:
384 if anonymous:
340 p = ''
385 p = ''
341 if request.environ.get('SCRIPT_NAME') != '/':
386 if request.environ.get('SCRIPT_NAME') != '/':
342 p += request.environ.get('SCRIPT_NAME')
387 p += request.environ.get('SCRIPT_NAME')
343
388
344 p += request.environ.get('PATH_INFO')
389 p += request.environ.get('PATH_INFO')
345 if request.environ.get('QUERY_STRING'):
390 if request.environ.get('QUERY_STRING'):
346 p += '?' + request.environ.get('QUERY_STRING')
391 p += '?' + request.environ.get('QUERY_STRING')
347 return redirect(url('login_home', came_from=p))
392 return redirect(url('login_home', came_from=p))
348 else:
393 else:
349 return func(*fargs, **fkwargs)
394 return func(*fargs, **fkwargs)
350
395
351 class PermsDecorator(object):
396 class PermsDecorator(object):
352 """Base class for decorators"""
397 """Base class for decorators"""
353
398
354 def __init__(self, *required_perms):
399 def __init__(self, *required_perms):
355 available_perms = config['available_permissions']
400 available_perms = config['available_permissions']
356 for perm in required_perms:
401 for perm in required_perms:
357 if perm not in available_perms:
402 if perm not in available_perms:
358 raise Exception("'%s' permission is not defined" % perm)
403 raise Exception("'%s' permission is not defined" % perm)
359 self.required_perms = set(required_perms)
404 self.required_perms = set(required_perms)
360 self.user_perms = None
405 self.user_perms = None
361
406
362 def __call__(self, func):
407 def __call__(self, func):
363 return decorator(self.__wrapper, func)
408 return decorator(self.__wrapper, func)
364
409
365
410
366 def __wrapper(self, func, *fargs, **fkwargs):
411 def __wrapper(self, func, *fargs, **fkwargs):
367 # _wrapper.__name__ = func.__name__
412 # _wrapper.__name__ = func.__name__
368 # _wrapper.__dict__.update(func.__dict__)
413 # _wrapper.__dict__.update(func.__dict__)
369 # _wrapper.__doc__ = func.__doc__
414 # _wrapper.__doc__ = func.__doc__
370 self.user = session.get('rhodecode_user', AuthUser())
415 self.user = session.get('rhodecode_user', AuthUser())
371 self.user_perms = self.user.permissions
416 self.user_perms = self.user.permissions
372 log.debug('checking %s permissions %s for %s %s',
417 log.debug('checking %s permissions %s for %s %s',
373 self.__class__.__name__, self.required_perms, func.__name__,
418 self.__class__.__name__, self.required_perms, func.__name__,
374 self.user)
419 self.user)
375
420
376 if self.check_permissions():
421 if self.check_permissions():
377 log.debug('Permission granted for %s %s', func.__name__, self.user)
422 log.debug('Permission granted for %s %s', func.__name__, self.user)
378
423
379 return func(*fargs, **fkwargs)
424 return func(*fargs, **fkwargs)
380
425
381 else:
426 else:
382 log.warning('Permission denied for %s %s', func.__name__, self.user)
427 log.warning('Permission denied for %s %s', func.__name__, self.user)
383 #redirect with forbidden ret code
428 #redirect with forbidden ret code
384 return abort(403)
429 return abort(403)
385
430
386
431
387
432
388 def check_permissions(self):
433 def check_permissions(self):
389 """Dummy function for overriding"""
434 """Dummy function for overriding"""
390 raise Exception('You have to write this function in child class')
435 raise Exception('You have to write this function in child class')
391
436
392 class HasPermissionAllDecorator(PermsDecorator):
437 class HasPermissionAllDecorator(PermsDecorator):
393 """Checks for access permission for all given predicates. All of them
438 """Checks for access permission for all given predicates. All of them
394 have to be meet in order to fulfill the request
439 have to be meet in order to fulfill the request
395 """
440 """
396
441
397 def check_permissions(self):
442 def check_permissions(self):
398 if self.required_perms.issubset(self.user_perms.get('global')):
443 if self.required_perms.issubset(self.user_perms.get('global')):
399 return True
444 return True
400 return False
445 return False
401
446
402
447
403 class HasPermissionAnyDecorator(PermsDecorator):
448 class HasPermissionAnyDecorator(PermsDecorator):
404 """Checks for access permission for any of given predicates. In order to
449 """Checks for access permission for any of given predicates. In order to
405 fulfill the request any of predicates must be meet
450 fulfill the request any of predicates must be meet
406 """
451 """
407
452
408 def check_permissions(self):
453 def check_permissions(self):
409 if self.required_perms.intersection(self.user_perms.get('global')):
454 if self.required_perms.intersection(self.user_perms.get('global')):
410 return True
455 return True
411 return False
456 return False
412
457
413 class HasRepoPermissionAllDecorator(PermsDecorator):
458 class HasRepoPermissionAllDecorator(PermsDecorator):
414 """Checks for access permission for all given predicates for specific
459 """Checks for access permission for all given predicates for specific
415 repository. All of them have to be meet in order to fulfill the request
460 repository. All of them have to be meet in order to fulfill the request
416 """
461 """
417
462
418 def check_permissions(self):
463 def check_permissions(self):
419 repo_name = get_repo_slug(request)
464 repo_name = get_repo_slug(request)
420 try:
465 try:
421 user_perms = set([self.user_perms['repositories'][repo_name]])
466 user_perms = set([self.user_perms['repositories'][repo_name]])
422 except KeyError:
467 except KeyError:
423 return False
468 return False
424 if self.required_perms.issubset(user_perms):
469 if self.required_perms.issubset(user_perms):
425 return True
470 return True
426 return False
471 return False
427
472
428
473
429 class HasRepoPermissionAnyDecorator(PermsDecorator):
474 class HasRepoPermissionAnyDecorator(PermsDecorator):
430 """Checks for access permission for any of given predicates for specific
475 """Checks for access permission for any of given predicates for specific
431 repository. In order to fulfill the request any of predicates must be meet
476 repository. In order to fulfill the request any of predicates must be meet
432 """
477 """
433
478
434 def check_permissions(self):
479 def check_permissions(self):
435 repo_name = get_repo_slug(request)
480 repo_name = get_repo_slug(request)
436
481
437 try:
482 try:
438 user_perms = set([self.user_perms['repositories'][repo_name]])
483 user_perms = set([self.user_perms['repositories'][repo_name]])
439 except KeyError:
484 except KeyError:
440 return False
485 return False
441 if self.required_perms.intersection(user_perms):
486 if self.required_perms.intersection(user_perms):
442 return True
487 return True
443 return False
488 return False
444 #===============================================================================
489 #===============================================================================
445 # CHECK FUNCTIONS
490 # CHECK FUNCTIONS
446 #===============================================================================
491 #===============================================================================
447
492
448 class PermsFunction(object):
493 class PermsFunction(object):
449 """Base function for other check functions"""
494 """Base function for other check functions"""
450
495
451 def __init__(self, *perms):
496 def __init__(self, *perms):
452 available_perms = config['available_permissions']
497 available_perms = config['available_permissions']
453
498
454 for perm in perms:
499 for perm in perms:
455 if perm not in available_perms:
500 if perm not in available_perms:
456 raise Exception("'%s' permission in not defined" % perm)
501 raise Exception("'%s' permission in not defined" % perm)
457 self.required_perms = set(perms)
502 self.required_perms = set(perms)
458 self.user_perms = None
503 self.user_perms = None
459 self.granted_for = ''
504 self.granted_for = ''
460 self.repo_name = None
505 self.repo_name = None
461
506
462 def __call__(self, check_Location=''):
507 def __call__(self, check_Location=''):
463 user = session.get('rhodecode_user', False)
508 user = session.get('rhodecode_user', False)
464 if not user:
509 if not user:
465 return False
510 return False
466 self.user_perms = user.permissions
511 self.user_perms = user.permissions
467 self.granted_for = user.username
512 self.granted_for = user.username
468 log.debug('checking %s %s %s', self.__class__.__name__,
513 log.debug('checking %s %s %s', self.__class__.__name__,
469 self.required_perms, user)
514 self.required_perms, user)
470
515
471 if self.check_permissions():
516 if self.check_permissions():
472 log.debug('Permission granted for %s @ %s %s', self.granted_for,
517 log.debug('Permission granted for %s @ %s %s', self.granted_for,
473 check_Location, user)
518 check_Location, user)
474 return True
519 return True
475
520
476 else:
521 else:
477 log.warning('Permission denied for %s @ %s %s', self.granted_for,
522 log.warning('Permission denied for %s @ %s %s', self.granted_for,
478 check_Location, user)
523 check_Location, user)
479 return False
524 return False
480
525
481 def check_permissions(self):
526 def check_permissions(self):
482 """Dummy function for overriding"""
527 """Dummy function for overriding"""
483 raise Exception('You have to write this function in child class')
528 raise Exception('You have to write this function in child class')
484
529
485 class HasPermissionAll(PermsFunction):
530 class HasPermissionAll(PermsFunction):
486 def check_permissions(self):
531 def check_permissions(self):
487 if self.required_perms.issubset(self.user_perms.get('global')):
532 if self.required_perms.issubset(self.user_perms.get('global')):
488 return True
533 return True
489 return False
534 return False
490
535
491 class HasPermissionAny(PermsFunction):
536 class HasPermissionAny(PermsFunction):
492 def check_permissions(self):
537 def check_permissions(self):
493 if self.required_perms.intersection(self.user_perms.get('global')):
538 if self.required_perms.intersection(self.user_perms.get('global')):
494 return True
539 return True
495 return False
540 return False
496
541
497 class HasRepoPermissionAll(PermsFunction):
542 class HasRepoPermissionAll(PermsFunction):
498
543
499 def __call__(self, repo_name=None, check_Location=''):
544 def __call__(self, repo_name=None, check_Location=''):
500 self.repo_name = repo_name
545 self.repo_name = repo_name
501 return super(HasRepoPermissionAll, self).__call__(check_Location)
546 return super(HasRepoPermissionAll, self).__call__(check_Location)
502
547
503 def check_permissions(self):
548 def check_permissions(self):
504 if not self.repo_name:
549 if not self.repo_name:
505 self.repo_name = get_repo_slug(request)
550 self.repo_name = get_repo_slug(request)
506
551
507 try:
552 try:
508 self.user_perms = set([self.user_perms['repositories']\
553 self.user_perms = set([self.user_perms['repositories']\
509 [self.repo_name]])
554 [self.repo_name]])
510 except KeyError:
555 except KeyError:
511 return False
556 return False
512 self.granted_for = self.repo_name
557 self.granted_for = self.repo_name
513 if self.required_perms.issubset(self.user_perms):
558 if self.required_perms.issubset(self.user_perms):
514 return True
559 return True
515 return False
560 return False
516
561
517 class HasRepoPermissionAny(PermsFunction):
562 class HasRepoPermissionAny(PermsFunction):
518
563
519 def __call__(self, repo_name=None, check_Location=''):
564 def __call__(self, repo_name=None, check_Location=''):
520 self.repo_name = repo_name
565 self.repo_name = repo_name
521 return super(HasRepoPermissionAny, self).__call__(check_Location)
566 return super(HasRepoPermissionAny, self).__call__(check_Location)
522
567
523 def check_permissions(self):
568 def check_permissions(self):
524 if not self.repo_name:
569 if not self.repo_name:
525 self.repo_name = get_repo_slug(request)
570 self.repo_name = get_repo_slug(request)
526
571
527 try:
572 try:
528 self.user_perms = set([self.user_perms['repositories']\
573 self.user_perms = set([self.user_perms['repositories']\
529 [self.repo_name]])
574 [self.repo_name]])
530 except KeyError:
575 except KeyError:
531 return False
576 return False
532 self.granted_for = self.repo_name
577 self.granted_for = self.repo_name
533 if self.required_perms.intersection(self.user_perms):
578 if self.required_perms.intersection(self.user_perms):
534 return True
579 return True
535 return False
580 return False
536
581
537 #===============================================================================
582 #===============================================================================
538 # SPECIAL VERSION TO HANDLE MIDDLEWARE AUTH
583 # SPECIAL VERSION TO HANDLE MIDDLEWARE AUTH
539 #===============================================================================
584 #===============================================================================
540
585
541 class HasPermissionAnyMiddleware(object):
586 class HasPermissionAnyMiddleware(object):
542 def __init__(self, *perms):
587 def __init__(self, *perms):
543 self.required_perms = set(perms)
588 self.required_perms = set(perms)
544
589
545 def __call__(self, user, repo_name):
590 def __call__(self, user, repo_name):
546 usr = AuthUser()
591 usr = AuthUser()
547 usr.user_id = user.user_id
592 usr.user_id = user.user_id
548 usr.username = user.username
593 usr.username = user.username
549 usr.is_admin = user.admin
594 usr.is_admin = user.admin
550
595
551 try:
596 try:
552 self.user_perms = set([fill_perms(usr)\
597 self.user_perms = set([fill_perms(usr)\
553 .permissions['repositories'][repo_name]])
598 .permissions['repositories'][repo_name]])
554 except:
599 except:
555 self.user_perms = set()
600 self.user_perms = set()
556 self.granted_for = ''
601 self.granted_for = ''
557 self.username = user.username
602 self.username = user.username
558 self.repo_name = repo_name
603 self.repo_name = repo_name
559 return self.check_permissions()
604 return self.check_permissions()
560
605
561 def check_permissions(self):
606 def check_permissions(self):
562 log.debug('checking mercurial protocol '
607 log.debug('checking mercurial protocol '
563 'permissions for user:%s repository:%s',
608 'permissions for user:%s repository:%s',
564 self.username, self.repo_name)
609 self.username, self.repo_name)
565 if self.required_perms.intersection(self.user_perms):
610 if self.required_perms.intersection(self.user_perms):
566 log.debug('permission granted')
611 log.debug('permission granted')
567 return True
612 return True
568 log.debug('permission denied')
613 log.debug('permission denied')
569 return False
614 return False
@@ -1,104 +1,104 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 # encoding: utf-8
2 # encoding: utf-8
3 # ldap authentication lib
3 # ldap authentication lib
4 # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
4 # Copyright (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
5 #
5 #
6 # This program is free software; you can redistribute it and/or
6 # This program is free software; you can redistribute it and/or
7 # modify it under the terms of the GNU General Public License
7 # modify it under the terms of the GNU General Public License
8 # as published by the Free Software Foundation; version 2
8 # as published by the Free Software Foundation; version 2
9 # of the License or (at your opinion) any later version of the license.
9 # of the License or (at your opinion) any later version of the license.
10 #
10 #
11 # This program is distributed in the hope that it will be useful,
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
14 # GNU General Public License for more details.
15 #
15 #
16 # You should have received a copy of the GNU General Public License
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19 # MA 02110-1301, USA.
19 # MA 02110-1301, USA.
20 """
20 """
21 Created on Nov 17, 2010
21 Created on Nov 17, 2010
22
22
23 @author: marcink
23 @author: marcink
24 """
24 """
25
25
26 from rhodecode.lib.exceptions import *
26 from rhodecode.lib.exceptions import *
27 import logging
27 import logging
28
28
29 log = logging.getLogger(__name__)
29 log = logging.getLogger(__name__)
30
30
31 try:
31 try:
32 import ldap
32 import ldap
33 except ImportError:
33 except ImportError:
34 pass
34 pass
35
35
36 class AuthLdap(object):
36 class AuthLdap(object):
37
37
38 def __init__(self, server, base_dn, port=389, bind_dn='', bind_pass='',
38 def __init__(self, server, base_dn, port=389, bind_dn='', bind_pass='',
39 use_ldaps=False, ldap_version=3):
39 use_ldaps=False, ldap_version=3):
40 self.ldap_version = ldap_version
40 self.ldap_version = ldap_version
41 if use_ldaps:
41 if use_ldaps:
42 port = port or 689
42 port = port or 689
43 self.LDAP_USE_LDAPS = use_ldaps
43 self.LDAP_USE_LDAPS = use_ldaps
44 self.LDAP_SERVER_ADDRESS = server
44 self.LDAP_SERVER_ADDRESS = server
45 self.LDAP_SERVER_PORT = port
45 self.LDAP_SERVER_PORT = port
46
46
47 #USE FOR READ ONLY BIND TO LDAP SERVER
47 #USE FOR READ ONLY BIND TO LDAP SERVER
48 self.LDAP_BIND_DN = bind_dn
48 self.LDAP_BIND_DN = bind_dn
49 self.LDAP_BIND_PASS = bind_pass
49 self.LDAP_BIND_PASS = bind_pass
50
50
51 ldap_server_type = 'ldap'
51 ldap_server_type = 'ldap'
52 if self.LDAP_USE_LDAPS:ldap_server_type = ldap_server_type + 's'
52 if self.LDAP_USE_LDAPS:ldap_server_type = ldap_server_type + 's'
53 self.LDAP_SERVER = "%s://%s:%s" % (ldap_server_type,
53 self.LDAP_SERVER = "%s://%s:%s" % (ldap_server_type,
54 self.LDAP_SERVER_ADDRESS,
54 self.LDAP_SERVER_ADDRESS,
55 self.LDAP_SERVER_PORT)
55 self.LDAP_SERVER_PORT)
56
56
57 self.BASE_DN = base_dn
57 self.BASE_DN = base_dn
58
58
59 def authenticate_ldap(self, username, password):
59 def authenticate_ldap(self, username, password):
60 """Authenticate a user via LDAP and return his/her LDAP properties.
60 """Authenticate a user via LDAP and return his/her LDAP properties.
61
61
62 Raises AuthenticationError if the credentials are rejected, or
62 Raises AuthenticationError if the credentials are rejected, or
63 EnvironmentError if the LDAP server can't be reached.
63 EnvironmentError if the LDAP server can't be reached.
64
64
65 :param username: username
65 :param username: username
66 :param password: password
66 :param password: password
67 """
67 """
68
68
69 from rhodecode.lib.helpers import chop_at
69 from rhodecode.lib.helpers import chop_at
70
70
71 uid = chop_at(username, "@%s" % self.LDAP_SERVER_ADDRESS)
71 uid = chop_at(username, "@%s" % self.LDAP_SERVER_ADDRESS)
72
72
73 if "," in username:
73 if "," in username:
74 raise LdapUsernameError("invalid character in username: ,")
74 raise LdapUsernameError("invalid character in username: ,")
75 try:
75 try:
76 ldap.set_option(ldap.OPT_X_TLS_CACERTDIR, '/etc/openldap/cacerts')
76 ldap.set_option(ldap.OPT_X_TLS_CACERTDIR, '/etc/openldap/cacerts')
77 ldap.set_option(ldap.OPT_NETWORK_TIMEOUT, 10)
77 ldap.set_option(ldap.OPT_NETWORK_TIMEOUT, 10)
78 server = ldap.initialize(self.LDAP_SERVER)
78 server = ldap.initialize(self.LDAP_SERVER)
79 if self.ldap_version == 2:
79 if self.ldap_version == 2:
80 server.protocol = ldap.VERSION2
80 server.protocol = ldap.VERSION2
81 else:
81 else:
82 server.protocol = ldap.VERSION3
82 server.protocol = ldap.VERSION3
83
83
84 if self.LDAP_BIND_DN and self.LDAP_BIND_PASS:
84 if self.LDAP_BIND_DN and self.LDAP_BIND_PASS:
85 server.simple_bind_s(self.LDAP_BIND_DN, self.LDAP_BIND_PASS)
85 server.simple_bind_s(self.LDAP_BIND_DN, self.LDAP_BIND_PASS)
86
86
87 dn = self.BASE_DN % {'user':uid}
87 dn = self.BASE_DN % {'user':uid}
88 log.debug("Authenticating %r at %s", dn, self.LDAP_SERVER)
88 log.debug("Authenticating %r at %s", dn, self.LDAP_SERVER)
89 server.simple_bind_s(dn, password)
89 server.simple_bind_s(dn, password)
90
90
91 properties = server.search_s(dn, ldap.SCOPE_SUBTREE)
91 properties = server.search_s(dn, ldap.SCOPE_SUBTREE)
92 if not properties:
92 if not properties:
93 raise ldap.NO_SUCH_OBJECT()
93 raise ldap.NO_SUCH_OBJECT()
94 except ldap.NO_SUCH_OBJECT, e:
94 except ldap.NO_SUCH_OBJECT, e:
95 log.debug("LDAP says no such user '%s' (%s)", uid, username)
95 log.debug("LDAP says no such user '%s' (%s)", uid, username)
96 raise LdapUsernameError()
96 raise LdapUsernameError()
97 except ldap.INVALID_CREDENTIALS, e:
97 except ldap.INVALID_CREDENTIALS, e:
98 log.debug("LDAP rejected password for user '%s' (%s)", uid, username)
98 log.debug("LDAP rejected password for user '%s' (%s)", uid, username)
99 raise LdapPasswordError()
99 raise LdapPasswordError()
100 except ldap.SERVER_DOWN, e:
100 except ldap.SERVER_DOWN, e:
101 raise LdapConnectionError("LDAP can't access authentication server")
101 raise LdapConnectionError("LDAP can't access authentication server")
102
102
103 return properties[0]
103 return properties[0]
104
104
@@ -1,493 +1,477 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.lib.db_manage
3 rhodecode.lib.db_manage
4 ~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 Database creation, and setup module for RhodeCode. Used for creation
6 Database creation, and setup module for RhodeCode. Used for creation
7 of database as well as for migration operations
7 of database as well as for migration operations
8
8
9 :created_on: Apr 10, 2010
9 :created_on: Apr 10, 2010
10 :author: marcink
10 :author: marcink
11 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
11 :copyright: (C) 2009-2011 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
14 # This program is free software; you can redistribute it and/or
15 # modify it under the terms of the GNU General Public License
15 # modify it under the terms of the GNU General Public License
16 # as published by the Free Software Foundation; version 2
16 # as published by the Free Software Foundation; version 2
17 # of the License or (at your opinion) any later version of the license.
17 # of the License or (at your opinion) any later version of the license.
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, write to the Free Software
25 # along with this program; if not, write to the Free Software
26 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
26 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
27 # MA 02110-1301, USA.
27 # MA 02110-1301, USA.
28
28
29 import os
29 import os
30 import sys
30 import sys
31 import uuid
31 import uuid
32 import logging
32 import logging
33 from os.path import dirname as dn, join as jn
33 from os.path import dirname as dn, join as jn
34
34
35 from rhodecode import __dbversion__
35 from rhodecode import __dbversion__
36 from rhodecode.model import meta
36 from rhodecode.model import meta
37
37
38 from rhodecode.lib.auth import get_crypt_password
38 from rhodecode.lib.auth import get_crypt_password
39 from rhodecode.lib.utils import ask_ok
39 from rhodecode.lib.utils import ask_ok
40 from rhodecode.model import init_model
40 from rhodecode.model import init_model
41 from rhodecode.model.db import User, Permission, RhodeCodeUi, RhodeCodeSettings, \
41 from rhodecode.model.db import User, Permission, RhodeCodeUi, RhodeCodeSettings, \
42 UserToPerm, DbMigrateVersion
42 UserToPerm, DbMigrateVersion
43
43
44 from sqlalchemy.engine import create_engine
44 from sqlalchemy.engine import create_engine
45
45
46 log = logging.getLogger(__name__)
46 log = logging.getLogger(__name__)
47
47
48 class DbManage(object):
48 class DbManage(object):
49 def __init__(self, log_sql, dbconf, root, tests=False):
49 def __init__(self, log_sql, dbconf, root, tests=False):
50 self.dbname = dbconf.split('/')[-1]
50 self.dbname = dbconf.split('/')[-1]
51 self.tests = tests
51 self.tests = tests
52 self.root = root
52 self.root = root
53 self.dburi = dbconf
53 self.dburi = dbconf
54 self.log_sql = log_sql
54 self.log_sql = log_sql
55 self.db_exists = False
55 self.db_exists = False
56 self.init_db()
56 self.init_db()
57
57
58 def init_db(self):
58 def init_db(self):
59 engine = create_engine(self.dburi, echo=self.log_sql)
59 engine = create_engine(self.dburi, echo=self.log_sql)
60 init_model(engine)
60 init_model(engine)
61 self.sa = meta.Session()
61 self.sa = meta.Session()
62
62
63 def check_for_db(self, override):
64 db_path = jn(self.root, self.dbname)
65 if self.dburi.startswith('sqlite'):
66 log.info('checking for existing db in %s', db_path)
67 if os.path.isfile(db_path):
68
69 self.db_exists = True
70 if not override:
71 raise Exception('database already exists')
72 return 'sqlite'
73 if self.dburi.startswith('postgresql'):
74 self.db_exists = True
75 return 'postgresql'
76
77
78 def create_tables(self, override=False):
63 def create_tables(self, override=False):
79 """Create a auth database
64 """Create a auth database
80 """
65 """
81
66
82 db_type = self.check_for_db(override)
67 log.info("Any existing database is going to be destroyed")
83 if self.db_exists:
84 log.info("database exist and it's going to be destroyed")
85 if self.tests:
68 if self.tests:
86 destroy = True
69 destroy = True
87 else:
70 else:
88 destroy = ask_ok('Are you sure to destroy old database ? [y/n]')
71 destroy = ask_ok('Are you sure to destroy old database ? [y/n]')
89 if not destroy:
72 if not destroy:
90 sys.exit()
73 sys.exit()
91 if self.db_exists and destroy:
74 if destroy:
92 if db_type == 'sqlite':
93 os.remove(jn(self.root, self.dbname))
94 if db_type == 'postgresql':
95 meta.Base.metadata.drop_all()
75 meta.Base.metadata.drop_all()
96
76
97 checkfirst = not override
77 checkfirst = not override
98 meta.Base.metadata.create_all(checkfirst=checkfirst)
78 meta.Base.metadata.create_all(checkfirst=checkfirst)
99 log.info('Created tables for %s', self.dbname)
79 log.info('Created tables for %s', self.dbname)
100
80
101
81
102
82
103 def set_db_version(self):
83 def set_db_version(self):
104 try:
84 try:
105 ver = DbMigrateVersion()
85 ver = DbMigrateVersion()
106 ver.version = __dbversion__
86 ver.version = __dbversion__
107 ver.repository_id = 'rhodecode_db_migrations'
87 ver.repository_id = 'rhodecode_db_migrations'
108 ver.repository_path = 'versions'
88 ver.repository_path = 'versions'
109 self.sa.add(ver)
89 self.sa.add(ver)
110 self.sa.commit()
90 self.sa.commit()
111 except:
91 except:
112 self.sa.rollback()
92 self.sa.rollback()
113 raise
93 raise
114 log.info('db version set to: %s', __dbversion__)
94 log.info('db version set to: %s', __dbversion__)
115
95
116
96
117 def upgrade(self):
97 def upgrade(self):
118 """Upgrades given database schema to given revision following
98 """Upgrades given database schema to given revision following
119 all needed steps, to perform the upgrade
99 all needed steps, to perform the upgrade
120
100
121 :param revision: revision to upgrade to
101 :param revision: revision to upgrade to
122 """
102 """
123
103
124 from rhodecode.lib.dbmigrate.migrate.versioning import api
104 from rhodecode.lib.dbmigrate.migrate.versioning import api
125 from rhodecode.lib.dbmigrate.migrate.exceptions import \
105 from rhodecode.lib.dbmigrate.migrate.exceptions import \
126 DatabaseNotControlledError
106 DatabaseNotControlledError
127
107
128 upgrade = ask_ok('You are about to perform database upgrade, make '
108 upgrade = ask_ok('You are about to perform database upgrade, make '
129 'sure You backed up your database before. '
109 'sure You backed up your database before. '
130 'Continue ? [y/n]')
110 'Continue ? [y/n]')
131 if not upgrade:
111 if not upgrade:
132 sys.exit('Nothing done')
112 sys.exit('Nothing done')
133
113
134 repository_path = jn(dn(dn(dn(os.path.realpath(__file__)))),
114 repository_path = jn(dn(dn(dn(os.path.realpath(__file__)))),
135 'rhodecode/lib/dbmigrate')
115 'rhodecode/lib/dbmigrate')
136 db_uri = self.dburi
116 db_uri = self.dburi
137
117
138 try:
118 try:
139 curr_version = api.db_version(db_uri, repository_path)
119 curr_version = api.db_version(db_uri, repository_path)
140 msg = ('Found current database under version'
120 msg = ('Found current database under version'
141 ' control with version %s' % curr_version)
121 ' control with version %s' % curr_version)
142
122
143 except (RuntimeError, DatabaseNotControlledError), e:
123 except (RuntimeError, DatabaseNotControlledError), e:
144 curr_version = 1
124 curr_version = 1
145 msg = ('Current database is not under version control. Setting'
125 msg = ('Current database is not under version control. Setting'
146 ' as version %s' % curr_version)
126 ' as version %s' % curr_version)
147 api.version_control(db_uri, repository_path, curr_version)
127 api.version_control(db_uri, repository_path, curr_version)
148
128
149 print (msg)
129 print (msg)
150
130
151 if curr_version == __dbversion__:
131 if curr_version == __dbversion__:
152 sys.exit('This database is already at the newest version')
132 sys.exit('This database is already at the newest version')
153
133
154 #======================================================================
134 #======================================================================
155 # UPGRADE STEPS
135 # UPGRADE STEPS
156 #======================================================================
136 #======================================================================
157 class UpgradeSteps(object):
137 class UpgradeSteps(object):
158 """Those steps follow schema versions so for example schema
138 """Those steps follow schema versions so for example schema
159 for example schema with seq 002 == step_2 and so on.
139 for example schema with seq 002 == step_2 and so on.
160 """
140 """
161
141
162 def __init__(self, klass):
142 def __init__(self, klass):
163 self.klass = klass
143 self.klass = klass
164
144
165 def step_0(self):
145 def step_0(self):
166 #step 0 is the schema upgrade, and than follow proper upgrades
146 #step 0 is the schema upgrade, and than follow proper upgrades
167 print ('attempting to do database upgrade to version %s' \
147 print ('attempting to do database upgrade to version %s' \
168 % __dbversion__)
148 % __dbversion__)
169 api.upgrade(db_uri, repository_path, __dbversion__)
149 api.upgrade(db_uri, repository_path, __dbversion__)
170 print ('Schema upgrade completed')
150 print ('Schema upgrade completed')
171
151
172 def step_1(self):
152 def step_1(self):
173 pass
153 pass
174
154
175 def step_2(self):
155 def step_2(self):
176 print ('Patching repo paths for newer version of RhodeCode')
156 print ('Patching repo paths for newer version of RhodeCode')
177 self.klass.fix_repo_paths()
157 self.klass.fix_repo_paths()
178
158
179 print ('Patching default user of RhodeCode')
159 print ('Patching default user of RhodeCode')
180 self.klass.fix_default_user()
160 self.klass.fix_default_user()
181
161
182 log.info('Changing ui settings')
162 log.info('Changing ui settings')
183 self.klass.create_ui_settings()
163 self.klass.create_ui_settings()
184
164
185
165
186 upgrade_steps = [0] + range(curr_version + 1, __dbversion__ + 1)
166 upgrade_steps = [0] + range(curr_version + 1, __dbversion__ + 1)
187
167
188 #CALL THE PROPER ORDER OF STEPS TO PERFORM FULL UPGRADE
168 #CALL THE PROPER ORDER OF STEPS TO PERFORM FULL UPGRADE
189 for step in upgrade_steps:
169 for step in upgrade_steps:
190 print ('performing upgrade step %s' % step)
170 print ('performing upgrade step %s' % step)
191 callable = getattr(UpgradeSteps(self), 'step_%s' % step)()
171 callable = getattr(UpgradeSteps(self), 'step_%s' % step)()
192
172
193
173
194
174
195 def fix_repo_paths(self):
175 def fix_repo_paths(self):
196 """Fixes a old rhodecode version path into new one without a '*'
176 """Fixes a old rhodecode version path into new one without a '*'
197 """
177 """
198
178
199 paths = self.sa.query(RhodeCodeUi)\
179 paths = self.sa.query(RhodeCodeUi)\
200 .filter(RhodeCodeUi.ui_key == '/')\
180 .filter(RhodeCodeUi.ui_key == '/')\
201 .scalar()
181 .scalar()
202
182
203 paths.ui_value = paths.ui_value.replace('*', '')
183 paths.ui_value = paths.ui_value.replace('*', '')
204
184
205 try:
185 try:
206 self.sa.add(paths)
186 self.sa.add(paths)
207 self.sa.commit()
187 self.sa.commit()
208 except:
188 except:
209 self.sa.rollback()
189 self.sa.rollback()
210 raise
190 raise
211
191
212 def fix_default_user(self):
192 def fix_default_user(self):
213 """Fixes a old default user with some 'nicer' default values,
193 """Fixes a old default user with some 'nicer' default values,
214 used mostly for anonymous access
194 used mostly for anonymous access
215 """
195 """
216 def_user = self.sa.query(User)\
196 def_user = self.sa.query(User)\
217 .filter(User.username == 'default')\
197 .filter(User.username == 'default')\
218 .one()
198 .one()
219
199
220 def_user.name = 'Anonymous'
200 def_user.name = 'Anonymous'
221 def_user.lastname = 'User'
201 def_user.lastname = 'User'
222 def_user.email = 'anonymous@rhodecode.org'
202 def_user.email = 'anonymous@rhodecode.org'
223
203
224 try:
204 try:
225 self.sa.add(def_user)
205 self.sa.add(def_user)
226 self.sa.commit()
206 self.sa.commit()
227 except:
207 except:
228 self.sa.rollback()
208 self.sa.rollback()
229 raise
209 raise
230
210
231
211
232
212
233 def admin_prompt(self, second=False):
213 def admin_prompt(self, second=False):
234 if not self.tests:
214 if not self.tests:
235 import getpass
215 import getpass
236
216
237
217
238 def get_password():
218 def get_password():
239 password = getpass.getpass('Specify admin password (min 6 chars):')
219 password = getpass.getpass('Specify admin password (min 6 chars):')
240 confirm = getpass.getpass('Confirm password:')
220 confirm = getpass.getpass('Confirm password:')
241
221
242 if password != confirm:
222 if password != confirm:
243 log.error('passwords mismatch')
223 log.error('passwords mismatch')
244 return False
224 return False
245 if len(password) < 6:
225 if len(password) < 6:
246 log.error('password is to short use at least 6 characters')
226 log.error('password is to short use at least 6 characters')
247 return False
227 return False
248
228
249 return password
229 return password
250
230
251 username = raw_input('Specify admin username:')
231 username = raw_input('Specify admin username:')
252
232
253 password = get_password()
233 password = get_password()
254 if not password:
234 if not password:
255 #second try
235 #second try
256 password = get_password()
236 password = get_password()
257 if not password:
237 if not password:
258 sys.exit()
238 sys.exit()
259
239
260 email = raw_input('Specify admin email:')
240 email = raw_input('Specify admin email:')
261 self.create_user(username, password, email, True)
241 self.create_user(username, password, email, True)
262 else:
242 else:
263 log.info('creating admin and regular test users')
243 log.info('creating admin and regular test users')
264 self.create_user('test_admin', 'test12', 'test_admin@mail.com', True)
244 self.create_user('test_admin', 'test12', 'test_admin@mail.com', True)
265 self.create_user('test_regular', 'test12', 'test_regular@mail.com', False)
245 self.create_user('test_regular', 'test12', 'test_regular@mail.com', False)
266 self.create_user('test_regular2', 'test12', 'test_regular2@mail.com', False)
246 self.create_user('test_regular2', 'test12', 'test_regular2@mail.com', False)
267
247
268 def create_ui_settings(self):
248 def create_ui_settings(self):
269 """Creates ui settings, fills out hooks
249 """Creates ui settings, fills out hooks
270 and disables dotencode
250 and disables dotencode
271
251
272 """
252 """
273 #HOOKS
253 #HOOKS
274 hooks1_key = 'changegroup.update'
254 hooks1_key = 'changegroup.update'
275 hooks1_ = self.sa.query(RhodeCodeUi)\
255 hooks1_ = self.sa.query(RhodeCodeUi)\
276 .filter(RhodeCodeUi.ui_key == hooks1_key).scalar()
256 .filter(RhodeCodeUi.ui_key == hooks1_key).scalar()
277
257
278 hooks1 = RhodeCodeUi() if hooks1_ is None else hooks1_
258 hooks1 = RhodeCodeUi() if hooks1_ is None else hooks1_
279 hooks1.ui_section = 'hooks'
259 hooks1.ui_section = 'hooks'
280 hooks1.ui_key = hooks1_key
260 hooks1.ui_key = hooks1_key
281 hooks1.ui_value = 'hg update >&2'
261 hooks1.ui_value = 'hg update >&2'
282 hooks1.ui_active = False
262 hooks1.ui_active = False
283
263
284 hooks2_key = 'changegroup.repo_size'
264 hooks2_key = 'changegroup.repo_size'
285 hooks2_ = self.sa.query(RhodeCodeUi)\
265 hooks2_ = self.sa.query(RhodeCodeUi)\
286 .filter(RhodeCodeUi.ui_key == hooks2_key).scalar()
266 .filter(RhodeCodeUi.ui_key == hooks2_key).scalar()
287
267
288 hooks2 = RhodeCodeUi() if hooks2_ is None else hooks2_
268 hooks2 = RhodeCodeUi() if hooks2_ is None else hooks2_
289 hooks2.ui_section = 'hooks'
269 hooks2.ui_section = 'hooks'
290 hooks2.ui_key = hooks2_key
270 hooks2.ui_key = hooks2_key
291 hooks2.ui_value = 'python:rhodecode.lib.hooks.repo_size'
271 hooks2.ui_value = 'python:rhodecode.lib.hooks.repo_size'
292
272
293 hooks3 = RhodeCodeUi()
273 hooks3 = RhodeCodeUi()
294 hooks3.ui_section = 'hooks'
274 hooks3.ui_section = 'hooks'
295 hooks3.ui_key = 'pretxnchangegroup.push_logger'
275 hooks3.ui_key = 'pretxnchangegroup.push_logger'
296 hooks3.ui_value = 'python:rhodecode.lib.hooks.log_push_action'
276 hooks3.ui_value = 'python:rhodecode.lib.hooks.log_push_action'
297
277
298 hooks4 = RhodeCodeUi()
278 hooks4 = RhodeCodeUi()
299 hooks4.ui_section = 'hooks'
279 hooks4.ui_section = 'hooks'
300 hooks4.ui_key = 'preoutgoing.pull_logger'
280 hooks4.ui_key = 'preoutgoing.pull_logger'
301 hooks4.ui_value = 'python:rhodecode.lib.hooks.log_pull_action'
281 hooks4.ui_value = 'python:rhodecode.lib.hooks.log_pull_action'
302
282
303 #For mercurial 1.7 set backward comapatibility with format
283 #For mercurial 1.7 set backward comapatibility with format
304 dotencode_disable = RhodeCodeUi()
284 dotencode_disable = RhodeCodeUi()
305 dotencode_disable.ui_section = 'format'
285 dotencode_disable.ui_section = 'format'
306 dotencode_disable.ui_key = 'dotencode'
286 dotencode_disable.ui_key = 'dotencode'
307 dotencode_disable.ui_value = 'false'
287 dotencode_disable.ui_value = 'false'
308
288
309 try:
289 try:
310 self.sa.add(hooks1)
290 self.sa.add(hooks1)
311 self.sa.add(hooks2)
291 self.sa.add(hooks2)
312 self.sa.add(hooks3)
292 self.sa.add(hooks3)
313 self.sa.add(hooks4)
293 self.sa.add(hooks4)
314 self.sa.add(dotencode_disable)
294 self.sa.add(dotencode_disable)
315 self.sa.commit()
295 self.sa.commit()
316 except:
296 except:
317 self.sa.rollback()
297 self.sa.rollback()
318 raise
298 raise
319
299
320
300
321 def create_ldap_options(self):
301 def create_ldap_options(self):
322 """Creates ldap settings"""
302 """Creates ldap settings"""
323
303
324 try:
304 try:
325 for k in ['ldap_active', 'ldap_host', 'ldap_port', 'ldap_ldaps',
305 for k, v in [('ldap_active', 'false'),
326 'ldap_dn_user', 'ldap_dn_pass', 'ldap_base_dn']:
306 ('ldap_host', ''),
307 ('ldap_port', '389'),
308 ('ldap_ldaps', 'false'),
309 ('ldap_dn_user', ''), ('ldap_dn_pass', ''),
310 ('ldap_base_dn', '')]:
327
311
328 setting = RhodeCodeSettings(k, '')
312 setting = RhodeCodeSettings(k, v)
329 self.sa.add(setting)
313 self.sa.add(setting)
330 self.sa.commit()
314 self.sa.commit()
331 except:
315 except:
332 self.sa.rollback()
316 self.sa.rollback()
333 raise
317 raise
334
318
335 def config_prompt(self, test_repo_path=''):
319 def config_prompt(self, test_repo_path=''):
336 log.info('Setting up repositories config')
320 log.info('Setting up repositories config')
337
321
338 if not self.tests and not test_repo_path:
322 if not self.tests and not test_repo_path:
339 path = raw_input('Specify valid full path to your repositories'
323 path = raw_input('Specify valid full path to your repositories'
340 ' you can change this later in application settings:')
324 ' you can change this later in application settings:')
341 else:
325 else:
342 path = test_repo_path
326 path = test_repo_path
343
327
344 if not os.path.isdir(path):
328 if not os.path.isdir(path):
345 log.error('You entered wrong path: %s', path)
329 log.error('You entered wrong path: %s', path)
346 sys.exit()
330 sys.exit()
347
331
348 self.create_ui_settings()
332 self.create_ui_settings()
349
333
350 #HG UI OPTIONS
334 #HG UI OPTIONS
351 web1 = RhodeCodeUi()
335 web1 = RhodeCodeUi()
352 web1.ui_section = 'web'
336 web1.ui_section = 'web'
353 web1.ui_key = 'push_ssl'
337 web1.ui_key = 'push_ssl'
354 web1.ui_value = 'false'
338 web1.ui_value = 'false'
355
339
356 web2 = RhodeCodeUi()
340 web2 = RhodeCodeUi()
357 web2.ui_section = 'web'
341 web2.ui_section = 'web'
358 web2.ui_key = 'allow_archive'
342 web2.ui_key = 'allow_archive'
359 web2.ui_value = 'gz zip bz2'
343 web2.ui_value = 'gz zip bz2'
360
344
361 web3 = RhodeCodeUi()
345 web3 = RhodeCodeUi()
362 web3.ui_section = 'web'
346 web3.ui_section = 'web'
363 web3.ui_key = 'allow_push'
347 web3.ui_key = 'allow_push'
364 web3.ui_value = '*'
348 web3.ui_value = '*'
365
349
366 web4 = RhodeCodeUi()
350 web4 = RhodeCodeUi()
367 web4.ui_section = 'web'
351 web4.ui_section = 'web'
368 web4.ui_key = 'baseurl'
352 web4.ui_key = 'baseurl'
369 web4.ui_value = '/'
353 web4.ui_value = '/'
370
354
371 paths = RhodeCodeUi()
355 paths = RhodeCodeUi()
372 paths.ui_section = 'paths'
356 paths.ui_section = 'paths'
373 paths.ui_key = '/'
357 paths.ui_key = '/'
374 paths.ui_value = path
358 paths.ui_value = path
375
359
376
360
377 hgsettings1 = RhodeCodeSettings('realm', 'RhodeCode authentication')
361 hgsettings1 = RhodeCodeSettings('realm', 'RhodeCode authentication')
378 hgsettings2 = RhodeCodeSettings('title', 'RhodeCode')
362 hgsettings2 = RhodeCodeSettings('title', 'RhodeCode')
379
363
380
364
381 try:
365 try:
382 self.sa.add(web1)
366 self.sa.add(web1)
383 self.sa.add(web2)
367 self.sa.add(web2)
384 self.sa.add(web3)
368 self.sa.add(web3)
385 self.sa.add(web4)
369 self.sa.add(web4)
386 self.sa.add(paths)
370 self.sa.add(paths)
387 self.sa.add(hgsettings1)
371 self.sa.add(hgsettings1)
388 self.sa.add(hgsettings2)
372 self.sa.add(hgsettings2)
389
373
390 self.sa.commit()
374 self.sa.commit()
391 except:
375 except:
392 self.sa.rollback()
376 self.sa.rollback()
393 raise
377 raise
394
378
395 self.create_ldap_options()
379 self.create_ldap_options()
396
380
397 log.info('created ui config')
381 log.info('created ui config')
398
382
399 def create_user(self, username, password, email='', admin=False):
383 def create_user(self, username, password, email='', admin=False):
400 log.info('creating administrator user %s', username)
384 log.info('creating administrator user %s', username)
401 new_user = User()
385 new_user = User()
402 new_user.username = username
386 new_user.username = username
403 new_user.password = get_crypt_password(password)
387 new_user.password = get_crypt_password(password)
404 new_user.name = 'RhodeCode'
388 new_user.name = 'RhodeCode'
405 new_user.lastname = 'Admin'
389 new_user.lastname = 'Admin'
406 new_user.email = email
390 new_user.email = email
407 new_user.admin = admin
391 new_user.admin = admin
408 new_user.active = True
392 new_user.active = True
409
393
410 try:
394 try:
411 self.sa.add(new_user)
395 self.sa.add(new_user)
412 self.sa.commit()
396 self.sa.commit()
413 except:
397 except:
414 self.sa.rollback()
398 self.sa.rollback()
415 raise
399 raise
416
400
417 def create_default_user(self):
401 def create_default_user(self):
418 log.info('creating default user')
402 log.info('creating default user')
419 #create default user for handling default permissions.
403 #create default user for handling default permissions.
420 def_user = User()
404 def_user = User()
421 def_user.username = 'default'
405 def_user.username = 'default'
422 def_user.password = get_crypt_password(str(uuid.uuid1())[:8])
406 def_user.password = get_crypt_password(str(uuid.uuid1())[:8])
423 def_user.name = 'Anonymous'
407 def_user.name = 'Anonymous'
424 def_user.lastname = 'User'
408 def_user.lastname = 'User'
425 def_user.email = 'anonymous@rhodecode.org'
409 def_user.email = 'anonymous@rhodecode.org'
426 def_user.admin = False
410 def_user.admin = False
427 def_user.active = False
411 def_user.active = False
428 try:
412 try:
429 self.sa.add(def_user)
413 self.sa.add(def_user)
430 self.sa.commit()
414 self.sa.commit()
431 except:
415 except:
432 self.sa.rollback()
416 self.sa.rollback()
433 raise
417 raise
434
418
435 def create_permissions(self):
419 def create_permissions(self):
436 #module.(access|create|change|delete)_[name]
420 #module.(access|create|change|delete)_[name]
437 #module.(read|write|owner)
421 #module.(read|write|owner)
438 perms = [('repository.none', 'Repository no access'),
422 perms = [('repository.none', 'Repository no access'),
439 ('repository.read', 'Repository read access'),
423 ('repository.read', 'Repository read access'),
440 ('repository.write', 'Repository write access'),
424 ('repository.write', 'Repository write access'),
441 ('repository.admin', 'Repository admin access'),
425 ('repository.admin', 'Repository admin access'),
442 ('hg.admin', 'Hg Administrator'),
426 ('hg.admin', 'Hg Administrator'),
443 ('hg.create.repository', 'Repository create'),
427 ('hg.create.repository', 'Repository create'),
444 ('hg.create.none', 'Repository creation disabled'),
428 ('hg.create.none', 'Repository creation disabled'),
445 ('hg.register.none', 'Register disabled'),
429 ('hg.register.none', 'Register disabled'),
446 ('hg.register.manual_activate', 'Register new user with rhodecode without manual activation'),
430 ('hg.register.manual_activate', 'Register new user with rhodecode without manual activation'),
447 ('hg.register.auto_activate', 'Register new user with rhodecode without auto activation'),
431 ('hg.register.auto_activate', 'Register new user with rhodecode without auto activation'),
448 ]
432 ]
449
433
450 for p in perms:
434 for p in perms:
451 new_perm = Permission()
435 new_perm = Permission()
452 new_perm.permission_name = p[0]
436 new_perm.permission_name = p[0]
453 new_perm.permission_longname = p[1]
437 new_perm.permission_longname = p[1]
454 try:
438 try:
455 self.sa.add(new_perm)
439 self.sa.add(new_perm)
456 self.sa.commit()
440 self.sa.commit()
457 except:
441 except:
458 self.sa.rollback()
442 self.sa.rollback()
459 raise
443 raise
460
444
461 def populate_default_permissions(self):
445 def populate_default_permissions(self):
462 log.info('creating default user permissions')
446 log.info('creating default user permissions')
463
447
464 default_user = self.sa.query(User)\
448 default_user = self.sa.query(User)\
465 .filter(User.username == 'default').scalar()
449 .filter(User.username == 'default').scalar()
466
450
467 reg_perm = UserToPerm()
451 reg_perm = UserToPerm()
468 reg_perm.user = default_user
452 reg_perm.user = default_user
469 reg_perm.permission = self.sa.query(Permission)\
453 reg_perm.permission = self.sa.query(Permission)\
470 .filter(Permission.permission_name == 'hg.register.manual_activate')\
454 .filter(Permission.permission_name == 'hg.register.manual_activate')\
471 .scalar()
455 .scalar()
472
456
473 create_repo_perm = UserToPerm()
457 create_repo_perm = UserToPerm()
474 create_repo_perm.user = default_user
458 create_repo_perm.user = default_user
475 create_repo_perm.permission = self.sa.query(Permission)\
459 create_repo_perm.permission = self.sa.query(Permission)\
476 .filter(Permission.permission_name == 'hg.create.repository')\
460 .filter(Permission.permission_name == 'hg.create.repository')\
477 .scalar()
461 .scalar()
478
462
479 default_repo_perm = UserToPerm()
463 default_repo_perm = UserToPerm()
480 default_repo_perm.user = default_user
464 default_repo_perm.user = default_user
481 default_repo_perm.permission = self.sa.query(Permission)\
465 default_repo_perm.permission = self.sa.query(Permission)\
482 .filter(Permission.permission_name == 'repository.read')\
466 .filter(Permission.permission_name == 'repository.read')\
483 .scalar()
467 .scalar()
484
468
485 try:
469 try:
486 self.sa.add(reg_perm)
470 self.sa.add(reg_perm)
487 self.sa.add(create_repo_perm)
471 self.sa.add(create_repo_perm)
488 self.sa.add(default_repo_perm)
472 self.sa.add(default_repo_perm)
489 self.sa.commit()
473 self.sa.commit()
490 except:
474 except:
491 self.sa.rollback()
475 self.sa.rollback()
492 raise
476 raise
493
477
@@ -1,555 +1,557 b''
1 """Helper functions
1 """Helper functions
2
2
3 Consists of functions to typically be used within templates, but also
3 Consists of functions to typically be used within templates, but also
4 available to Controllers. This module is available to both as 'h'.
4 available to Controllers. This module is available to both as 'h'.
5 """
5 """
6 import random
6 import random
7 import hashlib
7 import hashlib
8 from pygments.formatters import HtmlFormatter
8 from pygments.formatters import HtmlFormatter
9 from pygments import highlight as code_highlight
9 from pygments import highlight as code_highlight
10 from pylons import url, app_globals as g
10 from pylons import url, app_globals as g
11 from pylons.i18n.translation import _, ungettext
11 from pylons.i18n.translation import _, ungettext
12 from vcs.utils.annotate import annotate_highlight
12 from vcs.utils.annotate import annotate_highlight
13 from webhelpers.html import literal, HTML, escape
13 from webhelpers.html import literal, HTML, escape
14 from webhelpers.html.tools import *
14 from webhelpers.html.tools import *
15 from webhelpers.html.builder import make_tag
15 from webhelpers.html.builder import make_tag
16 from webhelpers.html.tags import auto_discovery_link, checkbox, css_classes, \
16 from webhelpers.html.tags import auto_discovery_link, checkbox, css_classes, \
17 end_form, file, form, hidden, image, javascript_link, link_to, link_to_if, \
17 end_form, file, form, hidden, image, javascript_link, link_to, link_to_if, \
18 link_to_unless, ol, required_legend, select, stylesheet_link, submit, text, \
18 link_to_unless, ol, required_legend, select, stylesheet_link, submit, text, \
19 password, textarea, title, ul, xml_declaration, radio
19 password, textarea, title, ul, xml_declaration, radio
20 from webhelpers.html.tools import auto_link, button_to, highlight, js_obfuscate, \
20 from webhelpers.html.tools import auto_link, button_to, highlight, js_obfuscate, \
21 mail_to, strip_links, strip_tags, tag_re
21 mail_to, strip_links, strip_tags, tag_re
22 from webhelpers.number import format_byte_size, format_bit_size
22 from webhelpers.number import format_byte_size, format_bit_size
23 from webhelpers.pylonslib import Flash as _Flash
23 from webhelpers.pylonslib import Flash as _Flash
24 from webhelpers.pylonslib.secure_form import secure_form
24 from webhelpers.pylonslib.secure_form import secure_form
25 from webhelpers.text import chop_at, collapse, convert_accented_entities, \
25 from webhelpers.text import chop_at, collapse, convert_accented_entities, \
26 convert_misc_entities, lchop, plural, rchop, remove_formatting, \
26 convert_misc_entities, lchop, plural, rchop, remove_formatting, \
27 replace_whitespace, urlify, truncate, wrap_paragraphs
27 replace_whitespace, urlify, truncate, wrap_paragraphs
28 from webhelpers.date import time_ago_in_words
28 from webhelpers.date import time_ago_in_words
29
29
30 from webhelpers.html.tags import _set_input_attrs, _set_id_attr, \
30 from webhelpers.html.tags import _set_input_attrs, _set_id_attr, \
31 convert_boolean_attrs, NotGiven
31 convert_boolean_attrs, NotGiven
32
32
33 def _reset(name, value=None, id=NotGiven, type="reset", **attrs):
33 def _reset(name, value=None, id=NotGiven, type="reset", **attrs):
34 _set_input_attrs(attrs, type, name, value)
34 _set_input_attrs(attrs, type, name, value)
35 _set_id_attr(attrs, id, name)
35 _set_id_attr(attrs, id, name)
36 convert_boolean_attrs(attrs, ["disabled"])
36 convert_boolean_attrs(attrs, ["disabled"])
37 return HTML.input(**attrs)
37 return HTML.input(**attrs)
38
38
39 reset = _reset
39 reset = _reset
40
40
41
41
42 def get_token():
42 def get_token():
43 """Return the current authentication token, creating one if one doesn't
43 """Return the current authentication token, creating one if one doesn't
44 already exist.
44 already exist.
45 """
45 """
46 token_key = "_authentication_token"
46 token_key = "_authentication_token"
47 from pylons import session
47 from pylons import session
48 if not token_key in session:
48 if not token_key in session:
49 try:
49 try:
50 token = hashlib.sha1(str(random.getrandbits(128))).hexdigest()
50 token = hashlib.sha1(str(random.getrandbits(128))).hexdigest()
51 except AttributeError: # Python < 2.4
51 except AttributeError: # Python < 2.4
52 token = hashlib.sha1(str(random.randrange(2 ** 128))).hexdigest()
52 token = hashlib.sha1(str(random.randrange(2 ** 128))).hexdigest()
53 session[token_key] = token
53 session[token_key] = token
54 if hasattr(session, 'save'):
54 if hasattr(session, 'save'):
55 session.save()
55 session.save()
56 return session[token_key]
56 return session[token_key]
57
57
58
58
59 #Custom helpers here :)
59 #Custom helpers here :)
60 class _Link(object):
60 class _Link(object):
61 '''
61 '''
62 Make a url based on label and url with help of url_for
62 Make a url based on label and url with help of url_for
63 :param label:name of link if not defined url is used
63 :param label:name of link if not defined url is used
64 :param url: the url for link
64 :param url: the url for link
65 '''
65 '''
66
66
67 def __call__(self, label='', *url_, **urlargs):
67 def __call__(self, label='', *url_, **urlargs):
68 if label is None or '':
68 if label is None or '':
69 label = url
69 label = url
70 link_fn = link_to(label, url(*url_, **urlargs))
70 link_fn = link_to(label, url(*url_, **urlargs))
71 return link_fn
71 return link_fn
72
72
73 link = _Link()
73 link = _Link()
74
74
75 class _GetError(object):
75 class _GetError(object):
76
76
77 def __call__(self, field_name, form_errors):
77 def __call__(self, field_name, form_errors):
78 tmpl = """<span class="error_msg">%s</span>"""
78 tmpl = """<span class="error_msg">%s</span>"""
79 if form_errors and form_errors.has_key(field_name):
79 if form_errors and form_errors.has_key(field_name):
80 return literal(tmpl % form_errors.get(field_name))
80 return literal(tmpl % form_errors.get(field_name))
81
81
82 get_error = _GetError()
82 get_error = _GetError()
83
83
84 def recursive_replace(str, replace=' '):
84 def recursive_replace(str, replace=' '):
85 """
85 """
86 Recursive replace of given sign to just one instance
86 Recursive replace of given sign to just one instance
87 :param str: given string
87 :param str: given string
88 :param replace:char to find and replace multiple instances
88 :param replace:char to find and replace multiple instances
89
89
90 Examples::
90 Examples::
91 >>> recursive_replace("Mighty---Mighty-Bo--sstones",'-')
91 >>> recursive_replace("Mighty---Mighty-Bo--sstones",'-')
92 'Mighty-Mighty-Bo-sstones'
92 'Mighty-Mighty-Bo-sstones'
93 """
93 """
94
94
95 if str.find(replace * 2) == -1:
95 if str.find(replace * 2) == -1:
96 return str
96 return str
97 else:
97 else:
98 str = str.replace(replace * 2, replace)
98 str = str.replace(replace * 2, replace)
99 return recursive_replace(str, replace)
99 return recursive_replace(str, replace)
100
100
101 class _ToolTip(object):
101 class _ToolTip(object):
102
102
103 def __call__(self, tooltip_title, trim_at=50):
103 def __call__(self, tooltip_title, trim_at=50):
104 """
104 """
105 Special function just to wrap our text into nice formatted autowrapped
105 Special function just to wrap our text into nice formatted autowrapped
106 text
106 text
107 :param tooltip_title:
107 :param tooltip_title:
108 """
108 """
109
109
110 return wrap_paragraphs(escape(tooltip_title), trim_at)\
110 return wrap_paragraphs(escape(tooltip_title), trim_at)\
111 .replace('\n', '<br/>')
111 .replace('\n', '<br/>')
112
112
113 def activate(self):
113 def activate(self):
114 """
114 """
115 Adds tooltip mechanism to the given Html all tooltips have to have
115 Adds tooltip mechanism to the given Html all tooltips have to have
116 set class tooltip and set attribute tooltip_title.
116 set class tooltip and set attribute tooltip_title.
117 Then a tooltip will be generated based on that
117 Then a tooltip will be generated based on that
118 All with yui js tooltip
118 All with yui js tooltip
119 """
119 """
120
120
121 js = '''
121 js = '''
122 YAHOO.util.Event.onDOMReady(function(){
122 YAHOO.util.Event.onDOMReady(function(){
123 function toolTipsId(){
123 function toolTipsId(){
124 var ids = [];
124 var ids = [];
125 var tts = YAHOO.util.Dom.getElementsByClassName('tooltip');
125 var tts = YAHOO.util.Dom.getElementsByClassName('tooltip');
126
126
127 for (var i = 0; i < tts.length; i++) {
127 for (var i = 0; i < tts.length; i++) {
128 //if element doesn't not have and id autogenerate one for tooltip
128 //if element doesn't not have and id autogenerate one for tooltip
129
129
130 if (!tts[i].id){
130 if (!tts[i].id){
131 tts[i].id='tt'+i*100;
131 tts[i].id='tt'+i*100;
132 }
132 }
133 ids.push(tts[i].id);
133 ids.push(tts[i].id);
134 }
134 }
135 return ids
135 return ids
136 };
136 };
137 var myToolTips = new YAHOO.widget.Tooltip("tooltip", {
137 var myToolTips = new YAHOO.widget.Tooltip("tooltip", {
138 context: toolTipsId(),
138 context: toolTipsId(),
139 monitorresize:false,
139 monitorresize:false,
140 xyoffset :[0,0],
140 xyoffset :[0,0],
141 autodismissdelay:300000,
141 autodismissdelay:300000,
142 hidedelay:5,
142 hidedelay:5,
143 showdelay:20,
143 showdelay:20,
144 });
144 });
145
145
146 //Mouse Over event disabled for new repositories since they don't
146 //Mouse Over event disabled for new repositories since they don't
147 //have last commit message
147 //have last commit message
148 myToolTips.contextMouseOverEvent.subscribe(
148 myToolTips.contextMouseOverEvent.subscribe(
149 function(type, args) {
149 function(type, args) {
150 var context = args[0];
150 var context = args[0];
151 var txt = context.getAttribute('tooltip_title');
151 var txt = context.getAttribute('tooltip_title');
152 if(txt){
152 if(txt){
153 return true;
153 return true;
154 }
154 }
155 else{
155 else{
156 return false;
156 return false;
157 }
157 }
158 });
158 });
159
159
160
160
161 // Set the text for the tooltip just before we display it. Lazy method
161 // Set the text for the tooltip just before we display it. Lazy method
162 myToolTips.contextTriggerEvent.subscribe(
162 myToolTips.contextTriggerEvent.subscribe(
163 function(type, args) {
163 function(type, args) {
164
164
165
165
166 var context = args[0];
166 var context = args[0];
167
167
168 var txt = context.getAttribute('tooltip_title');
168 var txt = context.getAttribute('tooltip_title');
169 this.cfg.setProperty("text", txt);
169 this.cfg.setProperty("text", txt);
170
170
171
171
172 // positioning of tooltip
172 // positioning of tooltip
173 var tt_w = this.element.clientWidth;
173 var tt_w = this.element.clientWidth;
174 var tt_h = this.element.clientHeight;
174 var tt_h = this.element.clientHeight;
175
175
176 var context_w = context.offsetWidth;
176 var context_w = context.offsetWidth;
177 var context_h = context.offsetHeight;
177 var context_h = context.offsetHeight;
178
178
179 var pos_x = YAHOO.util.Dom.getX(context);
179 var pos_x = YAHOO.util.Dom.getX(context);
180 var pos_y = YAHOO.util.Dom.getY(context);
180 var pos_y = YAHOO.util.Dom.getY(context);
181
181
182 var display_strategy = 'top';
182 var display_strategy = 'top';
183 var xy_pos = [0,0];
183 var xy_pos = [0,0];
184 switch (display_strategy){
184 switch (display_strategy){
185
185
186 case 'top':
186 case 'top':
187 var cur_x = (pos_x+context_w/2)-(tt_w/2);
187 var cur_x = (pos_x+context_w/2)-(tt_w/2);
188 var cur_y = (pos_y-tt_h-4);
188 var cur_y = (pos_y-tt_h-4);
189 xy_pos = [cur_x,cur_y];
189 xy_pos = [cur_x,cur_y];
190 break;
190 break;
191 case 'bottom':
191 case 'bottom':
192 var cur_x = (pos_x+context_w/2)-(tt_w/2);
192 var cur_x = (pos_x+context_w/2)-(tt_w/2);
193 var cur_y = pos_y+context_h+4;
193 var cur_y = pos_y+context_h+4;
194 xy_pos = [cur_x,cur_y];
194 xy_pos = [cur_x,cur_y];
195 break;
195 break;
196 case 'left':
196 case 'left':
197 var cur_x = (pos_x-tt_w-4);
197 var cur_x = (pos_x-tt_w-4);
198 var cur_y = pos_y-((tt_h/2)-context_h/2);
198 var cur_y = pos_y-((tt_h/2)-context_h/2);
199 xy_pos = [cur_x,cur_y];
199 xy_pos = [cur_x,cur_y];
200 break;
200 break;
201 case 'right':
201 case 'right':
202 var cur_x = (pos_x+context_w+4);
202 var cur_x = (pos_x+context_w+4);
203 var cur_y = pos_y-((tt_h/2)-context_h/2);
203 var cur_y = pos_y-((tt_h/2)-context_h/2);
204 xy_pos = [cur_x,cur_y];
204 xy_pos = [cur_x,cur_y];
205 break;
205 break;
206 default:
206 default:
207 var cur_x = (pos_x+context_w/2)-(tt_w/2);
207 var cur_x = (pos_x+context_w/2)-(tt_w/2);
208 var cur_y = pos_y-tt_h-4;
208 var cur_y = pos_y-tt_h-4;
209 xy_pos = [cur_x,cur_y];
209 xy_pos = [cur_x,cur_y];
210 break;
210 break;
211
211
212 }
212 }
213
213
214 this.cfg.setProperty("xy",xy_pos);
214 this.cfg.setProperty("xy",xy_pos);
215
215
216 });
216 });
217
217
218 //Mouse out
218 //Mouse out
219 myToolTips.contextMouseOutEvent.subscribe(
219 myToolTips.contextMouseOutEvent.subscribe(
220 function(type, args) {
220 function(type, args) {
221 var context = args[0];
221 var context = args[0];
222
222
223 });
223 });
224 });
224 });
225 '''
225 '''
226 return literal(js)
226 return literal(js)
227
227
228 tooltip = _ToolTip()
228 tooltip = _ToolTip()
229
229
230 class _FilesBreadCrumbs(object):
230 class _FilesBreadCrumbs(object):
231
231
232 def __call__(self, repo_name, rev, paths):
232 def __call__(self, repo_name, rev, paths):
233 if isinstance(paths, str):
234 paths = paths.decode('utf-8', 'replace')
233 url_l = [link_to(repo_name, url('files_home',
235 url_l = [link_to(repo_name, url('files_home',
234 repo_name=repo_name,
236 repo_name=repo_name,
235 revision=rev, f_path=''))]
237 revision=rev, f_path=''))]
236 paths_l = paths.split('/')
238 paths_l = paths.split('/')
237
239
238 for cnt, p in enumerate(paths_l):
240 for cnt, p in enumerate(paths_l):
239 if p != '':
241 if p != '':
240 url_l.append(link_to(p, url('files_home',
242 url_l.append(link_to(p, url('files_home',
241 repo_name=repo_name,
243 repo_name=repo_name,
242 revision=rev,
244 revision=rev,
243 f_path='/'.join(paths_l[:cnt + 1]))))
245 f_path='/'.join(paths_l[:cnt + 1]))))
244
246
245 return literal('/'.join(url_l))
247 return literal('/'.join(url_l))
246
248
247 files_breadcrumbs = _FilesBreadCrumbs()
249 files_breadcrumbs = _FilesBreadCrumbs()
248 class CodeHtmlFormatter(HtmlFormatter):
250 class CodeHtmlFormatter(HtmlFormatter):
249
251
250 def wrap(self, source, outfile):
252 def wrap(self, source, outfile):
251 return self._wrap_div(self._wrap_pre(self._wrap_code(source)))
253 return self._wrap_div(self._wrap_pre(self._wrap_code(source)))
252
254
253 def _wrap_code(self, source):
255 def _wrap_code(self, source):
254 for cnt, it in enumerate(source):
256 for cnt, it in enumerate(source):
255 i, t = it
257 i, t = it
256 t = '<div id="#S-%s">%s</div>' % (cnt + 1, t)
258 t = '<div id="#S-%s">%s</div>' % (cnt + 1, t)
257 yield i, t
259 yield i, t
258 def pygmentize(filenode, **kwargs):
260 def pygmentize(filenode, **kwargs):
259 """
261 """
260 pygmentize function using pygments
262 pygmentize function using pygments
261 :param filenode:
263 :param filenode:
262 """
264 """
263 return literal(code_highlight(filenode.content,
265 return literal(code_highlight(filenode.content,
264 filenode.lexer, CodeHtmlFormatter(**kwargs)))
266 filenode.lexer, CodeHtmlFormatter(**kwargs)))
265
267
266 def pygmentize_annotation(filenode, **kwargs):
268 def pygmentize_annotation(filenode, **kwargs):
267 """
269 """
268 pygmentize function for annotation
270 pygmentize function for annotation
269 :param filenode:
271 :param filenode:
270 """
272 """
271
273
272 color_dict = {}
274 color_dict = {}
273 def gen_color():
275 def gen_color():
274 """generator for getting 10k of evenly distibuted colors using hsv color
276 """generator for getting 10k of evenly distibuted colors using hsv color
275 and golden ratio.
277 and golden ratio.
276 """
278 """
277 import colorsys
279 import colorsys
278 n = 10000
280 n = 10000
279 golden_ratio = 0.618033988749895
281 golden_ratio = 0.618033988749895
280 h = 0.22717784590367374
282 h = 0.22717784590367374
281 #generate 10k nice web friendly colors in the same order
283 #generate 10k nice web friendly colors in the same order
282 for c in xrange(n):
284 for c in xrange(n):
283 h += golden_ratio
285 h += golden_ratio
284 h %= 1
286 h %= 1
285 HSV_tuple = [h, 0.95, 0.95]
287 HSV_tuple = [h, 0.95, 0.95]
286 RGB_tuple = colorsys.hsv_to_rgb(*HSV_tuple)
288 RGB_tuple = colorsys.hsv_to_rgb(*HSV_tuple)
287 yield map(lambda x:str(int(x * 256)), RGB_tuple)
289 yield map(lambda x:str(int(x * 256)), RGB_tuple)
288
290
289 cgenerator = gen_color()
291 cgenerator = gen_color()
290
292
291 def get_color_string(cs):
293 def get_color_string(cs):
292 if color_dict.has_key(cs):
294 if color_dict.has_key(cs):
293 col = color_dict[cs]
295 col = color_dict[cs]
294 else:
296 else:
295 col = color_dict[cs] = cgenerator.next()
297 col = color_dict[cs] = cgenerator.next()
296 return "color: rgb(%s)! important;" % (', '.join(col))
298 return "color: rgb(%s)! important;" % (', '.join(col))
297
299
298 def url_func(changeset):
300 def url_func(changeset):
299 tooltip_html = "<div style='font-size:0.8em'><b>Author:</b>" + \
301 tooltip_html = "<div style='font-size:0.8em'><b>Author:</b>" + \
300 " %s<br/><b>Date:</b> %s</b><br/><b>Message:</b> %s<br/></div>"
302 " %s<br/><b>Date:</b> %s</b><br/><b>Message:</b> %s<br/></div>"
301
303
302 tooltip_html = tooltip_html % (changeset.author,
304 tooltip_html = tooltip_html % (changeset.author,
303 changeset.date,
305 changeset.date,
304 tooltip(changeset.message))
306 tooltip(changeset.message))
305 lnk_format = '%5s:%s' % ('r%s' % changeset.revision,
307 lnk_format = '%5s:%s' % ('r%s' % changeset.revision,
306 short_id(changeset.raw_id))
308 short_id(changeset.raw_id))
307 uri = link_to(
309 uri = link_to(
308 lnk_format,
310 lnk_format,
309 url('changeset_home', repo_name=changeset.repository.name,
311 url('changeset_home', repo_name=changeset.repository.name,
310 revision=changeset.raw_id),
312 revision=changeset.raw_id),
311 style=get_color_string(changeset.raw_id),
313 style=get_color_string(changeset.raw_id),
312 class_='tooltip',
314 class_='tooltip',
313 tooltip_title=tooltip_html
315 tooltip_title=tooltip_html
314 )
316 )
315
317
316 uri += '\n'
318 uri += '\n'
317 return uri
319 return uri
318 return literal(annotate_highlight(filenode, url_func, **kwargs))
320 return literal(annotate_highlight(filenode, url_func, **kwargs))
319
321
320 def repo_name_slug(value):
322 def repo_name_slug(value):
321 """Return slug of name of repository
323 """Return slug of name of repository
322 This function is called on each creation/modification
324 This function is called on each creation/modification
323 of repository to prevent bad names in repo
325 of repository to prevent bad names in repo
324 """
326 """
325 slug = remove_formatting(value)
327 slug = remove_formatting(value)
326 slug = strip_tags(slug)
328 slug = strip_tags(slug)
327
329
328 for c in """=[]\;'"<>,/~!@#$%^&*()+{}|: """:
330 for c in """=[]\;'"<>,/~!@#$%^&*()+{}|: """:
329 slug = slug.replace(c, '-')
331 slug = slug.replace(c, '-')
330 slug = recursive_replace(slug, '-')
332 slug = recursive_replace(slug, '-')
331 slug = collapse(slug, '-')
333 slug = collapse(slug, '-')
332 return slug
334 return slug
333
335
334 def get_changeset_safe(repo, rev):
336 def get_changeset_safe(repo, rev):
335 from vcs.backends.base import BaseRepository
337 from vcs.backends.base import BaseRepository
336 from vcs.exceptions import RepositoryError
338 from vcs.exceptions import RepositoryError
337 if not isinstance(repo, BaseRepository):
339 if not isinstance(repo, BaseRepository):
338 raise Exception('You must pass an Repository '
340 raise Exception('You must pass an Repository '
339 'object as first argument got %s', type(repo))
341 'object as first argument got %s', type(repo))
340
342
341 try:
343 try:
342 cs = repo.get_changeset(rev)
344 cs = repo.get_changeset(rev)
343 except RepositoryError:
345 except RepositoryError:
344 from rhodecode.lib.utils import EmptyChangeset
346 from rhodecode.lib.utils import EmptyChangeset
345 cs = EmptyChangeset()
347 cs = EmptyChangeset()
346 return cs
348 return cs
347
349
348
350
349 flash = _Flash()
351 flash = _Flash()
350
352
351
353
352 #==============================================================================
354 #==============================================================================
353 # MERCURIAL FILTERS available via h.
355 # MERCURIAL FILTERS available via h.
354 #==============================================================================
356 #==============================================================================
355 from mercurial import util
357 from mercurial import util
356 from mercurial.templatefilters import person as _person
358 from mercurial.templatefilters import person as _person
357
359
358
360
359
361
360 def _age(curdate):
362 def _age(curdate):
361 """turns a datetime into an age string."""
363 """turns a datetime into an age string."""
362
364
363 if not curdate:
365 if not curdate:
364 return ''
366 return ''
365
367
366 from datetime import timedelta, datetime
368 from datetime import timedelta, datetime
367
369
368 agescales = [("year", 3600 * 24 * 365),
370 agescales = [("year", 3600 * 24 * 365),
369 ("month", 3600 * 24 * 30),
371 ("month", 3600 * 24 * 30),
370 ("day", 3600 * 24),
372 ("day", 3600 * 24),
371 ("hour", 3600),
373 ("hour", 3600),
372 ("minute", 60),
374 ("minute", 60),
373 ("second", 1), ]
375 ("second", 1), ]
374
376
375 age = datetime.now() - curdate
377 age = datetime.now() - curdate
376 age_seconds = (age.days * agescales[2][1]) + age.seconds
378 age_seconds = (age.days * agescales[2][1]) + age.seconds
377 pos = 1
379 pos = 1
378 for scale in agescales:
380 for scale in agescales:
379 if scale[1] <= age_seconds:
381 if scale[1] <= age_seconds:
380 if pos == 6:pos = 5
382 if pos == 6:pos = 5
381 return time_ago_in_words(curdate, agescales[pos][0]) + ' ' + _('ago')
383 return time_ago_in_words(curdate, agescales[pos][0]) + ' ' + _('ago')
382 pos += 1
384 pos += 1
383
385
384 return _('just now')
386 return _('just now')
385
387
386 age = lambda x:_age(x)
388 age = lambda x:_age(x)
387 capitalize = lambda x: x.capitalize()
389 capitalize = lambda x: x.capitalize()
388 email = util.email
390 email = util.email
389 email_or_none = lambda x: util.email(x) if util.email(x) != x else None
391 email_or_none = lambda x: util.email(x) if util.email(x) != x else None
390 person = lambda x: _person(x)
392 person = lambda x: _person(x)
391 short_id = lambda x: x[:12]
393 short_id = lambda x: x[:12]
392
394
393
395
394 def bool2icon(value):
396 def bool2icon(value):
395 """
397 """
396 Returns True/False values represented as small html image of true/false
398 Returns True/False values represented as small html image of true/false
397 icons
399 icons
398 :param value: bool value
400 :param value: bool value
399 """
401 """
400
402
401 if value is True:
403 if value is True:
402 return HTML.tag('img', src=url("/images/icons/accept.png"), alt=_('True'))
404 return HTML.tag('img', src=url("/images/icons/accept.png"), alt=_('True'))
403
405
404 if value is False:
406 if value is False:
405 return HTML.tag('img', src=url("/images/icons/cancel.png"), alt=_('False'))
407 return HTML.tag('img', src=url("/images/icons/cancel.png"), alt=_('False'))
406
408
407 return value
409 return value
408
410
409
411
410 def action_parser(user_log):
412 def action_parser(user_log):
411 """
413 """
412 This helper will map the specified string action into translated
414 This helper will map the specified string action into translated
413 fancy names with icons and links
415 fancy names with icons and links
414
416
415 @param action:
417 @param action:
416 """
418 """
417 action = user_log.action
419 action = user_log.action
418 action_params = ' '
420 action_params = ' '
419
421
420 x = action.split(':')
422 x = action.split(':')
421
423
422 if len(x) > 1:
424 if len(x) > 1:
423 action, action_params = x
425 action, action_params = x
424
426
425 def get_cs_links():
427 def get_cs_links():
426 revs_limit = 5
428 revs_limit = 5
427 revs = action_params.split(',')
429 revs = action_params.split(',')
428 cs_links = " " + ', '.join ([link(rev,
430 cs_links = " " + ', '.join ([link(rev,
429 url('changeset_home',
431 url('changeset_home',
430 repo_name=user_log.repository.repo_name,
432 repo_name=user_log.repository.repo_name,
431 revision=rev)) for rev in revs[:revs_limit] ])
433 revision=rev)) for rev in revs[:revs_limit] ])
432 if len(revs) > revs_limit:
434 if len(revs) > revs_limit:
433 uniq_id = revs[0]
435 uniq_id = revs[0]
434 html_tmpl = ('<span> %s '
436 html_tmpl = ('<span> %s '
435 '<a class="show_more" id="_%s" href="#">%s</a> '
437 '<a class="show_more" id="_%s" href="#">%s</a> '
436 '%s</span>')
438 '%s</span>')
437 cs_links += html_tmpl % (_('and'), uniq_id, _('%s more') \
439 cs_links += html_tmpl % (_('and'), uniq_id, _('%s more') \
438 % (len(revs) - revs_limit),
440 % (len(revs) - revs_limit),
439 _('revisions'))
441 _('revisions'))
440
442
441 html_tmpl = '<span id="%s" style="display:none"> %s </span>'
443 html_tmpl = '<span id="%s" style="display:none"> %s </span>'
442 cs_links += html_tmpl % (uniq_id, ', '.join([link(rev,
444 cs_links += html_tmpl % (uniq_id, ', '.join([link(rev,
443 url('changeset_home',
445 url('changeset_home',
444 repo_name=user_log.repository.repo_name,
446 repo_name=user_log.repository.repo_name,
445 revision=rev)) for rev in revs[revs_limit:] ]))
447 revision=rev)) for rev in revs[revs_limit:] ]))
446
448
447 return cs_links
449 return cs_links
448
450
449 def get_fork_name():
451 def get_fork_name():
450 repo_name = action_params
452 repo_name = action_params
451 return str(link_to(action_params, url('summary_home',
453 return str(link_to(action_params, url('summary_home',
452 repo_name=repo_name,)))
454 repo_name=repo_name,)))
453
455
454 map = {'user_deleted_repo':(_('[deleted] repository'), None),
456 map = {'user_deleted_repo':(_('[deleted] repository'), None),
455 'user_created_repo':(_('[created] repository'), None),
457 'user_created_repo':(_('[created] repository'), None),
456 'user_forked_repo':(_('[forked] repository as'), get_fork_name),
458 'user_forked_repo':(_('[forked] repository as'), get_fork_name),
457 'user_updated_repo':(_('[updated] repository'), None),
459 'user_updated_repo':(_('[updated] repository'), None),
458 'admin_deleted_repo':(_('[delete] repository'), None),
460 'admin_deleted_repo':(_('[delete] repository'), None),
459 'admin_created_repo':(_('[created] repository'), None),
461 'admin_created_repo':(_('[created] repository'), None),
460 'admin_forked_repo':(_('[forked] repository'), None),
462 'admin_forked_repo':(_('[forked] repository'), None),
461 'admin_updated_repo':(_('[updated] repository'), None),
463 'admin_updated_repo':(_('[updated] repository'), None),
462 'push':(_('[pushed] '), get_cs_links),
464 'push':(_('[pushed] '), get_cs_links),
463 'pull':(_('[pulled] '), None),
465 'pull':(_('[pulled] '), None),
464 'started_following_repo':(_('[started following] repository'), None),
466 'started_following_repo':(_('[started following] repository'), None),
465 'stopped_following_repo':(_('[stopped following] repository'), None),
467 'stopped_following_repo':(_('[stopped following] repository'), None),
466 }
468 }
467
469
468 action_str = map.get(action, action)
470 action_str = map.get(action, action)
469 action = action_str[0].replace('[', '<span class="journal_highlight">')\
471 action = action_str[0].replace('[', '<span class="journal_highlight">')\
470 .replace(']', '</span>')
472 .replace(']', '</span>')
471 action_params_func = lambda :""
473 action_params_func = lambda :""
472
474
473 if action_str[1] is not None:
475 if action_str[1] is not None:
474 action_params_func = action_str[1]
476 action_params_func = action_str[1]
475
477
476 return literal(action + " " + action_params_func())
478 return literal(action + " " + action_params_func())
477
479
478 def action_parser_icon(user_log):
480 def action_parser_icon(user_log):
479 action = user_log.action
481 action = user_log.action
480 action_params = None
482 action_params = None
481 x = action.split(':')
483 x = action.split(':')
482
484
483 if len(x) > 1:
485 if len(x) > 1:
484 action, action_params = x
486 action, action_params = x
485
487
486 tmpl = """<img src="%s/%s" alt="%s"/>"""
488 tmpl = """<img src="%s%s" alt="%s"/>"""
487 map = {'user_deleted_repo':'database_delete.png',
489 map = {'user_deleted_repo':'database_delete.png',
488 'user_created_repo':'database_add.png',
490 'user_created_repo':'database_add.png',
489 'user_forked_repo':'arrow_divide.png',
491 'user_forked_repo':'arrow_divide.png',
490 'user_updated_repo':'database_edit.png',
492 'user_updated_repo':'database_edit.png',
491 'admin_deleted_repo':'database_delete.png',
493 'admin_deleted_repo':'database_delete.png',
492 'admin_created_repo':'database_add.png',
494 'admin_created_repo':'database_add.png',
493 'admin_forked_repo':'arrow_divide.png',
495 'admin_forked_repo':'arrow_divide.png',
494 'admin_updated_repo':'database_edit.png',
496 'admin_updated_repo':'database_edit.png',
495 'push':'script_add.png',
497 'push':'script_add.png',
496 'pull':'down_16.png',
498 'pull':'down_16.png',
497 'started_following_repo':'heart_add.png',
499 'started_following_repo':'heart_add.png',
498 'stopped_following_repo':'heart_delete.png',
500 'stopped_following_repo':'heart_delete.png',
499 }
501 }
500 return literal(tmpl % ((url('/images/icons/')),
502 return literal(tmpl % ((url('/images/icons/')),
501 map.get(action, action), action))
503 map.get(action, action), action))
502
504
503
505
504 #==============================================================================
506 #==============================================================================
505 # PERMS
507 # PERMS
506 #==============================================================================
508 #==============================================================================
507 from rhodecode.lib.auth import HasPermissionAny, HasPermissionAll, \
509 from rhodecode.lib.auth import HasPermissionAny, HasPermissionAll, \
508 HasRepoPermissionAny, HasRepoPermissionAll
510 HasRepoPermissionAny, HasRepoPermissionAll
509
511
510 #==============================================================================
512 #==============================================================================
511 # GRAVATAR URL
513 # GRAVATAR URL
512 #==============================================================================
514 #==============================================================================
513 import hashlib
515 import hashlib
514 import urllib
516 import urllib
515 from pylons import request
517 from pylons import request
516
518
517 def gravatar_url(email_address, size=30):
519 def gravatar_url(email_address, size=30):
518 ssl_enabled = 'https' == request.environ.get('wsgi.url_scheme')
520 ssl_enabled = 'https' == request.environ.get('wsgi.url_scheme')
519 default = 'identicon'
521 default = 'identicon'
520 baseurl_nossl = "http://www.gravatar.com/avatar/"
522 baseurl_nossl = "http://www.gravatar.com/avatar/"
521 baseurl_ssl = "https://secure.gravatar.com/avatar/"
523 baseurl_ssl = "https://secure.gravatar.com/avatar/"
522 baseurl = baseurl_ssl if ssl_enabled else baseurl_nossl
524 baseurl = baseurl_ssl if ssl_enabled else baseurl_nossl
523
525
524
526
525 # construct the url
527 # construct the url
526 gravatar_url = baseurl + hashlib.md5(email_address.lower()).hexdigest() + "?"
528 gravatar_url = baseurl + hashlib.md5(email_address.lower()).hexdigest() + "?"
527 gravatar_url += urllib.urlencode({'d':default, 's':str(size)})
529 gravatar_url += urllib.urlencode({'d':default, 's':str(size)})
528
530
529 return gravatar_url
531 return gravatar_url
530
532
531 def safe_unicode(str):
533 def safe_unicode(str):
532 """safe unicode function. In case of UnicodeDecode error we try to return
534 """safe unicode function. In case of UnicodeDecode error we try to return
533 unicode with errors replace, if this failes we return unicode with
535 unicode with errors replace, if this failes we return unicode with
534 string_escape decoding """
536 string_escape decoding """
535
537
536 try:
538 try:
537 u_str = unicode(str)
539 u_str = unicode(str)
538 except UnicodeDecodeError:
540 except UnicodeDecodeError:
539 try:
541 try:
540 u_str = unicode(str, 'utf-8', 'replace')
542 u_str = unicode(str, 'utf-8', 'replace')
541 except UnicodeDecodeError:
543 except UnicodeDecodeError:
542 #incase we have a decode error just represent as byte string
544 #incase we have a decode error just represent as byte string
543 u_str = unicode(str(str).encode('string_escape'))
545 u_str = unicode(str(str).encode('string_escape'))
544
546
545 return u_str
547 return u_str
546
548
547 def changed_tooltip(nodes):
549 def changed_tooltip(nodes):
548 if nodes:
550 if nodes:
549 pref = ': <br/> '
551 pref = ': <br/> '
550 suf = ''
552 suf = ''
551 if len(nodes) > 30:
553 if len(nodes) > 30:
552 suf = '<br/>' + _(' and %s more') % (len(nodes) - 30)
554 suf = '<br/>' + _(' and %s more') % (len(nodes) - 30)
553 return literal(pref + '<br/> '.join([x.path for x in nodes[:30]]) + suf)
555 return literal(pref + '<br/> '.join([x.path.decode('utf-8', 'replace') for x in nodes[:30]]) + suf)
554 else:
556 else:
555 return ': ' + _('No Files')
557 return ': ' + _('No Files')
@@ -1,271 +1,249 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) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2009-2011 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
13 # This program is free software; you can redistribute it and/or
14 # modify it under the terms of the GNU General Public License
14 # modify it under the terms of the GNU General Public License
15 # as published by the Free Software Foundation; version 2
15 # as published by the Free Software Foundation; version 2
16 # of the License or (at your opinion) any later version of the license.
16 # of the License or (at your opinion) any later version of the license.
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, write to the Free Software
24 # along with this program; if not, write to the Free Software
25 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
25 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
26 # MA 02110-1301, USA.
26 # MA 02110-1301, USA.
27 import logging
27 import logging
28 import datetime
28 import datetime
29 from datetime import date
29 from datetime import date
30
30
31 from sqlalchemy import *
31 from sqlalchemy import *
32 from sqlalchemy.exc import DatabaseError
32 from sqlalchemy.exc import DatabaseError
33 from sqlalchemy.orm import relationship, backref, class_mapper
33 from sqlalchemy.orm import relationship, backref
34 from sqlalchemy.orm.session import Session
34 from sqlalchemy.orm.interfaces import MapperExtension
35
35
36 from rhodecode.model.meta import Base
36 from rhodecode.model.meta import Base, Session
37
37
38 log = logging.getLogger(__name__)
38 log = logging.getLogger(__name__)
39
39
40 class BaseModel(object):
41
40
42 @classmethod
41 class RhodeCodeSettings(Base):
43 def _get_keys(cls):
44 """return column names for this model """
45 return class_mapper(cls).c.keys()
46
47 def get_dict(self):
48 """return dict with keys and values corresponding
49 to this model data """
50
51 d = {}
52 for k in self._get_keys():
53 d[k] = getattr(self, k)
54 return d
55
56 def get_appstruct(self):
57 """return list with keys and values tupples corresponding
58 to this model data """
59
60 l = []
61 for k in self._get_keys():
62 l.append((k, getattr(self, k),))
63 return l
64
65 def populate_obj(self, populate_dict):
66 """populate model with data from given populate_dict"""
67
68 for k in self._get_keys():
69 if k in populate_dict:
70 setattr(self, k, populate_dict[k])
71
72 class RhodeCodeSettings(Base, BaseModel):
73 __tablename__ = 'rhodecode_settings'
42 __tablename__ = 'rhodecode_settings'
74 __table_args__ = (UniqueConstraint('app_settings_name'), {'useexisting':True})
43 __table_args__ = (UniqueConstraint('app_settings_name'), {'useexisting':True})
75 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
44 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
76 app_settings_name = Column("app_settings_name", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
45 app_settings_name = Column("app_settings_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
77 app_settings_value = Column("app_settings_value", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
46 app_settings_value = Column("app_settings_value", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
78
47
79 def __init__(self, k='', v=''):
48 def __init__(self, k='', v=''):
80 self.app_settings_name = k
49 self.app_settings_name = k
81 self.app_settings_value = v
50 self.app_settings_value = v
82
51
83 def __repr__(self):
52 def __repr__(self):
84 return "<%s('%s:%s')>" % (self.__class__.__name__,
53 return "<%s('%s:%s')>" % (self.__class__.__name__,
85 self.app_settings_name, self.app_settings_value)
54 self.app_settings_name, self.app_settings_value)
86
55
87 class RhodeCodeUi(Base, BaseModel):
56 class RhodeCodeUi(Base):
88 __tablename__ = 'rhodecode_ui'
57 __tablename__ = 'rhodecode_ui'
89 __table_args__ = {'useexisting':True}
58 __table_args__ = {'useexisting':True}
90 ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
59 ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
91 ui_section = Column("ui_section", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
60 ui_section = Column("ui_section", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
92 ui_key = Column("ui_key", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
61 ui_key = Column("ui_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
93 ui_value = Column("ui_value", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
62 ui_value = Column("ui_value", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
94 ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True)
63 ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True)
95
64
96
65
97 class User(Base, BaseModel):
66 class User(Base):
98 __tablename__ = 'users'
67 __tablename__ = 'users'
99 __table_args__ = (UniqueConstraint('username'), UniqueConstraint('email'), {'useexisting':True})
68 __table_args__ = (UniqueConstraint('username'), UniqueConstraint('email'), {'useexisting':True})
100 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
69 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
101 username = Column("username", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
70 username = Column("username", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
102 password = Column("password", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
71 password = Column("password", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
103 active = Column("active", Boolean(), nullable=True, unique=None, default=None)
72 active = Column("active", Boolean(), nullable=True, unique=None, default=None)
104 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
73 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
105 name = Column("name", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
74 name = Column("name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
106 lastname = Column("lastname", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
75 lastname = Column("lastname", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
107 email = Column("email", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
76 email = Column("email", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
108 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
77 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
109 is_ldap = Column("is_ldap", Boolean(), nullable=False, unique=None, default=False)
78 is_ldap = Column("is_ldap", Boolean(), nullable=False, unique=None, default=False)
110
79
111 user_log = relationship('UserLog', cascade='all')
80 user_log = relationship('UserLog', cascade='all')
112 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
81 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
113
82
114 repositories = relationship('Repository')
83 repositories = relationship('Repository')
115 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
84 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
116
85
117 @property
86 @property
118 def full_contact(self):
87 def full_contact(self):
119 return '%s %s <%s>' % (self.name, self.lastname, self.email)
88 return '%s %s <%s>' % (self.name, self.lastname, self.email)
120
89
90 @property
91 def short_contact(self):
92 return '%s %s' % (self.name, self.lastname)
93
121
94
122 @property
95 @property
123 def is_admin(self):
96 def is_admin(self):
124 return self.admin
97 return self.admin
125
98
126 def __repr__(self):
99 def __repr__(self):
127 return "<%s('id:%s:%s')>" % (self.__class__.__name__,
100 return "<%s('id:%s:%s')>" % (self.__class__.__name__,
128 self.user_id, self.username)
101 self.user_id, self.username)
129
102
103 @classmethod
104 def by_username(cls, username):
105 return Session.query(cls).filter(cls.username == username).one()
106
107
130 def update_lastlogin(self):
108 def update_lastlogin(self):
131 """Update user lastlogin"""
109 """Update user lastlogin"""
132
110
133 try:
111 try:
134 session = Session.object_session(self)
112 session = Session.object_session(self)
135 self.last_login = datetime.datetime.now()
113 self.last_login = datetime.datetime.now()
136 session.add(self)
114 session.add(self)
137 session.commit()
115 session.commit()
138 log.debug('updated user %s lastlogin', self.username)
116 log.debug('updated user %s lastlogin', self.username)
139 except (DatabaseError,):
117 except (DatabaseError,):
140 session.rollback()
118 session.rollback()
141
119
142
120
143 class UserLog(Base, BaseModel):
121 class UserLog(Base):
144 __tablename__ = 'user_logs'
122 __tablename__ = 'user_logs'
145 __table_args__ = {'useexisting':True}
123 __table_args__ = {'useexisting':True}
146 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
124 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
147 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
125 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
148 repository_id = Column("repository_id", Integer(length=None, convert_unicode=False, assert_unicode=None), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
126 repository_id = Column("repository_id", Integer(length=None, convert_unicode=False, assert_unicode=None), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
149 repository_name = Column("repository_name", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
127 repository_name = Column("repository_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
150 user_ip = Column("user_ip", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
128 user_ip = Column("user_ip", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
151 action = Column("action", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
129 action = Column("action", UnicodeText(length=1200000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
152 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
130 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
153
131
154 @property
132 @property
155 def action_as_day(self):
133 def action_as_day(self):
156 return date(*self.action_date.timetuple()[:3])
134 return date(*self.action_date.timetuple()[:3])
157
135
158 user = relationship('User')
136 user = relationship('User')
159 repository = relationship('Repository')
137 repository = relationship('Repository')
160
138
161 class Repository(Base, BaseModel):
139 class Repository(Base):
162 __tablename__ = 'repositories'
140 __tablename__ = 'repositories'
163 __table_args__ = (UniqueConstraint('repo_name'), {'useexisting':True},)
141 __table_args__ = (UniqueConstraint('repo_name'), {'useexisting':True},)
164 repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
142 repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
165 repo_name = Column("repo_name", String(length=None, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
143 repo_name = Column("repo_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
166 repo_type = Column("repo_type", String(length=None, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default='hg')
144 repo_type = Column("repo_type", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default='hg')
167 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
145 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
168 private = Column("private", Boolean(), nullable=True, unique=None, default=None)
146 private = Column("private", Boolean(), nullable=True, unique=None, default=None)
169 enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True)
147 enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True)
170 description = Column("description", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
148 description = Column("description", String(length=10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
171 fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None)
149 fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None)
172
150
173 user = relationship('User')
151 user = relationship('User')
174 fork = relationship('Repository', remote_side=repo_id)
152 fork = relationship('Repository', remote_side=repo_id)
175 repo_to_perm = relationship('RepoToPerm', cascade='all')
153 repo_to_perm = relationship('RepoToPerm', cascade='all')
176 stats = relationship('Statistics', cascade='all', uselist=False)
154 stats = relationship('Statistics', cascade='all', uselist=False)
177
155
178 repo_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id', cascade='all')
156 repo_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id', cascade='all')
179
157
180 logs = relationship('UserLog', cascade='all')
158 logs = relationship('UserLog', cascade='all')
181
159
182 def __repr__(self):
160 def __repr__(self):
183 return "<%s('%s:%s')>" % (self.__class__.__name__,
161 return "<%s('%s:%s')>" % (self.__class__.__name__,
184 self.repo_id, self.repo_name)
162 self.repo_id, self.repo_name)
185
163
186 class Permission(Base, BaseModel):
164 class Permission(Base):
187 __tablename__ = 'permissions'
165 __tablename__ = 'permissions'
188 __table_args__ = {'useexisting':True}
166 __table_args__ = {'useexisting':True}
189 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
167 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
190 permission_name = Column("permission_name", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
168 permission_name = Column("permission_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
191 permission_longname = Column("permission_longname", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
169 permission_longname = Column("permission_longname", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
192
170
193 def __repr__(self):
171 def __repr__(self):
194 return "<%s('%s:%s')>" % (self.__class__.__name__,
172 return "<%s('%s:%s')>" % (self.__class__.__name__,
195 self.permission_id, self.permission_name)
173 self.permission_id, self.permission_name)
196
174
197 class RepoToPerm(Base, BaseModel):
175 class RepoToPerm(Base):
198 __tablename__ = 'repo_to_perm'
176 __tablename__ = 'repo_to_perm'
199 __table_args__ = (UniqueConstraint('user_id', 'repository_id'), {'useexisting':True})
177 __table_args__ = (UniqueConstraint('user_id', 'repository_id'), {'useexisting':True})
200 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
178 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
201 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
179 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
202 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
180 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
203 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
181 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
204
182
205 user = relationship('User')
183 user = relationship('User')
206 permission = relationship('Permission')
184 permission = relationship('Permission')
207 repository = relationship('Repository')
185 repository = relationship('Repository')
208
186
209 class UserToPerm(Base, BaseModel):
187 class UserToPerm(Base):
210 __tablename__ = 'user_to_perm'
188 __tablename__ = 'user_to_perm'
211 __table_args__ = (UniqueConstraint('user_id', 'permission_id'), {'useexisting':True})
189 __table_args__ = (UniqueConstraint('user_id', 'permission_id'), {'useexisting':True})
212 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
190 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
213 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
191 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
214 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
192 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
215
193
216 user = relationship('User')
194 user = relationship('User')
217 permission = relationship('Permission')
195 permission = relationship('Permission')
218
196
219 class Statistics(Base, BaseModel):
197 class Statistics(Base):
220 __tablename__ = 'statistics'
198 __tablename__ = 'statistics'
221 __table_args__ = (UniqueConstraint('repository_id'), {'useexisting':True})
199 __table_args__ = (UniqueConstraint('repository_id'), {'useexisting':True})
222 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
200 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
223 repository_id = Column("repository_id", Integer(), ForeignKey(u'repositories.repo_id'), nullable=False, unique=True, default=None)
201 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
224 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
202 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
225 commit_activity = Column("commit_activity", LargeBinary(), nullable=False)#JSON data
203 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
226 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
204 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
227 languages = Column("languages", LargeBinary(), nullable=False)#JSON data
205 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
228
206
229 repository = relationship('Repository', single_parent=True)
207 repository = relationship('Repository', single_parent=True)
230
208
231 class UserFollowing(Base, BaseModel):
209 class UserFollowing(Base):
232 __tablename__ = 'user_followings'
210 __tablename__ = 'user_followings'
233 __table_args__ = (UniqueConstraint('user_id', 'follows_repository_id'),
211 __table_args__ = (UniqueConstraint('user_id', 'follows_repository_id'),
234 UniqueConstraint('user_id', 'follows_user_id')
212 UniqueConstraint('user_id', 'follows_user_id')
235 , {'useexisting':True})
213 , {'useexisting':True})
236
214
237 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
215 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
238 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
216 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
239 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
217 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
240 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
218 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
241
219
242 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
220 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
243
221
244 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
222 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
245 follows_repository = relationship('Repository', order_by='Repository.repo_name')
223 follows_repository = relationship('Repository', order_by='Repository.repo_name')
246
224
247 class CacheInvalidation(Base, BaseModel):
225 class CacheInvalidation(Base):
248 __tablename__ = 'cache_invalidation'
226 __tablename__ = 'cache_invalidation'
249 __table_args__ = (UniqueConstraint('cache_key'), {'useexisting':True})
227 __table_args__ = (UniqueConstraint('cache_key'), {'useexisting':True})
250 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
228 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
251 cache_key = Column("cache_key", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
229 cache_key = Column("cache_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
252 cache_args = Column("cache_args", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
230 cache_args = Column("cache_args", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
253 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
231 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
254
232
255
233
256 def __init__(self, cache_key, cache_args=''):
234 def __init__(self, cache_key, cache_args=''):
257 self.cache_key = cache_key
235 self.cache_key = cache_key
258 self.cache_args = cache_args
236 self.cache_args = cache_args
259 self.cache_active = False
237 self.cache_active = False
260
238
261 def __repr__(self):
239 def __repr__(self):
262 return "<%s('%s:%s')>" % (self.__class__.__name__,
240 return "<%s('%s:%s')>" % (self.__class__.__name__,
263 self.cache_id, self.cache_key)
241 self.cache_id, self.cache_key)
264
242
265 class DbMigrateVersion(Base, BaseModel):
243 class DbMigrateVersion(Base):
266 __tablename__ = 'db_migrate_version'
244 __tablename__ = 'db_migrate_version'
267 __table_args__ = {'useexisting':True}
245 __table_args__ = {'useexisting':True}
268 repository_id = Column('repository_id', String(250), primary_key=True)
246 repository_id = Column('repository_id', String(255), primary_key=True)
269 repository_path = Column('repository_path', Text)
247 repository_path = Column('repository_path', Text)
270 version = Column('version', Integer)
248 version = Column('version', Integer)
271
249
@@ -1,26 +1,70 b''
1 """SQLAlchemy Metadata and Session object"""
1 """SQLAlchemy Metadata and Session object"""
2 from sqlalchemy.ext.declarative import declarative_base
2 from sqlalchemy.ext.declarative import declarative_base
3 from sqlalchemy.orm import scoped_session, sessionmaker
3 from sqlalchemy.orm import scoped_session, sessionmaker, class_mapper
4 from beaker import cache
5
4 from rhodecode.model import caching_query
6 from rhodecode.model import caching_query
5 from beaker import cache
7
6
8
7 # Beaker CacheManager. A home base for cache configurations.
9 # Beaker CacheManager. A home base for cache configurations.
8 cache_manager = cache.CacheManager()
10 cache_manager = cache.CacheManager()
9
11
10 __all__ = ['Base', 'Session']
12 __all__ = ['Base', 'Session']
11 #
13 #
12 # SQLAlchemy session manager. Updated by model.init_model()
14 # SQLAlchemy session manager. Updated by model.init_model()
13 #
15 #
14 Session = scoped_session(
16 Session = scoped_session(
15 sessionmaker(
17 sessionmaker(
16 query_cls=caching_query.query_callable(cache_manager)
18 query_cls=caching_query.query_callable(cache_manager)
17 )
19 )
18 )
20 )
19
21
22 class BaseModel(object):
23 """Base Model for all classess
24
25 """
26
27 @classmethod
28 def _get_keys(cls):
29 """return column names for this model """
30 return class_mapper(cls).c.keys()
31
32 def get_dict(self):
33 """return dict with keys and values corresponding
34 to this model data """
35
36 d = {}
37 for k in self._get_keys():
38 d[k] = getattr(self, k)
39 return d
40
41 def get_appstruct(self):
42 """return list with keys and values tupples corresponding
43 to this model data """
44
45 l = []
46 for k in self._get_keys():
47 l.append((k, getattr(self, k),))
48 return l
49
50 def populate_obj(self, populate_dict):
51 """populate model with data from given populate_dict"""
52
53 for k in self._get_keys():
54 if k in populate_dict:
55 setattr(self, k, populate_dict[k])
56
57 @classmethod
58 def query(cls):
59 return Session.query(cls)
60
61 @classmethod
62 def get(cls, id_):
63 return Session.query(cls).get(id_)
64
65
20 # The declarative Base
66 # The declarative Base
21 Base = declarative_base()
67 Base = declarative_base(cls=BaseModel)
22 #For another db...
23 #Base2 = declarative_base()
24
68
25 #to use cache use this in query
69 #to use cache use this in query
26 #.options(FromCache("sqlalchemy_cache_type", "cachekey"))
70 #.options(FromCache("sqlalchemy_cache_type", "cachekey"))
@@ -1,377 +1,378 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.model.scm
3 rhodecode.model.scm
4 ~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~
5
5
6 Scm model for RhodeCode
6 Scm model for RhodeCode
7
7
8 :created_on: Apr 9, 2010
8 :created_on: Apr 9, 2010
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2009-2010 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
13 # This program is free software; you can redistribute it and/or
14 # modify it under the terms of the GNU General Public License
14 # modify it under the terms of the GNU General Public License
15 # as published by the Free Software Foundation; version 2
15 # as published by the Free Software Foundation; version 2
16 # of the License or (at your opinion) any later version of the license.
16 # of the License or (at your opinion) any later version of the license.
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, write to the Free Software
24 # along with this program; if not, write to the Free Software
25 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
25 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
26 # MA 02110-1301, USA.
26 # MA 02110-1301, USA.
27 import os
27 import os
28 import time
28 import time
29 import traceback
29 import traceback
30 import logging
30 import logging
31
31
32 from vcs import get_backend
32 from vcs import get_backend
33 from vcs.utils.helpers import get_scm
33 from vcs.utils.helpers import get_scm
34 from vcs.exceptions import RepositoryError, VCSError
34 from vcs.exceptions import RepositoryError, VCSError
35 from vcs.utils.lazy import LazyProperty
35 from vcs.utils.lazy import LazyProperty
36
36
37 from mercurial import ui
37 from mercurial import ui
38
38
39 from beaker.cache import cache_region, region_invalidate
39 from beaker.cache import cache_region, region_invalidate
40
40
41 from rhodecode import BACKENDS
41 from rhodecode import BACKENDS
42 from rhodecode.lib import helpers as h
42 from rhodecode.lib import helpers as h
43 from rhodecode.lib.auth import HasRepoPermissionAny
43 from rhodecode.lib.auth import HasRepoPermissionAny
44 from rhodecode.lib.utils import get_repos, make_ui, action_logger
44 from rhodecode.lib.utils import get_repos, make_ui, action_logger
45 from rhodecode.model import BaseModel
45 from rhodecode.model import BaseModel
46 from rhodecode.model.user import UserModel
46 from rhodecode.model.user import UserModel
47
47
48 from rhodecode.model.db import Repository, RhodeCodeUi, CacheInvalidation, \
48 from rhodecode.model.db import Repository, RhodeCodeUi, CacheInvalidation, \
49 UserFollowing, UserLog
49 UserFollowing, UserLog
50 from rhodecode.model.caching_query import FromCache
50 from rhodecode.model.caching_query import FromCache
51
51
52 from sqlalchemy.orm import joinedload
52 from sqlalchemy.orm import joinedload
53 from sqlalchemy.orm.session import make_transient
53 from sqlalchemy.orm.session import make_transient
54 from sqlalchemy.exc import DatabaseError
54 from sqlalchemy.exc import DatabaseError
55
55
56 log = logging.getLogger(__name__)
56 log = logging.getLogger(__name__)
57
57
58
58
59 class UserTemp(object):
59 class UserTemp(object):
60 def __init__(self, user_id):
60 def __init__(self, user_id):
61 self.user_id = user_id
61 self.user_id = user_id
62 class RepoTemp(object):
62 class RepoTemp(object):
63 def __init__(self, repo_id):
63 def __init__(self, repo_id):
64 self.repo_id = repo_id
64 self.repo_id = repo_id
65
65
66 class ScmModel(BaseModel):
66 class ScmModel(BaseModel):
67 """Generic Scm Model
67 """Generic Scm Model
68 """
68 """
69
69
70 @LazyProperty
70 @LazyProperty
71 def repos_path(self):
71 def repos_path(self):
72 """Get's the repositories root path from database
72 """Get's the repositories root path from database
73 """
73 """
74
74
75 q = self.sa.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key == '/').one()
75 q = self.sa.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key == '/').one()
76
76
77 return q.ui_value
77 return q.ui_value
78
78
79 def repo_scan(self, repos_path, baseui):
79 def repo_scan(self, repos_path, baseui):
80 """Listing of repositories in given path. This path should not be a
80 """Listing of repositories in given path. This path should not be a
81 repository itself. Return a dictionary of repository objects
81 repository itself. Return a dictionary of repository objects
82
82
83 :param repos_path: path to directory containing repositories
83 :param repos_path: path to directory containing repositories
84 :param baseui: baseui instance to instantiate MercurialRepostitory with
84 :param baseui: baseui instance to instantiate MercurialRepostitory with
85 """
85 """
86
86
87 log.info('scanning for repositories in %s', repos_path)
87 log.info('scanning for repositories in %s', repos_path)
88
88
89 if not isinstance(baseui, ui.ui):
89 if not isinstance(baseui, ui.ui):
90 baseui = make_ui('db')
90 baseui = make_ui('db')
91 repos_list = {}
91 repos_list = {}
92
92
93 for name, path in get_repos(repos_path):
93 for name, path in get_repos(repos_path):
94 try:
94 try:
95 if repos_list.has_key(name):
95 if repos_list.has_key(name):
96 raise RepositoryError('Duplicate repository name %s '
96 raise RepositoryError('Duplicate repository name %s '
97 'found in %s' % (name, path))
97 'found in %s' % (name, path))
98 else:
98 else:
99
99
100 klass = get_backend(path[0])
100 klass = get_backend(path[0])
101
101
102 if path[0] == 'hg' and path[0] in BACKENDS.keys():
102 if path[0] == 'hg' and path[0] in BACKENDS.keys():
103 repos_list[name] = klass(path[1], baseui=baseui)
103 repos_list[name] = klass(path[1], baseui=baseui)
104
104
105 if path[0] == 'git' and path[0] in BACKENDS.keys():
105 if path[0] == 'git' and path[0] in BACKENDS.keys():
106 repos_list[name] = klass(path[1])
106 repos_list[name] = klass(path[1])
107 except OSError:
107 except OSError:
108 continue
108 continue
109
109
110 return repos_list
110 return repos_list
111
111
112 def get_repos(self, all_repos=None):
112 def get_repos(self, all_repos=None):
113 """Get all repos from db and for each repo create it's backend instance.
113 """Get all repos from db and for each repo create it's backend instance.
114 and fill that backed with information from database
114 and fill that backed with information from database
115
115
116 :param all_repos: give specific repositories list, good for filtering
116 :param all_repos: give specific repositories list, good for filtering
117 """
117 """
118
118
119 if all_repos is None:
119 if all_repos is None:
120 all_repos = self.sa.query(Repository)\
120 all_repos = self.sa.query(Repository)\
121 .order_by(Repository.repo_name).all()
121 .order_by(Repository.repo_name).all()
122
122
123 #get the repositories that should be invalidated
123 #get the repositories that should be invalidated
124 invalidation_list = [str(x.cache_key) for x in \
124 invalidation_list = [str(x.cache_key) for x in \
125 self.sa.query(CacheInvalidation.cache_key)\
125 self.sa.query(CacheInvalidation.cache_key)\
126 .filter(CacheInvalidation.cache_active == False)\
126 .filter(CacheInvalidation.cache_active == False)\
127 .all()]
127 .all()]
128
128
129 for r in all_repos:
129 for r in all_repos:
130
130
131 repo = self.get(r.repo_name, invalidation_list)
131 repo = self.get(r.repo_name, invalidation_list)
132
132
133 if repo is not None:
133 if repo is not None:
134 last_change = repo.last_change
134 last_change = repo.last_change
135 tip = h.get_changeset_safe(repo, 'tip')
135 tip = h.get_changeset_safe(repo, 'tip')
136
136
137 tmp_d = {}
137 tmp_d = {}
138 tmp_d['name'] = repo.name
138 tmp_d['name'] = repo.name
139 tmp_d['name_sort'] = tmp_d['name'].lower()
139 tmp_d['name_sort'] = tmp_d['name'].lower()
140 tmp_d['description'] = repo.dbrepo.description
140 tmp_d['description'] = repo.dbrepo.description
141 tmp_d['description_sort'] = tmp_d['description']
141 tmp_d['description_sort'] = tmp_d['description']
142 tmp_d['last_change'] = last_change
142 tmp_d['last_change'] = last_change
143 tmp_d['last_change_sort'] = time.mktime(last_change.timetuple())
143 tmp_d['last_change_sort'] = time.mktime(last_change.timetuple())
144 tmp_d['tip'] = tip.raw_id
144 tmp_d['tip'] = tip.raw_id
145 tmp_d['tip_sort'] = tip.revision
145 tmp_d['tip_sort'] = tip.revision
146 tmp_d['rev'] = tip.revision
146 tmp_d['rev'] = tip.revision
147 tmp_d['contact'] = repo.dbrepo.user.full_contact
147 tmp_d['contact'] = repo.dbrepo.user.full_contact
148 tmp_d['contact_sort'] = tmp_d['contact']
148 tmp_d['contact_sort'] = tmp_d['contact']
149 tmp_d['owner_sort'] = tmp_d['contact']
149 tmp_d['repo_archives'] = list(repo._get_archives())
150 tmp_d['repo_archives'] = list(repo._get_archives())
150 tmp_d['last_msg'] = tip.message
151 tmp_d['last_msg'] = tip.message
151 tmp_d['repo'] = repo
152 tmp_d['repo'] = repo
152 yield tmp_d
153 yield tmp_d
153
154
154 def get_repo(self, repo_name):
155 def get_repo(self, repo_name):
155 return self.get(repo_name)
156 return self.get(repo_name)
156
157
157 def get(self, repo_name, invalidation_list=None):
158 def get(self, repo_name, invalidation_list=None):
158 """Get's repository from given name, creates BackendInstance and
159 """Get's repository from given name, creates BackendInstance and
159 propagates it's data from database with all additional information
160 propagates it's data from database with all additional information
160
161
161 :param repo_name:
162 :param repo_name:
162 :param invalidation_list: if a invalidation list is given the get
163 :param invalidation_list: if a invalidation list is given the get
163 method should not manually check if this repository needs
164 method should not manually check if this repository needs
164 invalidation and just invalidate the repositories in list
165 invalidation and just invalidate the repositories in list
165
166
166 """
167 """
167 if not HasRepoPermissionAny('repository.read', 'repository.write',
168 if not HasRepoPermissionAny('repository.read', 'repository.write',
168 'repository.admin')(repo_name, 'get repo check'):
169 'repository.admin')(repo_name, 'get repo check'):
169 return
170 return
170
171
171 #======================================================================
172 #======================================================================
172 # CACHE FUNCTION
173 # CACHE FUNCTION
173 #======================================================================
174 #======================================================================
174 @cache_region('long_term')
175 @cache_region('long_term')
175 def _get_repo(repo_name):
176 def _get_repo(repo_name):
176
177
177 repo_path = os.path.join(self.repos_path, repo_name)
178 repo_path = os.path.join(self.repos_path, repo_name)
178
179
179 try:
180 try:
180 alias = get_scm(repo_path)[0]
181 alias = get_scm(repo_path)[0]
181
182
182 log.debug('Creating instance of %s repository', alias)
183 log.debug('Creating instance of %s repository', alias)
183 backend = get_backend(alias)
184 backend = get_backend(alias)
184 except VCSError:
185 except VCSError:
185 log.error(traceback.format_exc())
186 log.error(traceback.format_exc())
186 return
187 return
187
188
188 if alias == 'hg':
189 if alias == 'hg':
189 from pylons import app_globals as g
190 from pylons import app_globals as g
190 repo = backend(repo_path, create=False, baseui=g.baseui)
191 repo = backend(repo_path, create=False, baseui=g.baseui)
191 #skip hidden web repository
192 #skip hidden web repository
192 if repo._get_hidden():
193 if repo._get_hidden():
193 return
194 return
194 else:
195 else:
195 repo = backend(repo_path, create=False)
196 repo = backend(repo_path, create=False)
196
197
197 dbrepo = self.sa.query(Repository)\
198 dbrepo = self.sa.query(Repository)\
198 .options(joinedload(Repository.fork))\
199 .options(joinedload(Repository.fork))\
199 .options(joinedload(Repository.user))\
200 .options(joinedload(Repository.user))\
200 .filter(Repository.repo_name == repo_name)\
201 .filter(Repository.repo_name == repo_name)\
201 .scalar()
202 .scalar()
202
203
203 make_transient(dbrepo)
204 make_transient(dbrepo)
204 if dbrepo.user:
205 if dbrepo.user:
205 make_transient(dbrepo.user)
206 make_transient(dbrepo.user)
206 if dbrepo.fork:
207 if dbrepo.fork:
207 make_transient(dbrepo.fork)
208 make_transient(dbrepo.fork)
208
209
209 repo.dbrepo = dbrepo
210 repo.dbrepo = dbrepo
210 return repo
211 return repo
211
212
212 pre_invalidate = True
213 pre_invalidate = True
213 if invalidation_list is not None:
214 if invalidation_list is not None:
214 pre_invalidate = repo_name in invalidation_list
215 pre_invalidate = repo_name in invalidation_list
215
216
216 if pre_invalidate:
217 if pre_invalidate:
217 invalidate = self._should_invalidate(repo_name)
218 invalidate = self._should_invalidate(repo_name)
218
219
219 if invalidate:
220 if invalidate:
220 log.info('invalidating cache for repository %s', repo_name)
221 log.info('invalidating cache for repository %s', repo_name)
221 region_invalidate(_get_repo, None, repo_name)
222 region_invalidate(_get_repo, None, repo_name)
222 self._mark_invalidated(invalidate)
223 self._mark_invalidated(invalidate)
223
224
224 return _get_repo(repo_name)
225 return _get_repo(repo_name)
225
226
226
227
227
228
228 def mark_for_invalidation(self, repo_name):
229 def mark_for_invalidation(self, repo_name):
229 """Puts cache invalidation task into db for
230 """Puts cache invalidation task into db for
230 further global cache invalidation
231 further global cache invalidation
231
232
232 :param repo_name: this repo that should invalidation take place
233 :param repo_name: this repo that should invalidation take place
233 """
234 """
234
235
235 log.debug('marking %s for invalidation', repo_name)
236 log.debug('marking %s for invalidation', repo_name)
236 cache = self.sa.query(CacheInvalidation)\
237 cache = self.sa.query(CacheInvalidation)\
237 .filter(CacheInvalidation.cache_key == repo_name).scalar()
238 .filter(CacheInvalidation.cache_key == repo_name).scalar()
238
239
239 if cache:
240 if cache:
240 #mark this cache as inactive
241 #mark this cache as inactive
241 cache.cache_active = False
242 cache.cache_active = False
242 else:
243 else:
243 log.debug('cache key not found in invalidation db -> creating one')
244 log.debug('cache key not found in invalidation db -> creating one')
244 cache = CacheInvalidation(repo_name)
245 cache = CacheInvalidation(repo_name)
245
246
246 try:
247 try:
247 self.sa.add(cache)
248 self.sa.add(cache)
248 self.sa.commit()
249 self.sa.commit()
249 except (DatabaseError,):
250 except (DatabaseError,):
250 log.error(traceback.format_exc())
251 log.error(traceback.format_exc())
251 self.sa.rollback()
252 self.sa.rollback()
252
253
253
254
254 def toggle_following_repo(self, follow_repo_id, user_id):
255 def toggle_following_repo(self, follow_repo_id, user_id):
255
256
256 f = self.sa.query(UserFollowing)\
257 f = self.sa.query(UserFollowing)\
257 .filter(UserFollowing.follows_repo_id == follow_repo_id)\
258 .filter(UserFollowing.follows_repo_id == follow_repo_id)\
258 .filter(UserFollowing.user_id == user_id).scalar()
259 .filter(UserFollowing.user_id == user_id).scalar()
259
260
260 if f is not None:
261 if f is not None:
261
262
262 try:
263 try:
263 self.sa.delete(f)
264 self.sa.delete(f)
264 self.sa.commit()
265 self.sa.commit()
265 action_logger(UserTemp(user_id),
266 action_logger(UserTemp(user_id),
266 'stopped_following_repo',
267 'stopped_following_repo',
267 RepoTemp(follow_repo_id))
268 RepoTemp(follow_repo_id))
268 return
269 return
269 except:
270 except:
270 log.error(traceback.format_exc())
271 log.error(traceback.format_exc())
271 self.sa.rollback()
272 self.sa.rollback()
272 raise
273 raise
273
274
274
275
275 try:
276 try:
276 f = UserFollowing()
277 f = UserFollowing()
277 f.user_id = user_id
278 f.user_id = user_id
278 f.follows_repo_id = follow_repo_id
279 f.follows_repo_id = follow_repo_id
279 self.sa.add(f)
280 self.sa.add(f)
280 self.sa.commit()
281 self.sa.commit()
281 action_logger(UserTemp(user_id),
282 action_logger(UserTemp(user_id),
282 'started_following_repo',
283 'started_following_repo',
283 RepoTemp(follow_repo_id))
284 RepoTemp(follow_repo_id))
284 except:
285 except:
285 log.error(traceback.format_exc())
286 log.error(traceback.format_exc())
286 self.sa.rollback()
287 self.sa.rollback()
287 raise
288 raise
288
289
289 def toggle_following_user(self, follow_user_id , user_id):
290 def toggle_following_user(self, follow_user_id , user_id):
290 f = self.sa.query(UserFollowing)\
291 f = self.sa.query(UserFollowing)\
291 .filter(UserFollowing.follows_user_id == follow_user_id)\
292 .filter(UserFollowing.follows_user_id == follow_user_id)\
292 .filter(UserFollowing.user_id == user_id).scalar()
293 .filter(UserFollowing.user_id == user_id).scalar()
293
294
294 if f is not None:
295 if f is not None:
295 try:
296 try:
296 self.sa.delete(f)
297 self.sa.delete(f)
297 self.sa.commit()
298 self.sa.commit()
298 return
299 return
299 except:
300 except:
300 log.error(traceback.format_exc())
301 log.error(traceback.format_exc())
301 self.sa.rollback()
302 self.sa.rollback()
302 raise
303 raise
303
304
304 try:
305 try:
305 f = UserFollowing()
306 f = UserFollowing()
306 f.user_id = user_id
307 f.user_id = user_id
307 f.follows_user_id = follow_user_id
308 f.follows_user_id = follow_user_id
308 self.sa.add(f)
309 self.sa.add(f)
309 self.sa.commit()
310 self.sa.commit()
310 except:
311 except:
311 log.error(traceback.format_exc())
312 log.error(traceback.format_exc())
312 self.sa.rollback()
313 self.sa.rollback()
313 raise
314 raise
314
315
315 def is_following_repo(self, repo_name, user_id):
316 def is_following_repo(self, repo_name, user_id):
316 r = self.sa.query(Repository)\
317 r = self.sa.query(Repository)\
317 .filter(Repository.repo_name == repo_name).scalar()
318 .filter(Repository.repo_name == repo_name).scalar()
318
319
319 f = self.sa.query(UserFollowing)\
320 f = self.sa.query(UserFollowing)\
320 .filter(UserFollowing.follows_repository == r)\
321 .filter(UserFollowing.follows_repository == r)\
321 .filter(UserFollowing.user_id == user_id).scalar()
322 .filter(UserFollowing.user_id == user_id).scalar()
322
323
323 return f is not None
324 return f is not None
324
325
325 def is_following_user(self, username, user_id):
326 def is_following_user(self, username, user_id):
326 u = UserModel(self.sa).get_by_username(username)
327 u = UserModel(self.sa).get_by_username(username)
327
328
328 f = self.sa.query(UserFollowing)\
329 f = self.sa.query(UserFollowing)\
329 .filter(UserFollowing.follows_user == u)\
330 .filter(UserFollowing.follows_user == u)\
330 .filter(UserFollowing.user_id == user_id).scalar()
331 .filter(UserFollowing.user_id == user_id).scalar()
331
332
332 return f is not None
333 return f is not None
333
334
334 def get_followers(self, repo_id):
335 def get_followers(self, repo_id):
335 return self.sa.query(UserFollowing)\
336 return self.sa.query(UserFollowing)\
336 .filter(UserFollowing.follows_repo_id == repo_id).count()
337 .filter(UserFollowing.follows_repo_id == repo_id).count()
337
338
338 def get_forks(self, repo_id):
339 def get_forks(self, repo_id):
339 return self.sa.query(Repository)\
340 return self.sa.query(Repository)\
340 .filter(Repository.fork_id == repo_id).count()
341 .filter(Repository.fork_id == repo_id).count()
341
342
342
343
343 def get_unread_journal(self):
344 def get_unread_journal(self):
344 return self.sa.query(UserLog).count()
345 return self.sa.query(UserLog).count()
345
346
346
347
347 def _should_invalidate(self, repo_name):
348 def _should_invalidate(self, repo_name):
348 """Looks up database for invalidation signals for this repo_name
349 """Looks up database for invalidation signals for this repo_name
349
350
350 :param repo_name:
351 :param repo_name:
351 """
352 """
352
353
353 ret = self.sa.query(CacheInvalidation)\
354 ret = self.sa.query(CacheInvalidation)\
354 .options(FromCache('sql_cache_short',
355 .options(FromCache('sql_cache_short',
355 'get_invalidation_%s' % repo_name))\
356 'get_invalidation_%s' % repo_name))\
356 .filter(CacheInvalidation.cache_key == repo_name)\
357 .filter(CacheInvalidation.cache_key == repo_name)\
357 .filter(CacheInvalidation.cache_active == False)\
358 .filter(CacheInvalidation.cache_active == False)\
358 .scalar()
359 .scalar()
359
360
360 return ret
361 return ret
361
362
362 def _mark_invalidated(self, cache_key):
363 def _mark_invalidated(self, cache_key):
363 """ Marks all occurences of cache to invaldation as already invalidated
364 """ Marks all occurences of cache to invaldation as already invalidated
364
365
365 :param cache_key:
366 :param cache_key:
366 """
367 """
367
368
368 if cache_key:
369 if cache_key:
369 log.debug('marking %s as already invalidated', cache_key)
370 log.debug('marking %s as already invalidated', cache_key)
370 try:
371 try:
371 cache_key.cache_active = True
372 cache_key.cache_active = True
372 self.sa.add(cache_key)
373 self.sa.add(cache_key)
373 self.sa.commit()
374 self.sa.commit()
374 except (DatabaseError,):
375 except (DatabaseError,):
375 log.error(traceback.format_exc())
376 log.error(traceback.format_exc())
376 self.sa.rollback()
377 self.sa.rollback()
377
378
@@ -1,104 +1,112 b''
1 import sys
1 import sys
2 from rhodecode import get_version
3 from rhodecode import __platform__
4
2 py_version = sys.version_info
5 py_version = sys.version_info
3
6
4 from rhodecode import get_version
5
6 requirements = [
7 requirements = [
7 "Pylons==1.0.0",
8 "Pylons==1.0.0",
8 "WebHelpers==1.2",
9 "WebHelpers==1.2",
9 "SQLAlchemy==0.6.6",
10 "SQLAlchemy==0.6.6",
10 "Mako==0.3.6",
11 "Mako==0.4.0",
11 "vcs==0.1.10",
12 "vcs==0.1.11",
12 "pygments==1.3.1",
13 "pygments==1.4.0",
13 "mercurial==1.7.5",
14 "mercurial==1.7.5",
14 "whoosh==1.3.4",
15 "whoosh==1.3.4",
15 "celery==2.1.4",
16 "celery==2.2.4",
16 "py-bcrypt",
17 "babel",
17 "babel",
18 ]
18 ]
19
19
20 classifiers = ['Development Status :: 5 - Production/Stable',
20 classifiers = ['Development Status :: 5 - Production/Stable',
21 'Environment :: Web Environment',
21 'Environment :: Web Environment',
22 'Framework :: Pylons',
22 'Framework :: Pylons',
23 'Intended Audience :: Developers',
23 'Intended Audience :: Developers',
24 'License :: OSI Approved :: BSD License',
24 'License :: OSI Approved :: BSD License',
25 'Operating System :: OS Independent',
25 'Operating System :: OS Independent',
26 'Programming Language :: Python', ]
26 'Programming Language :: Python', ]
27
27
28 if sys.version_info < (2, 6):
28 if py_version < (2, 6):
29 requirements.append("simplejson")
29 requirements.append("simplejson")
30 requirements.append("pysqlite")
30 requirements.append("pysqlite")
31
31
32 if __platform__ in ('Linux', 'Darwin'):
33 requirements.append("py-bcrypt")
34
35
32 #additional files from project that goes somewhere in the filesystem
36 #additional files from project that goes somewhere in the filesystem
33 #relative to sys.prefix
37 #relative to sys.prefix
34 data_files = []
38 data_files = []
35
39
36 #additional files that goes into package itself
40 #additional files that goes into package itself
37 package_data = {'rhodecode': ['i18n/*/LC_MESSAGES/*.mo', ], }
41 package_data = {'rhodecode': ['i18n/*/LC_MESSAGES/*.mo', ], }
38
42
39 description = ('Mercurial repository browser/management with '
43 description = ('Mercurial repository browser/management with '
40 'build in push/pull server and full text search')
44 'build in push/pull server and full text search')
45 keywords = ' '.join (['rhodecode', 'rhodiumcode', 'mercurial', 'git',
46 'repository management', 'hgweb replacement'
47 'hgwebdir', 'gitweb replacement', 'serving hgweb',
48 ])
41 #long description
49 #long description
42 try:
50 try:
43 readme_file = 'README.rst'
51 readme_file = 'README.rst'
44 changelog_file = 'docs/changelog.rst'
52 changelog_file = 'docs/changelog.rst'
45 long_description = open(readme_file).read() + '\n\n' + \
53 long_description = open(readme_file).read() + '\n\n' + \
46 open(changelog_file).read()
54 open(changelog_file).read()
47
55
48 except IOError, err:
56 except IOError, err:
49 sys.stderr.write("[WARNING] Cannot find file specified as "
57 sys.stderr.write("[WARNING] Cannot find file specified as "
50 "long_description (%s)\n or changelog (%s) skipping that file" \
58 "long_description (%s)\n or changelog (%s) skipping that file" \
51 % (readme_file, changelog_file))
59 % (readme_file, changelog_file))
52 long_description = description
60 long_description = description
53
61
54
62
55 try:
63 try:
56 from setuptools import setup, find_packages
64 from setuptools import setup, find_packages
57 except ImportError:
65 except ImportError:
58 from ez_setup import use_setuptools
66 from ez_setup import use_setuptools
59 use_setuptools()
67 use_setuptools()
60 from setuptools import setup, find_packages
68 from setuptools import setup, find_packages
61 #packages
69 #packages
62 packages = find_packages(exclude=['ez_setup'])
70 packages = find_packages(exclude=['ez_setup'])
63
71
64 setup(
72 setup(
65 name='RhodeCode',
73 name='RhodeCode',
66 version=get_version(),
74 version=get_version(),
67 description=description,
75 description=description,
68 long_description=long_description,
76 long_description=long_description,
69 keywords='rhodiumcode mercurial web hgwebdir gitweb git replacement serving hgweb rhodecode',
77 keywords=keywords,
70 license='BSD',
78 license='BSD',
71 author='Marcin Kuzminski',
79 author='Marcin Kuzminski',
72 author_email='marcin@python-works.com',
80 author_email='marcin@python-works.com',
73 url='http://hg.python-works.com',
81 url='http://hg.python-works.com',
74 install_requires=requirements,
82 install_requires=requirements,
75 classifiers=classifiers,
83 classifiers=classifiers,
76 setup_requires=["PasteScript>=1.6.3"],
84 setup_requires=["PasteScript>=1.6.3"],
77 data_files=data_files,
85 data_files=data_files,
78 packages=packages,
86 packages=packages,
79 include_package_data=True,
87 include_package_data=True,
80 test_suite='nose.collector',
88 test_suite='nose.collector',
81 package_data=package_data,
89 package_data=package_data,
82 message_extractors={'rhodecode': [
90 message_extractors={'rhodecode': [
83 ('**.py', 'python', None),
91 ('**.py', 'python', None),
84 ('templates/**.mako', 'mako', {'input_encoding': 'utf-8'}),
92 ('templates/**.mako', 'mako', {'input_encoding': 'utf-8'}),
85 ('public/**', 'ignore', None)]},
93 ('public/**', 'ignore', None)]},
86 zip_safe=False,
94 zip_safe=False,
87 paster_plugins=['PasteScript', 'Pylons'],
95 paster_plugins=['PasteScript', 'Pylons'],
88 entry_points="""
96 entry_points="""
89 [paste.app_factory]
97 [paste.app_factory]
90 main = rhodecode.config.middleware:make_app
98 main = rhodecode.config.middleware:make_app
91
99
92 [paste.app_install]
100 [paste.app_install]
93 main = pylons.util:PylonsInstaller
101 main = pylons.util:PylonsInstaller
94
102
95 [paste.global_paster_command]
103 [paste.global_paster_command]
96 make-index = rhodecode.lib.indexers:MakeIndex
104 make-index = rhodecode.lib.indexers:MakeIndex
97 upgrade-db = rhodecode.lib.dbmigrate:UpgradeDb
105 upgrade-db = rhodecode.lib.dbmigrate:UpgradeDb
98 celeryd=rhodecode.lib.celerypylons.commands:CeleryDaemonCommand
106 celeryd=rhodecode.lib.celerypylons.commands:CeleryDaemonCommand
99 celerybeat=rhodecode.lib.celerypylons.commands:CeleryBeatCommand
107 celerybeat=rhodecode.lib.celerypylons.commands:CeleryBeatCommand
100 camqadm=rhodecode.lib.celerypylons.commands:CAMQPAdminCommand
108 camqadm=rhodecode.lib.celerypylons.commands:CAMQPAdminCommand
101 celeryev=rhodecode.lib.celerypylons.commands:CeleryEventCommand
109 celeryev=rhodecode.lib.celerypylons.commands:CeleryEventCommand
102
110
103 """,
111 """,
104 )
112 )
General Comments 0
You need to be logged in to leave comments. Login now