##// END OF EJS Templates
"Users groups" is grammatically incorrect English - rename to "user groups"...
Mads Kiilerich -
r3410:5f1850e4 beta
parent child Browse files
Show More
@@ -1,179 +1,179 b''
1 =========
1 =========
2 RhodeCode
2 RhodeCode
3 =========
3 =========
4
4
5 About
5 About
6 -----
6 -----
7
7
8 ``RhodeCode`` is a fast and powerful management tool for Mercurial_ and GIT_
8 ``RhodeCode`` is a fast and powerful management tool for Mercurial_ and GIT_
9 with a built in push/pull server, full text search and code-review.
9 with a built in push/pull server, full text search and code-review.
10 It works on http/https and has a built in permission/authentication system with
10 It works on http/https and has a built in permission/authentication system with
11 the ability to authenticate via LDAP or ActiveDirectory. RhodeCode also provides
11 the ability to authenticate via LDAP or ActiveDirectory. RhodeCode also provides
12 simple API so it's easy integrable with existing external systems.
12 simple API so it's easy integrable with existing external systems.
13
13
14 RhodeCode is similar in some respects to github_ or bitbucket_,
14 RhodeCode is similar in some respects to github_ or bitbucket_,
15 however RhodeCode can be run as standalone hosted application on your own server.
15 however RhodeCode can be run as standalone hosted application on your own server.
16 It is open source and donation ware and focuses more on providing a customized,
16 It is open source and donation ware and focuses more on providing a customized,
17 self administered interface for Mercurial_ and GIT_ repositories.
17 self administered interface for Mercurial_ and GIT_ repositories.
18 RhodeCode works on \*nix systems and Windows it is powered by a vcs_ library
18 RhodeCode works on \*nix systems and Windows it is powered by a vcs_ library
19 that Lukasz Balcerzak and Marcin Kuzminski created to handle multiple
19 that Lukasz Balcerzak and Marcin Kuzminski created to handle multiple
20 different version control systems.
20 different version control systems.
21
21
22 RhodeCode uses `PEP386 versioning <http://www.python.org/dev/peps/pep-0386/>`_
22 RhodeCode uses `PEP386 versioning <http://www.python.org/dev/peps/pep-0386/>`_
23
23
24 Installation
24 Installation
25 ------------
25 ------------
26 Stable releases of RhodeCode are best installed via::
26 Stable releases of RhodeCode are best installed via::
27
27
28 easy_install rhodecode
28 easy_install rhodecode
29
29
30 Or::
30 Or::
31
31
32 pip install rhodecode
32 pip install rhodecode
33
33
34 Detailed instructions and links may be found on the Installation page.
34 Detailed instructions and links may be found on the Installation page.
35
35
36 Please visit http://packages.python.org/RhodeCode/installation.html for
36 Please visit http://packages.python.org/RhodeCode/installation.html for
37 more details
37 more details
38
38
39 RhodeCode demo
39 RhodeCode demo
40 --------------
40 --------------
41
41
42 http://demo.rhodecode.org
42 http://demo.rhodecode.org
43
43
44 The default access is anonymous but you can login to an administrative account
44 The default access is anonymous but you can login to an administrative account
45 using the following credentials:
45 using the following credentials:
46
46
47 - username: demo
47 - username: demo
48 - password: demo12
48 - password: demo12
49
49
50 Source code
50 Source code
51 -----------
51 -----------
52
52
53 The latest sources can be obtained from official RhodeCode instance
53 The latest sources can be obtained from official RhodeCode instance
54 https://secure.rhodecode.org
54 https://secure.rhodecode.org
55
55
56
56
57 MIRRORS:
57 MIRRORS:
58
58
59 Issue tracker and sources at bitbucket_
59 Issue tracker and sources at bitbucket_
60
60
61 http://bitbucket.org/marcinkuzminski/rhodecode
61 http://bitbucket.org/marcinkuzminski/rhodecode
62
62
63 Sources at github_
63 Sources at github_
64
64
65 https://github.com/marcinkuzminski/rhodecode
65 https://github.com/marcinkuzminski/rhodecode
66
66
67
67
68 RhodeCode Features
68 RhodeCode Features
69 ------------------
69 ------------------
70
70
71 - Has its own middleware to handle mercurial_ and git_ protocol requests.
71 - Has its own middleware to handle mercurial_ and git_ protocol requests.
72 Each request is authenticated and logged together with IP address.
72 Each request is authenticated and logged together with IP address.
73 - Build for speed and performance. You can make multiple pulls/pushes simultaneous.
73 - Build for speed and performance. You can make multiple pulls/pushes simultaneous.
74 Proven to work with 1000s of repositories and users
74 Proven to work with 1000s of repositories and users
75 - Supports http/https, LDAP, AD, proxy-pass authentication.
75 - Supports http/https, LDAP, AD, proxy-pass authentication.
76 - Full permissions (private/read/write/admin) together with IP restrictions for each repository,
76 - Full permissions (private/read/write/admin) together with IP restrictions for each repository,
77 additional explicit forking and repository creation permissions.
77 additional explicit forking and repository creation permissions.
78 - Users groups for easier permission management
78 - User groups for easier permission management
79 - Repository groups let you group repos and manage them easier.
79 - Repository groups let you group repos and manage them easier.
80 - Users can fork other users repos, and compare them at any time.
80 - Users can fork other users repos, and compare them at any time.
81 - Integrates easily with other systems, with custom created mappers you can connect it to almost
81 - Integrates easily with other systems, with custom created mappers you can connect it to almost
82 any issue tracker, and with an JSON-RPC API you can make much more
82 any issue tracker, and with an JSON-RPC API you can make much more
83 - Build in commit-api let's you add, edit and commit files right from RhodeCode
83 - Build in commit-api let's you add, edit and commit files right from RhodeCode
84 web interface using simple editor or upload binary files using simple form.
84 web interface using simple editor or upload binary files using simple form.
85 - Powerfull pull-request driven review system with inline commenting,
85 - Powerfull pull-request driven review system with inline commenting,
86 changeset statuses, and notification system.
86 changeset statuses, and notification system.
87 - Importing and syncing repositories from remote locations for GIT_, Mercurial_ and SVN.
87 - Importing and syncing repositories from remote locations for GIT_, Mercurial_ and SVN.
88 - Mako templates let's you customize the look and feel of the application.
88 - Mako templates let's you customize the look and feel of the application.
89 - Beautiful diffs, annotations and source code browsing all colored by pygments.
89 - Beautiful diffs, annotations and source code browsing all colored by pygments.
90 Raw diffs are made in git-diff format for both VCS systems, including GIT_ binary-patches
90 Raw diffs are made in git-diff format for both VCS systems, including GIT_ binary-patches
91 - Mercurial_ and Git_ DAG graphs and yui-flot powered graphs with zooming and statistics
91 - Mercurial_ and Git_ DAG graphs and yui-flot powered graphs with zooming and statistics
92 to track activity for repositories
92 to track activity for repositories
93 - Admin interface with user/permission management. Admin activity journal, logs
93 - Admin interface with user/permission management. Admin activity journal, logs
94 pulls, pushes, forks, registrations and other actions made by all users.
94 pulls, pushes, forks, registrations and other actions made by all users.
95 - Server side forks. It is possible to fork a project and modify it freely
95 - Server side forks. It is possible to fork a project and modify it freely
96 without breaking the main repository.
96 without breaking the main repository.
97 - rst and markdown README support for repositories.
97 - rst and markdown README support for repositories.
98 - Full text search powered by Whoosh on the source files, commit messages, and file names.
98 - Full text search powered by Whoosh on the source files, commit messages, and file names.
99 Build in indexing daemons, with optional incremental index build
99 Build in indexing daemons, with optional incremental index build
100 (no external search servers required all in one application)
100 (no external search servers required all in one application)
101 - Setup project descriptions/tags and info inside built in db for easy, non
101 - Setup project descriptions/tags and info inside built in db for easy, non
102 file-system operations.
102 file-system operations.
103 - Intelligent cache with invalidation after push or project change, provides
103 - Intelligent cache with invalidation after push or project change, provides
104 high performance and always up to date data.
104 high performance and always up to date data.
105 - RSS / Atom feeds, gravatar support, downloadable sources as zip/tar/gz
105 - RSS / Atom feeds, gravatar support, downloadable sources as zip/tar/gz
106 - Optional async tasks for speed and performance using celery_
106 - Optional async tasks for speed and performance using celery_
107 - Backup scripts can do backup of whole app and send it over scp to desired
107 - Backup scripts can do backup of whole app and send it over scp to desired
108 location
108 location
109 - Based on pylons / sqlalchemy / sqlite / whoosh / vcs
109 - Based on pylons / sqlalchemy / sqlite / whoosh / vcs
110
110
111
111
112 Incoming / Plans
112 Incoming / Plans
113 ----------------
113 ----------------
114
114
115 - Finer granular permissions per branch, or subrepo
115 - Finer granular permissions per branch, or subrepo
116 - Web based merges for pull requests
116 - Web based merges for pull requests
117 - Tracking history for each lines in files
117 - Tracking history for each lines in files
118 - Simple issue tracker
118 - Simple issue tracker
119 - SSH based authentication with server side key management
119 - SSH based authentication with server side key management
120 - Commit based built in wiki system
120 - Commit based built in wiki system
121 - Gist server
121 - Gist server
122 - More statistics and graph (global annotation + some more statistics)
122 - More statistics and graph (global annotation + some more statistics)
123 - Other advancements as development continues (or you can of course make
123 - Other advancements as development continues (or you can of course make
124 additions and or requests)
124 additions and or requests)
125
125
126 License
126 License
127 -------
127 -------
128
128
129 ``RhodeCode`` is released under the GPLv3 license.
129 ``RhodeCode`` is released under the GPLv3 license.
130
130
131
131
132 Getting help
132 Getting help
133 ------------
133 ------------
134
134
135 Listed bellow are various support resources that should help.
135 Listed bellow are various support resources that should help.
136
136
137 .. note::
137 .. note::
138
138
139 Please try to read the documentation before posting any issues, especially
139 Please try to read the documentation before posting any issues, especially
140 the **troubleshooting section**
140 the **troubleshooting section**
141
141
142 - Join the `Google group <http://groups.google.com/group/rhodecode>`_ and ask
142 - Join the `Google group <http://groups.google.com/group/rhodecode>`_ and ask
143 any questions.
143 any questions.
144
144
145 - Open an issue at `issue tracker <http://bitbucket.org/marcinkuzminski/rhodecode/issues>`_
145 - Open an issue at `issue tracker <http://bitbucket.org/marcinkuzminski/rhodecode/issues>`_
146
146
147 - Join #rhodecode on FreeNode (irc.freenode.net)
147 - Join #rhodecode on FreeNode (irc.freenode.net)
148 or use http://webchat.freenode.net/?channels=rhodecode for web access to irc.
148 or use http://webchat.freenode.net/?channels=rhodecode for web access to irc.
149
149
150 - You can also follow me on twitter **@marcinkuzminski** where i often post some
150 - You can also follow me on twitter **@marcinkuzminski** where i often post some
151 news about RhodeCode
151 news about RhodeCode
152
152
153
153
154 Online documentation
154 Online documentation
155 --------------------
155 --------------------
156
156
157 Online documentation for the current version of RhodeCode is available at
157 Online documentation for the current version of RhodeCode is available at
158 - http://packages.python.org/RhodeCode/
158 - http://packages.python.org/RhodeCode/
159 - http://rhodecode.readthedocs.org/en/latest/index.html
159 - http://rhodecode.readthedocs.org/en/latest/index.html
160
160
161 You may also build the documentation for yourself - go into ``docs/`` and run::
161 You may also build the documentation for yourself - go into ``docs/`` and run::
162
162
163 make html
163 make html
164
164
165 (You need to have sphinx_ installed to build the documentation. If you don't
165 (You need to have sphinx_ installed to build the documentation. If you don't
166 have sphinx_ installed you can install it via the command:
166 have sphinx_ installed you can install it via the command:
167 ``easy_install sphinx``)
167 ``easy_install sphinx``)
168
168
169 .. _virtualenv: http://pypi.python.org/pypi/virtualenv
169 .. _virtualenv: http://pypi.python.org/pypi/virtualenv
170 .. _python: http://www.python.org/
170 .. _python: http://www.python.org/
171 .. _sphinx: http://sphinx.pocoo.org/
171 .. _sphinx: http://sphinx.pocoo.org/
172 .. _mercurial: http://mercurial.selenic.com/
172 .. _mercurial: http://mercurial.selenic.com/
173 .. _bitbucket: http://bitbucket.org/
173 .. _bitbucket: http://bitbucket.org/
174 .. _github: http://github.com/
174 .. _github: http://github.com/
175 .. _subversion: http://subversion.tigris.org/
175 .. _subversion: http://subversion.tigris.org/
176 .. _git: http://git-scm.com/
176 .. _git: http://git-scm.com/
177 .. _celery: http://celeryproject.org/
177 .. _celery: http://celeryproject.org/
178 .. _Sphinx: http://sphinx.pocoo.org/
178 .. _Sphinx: http://sphinx.pocoo.org/
179 .. _vcs: http://pypi.python.org/pypi/vcs
179 .. _vcs: http://pypi.python.org/pypi/vcs
@@ -1,981 +1,981 b''
1 .. _api:
1 .. _api:
2
2
3 ===
3 ===
4 API
4 API
5 ===
5 ===
6
6
7
7
8 Starting from RhodeCode version 1.2 a simple API was implemented.
8 Starting from RhodeCode version 1.2 a simple API was implemented.
9 There's a single schema for calling all api methods. API is implemented
9 There's a single schema for calling all api methods. API is implemented
10 with JSON protocol both ways. An url to send API request to RhodeCode is
10 with JSON protocol both ways. An url to send API request to RhodeCode is
11 <your_server>/_admin/api
11 <your_server>/_admin/api
12
12
13 API ACCESS FOR WEB VIEWS
13 API ACCESS FOR WEB VIEWS
14 ++++++++++++++++++++++++
14 ++++++++++++++++++++++++
15
15
16 API access can also be turned on for each web view in RhodeCode that is
16 API access can also be turned on for each web view in RhodeCode that is
17 decorated with `@LoginRequired` decorator. To enable API access simple change
17 decorated with `@LoginRequired` decorator. To enable API access simple change
18 the standard login decorator to `@LoginRequired(api_access=True)`.
18 the standard login decorator to `@LoginRequired(api_access=True)`.
19 After this change, a rhodecode view can be accessed without login by adding a
19 After this change, a rhodecode view can be accessed without login by adding a
20 GET parameter `?api_key=<api_key>` to url. By default this is only
20 GET parameter `?api_key=<api_key>` to url. By default this is only
21 enabled on RSS/ATOM feed views.
21 enabled on RSS/ATOM feed views.
22
22
23
23
24 API ACCESS
24 API ACCESS
25 ++++++++++
25 ++++++++++
26
26
27 All clients are required to send JSON-RPC spec JSON data::
27 All clients are required to send JSON-RPC spec JSON data::
28
28
29 {
29 {
30 "id:"<id>",
30 "id:"<id>",
31 "api_key":"<api_key>",
31 "api_key":"<api_key>",
32 "method":"<method_name>",
32 "method":"<method_name>",
33 "args":{"<arg_key>":"<arg_val>"}
33 "args":{"<arg_key>":"<arg_val>"}
34 }
34 }
35
35
36 Example call for autopulling remotes repos using curl::
36 Example call for autopulling remotes repos using curl::
37 curl https://server.com/_admin/api -X POST -H 'content-type:text/plain' --data-binary '{"id":1,"api_key":"xe7cdb2v278e4evbdf5vs04v832v0efvcbcve4a3","method":"pull","args":{"repo":"CPython"}}'
37 curl https://server.com/_admin/api -X POST -H 'content-type:text/plain' --data-binary '{"id":1,"api_key":"xe7cdb2v278e4evbdf5vs04v832v0efvcbcve4a3","method":"pull","args":{"repo":"CPython"}}'
38
38
39 Simply provide
39 Simply provide
40 - *id* A value of any type, which is used to match the response with the request that it is replying to.
40 - *id* A value of any type, which is used to match the response with the request that it is replying to.
41 - *api_key* for access and permission validation.
41 - *api_key* for access and permission validation.
42 - *method* is name of method to call
42 - *method* is name of method to call
43 - *args* is an key:value list of arguments to pass to method
43 - *args* is an key:value list of arguments to pass to method
44
44
45 .. note::
45 .. note::
46
46
47 api_key can be found in your user account page
47 api_key can be found in your user account page
48
48
49
49
50 RhodeCode API will return always a JSON-RPC response::
50 RhodeCode API will return always a JSON-RPC response::
51
51
52 {
52 {
53 "id":<id>, # matching id sent by request
53 "id":<id>, # matching id sent by request
54 "result": "<result>"|null, # JSON formatted result, null if any errors
54 "result": "<result>"|null, # JSON formatted result, null if any errors
55 "error": "null"|<error_message> # JSON formatted error (if any)
55 "error": "null"|<error_message> # JSON formatted error (if any)
56 }
56 }
57
57
58 All responses from API will be `HTTP/1.0 200 OK`, if there's an error while
58 All responses from API will be `HTTP/1.0 200 OK`, if there's an error while
59 calling api *error* key from response will contain failure description
59 calling api *error* key from response will contain failure description
60 and result will be null.
60 and result will be null.
61
61
62
62
63 API CLIENT
63 API CLIENT
64 ++++++++++
64 ++++++++++
65
65
66 From version 1.4 RhodeCode adds a script that allows to easily
66 From version 1.4 RhodeCode adds a script that allows to easily
67 communicate with API. After installing RhodeCode a `rhodecode-api` script
67 communicate with API. After installing RhodeCode a `rhodecode-api` script
68 will be available.
68 will be available.
69
69
70 To get started quickly simply run::
70 To get started quickly simply run::
71
71
72 rhodecode-api _create_config --apikey=<youapikey> --apihost=<rhodecode host>
72 rhodecode-api _create_config --apikey=<youapikey> --apihost=<rhodecode host>
73
73
74 This will create a file named .config in the directory you executed it storing
74 This will create a file named .config in the directory you executed it storing
75 json config file with credentials. You can skip this step and always provide
75 json config file with credentials. You can skip this step and always provide
76 both of the arguments to be able to communicate with server
76 both of the arguments to be able to communicate with server
77
77
78
78
79 after that simply run any api command for example get_repo::
79 after that simply run any api command for example get_repo::
80
80
81 rhodecode-api get_repo
81 rhodecode-api get_repo
82
82
83 calling {"api_key": "<apikey>", "id": 75, "args": {}, "method": "get_repo"} to http://127.0.0.1:5000
83 calling {"api_key": "<apikey>", "id": 75, "args": {}, "method": "get_repo"} to http://127.0.0.1:5000
84 rhodecode said:
84 rhodecode said:
85 {'error': 'Missing non optional `repoid` arg in JSON DATA',
85 {'error': 'Missing non optional `repoid` arg in JSON DATA',
86 'id': 75,
86 'id': 75,
87 'result': None}
87 'result': None}
88
88
89 Ups looks like we forgot to add an argument
89 Ups looks like we forgot to add an argument
90
90
91 Let's try again now giving the repoid as parameters::
91 Let's try again now giving the repoid as parameters::
92
92
93 rhodecode-api get_repo repoid:rhodecode
93 rhodecode-api get_repo repoid:rhodecode
94
94
95 calling {"api_key": "<apikey>", "id": 39, "args": {"repoid": "rhodecode"}, "method": "get_repo"} to http://127.0.0.1:5000
95 calling {"api_key": "<apikey>", "id": 39, "args": {"repoid": "rhodecode"}, "method": "get_repo"} to http://127.0.0.1:5000
96 rhodecode said:
96 rhodecode said:
97 {'error': None,
97 {'error': None,
98 'id': 39,
98 'id': 39,
99 'result': <json data...>}
99 'result': <json data...>}
100
100
101
101
102
102
103 API METHODS
103 API METHODS
104 +++++++++++
104 +++++++++++
105
105
106
106
107 pull
107 pull
108 ----
108 ----
109
109
110 Pulls given repo from remote location. Can be used to automatically keep
110 Pulls given repo from remote location. Can be used to automatically keep
111 remote repos up to date. This command can be executed only using api_key
111 remote repos up to date. This command can be executed only using api_key
112 belonging to user with admin rights
112 belonging to user with admin rights
113
113
114 INPUT::
114 INPUT::
115
115
116 id : <id_for_response>
116 id : <id_for_response>
117 api_key : "<api_key>"
117 api_key : "<api_key>"
118 method : "pull"
118 method : "pull"
119 args : {
119 args : {
120 "repoid" : "<reponame or repo_id>"
120 "repoid" : "<reponame or repo_id>"
121 }
121 }
122
122
123 OUTPUT::
123 OUTPUT::
124
124
125 id : <id_given_in_input>
125 id : <id_given_in_input>
126 result : "Pulled from `<reponame>`"
126 result : "Pulled from `<reponame>`"
127 error : null
127 error : null
128
128
129
129
130 rescan_repos
130 rescan_repos
131 ------------
131 ------------
132
132
133 Dispatch rescan repositories action. If remove_obsolete is set
133 Dispatch rescan repositories action. If remove_obsolete is set
134 RhodeCode will delete repos that are in database but not in the filesystem.
134 RhodeCode will delete repos that are in database but not in the filesystem.
135 This command can be executed only using api_key belonging to user with admin
135 This command can be executed only using api_key belonging to user with admin
136 rights.
136 rights.
137
137
138 INPUT::
138 INPUT::
139
139
140 id : <id_for_response>
140 id : <id_for_response>
141 api_key : "<api_key>"
141 api_key : "<api_key>"
142 method : "rescan_repos"
142 method : "rescan_repos"
143 args : {
143 args : {
144 "remove_obsolete" : "<boolean = Optional(False)>"
144 "remove_obsolete" : "<boolean = Optional(False)>"
145 }
145 }
146
146
147 OUTPUT::
147 OUTPUT::
148
148
149 id : <id_given_in_input>
149 id : <id_given_in_input>
150 result : "{'added': [<list of names of added repos>],
150 result : "{'added': [<list of names of added repos>],
151 'removed': [<list of names of removed repos>]}"
151 'removed': [<list of names of removed repos>]}"
152 error : null
152 error : null
153
153
154
154
155 invalidate_cache
155 invalidate_cache
156 ----------------
156 ----------------
157
157
158 Invalidate cache for repository.
158 Invalidate cache for repository.
159 This command can be executed only using api_key belonging to user with admin
159 This command can be executed only using api_key belonging to user with admin
160 rights or regular user that have write or admin or write access to repository.
160 rights or regular user that have write or admin or write access to repository.
161
161
162 INPUT::
162 INPUT::
163
163
164 id : <id_for_response>
164 id : <id_for_response>
165 api_key : "<api_key>"
165 api_key : "<api_key>"
166 method : "invalidate_cache"
166 method : "invalidate_cache"
167 args : {
167 args : {
168 "repoid" : "<reponame or repo_id>"
168 "repoid" : "<reponame or repo_id>"
169 }
169 }
170
170
171 OUTPUT::
171 OUTPUT::
172
172
173 id : <id_given_in_input>
173 id : <id_given_in_input>
174 result : "Cache for repository `<reponame>` was invalidated: invalidated cache keys: <list_of_cache_keys>"
174 result : "Cache for repository `<reponame>` was invalidated: invalidated cache keys: <list_of_cache_keys>"
175 error : null
175 error : null
176
176
177 lock
177 lock
178 ----
178 ----
179
179
180 Set locking state on given repository by given user. If userid param is skipped
180 Set locking state on given repository by given user. If userid param is skipped
181 , then it is set to id of user whos calling this method.
181 , then it is set to id of user whos calling this method.
182 This command can be executed only using api_key belonging to user with admin
182 This command can be executed only using api_key belonging to user with admin
183 rights or regular user that have admin or write access to repository.
183 rights or regular user that have admin or write access to repository.
184
184
185 INPUT::
185 INPUT::
186
186
187 id : <id_for_response>
187 id : <id_for_response>
188 api_key : "<api_key>"
188 api_key : "<api_key>"
189 method : "lock"
189 method : "lock"
190 args : {
190 args : {
191 "repoid" : "<reponame or repo_id>"
191 "repoid" : "<reponame or repo_id>"
192 "userid" : "<user_id or username = Optional(=apiuser)>",
192 "userid" : "<user_id or username = Optional(=apiuser)>",
193 "locked" : "<bool true|false>"
193 "locked" : "<bool true|false>"
194 }
194 }
195
195
196 OUTPUT::
196 OUTPUT::
197
197
198 id : <id_given_in_input>
198 id : <id_given_in_input>
199 result : "User `<username>` set lock state for repo `<reponame>` to `true|false`"
199 result : "User `<username>` set lock state for repo `<reponame>` to `true|false`"
200 error : null
200 error : null
201
201
202
202
203 show_ip
203 show_ip
204 -------
204 -------
205
205
206 Shows IP address as seen from RhodeCode server, together with all
206 Shows IP address as seen from RhodeCode server, together with all
207 defined IP addresses for given user.
207 defined IP addresses for given user.
208 This command can be executed only using api_key belonging to user with admin
208 This command can be executed only using api_key belonging to user with admin
209 rights.
209 rights.
210
210
211 INPUT::
211 INPUT::
212
212
213 id : <id_for_response>
213 id : <id_for_response>
214 api_key : "<api_key>"
214 api_key : "<api_key>"
215 method : "show_ip"
215 method : "show_ip"
216 args : {
216 args : {
217 "userid" : "<user_id or username>",
217 "userid" : "<user_id or username>",
218 }
218 }
219
219
220 OUTPUT::
220 OUTPUT::
221
221
222 id : <id_given_in_input>
222 id : <id_given_in_input>
223 result : {
223 result : {
224 "ip_addr_server": <ip_from_clien>",
224 "ip_addr_server": <ip_from_clien>",
225 "user_ips": [
225 "user_ips": [
226 {
226 {
227 "ip_addr": "<ip_with_mask>",
227 "ip_addr": "<ip_with_mask>",
228 "ip_range": ["<start_ip>", "<end_ip>"],
228 "ip_range": ["<start_ip>", "<end_ip>"],
229 },
229 },
230 ...
230 ...
231 ]
231 ]
232 }
232 }
233
233
234 error : null
234 error : null
235
235
236
236
237 get_user
237 get_user
238 --------
238 --------
239
239
240 Get's an user by username or user_id, Returns empty result if user is not found.
240 Get's an user by username or user_id, Returns empty result if user is not found.
241 If userid param is skipped it is set to id of user who is calling this method.
241 If userid param is skipped it is set to id of user who is calling this method.
242 This command can be executed only using api_key belonging to user with admin
242 This command can be executed only using api_key belonging to user with admin
243 rights, or regular users that cannot specify different userid than theirs
243 rights, or regular users that cannot specify different userid than theirs
244
244
245
245
246 INPUT::
246 INPUT::
247
247
248 id : <id_for_response>
248 id : <id_for_response>
249 api_key : "<api_key>"
249 api_key : "<api_key>"
250 method : "get_user"
250 method : "get_user"
251 args : {
251 args : {
252 "userid" : "<username or user_id Optional(=apiuser)>"
252 "userid" : "<username or user_id Optional(=apiuser)>"
253 }
253 }
254
254
255 OUTPUT::
255 OUTPUT::
256
256
257 id : <id_given_in_input>
257 id : <id_given_in_input>
258 result: None if user does not exist or
258 result: None if user does not exist or
259 {
259 {
260 "user_id" : "<user_id>",
260 "user_id" : "<user_id>",
261 "api_key" : "<api_key>",
261 "api_key" : "<api_key>",
262 "username" : "<username>",
262 "username" : "<username>",
263 "firstname": "<firstname>",
263 "firstname": "<firstname>",
264 "lastname" : "<lastname>",
264 "lastname" : "<lastname>",
265 "email" : "<email>",
265 "email" : "<email>",
266 "emails": "<list_of_all_additional_emails>",
266 "emails": "<list_of_all_additional_emails>",
267 "ip_addresses": "<list_of_ip_addresses_for_user>",
267 "ip_addresses": "<list_of_ip_addresses_for_user>",
268 "active" : "<bool>",
268 "active" : "<bool>",
269 "admin" :Β  "<bool>",
269 "admin" :Β  "<bool>",
270 "ldap_dn" : "<ldap_dn>",
270 "ldap_dn" : "<ldap_dn>",
271 "last_login": "<last_login>",
271 "last_login": "<last_login>",
272 "permissions": {
272 "permissions": {
273 "global": ["hg.create.repository",
273 "global": ["hg.create.repository",
274 "repository.read",
274 "repository.read",
275 "hg.register.manual_activate"],
275 "hg.register.manual_activate"],
276 "repositories": {"repo1": "repository.none"},
276 "repositories": {"repo1": "repository.none"},
277 "repositories_groups": {"Group1": "group.read"}
277 "repositories_groups": {"Group1": "group.read"}
278 },
278 },
279 }
279 }
280
280
281 error: null
281 error: null
282
282
283
283
284 get_users
284 get_users
285 ---------
285 ---------
286
286
287 Lists all existing users. This command can be executed only using api_key
287 Lists all existing users. This command can be executed only using api_key
288 belonging to user with admin rights.
288 belonging to user with admin rights.
289
289
290
290
291 INPUT::
291 INPUT::
292
292
293 id : <id_for_response>
293 id : <id_for_response>
294 api_key : "<api_key>"
294 api_key : "<api_key>"
295 method : "get_users"
295 method : "get_users"
296 args : { }
296 args : { }
297
297
298 OUTPUT::
298 OUTPUT::
299
299
300 id : <id_given_in_input>
300 id : <id_given_in_input>
301 result: [
301 result: [
302 {
302 {
303 "user_id" : "<user_id>",
303 "user_id" : "<user_id>",
304 "username" : "<username>",
304 "username" : "<username>",
305 "firstname": "<firstname>",
305 "firstname": "<firstname>",
306 "lastname" : "<lastname>",
306 "lastname" : "<lastname>",
307 "email" : "<email>",
307 "email" : "<email>",
308 "emails": "<list_of_all_additional_emails>",
308 "emails": "<list_of_all_additional_emails>",
309 "ip_addresses": "<list_of_ip_addresses_for_user>",
309 "ip_addresses": "<list_of_ip_addresses_for_user>",
310 "active" : "<bool>",
310 "active" : "<bool>",
311 "admin" :Β  "<bool>",
311 "admin" :Β  "<bool>",
312 "ldap_dn" : "<ldap_dn>",
312 "ldap_dn" : "<ldap_dn>",
313 "last_login": "<last_login>",
313 "last_login": "<last_login>",
314 },
314 },
315 …
315 …
316 ]
316 ]
317 error: null
317 error: null
318
318
319
319
320 create_user
320 create_user
321 -----------
321 -----------
322
322
323 Creates new user. This command can
323 Creates new user. This command can
324 be executed only using api_key belonging to user with admin rights.
324 be executed only using api_key belonging to user with admin rights.
325
325
326
326
327 INPUT::
327 INPUT::
328
328
329 id : <id_for_response>
329 id : <id_for_response>
330 api_key : "<api_key>"
330 api_key : "<api_key>"
331 method : "create_user"
331 method : "create_user"
332 args : {
332 args : {
333 "username" : "<username>",
333 "username" : "<username>",
334 "email" : "<useremail>",
334 "email" : "<useremail>",
335 "password" : "<password>",
335 "password" : "<password>",
336 "firstname" : "<firstname> = Optional(None)",
336 "firstname" : "<firstname> = Optional(None)",
337 "lastname" : "<lastname> = Optional(None)",
337 "lastname" : "<lastname> = Optional(None)",
338 "active" : "<bool> = Optional(True)",
338 "active" : "<bool> = Optional(True)",
339 "admin" : "<bool> = Optional(False)",
339 "admin" : "<bool> = Optional(False)",
340 "ldap_dn" : "<ldap_dn> = Optional(None)"
340 "ldap_dn" : "<ldap_dn> = Optional(None)"
341 }
341 }
342
342
343 OUTPUT::
343 OUTPUT::
344
344
345 id : <id_given_in_input>
345 id : <id_given_in_input>
346 result: {
346 result: {
347 "msg" : "created new user `<username>`",
347 "msg" : "created new user `<username>`",
348 "user": {
348 "user": {
349 "user_id" : "<user_id>",
349 "user_id" : "<user_id>",
350 "username" : "<username>",
350 "username" : "<username>",
351 "firstname": "<firstname>",
351 "firstname": "<firstname>",
352 "lastname" : "<lastname>",
352 "lastname" : "<lastname>",
353 "email" : "<email>",
353 "email" : "<email>",
354 "emails": "<list_of_all_additional_emails>",
354 "emails": "<list_of_all_additional_emails>",
355 "active" : "<bool>",
355 "active" : "<bool>",
356 "admin" :Β  "<bool>",
356 "admin" :Β  "<bool>",
357 "ldap_dn" : "<ldap_dn>",
357 "ldap_dn" : "<ldap_dn>",
358 "last_login": "<last_login>",
358 "last_login": "<last_login>",
359 },
359 },
360 }
360 }
361 error: null
361 error: null
362
362
363
363
364 update_user
364 update_user
365 -----------
365 -----------
366
366
367 updates given user if such user exists. This command can
367 updates given user if such user exists. This command can
368 be executed only using api_key belonging to user with admin rights.
368 be executed only using api_key belonging to user with admin rights.
369
369
370
370
371 INPUT::
371 INPUT::
372
372
373 id : <id_for_response>
373 id : <id_for_response>
374 api_key : "<api_key>"
374 api_key : "<api_key>"
375 method : "update_user"
375 method : "update_user"
376 args : {
376 args : {
377 "userid" : "<user_id or username>",
377 "userid" : "<user_id or username>",
378 "username" : "<username> = Optional(None)",
378 "username" : "<username> = Optional(None)",
379 "email" : "<useremail> = Optional(None)",
379 "email" : "<useremail> = Optional(None)",
380 "password" : "<password> = Optional(None)",
380 "password" : "<password> = Optional(None)",
381 "firstname" : "<firstname> = Optional(None)",
381 "firstname" : "<firstname> = Optional(None)",
382 "lastname" : "<lastname> = Optional(None)",
382 "lastname" : "<lastname> = Optional(None)",
383 "active" : "<bool> = Optional(None)",
383 "active" : "<bool> = Optional(None)",
384 "admin" : "<bool> = Optional(None)",
384 "admin" : "<bool> = Optional(None)",
385 "ldap_dn" : "<ldap_dn> = Optional(None)"
385 "ldap_dn" : "<ldap_dn> = Optional(None)"
386 }
386 }
387
387
388 OUTPUT::
388 OUTPUT::
389
389
390 id : <id_given_in_input>
390 id : <id_given_in_input>
391 result: {
391 result: {
392 "msg" : "updated user ID:<userid> <username>",
392 "msg" : "updated user ID:<userid> <username>",
393 "user": {
393 "user": {
394 "user_id" : "<user_id>",
394 "user_id" : "<user_id>",
395 "username" : "<username>",
395 "username" : "<username>",
396 "firstname": "<firstname>",
396 "firstname": "<firstname>",
397 "lastname" : "<lastname>",
397 "lastname" : "<lastname>",
398 "email" : "<email>",
398 "email" : "<email>",
399 "emails": "<list_of_all_additional_emails>",
399 "emails": "<list_of_all_additional_emails>",
400 "active" : "<bool>",
400 "active" : "<bool>",
401 "admin" :Β  "<bool>",
401 "admin" :Β  "<bool>",
402 "ldap_dn" : "<ldap_dn>",
402 "ldap_dn" : "<ldap_dn>",
403 "last_login": "<last_login>",
403 "last_login": "<last_login>",
404 },
404 },
405 }
405 }
406 error: null
406 error: null
407
407
408
408
409 delete_user
409 delete_user
410 -----------
410 -----------
411
411
412
412
413 deletes givenuser if such user exists. This command can
413 deletes givenuser if such user exists. This command can
414 be executed only using api_key belonging to user with admin rights.
414 be executed only using api_key belonging to user with admin rights.
415
415
416
416
417 INPUT::
417 INPUT::
418
418
419 id : <id_for_response>
419 id : <id_for_response>
420 api_key : "<api_key>"
420 api_key : "<api_key>"
421 method : "delete_user"
421 method : "delete_user"
422 args : {
422 args : {
423 "userid" : "<user_id or username>",
423 "userid" : "<user_id or username>",
424 }
424 }
425
425
426 OUTPUT::
426 OUTPUT::
427
427
428 id : <id_given_in_input>
428 id : <id_given_in_input>
429 result: {
429 result: {
430 "msg" : "deleted user ID:<userid> <username>",
430 "msg" : "deleted user ID:<userid> <username>",
431 "user": null
431 "user": null
432 }
432 }
433 error: null
433 error: null
434
434
435
435
436 get_users_group
436 get_users_group
437 ---------------
437 ---------------
438
438
439 Gets an existing users group. This command can be executed only using api_key
439 Gets an existing user group. This command can be executed only using api_key
440 belonging to user with admin rights.
440 belonging to user with admin rights.
441
441
442
442
443 INPUT::
443 INPUT::
444
444
445 id : <id_for_response>
445 id : <id_for_response>
446 api_key : "<api_key>"
446 api_key : "<api_key>"
447 method : "get_users_group"
447 method : "get_users_group"
448 args : {
448 args : {
449 "usersgroupid" : "<users group id or name>"
449 "usersgroupid" : "<user group id or name>"
450 }
450 }
451
451
452 OUTPUT::
452 OUTPUT::
453
453
454 id : <id_given_in_input>
454 id : <id_given_in_input>
455 result : None if group not exist
455 result : None if group not exist
456 {
456 {
457 "users_group_id" : "<id>",
457 "users_group_id" : "<id>",
458 "group_name" : "<groupname>",
458 "group_name" : "<groupname>",
459 "active": "<bool>",
459 "active": "<bool>",
460 "members" : [
460 "members" : [
461 {
461 {
462 "user_id" : "<user_id>",
462 "user_id" : "<user_id>",
463 "username" : "<username>",
463 "username" : "<username>",
464 "firstname": "<firstname>",
464 "firstname": "<firstname>",
465 "lastname" : "<lastname>",
465 "lastname" : "<lastname>",
466 "email" : "<email>",
466 "email" : "<email>",
467 "emails": "<list_of_all_additional_emails>",
467 "emails": "<list_of_all_additional_emails>",
468 "active" : "<bool>",
468 "active" : "<bool>",
469 "admin" :Β  "<bool>",
469 "admin" :Β  "<bool>",
470 "ldap_dn" : "<ldap_dn>",
470 "ldap_dn" : "<ldap_dn>",
471 "last_login": "<last_login>",
471 "last_login": "<last_login>",
472 },
472 },
473 …
473 …
474 ]
474 ]
475 }
475 }
476 error : null
476 error : null
477
477
478
478
479 get_users_groups
479 get_users_groups
480 ----------------
480 ----------------
481
481
482 Lists all existing users groups. This command can be executed only using
482 Lists all existing user groups. This command can be executed only using
483 api_key belonging to user with admin rights.
483 api_key belonging to user with admin rights.
484
484
485
485
486 INPUT::
486 INPUT::
487
487
488 id : <id_for_response>
488 id : <id_for_response>
489 api_key : "<api_key>"
489 api_key : "<api_key>"
490 method : "get_users_groups"
490 method : "get_users_groups"
491 args : { }
491 args : { }
492
492
493 OUTPUT::
493 OUTPUT::
494
494
495 id : <id_given_in_input>
495 id : <id_given_in_input>
496 result : [
496 result : [
497 {
497 {
498 "users_group_id" : "<id>",
498 "users_group_id" : "<id>",
499 "group_name" : "<groupname>",
499 "group_name" : "<groupname>",
500 "active": "<bool>",
500 "active": "<bool>",
501 },
501 },
502 …
502 …
503 ]
503 ]
504 error : null
504 error : null
505
505
506
506
507 create_users_group
507 create_users_group
508 ------------------
508 ------------------
509
509
510 Creates new users group. This command can be executed only using api_key
510 Creates new user group. This command can be executed only using api_key
511 belonging to user with admin rights
511 belonging to user with admin rights
512
512
513
513
514 INPUT::
514 INPUT::
515
515
516 id : <id_for_response>
516 id : <id_for_response>
517 api_key : "<api_key>"
517 api_key : "<api_key>"
518 method : "create_users_group"
518 method : "create_users_group"
519 args: {
519 args: {
520 "group_name": "<groupname>",
520 "group_name": "<groupname>",
521 "active":"<bool> = Optional(True)"
521 "active":"<bool> = Optional(True)"
522 }
522 }
523
523
524 OUTPUT::
524 OUTPUT::
525
525
526 id : <id_given_in_input>
526 id : <id_given_in_input>
527 result: {
527 result: {
528 "msg": "created new users group `<groupname>`",
528 "msg": "created new user group `<groupname>`",
529 "users_group": {
529 "users_group": {
530 "users_group_id" : "<id>",
530 "users_group_id" : "<id>",
531 "group_name" : "<groupname>",
531 "group_name" : "<groupname>",
532 "active": "<bool>",
532 "active": "<bool>",
533 },
533 },
534 }
534 }
535 error: null
535 error: null
536
536
537
537
538 add_user_to_users_group
538 add_user_to_users_group
539 -----------------------
539 -----------------------
540
540
541 Adds a user to a users group. If user exists in that group success will be
541 Adds a user to a user group. If user exists in that group success will be
542 `false`. This command can be executed only using api_key
542 `false`. This command can be executed only using api_key
543 belonging to user with admin rights
543 belonging to user with admin rights
544
544
545
545
546 INPUT::
546 INPUT::
547
547
548 id : <id_for_response>
548 id : <id_for_response>
549 api_key : "<api_key>"
549 api_key : "<api_key>"
550 method : "add_user_users_group"
550 method : "add_user_users_group"
551 args: {
551 args: {
552 "usersgroupid" : "<users group id or name>",
552 "usersgroupid" : "<user group id or name>",
553 "userid" : "<user_id or username>",
553 "userid" : "<user_id or username>",
554 }
554 }
555
555
556 OUTPUT::
556 OUTPUT::
557
557
558 id : <id_given_in_input>
558 id : <id_given_in_input>
559 result: {
559 result: {
560 "success": True|False # depends on if member is in group
560 "success": True|False # depends on if member is in group
561 "msg": "added member `<username>` to users group `<groupname>` |
561 "msg": "added member `<username>` to user group `<groupname>` |
562 User is already in that group"
562 User is already in that group"
563 }
563 }
564 error: null
564 error: null
565
565
566
566
567 remove_user_from_users_group
567 remove_user_from_users_group
568 ----------------------------
568 ----------------------------
569
569
570 Removes a user from a users group. If user is not in given group success will
570 Removes a user from a user group. If user is not in given group success will
571 be `false`. This command can be executed only
571 be `false`. This command can be executed only
572 using api_key belonging to user with admin rights
572 using api_key belonging to user with admin rights
573
573
574
574
575 INPUT::
575 INPUT::
576
576
577 id : <id_for_response>
577 id : <id_for_response>
578 api_key : "<api_key>"
578 api_key : "<api_key>"
579 method : "remove_user_from_users_group"
579 method : "remove_user_from_users_group"
580 args: {
580 args: {
581 "usersgroupid" : "<users group id or name>",
581 "usersgroupid" : "<user group id or name>",
582 "userid" : "<user_id or username>",
582 "userid" : "<user_id or username>",
583 }
583 }
584
584
585 OUTPUT::
585 OUTPUT::
586
586
587 id : <id_given_in_input>
587 id : <id_given_in_input>
588 result: {
588 result: {
589 "success": True|False, # depends on if member is in group
589 "success": True|False, # depends on if member is in group
590 "msg": "removed member <username> from users group <groupname> |
590 "msg": "removed member <username> from user group <groupname> |
591 User wasn't in group"
591 User wasn't in group"
592 }
592 }
593 error: null
593 error: null
594
594
595
595
596 get_repo
596 get_repo
597 --------
597 --------
598
598
599 Gets an existing repository by it's name or repository_id. Members will return
599 Gets an existing repository by it's name or repository_id. Members will return
600 either users_group or user associated to that repository. This command can be
600 either users_group or user associated to that repository. This command can be
601 executed only using api_key belonging to user with admin
601 executed only using api_key belonging to user with admin
602 rights or regular user that have at least read access to repository.
602 rights or regular user that have at least read access to repository.
603
603
604
604
605 INPUT::
605 INPUT::
606
606
607 id : <id_for_response>
607 id : <id_for_response>
608 api_key : "<api_key>"
608 api_key : "<api_key>"
609 method : "get_repo"
609 method : "get_repo"
610 args: {
610 args: {
611 "repoid" : "<reponame or repo_id>"
611 "repoid" : "<reponame or repo_id>"
612 }
612 }
613
613
614 OUTPUT::
614 OUTPUT::
615
615
616 id : <id_given_in_input>
616 id : <id_given_in_input>
617 result: None if repository does not exist or
617 result: None if repository does not exist or
618 {
618 {
619 "repo_id" : "<repo_id>",
619 "repo_id" : "<repo_id>",
620 "repo_name" : "<reponame>"
620 "repo_name" : "<reponame>"
621 "repo_type" : "<repo_type>",
621 "repo_type" : "<repo_type>",
622 "clone_uri" : "<clone_uri>",
622 "clone_uri" : "<clone_uri>",
623 "enable_downloads": "<bool>",
623 "enable_downloads": "<bool>",
624 "enable_locking": "<bool>",
624 "enable_locking": "<bool>",
625 "enable_statistics": "<bool>",
625 "enable_statistics": "<bool>",
626 "private": "<bool>",
626 "private": "<bool>",
627 "created_on" : "<date_time_created>",
627 "created_on" : "<date_time_created>",
628 "description" : "<description>",
628 "description" : "<description>",
629 "landing_rev": "<landing_rev>",
629 "landing_rev": "<landing_rev>",
630 "last_changeset": {
630 "last_changeset": {
631 "author": "<full_author>",
631 "author": "<full_author>",
632 "date": "<date_time_of_commit>",
632 "date": "<date_time_of_commit>",
633 "message": "<commit_message>",
633 "message": "<commit_message>",
634 "raw_id": "<raw_id>",
634 "raw_id": "<raw_id>",
635 "revision": "<numeric_revision>",
635 "revision": "<numeric_revision>",
636 "short_id": "<short_id>"
636 "short_id": "<short_id>"
637 }
637 }
638 "owner": "<repo_owner>",
638 "owner": "<repo_owner>",
639 "fork_of": "<name_of_fork_parent>",
639 "fork_of": "<name_of_fork_parent>",
640 "members" : [
640 "members" : [
641 {
641 {
642 "type": "user",
642 "type": "user",
643 "user_id" : "<user_id>",
643 "user_id" : "<user_id>",
644 "username" : "<username>",
644 "username" : "<username>",
645 "firstname": "<firstname>",
645 "firstname": "<firstname>",
646 "lastname" : "<lastname>",
646 "lastname" : "<lastname>",
647 "email" : "<email>",
647 "email" : "<email>",
648 "emails": "<list_of_all_additional_emails>",
648 "emails": "<list_of_all_additional_emails>",
649 "active" : "<bool>",
649 "active" : "<bool>",
650 "admin" :Β  "<bool>",
650 "admin" :Β  "<bool>",
651 "ldap_dn" : "<ldap_dn>",
651 "ldap_dn" : "<ldap_dn>",
652 "last_login": "<last_login>",
652 "last_login": "<last_login>",
653 "permission" : "repository.(read|write|admin)"
653 "permission" : "repository.(read|write|admin)"
654 },
654 },
655 …
655 …
656 {
656 {
657 "type": "users_group",
657 "type": "users_group",
658 "id" : "<usersgroupid>",
658 "id" : "<usersgroupid>",
659 "name" : "<usersgroupname>",
659 "name" : "<usersgroupname>",
660 "active": "<bool>",
660 "active": "<bool>",
661 "permission" : "repository.(read|write|admin)"
661 "permission" : "repository.(read|write|admin)"
662 },
662 },
663 …
663 …
664 ]
664 ]
665 "followers": [
665 "followers": [
666 {
666 {
667 "user_id" : "<user_id>",
667 "user_id" : "<user_id>",
668 "username" : "<username>",
668 "username" : "<username>",
669 "firstname": "<firstname>",
669 "firstname": "<firstname>",
670 "lastname" : "<lastname>",
670 "lastname" : "<lastname>",
671 "email" : "<email>",
671 "email" : "<email>",
672 "emails": "<list_of_all_additional_emails>",
672 "emails": "<list_of_all_additional_emails>",
673 "ip_addresses": "<list_of_ip_addresses_for_user>",
673 "ip_addresses": "<list_of_ip_addresses_for_user>",
674 "active" : "<bool>",
674 "active" : "<bool>",
675 "admin" :Β  "<bool>",
675 "admin" :Β  "<bool>",
676 "ldap_dn" : "<ldap_dn>",
676 "ldap_dn" : "<ldap_dn>",
677 "last_login": "<last_login>",
677 "last_login": "<last_login>",
678 },
678 },
679 …
679 …
680 ]
680 ]
681 }
681 }
682 error: null
682 error: null
683
683
684
684
685 get_repos
685 get_repos
686 ---------
686 ---------
687
687
688 Lists all existing repositories. This command can be executed only using
688 Lists all existing repositories. This command can be executed only using
689 api_key belonging to user with admin rights or regular user that have
689 api_key belonging to user with admin rights or regular user that have
690 admin, write or read access to repository.
690 admin, write or read access to repository.
691
691
692
692
693 INPUT::
693 INPUT::
694
694
695 id : <id_for_response>
695 id : <id_for_response>
696 api_key : "<api_key>"
696 api_key : "<api_key>"
697 method : "get_repos"
697 method : "get_repos"
698 args: { }
698 args: { }
699
699
700 OUTPUT::
700 OUTPUT::
701
701
702 id : <id_given_in_input>
702 id : <id_given_in_input>
703 result: [
703 result: [
704 {
704 {
705 "repo_id" : "<repo_id>",
705 "repo_id" : "<repo_id>",
706 "repo_name" : "<reponame>"
706 "repo_name" : "<reponame>"
707 "repo_type" : "<repo_type>",
707 "repo_type" : "<repo_type>",
708 "clone_uri" : "<clone_uri>",
708 "clone_uri" : "<clone_uri>",
709 "private": : "<bool>",
709 "private": : "<bool>",
710 "created_on" : "<datetimecreated>",
710 "created_on" : "<datetimecreated>",
711 "description" : "<description>",
711 "description" : "<description>",
712 "landing_rev": "<landing_rev>",
712 "landing_rev": "<landing_rev>",
713 "owner": "<repo_owner>",
713 "owner": "<repo_owner>",
714 "fork_of": "<name_of_fork_parent>",
714 "fork_of": "<name_of_fork_parent>",
715 "enable_downloads": "<bool>",
715 "enable_downloads": "<bool>",
716 "enable_locking": "<bool>",
716 "enable_locking": "<bool>",
717 "enable_statistics": "<bool>",
717 "enable_statistics": "<bool>",
718 },
718 },
719 …
719 …
720 ]
720 ]
721 error: null
721 error: null
722
722
723
723
724 get_repo_nodes
724 get_repo_nodes
725 --------------
725 --------------
726
726
727 returns a list of nodes and it's children in a flat list for a given path
727 returns a list of nodes and it's children in a flat list for a given path
728 at given revision. It's possible to specify ret_type to show only `files` or
728 at given revision. It's possible to specify ret_type to show only `files` or
729 `dirs`. This command can be executed only using api_key belonging to user
729 `dirs`. This command can be executed only using api_key belonging to user
730 with admin rights
730 with admin rights
731
731
732
732
733 INPUT::
733 INPUT::
734
734
735 id : <id_for_response>
735 id : <id_for_response>
736 api_key : "<api_key>"
736 api_key : "<api_key>"
737 method : "get_repo_nodes"
737 method : "get_repo_nodes"
738 args: {
738 args: {
739 "repoid" : "<reponame or repo_id>"
739 "repoid" : "<reponame or repo_id>"
740 "revision" : "<revision>",
740 "revision" : "<revision>",
741 "root_path" : "<root_path>",
741 "root_path" : "<root_path>",
742 "ret_type" : "<ret_type> = Optional('all')"
742 "ret_type" : "<ret_type> = Optional('all')"
743 }
743 }
744
744
745 OUTPUT::
745 OUTPUT::
746
746
747 id : <id_given_in_input>
747 id : <id_given_in_input>
748 result: [
748 result: [
749 {
749 {
750 "name" : "<name>"
750 "name" : "<name>"
751 "type" : "<type>",
751 "type" : "<type>",
752 },
752 },
753 …
753 …
754 ]
754 ]
755 error: null
755 error: null
756
756
757
757
758 create_repo
758 create_repo
759 -----------
759 -----------
760
760
761 Creates a repository. If repository name contains "/", all needed repository
761 Creates a repository. If repository name contains "/", all needed repository
762 groups will be created. For example "foo/bar/baz" will create groups
762 groups will be created. For example "foo/bar/baz" will create groups
763 "foo", "bar" (with "foo" as parent), and create "baz" repository with
763 "foo", "bar" (with "foo" as parent), and create "baz" repository with
764 "bar" as group. This command can be executed only using api_key belonging to user with admin
764 "bar" as group. This command can be executed only using api_key belonging to user with admin
765 rights or regular user that have create repository permission. Regular users
765 rights or regular user that have create repository permission. Regular users
766 cannot specify owner parameter
766 cannot specify owner parameter
767
767
768
768
769 INPUT::
769 INPUT::
770
770
771 id : <id_for_response>
771 id : <id_for_response>
772 api_key : "<api_key>"
772 api_key : "<api_key>"
773 method : "create_repo"
773 method : "create_repo"
774 args: {
774 args: {
775 "repo_name" : "<reponame>",
775 "repo_name" : "<reponame>",
776 "owner" : "<onwer_name_or_id = Optional(=apiuser)>",
776 "owner" : "<onwer_name_or_id = Optional(=apiuser)>",
777 "repo_type" : "<repo_type> = Optional('hg')",
777 "repo_type" : "<repo_type> = Optional('hg')",
778 "description" : "<description> = Optional('')",
778 "description" : "<description> = Optional('')",
779 "private" : "<bool> = Optional(False)",
779 "private" : "<bool> = Optional(False)",
780 "clone_uri" : "<clone_uri> = Optional(None)",
780 "clone_uri" : "<clone_uri> = Optional(None)",
781 "landing_rev" : "<landing_rev> = Optional('tip')",
781 "landing_rev" : "<landing_rev> = Optional('tip')",
782 "enable_downloads": "<bool> = Optional(False)",
782 "enable_downloads": "<bool> = Optional(False)",
783 "enable_locking": "<bool> = Optional(False)",
783 "enable_locking": "<bool> = Optional(False)",
784 "enable_statistics": "<bool> = Optional(False)",
784 "enable_statistics": "<bool> = Optional(False)",
785 }
785 }
786
786
787 OUTPUT::
787 OUTPUT::
788
788
789 id : <id_given_in_input>
789 id : <id_given_in_input>
790 result: {
790 result: {
791 "msg": "Created new repository `<reponame>`",
791 "msg": "Created new repository `<reponame>`",
792 "repo": {
792 "repo": {
793 "repo_id" : "<repo_id>",
793 "repo_id" : "<repo_id>",
794 "repo_name" : "<reponame>"
794 "repo_name" : "<reponame>"
795 "repo_type" : "<repo_type>",
795 "repo_type" : "<repo_type>",
796 "clone_uri" : "<clone_uri>",
796 "clone_uri" : "<clone_uri>",
797 "private": : "<bool>",
797 "private": : "<bool>",
798 "created_on" : "<datetimecreated>",
798 "created_on" : "<datetimecreated>",
799 "description" : "<description>",
799 "description" : "<description>",
800 "landing_rev": "<landing_rev>",
800 "landing_rev": "<landing_rev>",
801 "owner": "<username or user_id>",
801 "owner": "<username or user_id>",
802 "fork_of": "<name_of_fork_parent>",
802 "fork_of": "<name_of_fork_parent>",
803 "enable_downloads": "<bool>",
803 "enable_downloads": "<bool>",
804 "enable_locking": "<bool>",
804 "enable_locking": "<bool>",
805 "enable_statistics": "<bool>",
805 "enable_statistics": "<bool>",
806 },
806 },
807 }
807 }
808 error: null
808 error: null
809
809
810
810
811 fork_repo
811 fork_repo
812 ---------
812 ---------
813
813
814 Creates a fork of given repo. In case of using celery this will
814 Creates a fork of given repo. In case of using celery this will
815 immidiatelly return success message, while fork is going to be created
815 immidiatelly return success message, while fork is going to be created
816 asynchronous. This command can be executed only using api_key belonging to
816 asynchronous. This command can be executed only using api_key belonging to
817 user with admin rights or regular user that have fork permission, and at least
817 user with admin rights or regular user that have fork permission, and at least
818 read access to forking repository. Regular users cannot specify owner parameter.
818 read access to forking repository. Regular users cannot specify owner parameter.
819
819
820
820
821 INPUT::
821 INPUT::
822
822
823 id : <id_for_response>
823 id : <id_for_response>
824 api_key : "<api_key>"
824 api_key : "<api_key>"
825 method : "fork_repo"
825 method : "fork_repo"
826 args: {
826 args: {
827 "repoid" : "<reponame or repo_id>",
827 "repoid" : "<reponame or repo_id>",
828 "fork_name": "<forkname>",
828 "fork_name": "<forkname>",
829 "owner": "<username or user_id = Optional(=apiuser)>",
829 "owner": "<username or user_id = Optional(=apiuser)>",
830 "description": "<description>",
830 "description": "<description>",
831 "copy_permissions": "<bool>",
831 "copy_permissions": "<bool>",
832 "private": "<bool>",
832 "private": "<bool>",
833 "landing_rev": "<landing_rev>"
833 "landing_rev": "<landing_rev>"
834
834
835 }
835 }
836
836
837 OUTPUT::
837 OUTPUT::
838
838
839 id : <id_given_in_input>
839 id : <id_given_in_input>
840 result: {
840 result: {
841 "msg": "Created fork of `<reponame>` as `<forkname>`",
841 "msg": "Created fork of `<reponame>` as `<forkname>`",
842 "success": true
842 "success": true
843 }
843 }
844 error: null
844 error: null
845
845
846
846
847 delete_repo
847 delete_repo
848 -----------
848 -----------
849
849
850 Deletes a repository. This command can be executed only using api_key belonging to user with admin
850 Deletes a repository. This command can be executed only using api_key belonging to user with admin
851 rights or regular user that have admin access to repository.
851 rights or regular user that have admin access to repository.
852
852
853
853
854 INPUT::
854 INPUT::
855
855
856 id : <id_for_response>
856 id : <id_for_response>
857 api_key : "<api_key>"
857 api_key : "<api_key>"
858 method : "delete_repo"
858 method : "delete_repo"
859 args: {
859 args: {
860 "repoid" : "<reponame or repo_id>"
860 "repoid" : "<reponame or repo_id>"
861 }
861 }
862
862
863 OUTPUT::
863 OUTPUT::
864
864
865 id : <id_given_in_input>
865 id : <id_given_in_input>
866 result: {
866 result: {
867 "msg": "Deleted repository `<reponame>`",
867 "msg": "Deleted repository `<reponame>`",
868 "success": true
868 "success": true
869 }
869 }
870 error: null
870 error: null
871
871
872
872
873 grant_user_permission
873 grant_user_permission
874 ---------------------
874 ---------------------
875
875
876 Grant permission for user on given repository, or update existing one
876 Grant permission for user on given repository, or update existing one
877 if found. This command can be executed only using api_key belonging to user
877 if found. This command can be executed only using api_key belonging to user
878 with admin rights.
878 with admin rights.
879
879
880
880
881 INPUT::
881 INPUT::
882
882
883 id : <id_for_response>
883 id : <id_for_response>
884 api_key : "<api_key>"
884 api_key : "<api_key>"
885 method : "grant_user_permission"
885 method : "grant_user_permission"
886 args: {
886 args: {
887 "repoid" : "<reponame or repo_id>"
887 "repoid" : "<reponame or repo_id>"
888 "userid" : "<username or user_id>"
888 "userid" : "<username or user_id>"
889 "perm" : "(repository.(none|read|write|admin))",
889 "perm" : "(repository.(none|read|write|admin))",
890 }
890 }
891
891
892 OUTPUT::
892 OUTPUT::
893
893
894 id : <id_given_in_input>
894 id : <id_given_in_input>
895 result: {
895 result: {
896 "msg" : "Granted perm: `<perm>` for user: `<username>` in repo: `<reponame>`",
896 "msg" : "Granted perm: `<perm>` for user: `<username>` in repo: `<reponame>`",
897 "success": true
897 "success": true
898 }
898 }
899 error: null
899 error: null
900
900
901
901
902 revoke_user_permission
902 revoke_user_permission
903 ----------------------
903 ----------------------
904
904
905 Revoke permission for user on given repository. This command can be executed
905 Revoke permission for user on given repository. This command can be executed
906 only using api_key belonging to user with admin rights.
906 only using api_key belonging to user with admin rights.
907
907
908
908
909 INPUT::
909 INPUT::
910
910
911 id : <id_for_response>
911 id : <id_for_response>
912 api_key : "<api_key>"
912 api_key : "<api_key>"
913 method : "revoke_user_permission"
913 method : "revoke_user_permission"
914 args: {
914 args: {
915 "repoid" : "<reponame or repo_id>"
915 "repoid" : "<reponame or repo_id>"
916 "userid" : "<username or user_id>"
916 "userid" : "<username or user_id>"
917 }
917 }
918
918
919 OUTPUT::
919 OUTPUT::
920
920
921 id : <id_given_in_input>
921 id : <id_given_in_input>
922 result: {
922 result: {
923 "msg" : "Revoked perm for user: `<username>` in repo: `<reponame>`",
923 "msg" : "Revoked perm for user: `<username>` in repo: `<reponame>`",
924 "success": true
924 "success": true
925 }
925 }
926 error: null
926 error: null
927
927
928
928
929 grant_users_group_permission
929 grant_users_group_permission
930 ----------------------------
930 ----------------------------
931
931
932 Grant permission for users group on given repository, or update
932 Grant permission for user group on given repository, or update
933 existing one if found. This command can be executed only using
933 existing one if found. This command can be executed only using
934 api_key belonging to user with admin rights.
934 api_key belonging to user with admin rights.
935
935
936
936
937 INPUT::
937 INPUT::
938
938
939 id : <id_for_response>
939 id : <id_for_response>
940 api_key : "<api_key>"
940 api_key : "<api_key>"
941 method : "grant_users_group_permission"
941 method : "grant_users_group_permission"
942 args: {
942 args: {
943 "repoid" : "<reponame or repo_id>"
943 "repoid" : "<reponame or repo_id>"
944 "usersgroupid" : "<users group id or name>"
944 "usersgroupid" : "<user group id or name>"
945 "perm" : "(repository.(none|read|write|admin))",
945 "perm" : "(repository.(none|read|write|admin))",
946 }
946 }
947
947
948 OUTPUT::
948 OUTPUT::
949
949
950 id : <id_given_in_input>
950 id : <id_given_in_input>
951 result: {
951 result: {
952 "msg" : "Granted perm: `<perm>` for group: `<usersgroupname>` in repo: `<reponame>`",
952 "msg" : "Granted perm: `<perm>` for group: `<usersgroupname>` in repo: `<reponame>`",
953 "success": true
953 "success": true
954 }
954 }
955 error: null
955 error: null
956
956
957
957
958 revoke_users_group_permission
958 revoke_users_group_permission
959 -----------------------------
959 -----------------------------
960
960
961 Revoke permission for users group on given repository.This command can be
961 Revoke permission for user group on given repository.This command can be
962 executed only using api_key belonging to user with admin rights.
962 executed only using api_key belonging to user with admin rights.
963
963
964 INPUT::
964 INPUT::
965
965
966 id : <id_for_response>
966 id : <id_for_response>
967 api_key : "<api_key>"
967 api_key : "<api_key>"
968 method : "revoke_users_group_permission"
968 method : "revoke_users_group_permission"
969 args: {
969 args: {
970 "repoid" : "<reponame or repo_id>"
970 "repoid" : "<reponame or repo_id>"
971 "usersgroupid" : "<users group id or name>"
971 "usersgroupid" : "<user group id or name>"
972 }
972 }
973
973
974 OUTPUT::
974 OUTPUT::
975
975
976 id : <id_given_in_input>
976 id : <id_given_in_input>
977 result: {
977 result: {
978 "msg" : "Revoked perm for group: `<usersgroupname>` in repo: `<reponame>`",
978 "msg" : "Revoked perm for group: `<usersgroupname>` in repo: `<reponame>`",
979 "success": true
979 "success": true
980 }
980 }
981 error: null
981 error: null
@@ -1,968 +1,968 b''
1 .. _changelog:
1 .. _changelog:
2
2
3 =========
3 =========
4 Changelog
4 Changelog
5 =========
5 =========
6
6
7 1.6.0 (**2013-XX-XX**)
7 1.6.0 (**2013-XX-XX**)
8 ----------------------
8 ----------------------
9
9
10 :status: in-progress
10 :status: in-progress
11 :branch: beta
11 :branch: beta
12
12
13 news
13 news
14 ++++
14 ++++
15
15
16 fixes
16 fixes
17 +++++
17 +++++
18
18
19 1.5.3 (**2013-02-12**)
19 1.5.3 (**2013-02-12**)
20 ----------------------
20 ----------------------
21
21
22 news
22 news
23 ++++
23 ++++
24
24
25 - IP restrictions now also enabled for IPv6
25 - IP restrictions now also enabled for IPv6
26
26
27 fixes
27 fixes
28 +++++
28 +++++
29
29
30 - fixed issues with private checkbox not always working
30 - fixed issues with private checkbox not always working
31 - fixed #746 unicodeDedode errors on feed controllers
31 - fixed #746 unicodeDedode errors on feed controllers
32 - fixes issue #756 cleanup repos didn't properly compose paths of repos to be cleaned up.
32 - fixes issue #756 cleanup repos didn't properly compose paths of repos to be cleaned up.
33 - fixed cache invalidation issues together with vcs_full_cache option
33 - fixed cache invalidation issues together with vcs_full_cache option
34 - repo scan should skip directories with starting with '.'
34 - repo scan should skip directories with starting with '.'
35 - fixes for issue #731, update-repoinfo sometimes failed to update data when changesets
35 - fixes for issue #731, update-repoinfo sometimes failed to update data when changesets
36 were initial commits
36 were initial commits
37 - recursive mode of setting permission skips private repositories
37 - recursive mode of setting permission skips private repositories
38
38
39 1.5.2 (**2013-01-14**)
39 1.5.2 (**2013-01-14**)
40 ----------------------
40 ----------------------
41
41
42 news
42 news
43 ++++
43 ++++
44
44
45 - IP restrictions for users. Each user can get a set of whitelist IP+mask for
45 - IP restrictions for users. Each user can get a set of whitelist IP+mask for
46 extra protection. Useful for buildbots etc.
46 extra protection. Useful for buildbots etc.
47 - added full last changeset info to lightweight dashboard. lightweight dashboard
47 - added full last changeset info to lightweight dashboard. lightweight dashboard
48 is now fully functional replacement of original dashboard.
48 is now fully functional replacement of original dashboard.
49 - implemented certain API calls for non-admin users.
49 - implemented certain API calls for non-admin users.
50 - enabled all Markdown Extra plugins
50 - enabled all Markdown Extra plugins
51 - implemented #725 Pull Request View - Show origin repo URL
51 - implemented #725 Pull Request View - Show origin repo URL
52 - show comments from pull requests into associated changesets
52 - show comments from pull requests into associated changesets
53
53
54 fixes
54 fixes
55 +++++
55 +++++
56
56
57 - update repoinfo script is more failsafe
57 - update repoinfo script is more failsafe
58 - fixed #687 Lazy loaded tooltip bug with simultaneous ajax requests
58 - fixed #687 Lazy loaded tooltip bug with simultaneous ajax requests
59 - fixed #691: Notifications for pull requests: move link to top for better
59 - fixed #691: Notifications for pull requests: move link to top for better
60 readability
60 readability
61 - fixed #699: fix missing fork docs for API
61 - fixed #699: fix missing fork docs for API
62 - fixed #693 Opening changeset from pull request fails
62 - fixed #693 Opening changeset from pull request fails
63 - fixed #710 File view stripping empty lines from beginning and end of file
63 - fixed #710 File view stripping empty lines from beginning and end of file
64 - fixed issues with getting repos by path on windows, caused GIT hooks to fail
64 - fixed issues with getting repos by path on windows, caused GIT hooks to fail
65 - fixed issues with groups paginator on main dashboard
65 - fixed issues with groups paginator on main dashboard
66 - improved fetch/pull command for git repos, now pulling all refs
66 - improved fetch/pull command for git repos, now pulling all refs
67 - fixed issue #719 Journal revision ID tooltip AJAX query path is incorrect
67 - fixed issue #719 Journal revision ID tooltip AJAX query path is incorrect
68 when running in a subdir
68 when running in a subdir
69 - fixed issue #702 API methods without arguments fail when "args":null
69 - fixed issue #702 API methods without arguments fail when "args":null
70 - set the status of changesets initially on pull request. Fixes issues #690 and #587
70 - set the status of changesets initially on pull request. Fixes issues #690 and #587
71
71
72 1.5.1 (**2012-12-13**)
72 1.5.1 (**2012-12-13**)
73 ----------------------
73 ----------------------
74
74
75 news
75 news
76 ++++
76 ++++
77
77
78 - implements #677: Don't allow to close pull requests when they are
78 - implements #677: Don't allow to close pull requests when they are
79 under-review status
79 under-review status
80 - implemented #670 Implementation of Roles in Pull Request
80 - implemented #670 Implementation of Roles in Pull Request
81
81
82 fixes
82 fixes
83 +++++
83 +++++
84
84
85 - default permissions can get duplicated after migration
85 - default permissions can get duplicated after migration
86 - fixed changeset status labels, they now select radio buttons
86 - fixed changeset status labels, they now select radio buttons
87 - #682 translation difficult for multi-line text
87 - #682 translation difficult for multi-line text
88 - #683 fixed difference between messages about not mapped repositories
88 - #683 fixed difference between messages about not mapped repositories
89 - email: fail nicely when no SMTP server has been configured
89 - email: fail nicely when no SMTP server has been configured
90
90
91 1.5.0 (**2012-12-12**)
91 1.5.0 (**2012-12-12**)
92 ----------------------
92 ----------------------
93
93
94 news
94 news
95 ++++
95 ++++
96
96
97 - new rewritten from scratch diff engine. 10x faster in edge cases. Handling
97 - new rewritten from scratch diff engine. 10x faster in edge cases. Handling
98 of file renames, copies, change flags and binary files
98 of file renames, copies, change flags and binary files
99 - added lightweight dashboard option. ref #500. New version of dashboard
99 - added lightweight dashboard option. ref #500. New version of dashboard
100 page that doesn't use any VCS data and is super fast to render. Recommended
100 page that doesn't use any VCS data and is super fast to render. Recommended
101 for large amount of repositories.
101 for large amount of repositories.
102 - implements #648 write Script for updating last modification time for
102 - implements #648 write Script for updating last modification time for
103 lightweight dashboard
103 lightweight dashboard
104 - implemented compare engine for git repositories.
104 - implemented compare engine for git repositories.
105 - LDAP failover, option to specify multiple servers
105 - LDAP failover, option to specify multiple servers
106 - added Errormator and Sentry support for monitoring RhodeCode
106 - added Errormator and Sentry support for monitoring RhodeCode
107 - implemented #628: Pass server URL to rc-extensions hooks
107 - implemented #628: Pass server URL to rc-extensions hooks
108 - new tooltip implementation - added lazy loading of changesets from journal
108 - new tooltip implementation - added lazy loading of changesets from journal
109 pages. This can significantly improve speed of rendering the page
109 pages. This can significantly improve speed of rendering the page
110 - implements #632,added branch/tag/bookmarks info into feeds
110 - implements #632,added branch/tag/bookmarks info into feeds
111 added changeset link to body of message
111 added changeset link to body of message
112 - implemented #638 permissions overview to groups
112 - implemented #638 permissions overview to groups
113 - implements #636, lazy loading of history and authors to speed up source
113 - implements #636, lazy loading of history and authors to speed up source
114 pages rendering
114 pages rendering
115 - implemented #647, option to pass list of default encoding used to
115 - implemented #647, option to pass list of default encoding used to
116 encode to/decode from unicode
116 encode to/decode from unicode
117 - added caching layer into RSS/ATOM feeds.
117 - added caching layer into RSS/ATOM feeds.
118 - basic implementation of cherry picking changesets for pull request, ref #575
118 - basic implementation of cherry picking changesets for pull request, ref #575
119 - implemented #661 Add option to include diff in RSS feed
119 - implemented #661 Add option to include diff in RSS feed
120 - implemented file history page for showing detailed changelog for a given file
120 - implemented file history page for showing detailed changelog for a given file
121 - implemented #663 Admin/permission: specify default repogroup perms
121 - implemented #663 Admin/permission: specify default repogroup perms
122 - implemented #379 defaults settings page for creation of repositories, locking
122 - implemented #379 defaults settings page for creation of repositories, locking
123 statistics, downloads, repository type
123 statistics, downloads, repository type
124 - implemented #210 filtering of admin journal based on Whoosh Query language
124 - implemented #210 filtering of admin journal based on Whoosh Query language
125 - added parents/children links in changeset viewref #650
125 - added parents/children links in changeset viewref #650
126
126
127 fixes
127 fixes
128 +++++
128 +++++
129
129
130 - fixed git version checker
130 - fixed git version checker
131 - #586 patched basic auth handler to fix issues with git behind proxy
131 - #586 patched basic auth handler to fix issues with git behind proxy
132 - #589 search urlgenerator didn't properly escape special characters
132 - #589 search urlgenerator didn't properly escape special characters
133 - fixed issue #614 Include repo name in delete confirmation dialog
133 - fixed issue #614 Include repo name in delete confirmation dialog
134 - fixed #623: Lang meta-tag doesn't work with C#/C++
134 - fixed #623: Lang meta-tag doesn't work with C#/C++
135 - fixes #612 Double quotes to Single quotes result in bad html in diff
135 - fixes #612 Double quotes to Single quotes result in bad html in diff
136 - fixes #630 git statistics do too much work making them slow.
136 - fixes #630 git statistics do too much work making them slow.
137 - fixes #625 Git-Tags are not displayed in Shortlog
137 - fixes #625 Git-Tags are not displayed in Shortlog
138 - fix for issue #602, enforce str when setting mercurial UI object.
138 - fix for issue #602, enforce str when setting mercurial UI object.
139 When this is used together with mercurial internal translation system
139 When this is used together with mercurial internal translation system
140 it can lead to UnicodeDecodeErrors
140 it can lead to UnicodeDecodeErrors
141 - fixes #645 Fix git handler when doing delete remote branch
141 - fixes #645 Fix git handler when doing delete remote branch
142 - implements #649 added two seperate method for author and commiter to VCS
142 - implements #649 added two seperate method for author and commiter to VCS
143 changeset class switch author for git backed to be the real author not commiter
143 changeset class switch author for git backed to be the real author not commiter
144 - fix issue #504 RhodeCode is showing different versions of README on
144 - fix issue #504 RhodeCode is showing different versions of README on
145 different summary page loads
145 different summary page loads
146 - implemented #658 Changing username in LDAP-Mode should not be allowed.
146 - implemented #658 Changing username in LDAP-Mode should not be allowed.
147 - fixes #652 switch to generator approach when doing file annotation to prevent
147 - fixes #652 switch to generator approach when doing file annotation to prevent
148 huge memory consumption
148 huge memory consumption
149 - fixes #666 move lockkey path location to cache_dir to ensure this path is
149 - fixes #666 move lockkey path location to cache_dir to ensure this path is
150 always writable for rhodecode server
150 always writable for rhodecode server
151 - many more small fixes and improvements
151 - many more small fixes and improvements
152 - fixed issues with recursive scans on removed repositories that could take
152 - fixed issues with recursive scans on removed repositories that could take
153 long time on instance start
153 long time on instance start
154
154
155 1.4.4 (**2012-10-08**)
155 1.4.4 (**2012-10-08**)
156 ----------------------
156 ----------------------
157
157
158 news
158 news
159 ++++
159 ++++
160
160
161 - obfuscate db password in logs for engine connection string
161 - obfuscate db password in logs for engine connection string
162 - #574 Show pull request status also in shortlog (if any)
162 - #574 Show pull request status also in shortlog (if any)
163 - remember selected tab in my account page
163 - remember selected tab in my account page
164 - Bumped mercurial version to 2.3.2
164 - Bumped mercurial version to 2.3.2
165 - #595 rcextension hook for repository delete
165 - #595 rcextension hook for repository delete
166
166
167 fixes
167 fixes
168 +++++
168 +++++
169
169
170 - Add git version detection to warn users that Git used in system is to
170 - Add git version detection to warn users that Git used in system is to
171 old. Ref #588 - also show git version in system details in settings page
171 old. Ref #588 - also show git version in system details in settings page
172 - fixed files quick filter links
172 - fixed files quick filter links
173 - #590 Add GET flag that controls the way the diff are generated, for pull
173 - #590 Add GET flag that controls the way the diff are generated, for pull
174 requests we want to use non-bundle based diffs, That are far better for
174 requests we want to use non-bundle based diffs, That are far better for
175 doing code reviews. The /compare url still uses bundle compare for full
175 doing code reviews. The /compare url still uses bundle compare for full
176 comparison including the incoming changesets
176 comparison including the incoming changesets
177 - Fixed #585, checks for status of revision where to strict, and made
177 - Fixed #585, checks for status of revision where to strict, and made
178 opening pull request with those revision impossible due to previously set
178 opening pull request with those revision impossible due to previously set
179 status. Checks now are made also for the repository.
179 status. Checks now are made also for the repository.
180 - fixes #591 git backend was causing encoding errors when handling binary
180 - fixes #591 git backend was causing encoding errors when handling binary
181 files - added a test case for VCS lib tests
181 files - added a test case for VCS lib tests
182 - fixed #597 commits in future get negative age.
182 - fixed #597 commits in future get negative age.
183 - fixed #598 API docs methods had wrong members parameter as returned data
183 - fixed #598 API docs methods had wrong members parameter as returned data
184
184
185 1.4.3 (**2012-09-28**)
185 1.4.3 (**2012-09-28**)
186 ----------------------
186 ----------------------
187
187
188 news
188 news
189 ++++
189 ++++
190
190
191 - #558 Added config file to hooks extra data
191 - #558 Added config file to hooks extra data
192 - bumped mercurial version to 2.3.1
192 - bumped mercurial version to 2.3.1
193 - #518 added possibility of specifying multiple patterns for issues
193 - #518 added possibility of specifying multiple patterns for issues
194 - update codemirror to latest version
194 - update codemirror to latest version
195
195
196 fixes
196 fixes
197 +++++
197 +++++
198
198
199 - fixed #570 explicit users group permissions can overwrite owner permissions
199 - fixed #570 explicit user group permissions can overwrite owner permissions
200 - fixed #578 set proper PATH with current Python for Git
200 - fixed #578 set proper PATH with current Python for Git
201 hooks to execute within same Python as RhodeCode
201 hooks to execute within same Python as RhodeCode
202 - fixed issue with Git bare repos that ends with .git in name
202 - fixed issue with Git bare repos that ends with .git in name
203
203
204 1.4.2 (**2012-09-12**)
204 1.4.2 (**2012-09-12**)
205 ----------------------
205 ----------------------
206
206
207 news
207 news
208 ++++
208 ++++
209
209
210 - added option to menu to quick lock/unlock repository for users that have
210 - added option to menu to quick lock/unlock repository for users that have
211 write access to
211 write access to
212 - Implemented permissions for writing to repo
212 - Implemented permissions for writing to repo
213 groups. Now only write access to group allows to create a repostiory
213 groups. Now only write access to group allows to create a repostiory
214 within that group
214 within that group
215 - #565 Add support for {netloc} and {scheme} to alternative_gravatar_url
215 - #565 Add support for {netloc} and {scheme} to alternative_gravatar_url
216 - updated translation for zh_CN
216 - updated translation for zh_CN
217
217
218 fixes
218 fixes
219 +++++
219 +++++
220
220
221 - fixed visual permissions check on repos groups inside groups
221 - fixed visual permissions check on repos groups inside groups
222 - fixed issues with non-ascii search terms in search, and indexers
222 - fixed issues with non-ascii search terms in search, and indexers
223 - fixed parsing of page number in GET parameters
223 - fixed parsing of page number in GET parameters
224 - fixed issues with generating pull-request overview for repos with
224 - fixed issues with generating pull-request overview for repos with
225 bookmarks and tags, also preview doesn't loose chosen revision from
225 bookmarks and tags, also preview doesn't loose chosen revision from
226 select dropdown
226 select dropdown
227
227
228 1.4.1 (**2012-09-07**)
228 1.4.1 (**2012-09-07**)
229 ----------------------
229 ----------------------
230
230
231 news
231 news
232 ++++
232 ++++
233
233
234 - always put a comment about code-review status change even if user send
234 - always put a comment about code-review status change even if user send
235 empty data
235 empty data
236 - modified_on column saves repository update and it's going to be used
236 - modified_on column saves repository update and it's going to be used
237 later for light version of main page ref #500
237 later for light version of main page ref #500
238 - pull request notifications send much nicer emails with details about pull
238 - pull request notifications send much nicer emails with details about pull
239 request
239 request
240 - #551 show breadcrumbs in summary view for repositories inside a group
240 - #551 show breadcrumbs in summary view for repositories inside a group
241
241
242 fixes
242 fixes
243 +++++
243 +++++
244
244
245 - fixed migrations of permissions that can lead to inconsistency.
245 - fixed migrations of permissions that can lead to inconsistency.
246 Some users sent feedback that after upgrading from older versions issues
246 Some users sent feedback that after upgrading from older versions issues
247 with updating default permissions occurred. RhodeCode detects that now and
247 with updating default permissions occurred. RhodeCode detects that now and
248 resets default user permission to initial state if there is a need for that.
248 resets default user permission to initial state if there is a need for that.
249 Also forces users to set the default value for new forking permission.
249 Also forces users to set the default value for new forking permission.
250 - #535 improved apache wsgi example configuration in docs
250 - #535 improved apache wsgi example configuration in docs
251 - fixes #550 mercurial repositories comparision failed when origin repo had
251 - fixes #550 mercurial repositories comparision failed when origin repo had
252 additional not-common changesets
252 additional not-common changesets
253 - fixed status of code-review in preview windows of pull request
253 - fixed status of code-review in preview windows of pull request
254 - git forks were not initialized at bare repos
254 - git forks were not initialized at bare repos
255 - fixes #555 fixes issues with comparing non-related repositories
255 - fixes #555 fixes issues with comparing non-related repositories
256 - fixes #557 follower counter always counts up
256 - fixes #557 follower counter always counts up
257 - fixed issue #560 require push ssl checkbox wasn't shown when option was
257 - fixed issue #560 require push ssl checkbox wasn't shown when option was
258 enabled
258 enabled
259 - fixed #559
259 - fixed #559
260 - fixed issue #559 fixed bug in routing that mapped repo names with <name>_<num> in name as
260 - fixed issue #559 fixed bug in routing that mapped repo names with <name>_<num> in name as
261 if it was a request to url by repository ID
261 if it was a request to url by repository ID
262
262
263 1.4.0 (**2012-09-03**)
263 1.4.0 (**2012-09-03**)
264 ----------------------
264 ----------------------
265
265
266 news
266 news
267 ++++
267 ++++
268
268
269 - new codereview system
269 - new codereview system
270 - email map, allowing users to have multiple email addresses mapped into
270 - email map, allowing users to have multiple email addresses mapped into
271 their accounts
271 their accounts
272 - improved git-hook system. Now all actions for git are logged into journal
272 - improved git-hook system. Now all actions for git are logged into journal
273 including pushed revisions, user and IP address
273 including pushed revisions, user and IP address
274 - changed setup-app into setup-rhodecode and added default options to it.
274 - changed setup-app into setup-rhodecode and added default options to it.
275 - new git repos are created as bare now by default
275 - new git repos are created as bare now by default
276 - #464 added links to groups in permission box
276 - #464 added links to groups in permission box
277 - #465 mentions autocomplete inside comments boxes
277 - #465 mentions autocomplete inside comments boxes
278 - #469 added --update-only option to whoosh to re-index only given list
278 - #469 added --update-only option to whoosh to re-index only given list
279 of repos in index
279 of repos in index
280 - rhodecode-api CLI client
280 - rhodecode-api CLI client
281 - new git http protocol replaced buggy dulwich implementation.
281 - new git http protocol replaced buggy dulwich implementation.
282 Now based on pygrack & gitweb
282 Now based on pygrack & gitweb
283 - Improved RSS/ATOM feeds. Discoverable by browsers using proper headers, and
283 - Improved RSS/ATOM feeds. Discoverable by browsers using proper headers, and
284 reformated based on user suggestions. Additional rss/atom feeds for user
284 reformated based on user suggestions. Additional rss/atom feeds for user
285 journal
285 journal
286 - various i18n improvements
286 - various i18n improvements
287 - #478 permissions overview for admin in user edit view
287 - #478 permissions overview for admin in user edit view
288 - File view now displays small gravatars off all authors of given file
288 - File view now displays small gravatars off all authors of given file
289 - Implemented landing revisions. Each repository will get landing_rev attribute
289 - Implemented landing revisions. Each repository will get landing_rev attribute
290 that defines 'default' revision/branch for generating readme files
290 that defines 'default' revision/branch for generating readme files
291 - Implemented #509, RhodeCode enforces SSL for push/pulling if requested at
291 - Implemented #509, RhodeCode enforces SSL for push/pulling if requested at
292 earliest possible call.
292 earliest possible call.
293 - Import remote svn repositories to mercurial using hgsubversion.
293 - Import remote svn repositories to mercurial using hgsubversion.
294 - Fixed #508 RhodeCode now has a option to explicitly set forking permissions
294 - Fixed #508 RhodeCode now has a option to explicitly set forking permissions
295 - RhodeCode can use alternative server for generating avatar icons
295 - RhodeCode can use alternative server for generating avatar icons
296 - implemented repositories locking. Pull locks, push unlocks. Also can be done
296 - implemented repositories locking. Pull locks, push unlocks. Also can be done
297 via API calls
297 via API calls
298 - #538 form for permissions can handle multiple users at once
298 - #538 form for permissions can handle multiple users at once
299
299
300 fixes
300 fixes
301 +++++
301 +++++
302
302
303 - improved translations
303 - improved translations
304 - fixes issue #455 Creating an archive generates an exception on Windows
304 - fixes issue #455 Creating an archive generates an exception on Windows
305 - fixes #448 Download ZIP archive keeps file in /tmp open and results
305 - fixes #448 Download ZIP archive keeps file in /tmp open and results
306 in out of disk space
306 in out of disk space
307 - fixes issue #454 Search results under Windows include proceeding
307 - fixes issue #454 Search results under Windows include proceeding
308 backslash
308 backslash
309 - fixed issue #450. Rhodecode no longer will crash when bad revision is
309 - fixed issue #450. Rhodecode no longer will crash when bad revision is
310 present in journal data.
310 present in journal data.
311 - fix for issue #417, git execution was broken on windows for certain
311 - fix for issue #417, git execution was broken on windows for certain
312 commands.
312 commands.
313 - fixed #413. Don't disable .git directory for bare repos on deleting
313 - fixed #413. Don't disable .git directory for bare repos on deleting
314 - fixed issue #459. Changed the way of obtaining logger in reindex task.
314 - fixed issue #459. Changed the way of obtaining logger in reindex task.
315 - fixed #453 added ID field in whoosh SCHEMA that solves the issue of
315 - fixed #453 added ID field in whoosh SCHEMA that solves the issue of
316 reindexing modified files
316 reindexing modified files
317 - fixed #481 rhodecode emails are sent without Date header
317 - fixed #481 rhodecode emails are sent without Date header
318 - fixed #458 wrong count when no repos are present
318 - fixed #458 wrong count when no repos are present
319 - fixed issue #492 missing `\ No newline at end of file` test at the end of
319 - fixed issue #492 missing `\ No newline at end of file` test at the end of
320 new chunk in html diff
320 new chunk in html diff
321 - full text search now works also for commit messages
321 - full text search now works also for commit messages
322
322
323 1.3.6 (**2012-05-17**)
323 1.3.6 (**2012-05-17**)
324 ----------------------
324 ----------------------
325
325
326 news
326 news
327 ++++
327 ++++
328
328
329 - chinese traditional translation
329 - chinese traditional translation
330 - changed setup-app into setup-rhodecode and added arguments for auto-setup
330 - changed setup-app into setup-rhodecode and added arguments for auto-setup
331 mode that doesn't need user interaction
331 mode that doesn't need user interaction
332
332
333 fixes
333 fixes
334 +++++
334 +++++
335
335
336 - fixed no scm found warning
336 - fixed no scm found warning
337 - fixed __future__ import error on rcextensions
337 - fixed __future__ import error on rcextensions
338 - made simplejson required lib for speedup on JSON encoding
338 - made simplejson required lib for speedup on JSON encoding
339 - fixes #449 bad regex could get more than revisions from parsing history
339 - fixes #449 bad regex could get more than revisions from parsing history
340 - don't clear DB session when CELERY_EAGER is turned ON
340 - don't clear DB session when CELERY_EAGER is turned ON
341
341
342 1.3.5 (**2012-05-10**)
342 1.3.5 (**2012-05-10**)
343 ----------------------
343 ----------------------
344
344
345 news
345 news
346 ++++
346 ++++
347
347
348 - use ext_json for json module
348 - use ext_json for json module
349 - unified annotation view with file source view
349 - unified annotation view with file source view
350 - notification improvements, better inbox + css
350 - notification improvements, better inbox + css
351 - #419 don't strip passwords for login forms, make rhodecode
351 - #419 don't strip passwords for login forms, make rhodecode
352 more compatible with LDAP servers
352 more compatible with LDAP servers
353 - Added HTTP_X_FORWARDED_FOR as another method of extracting
353 - Added HTTP_X_FORWARDED_FOR as another method of extracting
354 IP for pull/push logs. - moved all to base controller
354 IP for pull/push logs. - moved all to base controller
355 - #415: Adding comment to changeset causes reload.
355 - #415: Adding comment to changeset causes reload.
356 Comments are now added via ajax and doesn't reload the page
356 Comments are now added via ajax and doesn't reload the page
357 - #374 LDAP config is discarded when LDAP can't be activated
357 - #374 LDAP config is discarded when LDAP can't be activated
358 - limited push/pull operations are now logged for git in the journal
358 - limited push/pull operations are now logged for git in the journal
359 - bumped mercurial to 2.2.X series
359 - bumped mercurial to 2.2.X series
360 - added support for displaying submodules in file-browser
360 - added support for displaying submodules in file-browser
361 - #421 added bookmarks in changelog view
361 - #421 added bookmarks in changelog view
362
362
363 fixes
363 fixes
364 +++++
364 +++++
365
365
366 - fixed dev-version marker for stable when served from source codes
366 - fixed dev-version marker for stable when served from source codes
367 - fixed missing permission checks on show forks page
367 - fixed missing permission checks on show forks page
368 - #418 cast to unicode fixes in notification objects
368 - #418 cast to unicode fixes in notification objects
369 - #426 fixed mention extracting regex
369 - #426 fixed mention extracting regex
370 - fixed remote-pulling for git remotes remopositories
370 - fixed remote-pulling for git remotes remopositories
371 - fixed #434: Error when accessing files or changesets of a git repository
371 - fixed #434: Error when accessing files or changesets of a git repository
372 with submodules
372 with submodules
373 - fixed issue with empty APIKEYS for users after registration ref. #438
373 - fixed issue with empty APIKEYS for users after registration ref. #438
374 - fixed issue with getting README files from git repositories
374 - fixed issue with getting README files from git repositories
375
375
376 1.3.4 (**2012-03-28**)
376 1.3.4 (**2012-03-28**)
377 ----------------------
377 ----------------------
378
378
379 news
379 news
380 ++++
380 ++++
381
381
382 - Whoosh logging is now controlled by the .ini files logging setup
382 - Whoosh logging is now controlled by the .ini files logging setup
383 - added clone-url into edit form on /settings page
383 - added clone-url into edit form on /settings page
384 - added help text into repo add/edit forms
384 - added help text into repo add/edit forms
385 - created rcextensions module with additional mappings (ref #322) and
385 - created rcextensions module with additional mappings (ref #322) and
386 post push/pull/create repo hooks callbacks
386 post push/pull/create repo hooks callbacks
387 - implemented #377 Users view for his own permissions on account page
387 - implemented #377 Users view for his own permissions on account page
388 - #399 added inheritance of permissions for users group on repos groups
388 - #399 added inheritance of permissions for user group on repos groups
389 - #401 repository group is automatically pre-selected when adding repos
389 - #401 repository group is automatically pre-selected when adding repos
390 inside a repository group
390 inside a repository group
391 - added alternative HTTP 403 response when client failed to authenticate. Helps
391 - added alternative HTTP 403 response when client failed to authenticate. Helps
392 solving issues with Mercurial and LDAP
392 solving issues with Mercurial and LDAP
393 - #402 removed group prefix from repository name when listing repositories
393 - #402 removed group prefix from repository name when listing repositories
394 inside a group
394 inside a group
395 - added gravatars into permission view and permissions autocomplete
395 - added gravatars into permission view and permissions autocomplete
396 - #347 when running multiple RhodeCode instances, properly invalidates cache
396 - #347 when running multiple RhodeCode instances, properly invalidates cache
397 for all registered servers
397 for all registered servers
398
398
399 fixes
399 fixes
400 +++++
400 +++++
401
401
402 - fixed #390 cache invalidation problems on repos inside group
402 - fixed #390 cache invalidation problems on repos inside group
403 - fixed #385 clone by ID url was loosing proxy prefix in URL
403 - fixed #385 clone by ID url was loosing proxy prefix in URL
404 - fixed some unicode problems with waitress
404 - fixed some unicode problems with waitress
405 - fixed issue with escaping < and > in changeset commits
405 - fixed issue with escaping < and > in changeset commits
406 - fixed error occurring during recursive group creation in API
406 - fixed error occurring during recursive group creation in API
407 create_repo function
407 create_repo function
408 - fixed #393 py2.5 fixes for routes url generator
408 - fixed #393 py2.5 fixes for routes url generator
409 - fixed #397 Private repository groups shows up before login
409 - fixed #397 Private repository groups shows up before login
410 - fixed #396 fixed problems with revoking users in nested groups
410 - fixed #396 fixed problems with revoking users in nested groups
411 - fixed mysql unicode issues + specified InnoDB as default engine with
411 - fixed mysql unicode issues + specified InnoDB as default engine with
412 utf8 charset
412 utf8 charset
413 - #406 trim long branch/tag names in changelog to not break UI
413 - #406 trim long branch/tag names in changelog to not break UI
414
414
415 1.3.3 (**2012-03-02**)
415 1.3.3 (**2012-03-02**)
416 ----------------------
416 ----------------------
417
417
418 news
418 news
419 ++++
419 ++++
420
420
421
421
422 fixes
422 fixes
423 +++++
423 +++++
424
424
425 - fixed some python2.5 compatibility issues
425 - fixed some python2.5 compatibility issues
426 - fixed issues with removed repos was accidentally added as groups, after
426 - fixed issues with removed repos was accidentally added as groups, after
427 full rescan of paths
427 full rescan of paths
428 - fixes #376 Cannot edit user (using container auth)
428 - fixes #376 Cannot edit user (using container auth)
429 - fixes #378 Invalid image urls on changeset screen with proxy-prefix
429 - fixes #378 Invalid image urls on changeset screen with proxy-prefix
430 configuration
430 configuration
431 - fixed initial sorting of repos inside repo group
431 - fixed initial sorting of repos inside repo group
432 - fixes issue when user tried to resubmit same permission into user/user_groups
432 - fixes issue when user tried to resubmit same permission into user/user_groups
433 - bumped beaker version that fixes #375 leap error bug
433 - bumped beaker version that fixes #375 leap error bug
434 - fixed raw_changeset for git. It was generated with hg patch headers
434 - fixed raw_changeset for git. It was generated with hg patch headers
435 - fixed vcs issue with last_changeset for filenodes
435 - fixed vcs issue with last_changeset for filenodes
436 - fixed missing commit after hook delete
436 - fixed missing commit after hook delete
437 - fixed #372 issues with git operation detection that caused a security issue
437 - fixed #372 issues with git operation detection that caused a security issue
438 for git repos
438 for git repos
439
439
440 1.3.2 (**2012-02-28**)
440 1.3.2 (**2012-02-28**)
441 ----------------------
441 ----------------------
442
442
443 news
443 news
444 ++++
444 ++++
445
445
446
446
447 fixes
447 fixes
448 +++++
448 +++++
449
449
450 - fixed git protocol issues with repos-groups
450 - fixed git protocol issues with repos-groups
451 - fixed git remote repos validator that prevented from cloning remote git repos
451 - fixed git remote repos validator that prevented from cloning remote git repos
452 - fixes #370 ending slashes fixes for repo and groups
452 - fixes #370 ending slashes fixes for repo and groups
453 - fixes #368 improved git-protocol detection to handle other clients
453 - fixes #368 improved git-protocol detection to handle other clients
454 - fixes #366 When Setting Repository Group To Blank Repo Group Wont Be
454 - fixes #366 When Setting Repository Group To Blank Repo Group Wont Be
455 Moved To Root
455 Moved To Root
456 - fixes #371 fixed issues with beaker/sqlalchemy and non-ascii cache keys
456 - fixes #371 fixed issues with beaker/sqlalchemy and non-ascii cache keys
457 - fixed #373 missing cascade drop on user_group_to_perm table
457 - fixed #373 missing cascade drop on user_group_to_perm table
458
458
459 1.3.1 (**2012-02-27**)
459 1.3.1 (**2012-02-27**)
460 ----------------------
460 ----------------------
461
461
462 news
462 news
463 ++++
463 ++++
464
464
465
465
466 fixes
466 fixes
467 +++++
467 +++++
468
468
469 - redirection loop occurs when remember-me wasn't checked during login
469 - redirection loop occurs when remember-me wasn't checked during login
470 - fixes issues with git blob history generation
470 - fixes issues with git blob history generation
471 - don't fetch branch for git in file history dropdown. Causes unneeded slowness
471 - don't fetch branch for git in file history dropdown. Causes unneeded slowness
472
472
473 1.3.0 (**2012-02-26**)
473 1.3.0 (**2012-02-26**)
474 ----------------------
474 ----------------------
475
475
476 news
476 news
477 ++++
477 ++++
478
478
479 - code review, inspired by github code-comments
479 - code review, inspired by github code-comments
480 - #215 rst and markdown README files support
480 - #215 rst and markdown README files support
481 - #252 Container-based and proxy pass-through authentication support
481 - #252 Container-based and proxy pass-through authentication support
482 - #44 branch browser. Filtering of changelog by branches
482 - #44 branch browser. Filtering of changelog by branches
483 - mercurial bookmarks support
483 - mercurial bookmarks support
484 - new hover top menu, optimized to add maximum size for important views
484 - new hover top menu, optimized to add maximum size for important views
485 - configurable clone url template with possibility to specify protocol like
485 - configurable clone url template with possibility to specify protocol like
486 ssh:// or http:// and also manually alter other parts of clone_url.
486 ssh:// or http:// and also manually alter other parts of clone_url.
487 - enabled largefiles extension by default
487 - enabled largefiles extension by default
488 - optimized summary file pages and saved a lot of unused space in them
488 - optimized summary file pages and saved a lot of unused space in them
489 - #239 option to manually mark repository as fork
489 - #239 option to manually mark repository as fork
490 - #320 mapping of commit authors to RhodeCode users
490 - #320 mapping of commit authors to RhodeCode users
491 - #304 hashes are displayed using monospace font
491 - #304 hashes are displayed using monospace font
492 - diff configuration, toggle white lines and context lines
492 - diff configuration, toggle white lines and context lines
493 - #307 configurable diffs, whitespace toggle, increasing context lines
493 - #307 configurable diffs, whitespace toggle, increasing context lines
494 - sorting on branches, tags and bookmarks using YUI datatable
494 - sorting on branches, tags and bookmarks using YUI datatable
495 - improved file filter on files page
495 - improved file filter on files page
496 - implements #330 api method for listing nodes ar particular revision
496 - implements #330 api method for listing nodes ar particular revision
497 - #73 added linking issues in commit messages to chosen issue tracker url
497 - #73 added linking issues in commit messages to chosen issue tracker url
498 based on user defined regular expression
498 based on user defined regular expression
499 - added linking of changesets in commit messages
499 - added linking of changesets in commit messages
500 - new compact changelog with expandable commit messages
500 - new compact changelog with expandable commit messages
501 - firstname and lastname are optional in user creation
501 - firstname and lastname are optional in user creation
502 - #348 added post-create repository hook
502 - #348 added post-create repository hook
503 - #212 global encoding settings is now configurable from .ini files
503 - #212 global encoding settings is now configurable from .ini files
504 - #227 added repository groups permissions
504 - #227 added repository groups permissions
505 - markdown gets codehilite extensions
505 - markdown gets codehilite extensions
506 - new API methods, delete_repositories, grante/revoke permissions for groups
506 - new API methods, delete_repositories, grante/revoke permissions for groups
507 and repos
507 and repos
508
508
509
509
510 fixes
510 fixes
511 +++++
511 +++++
512
512
513 - rewrote dbsession management for atomic operations, and better error handling
513 - rewrote dbsession management for atomic operations, and better error handling
514 - fixed sorting of repo tables
514 - fixed sorting of repo tables
515 - #326 escape of special html entities in diffs
515 - #326 escape of special html entities in diffs
516 - normalized user_name => username in api attributes
516 - normalized user_name => username in api attributes
517 - fixes #298 ldap created users with mixed case emails created conflicts
517 - fixes #298 ldap created users with mixed case emails created conflicts
518 on saving a form
518 on saving a form
519 - fixes issue when owner of a repo couldn't revoke permissions for users
519 - fixes issue when owner of a repo couldn't revoke permissions for users
520 and groups
520 and groups
521 - fixes #271 rare JSON serialization problem with statistics
521 - fixes #271 rare JSON serialization problem with statistics
522 - fixes #337 missing validation check for conflicting names of a group with a
522 - fixes #337 missing validation check for conflicting names of a group with a
523 repositories group
523 repositories group
524 - #340 fixed session problem for mysql and celery tasks
524 - #340 fixed session problem for mysql and celery tasks
525 - fixed #331 RhodeCode mangles repository names if the a repository group
525 - fixed #331 RhodeCode mangles repository names if the a repository group
526 contains the "full path" to the repositories
526 contains the "full path" to the repositories
527 - #355 RhodeCode doesn't store encrypted LDAP passwords
527 - #355 RhodeCode doesn't store encrypted LDAP passwords
528
528
529 1.2.5 (**2012-01-28**)
529 1.2.5 (**2012-01-28**)
530 ----------------------
530 ----------------------
531
531
532 news
532 news
533 ++++
533 ++++
534
534
535 fixes
535 fixes
536 +++++
536 +++++
537
537
538 - #340 Celery complains about MySQL server gone away, added session cleanup
538 - #340 Celery complains about MySQL server gone away, added session cleanup
539 for celery tasks
539 for celery tasks
540 - #341 "scanning for repositories in None" log message during Rescan was missing
540 - #341 "scanning for repositories in None" log message during Rescan was missing
541 a parameter
541 a parameter
542 - fixed creating archives with subrepos. Some hooks were triggered during that
542 - fixed creating archives with subrepos. Some hooks were triggered during that
543 operation leading to crash.
543 operation leading to crash.
544 - fixed missing email in account page.
544 - fixed missing email in account page.
545 - Reverted Mercurial to 2.0.1 for windows due to bug in Mercurial that makes
545 - Reverted Mercurial to 2.0.1 for windows due to bug in Mercurial that makes
546 forking on windows impossible
546 forking on windows impossible
547
547
548 1.2.4 (**2012-01-19**)
548 1.2.4 (**2012-01-19**)
549 ----------------------
549 ----------------------
550
550
551 news
551 news
552 ++++
552 ++++
553
553
554 - RhodeCode is bundled with mercurial series 2.0.X by default, with
554 - RhodeCode is bundled with mercurial series 2.0.X by default, with
555 full support to largefiles extension. Enabled by default in new installations
555 full support to largefiles extension. Enabled by default in new installations
556 - #329 Ability to Add/Remove Groups to/from a Repository via AP
556 - #329 Ability to Add/Remove Groups to/from a Repository via AP
557 - added requires.txt file with requirements
557 - added requires.txt file with requirements
558
558
559 fixes
559 fixes
560 +++++
560 +++++
561
561
562 - fixes db session issues with celery when emailing admins
562 - fixes db session issues with celery when emailing admins
563 - #331 RhodeCode mangles repository names if the a repository group
563 - #331 RhodeCode mangles repository names if the a repository group
564 contains the "full path" to the repositories
564 contains the "full path" to the repositories
565 - #298 Conflicting e-mail addresses for LDAP and RhodeCode users
565 - #298 Conflicting e-mail addresses for LDAP and RhodeCode users
566 - DB session cleanup after hg protocol operations, fixes issues with
566 - DB session cleanup after hg protocol operations, fixes issues with
567 `mysql has gone away` errors
567 `mysql has gone away` errors
568 - #333 doc fixes for get_repo api function
568 - #333 doc fixes for get_repo api function
569 - #271 rare JSON serialization problem with statistics enabled
569 - #271 rare JSON serialization problem with statistics enabled
570 - #337 Fixes issues with validation of repository name conflicting with
570 - #337 Fixes issues with validation of repository name conflicting with
571 a group name. A proper message is now displayed.
571 a group name. A proper message is now displayed.
572 - #292 made ldap_dn in user edit readonly, to get rid of confusion that field
572 - #292 made ldap_dn in user edit readonly, to get rid of confusion that field
573 doesn't work
573 doesn't work
574 - #316 fixes issues with web description in hgrc files
574 - #316 fixes issues with web description in hgrc files
575
575
576 1.2.3 (**2011-11-02**)
576 1.2.3 (**2011-11-02**)
577 ----------------------
577 ----------------------
578
578
579 news
579 news
580 ++++
580 ++++
581
581
582 - added option to manage repos group for non admin users
582 - added option to manage repos group for non admin users
583 - added following API methods for get_users, create_user, get_users_groups,
583 - added following API methods for get_users, create_user, get_users_groups,
584 get_users_group, create_users_group, add_user_to_users_groups, get_repos,
584 get_users_group, create_users_group, add_user_to_users_groups, get_repos,
585 get_repo, create_repo, add_user_to_repo
585 get_repo, create_repo, add_user_to_repo
586 - implements #237 added password confirmation for my account
586 - implements #237 added password confirmation for my account
587 and admin edit user.
587 and admin edit user.
588 - implements #291 email notification for global events are now sent to all
588 - implements #291 email notification for global events are now sent to all
589 administrator users, and global config email.
589 administrator users, and global config email.
590
590
591 fixes
591 fixes
592 +++++
592 +++++
593
593
594 - added option for passing auth method for smtp mailer
594 - added option for passing auth method for smtp mailer
595 - #276 issue with adding a single user with id>10 to usergroups
595 - #276 issue with adding a single user with id>10 to usergroups
596 - #277 fixes windows LDAP settings in which missing values breaks the ldap auth
596 - #277 fixes windows LDAP settings in which missing values breaks the ldap auth
597 - #288 fixes managing of repos in a group for non admin user
597 - #288 fixes managing of repos in a group for non admin user
598
598
599 1.2.2 (**2011-10-17**)
599 1.2.2 (**2011-10-17**)
600 ----------------------
600 ----------------------
601
601
602 news
602 news
603 ++++
603 ++++
604
604
605 - #226 repo groups are available by path instead of numerical id
605 - #226 repo groups are available by path instead of numerical id
606
606
607 fixes
607 fixes
608 +++++
608 +++++
609
609
610 - #259 Groups with the same name but with different parent group
610 - #259 Groups with the same name but with different parent group
611 - #260 Put repo in group, then move group to another group -> repo becomes unavailable
611 - #260 Put repo in group, then move group to another group -> repo becomes unavailable
612 - #258 RhodeCode 1.2 assumes egg folder is writable (lockfiles problems)
612 - #258 RhodeCode 1.2 assumes egg folder is writable (lockfiles problems)
613 - #265 ldap save fails sometimes on converting attributes to booleans,
613 - #265 ldap save fails sometimes on converting attributes to booleans,
614 added getter and setter into model that will prevent from this on db model level
614 added getter and setter into model that will prevent from this on db model level
615 - fixed problems with timestamps issues #251 and #213
615 - fixed problems with timestamps issues #251 and #213
616 - fixes #266 RhodeCode allows to create repo with the same name and in
616 - fixes #266 RhodeCode allows to create repo with the same name and in
617 the same parent as group
617 the same parent as group
618 - fixes #245 Rescan of the repositories on Windows
618 - fixes #245 Rescan of the repositories on Windows
619 - fixes #248 cannot edit repos inside a group on windows
619 - fixes #248 cannot edit repos inside a group on windows
620 - fixes #219 forking problems on windows
620 - fixes #219 forking problems on windows
621
621
622 1.2.1 (**2011-10-08**)
622 1.2.1 (**2011-10-08**)
623 ----------------------
623 ----------------------
624
624
625 news
625 news
626 ++++
626 ++++
627
627
628
628
629 fixes
629 fixes
630 +++++
630 +++++
631
631
632 - fixed problems with basic auth and push problems
632 - fixed problems with basic auth and push problems
633 - gui fixes
633 - gui fixes
634 - fixed logger
634 - fixed logger
635
635
636 1.2.0 (**2011-10-07**)
636 1.2.0 (**2011-10-07**)
637 ----------------------
637 ----------------------
638
638
639 news
639 news
640 ++++
640 ++++
641
641
642 - implemented #47 repository groups
642 - implemented #47 repository groups
643 - implemented #89 Can setup google analytics code from settings menu
643 - implemented #89 Can setup google analytics code from settings menu
644 - implemented #91 added nicer looking archive urls with more download options
644 - implemented #91 added nicer looking archive urls with more download options
645 like tags, branches
645 like tags, branches
646 - implemented #44 into file browsing, and added follow branch option
646 - implemented #44 into file browsing, and added follow branch option
647 - implemented #84 downloads can be enabled/disabled for each repository
647 - implemented #84 downloads can be enabled/disabled for each repository
648 - anonymous repository can be cloned without having to pass default:default
648 - anonymous repository can be cloned without having to pass default:default
649 into clone url
649 into clone url
650 - fixed #90 whoosh indexer can index chooses repositories passed in command
650 - fixed #90 whoosh indexer can index chooses repositories passed in command
651 line
651 line
652 - extended journal with day aggregates and paging
652 - extended journal with day aggregates and paging
653 - implemented #107 source code lines highlight ranges
653 - implemented #107 source code lines highlight ranges
654 - implemented #93 customizable changelog on combined revision ranges -
654 - implemented #93 customizable changelog on combined revision ranges -
655 equivalent of githubs compare view
655 equivalent of githubs compare view
656 - implemented #108 extended and more powerful LDAP configuration
656 - implemented #108 extended and more powerful LDAP configuration
657 - implemented #56 users groups
657 - implemented #56 user groups
658 - major code rewrites optimized codes for speed and memory usage
658 - major code rewrites optimized codes for speed and memory usage
659 - raw and diff downloads are now in git format
659 - raw and diff downloads are now in git format
660 - setup command checks for write access to given path
660 - setup command checks for write access to given path
661 - fixed many issues with international characters and unicode. It uses utf8
661 - fixed many issues with international characters and unicode. It uses utf8
662 decode with replace to provide less errors even with non utf8 encoded strings
662 decode with replace to provide less errors even with non utf8 encoded strings
663 - #125 added API KEY access to feeds
663 - #125 added API KEY access to feeds
664 - #109 Repository can be created from external Mercurial link (aka. remote
664 - #109 Repository can be created from external Mercurial link (aka. remote
665 repository, and manually updated (via pull) from admin panel
665 repository, and manually updated (via pull) from admin panel
666 - beta git support - push/pull server + basic view for git repos
666 - beta git support - push/pull server + basic view for git repos
667 - added followers page and forks page
667 - added followers page and forks page
668 - server side file creation (with binary file upload interface)
668 - server side file creation (with binary file upload interface)
669 and edition with commits powered by codemirror
669 and edition with commits powered by codemirror
670 - #111 file browser file finder, quick lookup files on whole file tree
670 - #111 file browser file finder, quick lookup files on whole file tree
671 - added quick login sliding menu into main page
671 - added quick login sliding menu into main page
672 - changelog uses lazy loading of affected files details, in some scenarios
672 - changelog uses lazy loading of affected files details, in some scenarios
673 this can improve speed of changelog page dramatically especially for
673 this can improve speed of changelog page dramatically especially for
674 larger repositories.
674 larger repositories.
675 - implements #214 added support for downloading subrepos in download menu.
675 - implements #214 added support for downloading subrepos in download menu.
676 - Added basic API for direct operations on rhodecode via JSON
676 - Added basic API for direct operations on rhodecode via JSON
677 - Implemented advanced hook management
677 - Implemented advanced hook management
678
678
679 fixes
679 fixes
680 +++++
680 +++++
681
681
682 - fixed file browser bug, when switching into given form revision the url was
682 - fixed file browser bug, when switching into given form revision the url was
683 not changing
683 not changing
684 - fixed propagation to error controller on simplehg and simplegit middlewares
684 - fixed propagation to error controller on simplehg and simplegit middlewares
685 - fixed error when trying to make a download on empty repository
685 - fixed error when trying to make a download on empty repository
686 - fixed problem with '[' chars in commit messages in journal
686 - fixed problem with '[' chars in commit messages in journal
687 - fixed #99 Unicode errors, on file node paths with non utf-8 characters
687 - fixed #99 Unicode errors, on file node paths with non utf-8 characters
688 - journal fork fixes
688 - journal fork fixes
689 - removed issue with space inside renamed repository after deletion
689 - removed issue with space inside renamed repository after deletion
690 - fixed strange issue on formencode imports
690 - fixed strange issue on formencode imports
691 - fixed #126 Deleting repository on Windows, rename used incompatible chars.
691 - fixed #126 Deleting repository on Windows, rename used incompatible chars.
692 - #150 fixes for errors on repositories mapped in db but corrupted in
692 - #150 fixes for errors on repositories mapped in db but corrupted in
693 filesystem
693 filesystem
694 - fixed problem with ascendant characters in realm #181
694 - fixed problem with ascendant characters in realm #181
695 - fixed problem with sqlite file based database connection pool
695 - fixed problem with sqlite file based database connection pool
696 - whoosh indexer and code stats share the same dynamic extensions map
696 - whoosh indexer and code stats share the same dynamic extensions map
697 - fixes #188 - relationship delete of repo_to_perm entry on user removal
697 - fixes #188 - relationship delete of repo_to_perm entry on user removal
698 - fixes issue #189 Trending source files shows "show more" when no more exist
698 - fixes issue #189 Trending source files shows "show more" when no more exist
699 - fixes issue #197 Relative paths for pidlocks
699 - fixes issue #197 Relative paths for pidlocks
700 - fixes issue #198 password will require only 3 chars now for login form
700 - fixes issue #198 password will require only 3 chars now for login form
701 - fixes issue #199 wrong redirection for non admin users after creating a repository
701 - fixes issue #199 wrong redirection for non admin users after creating a repository
702 - fixes issues #202, bad db constraint made impossible to attach same group
702 - fixes issues #202, bad db constraint made impossible to attach same group
703 more than one time. Affects only mysql/postgres
703 more than one time. Affects only mysql/postgres
704 - fixes #218 os.kill patch for windows was missing sig param
704 - fixes #218 os.kill patch for windows was missing sig param
705 - improved rendering of dag (they are not trimmed anymore when number of
705 - improved rendering of dag (they are not trimmed anymore when number of
706 heads exceeds 5)
706 heads exceeds 5)
707
707
708 1.1.8 (**2011-04-12**)
708 1.1.8 (**2011-04-12**)
709 ----------------------
709 ----------------------
710
710
711 news
711 news
712 ++++
712 ++++
713
713
714 - improved windows support
714 - improved windows support
715
715
716 fixes
716 fixes
717 +++++
717 +++++
718
718
719 - fixed #140 freeze of python dateutil library, since new version is python2.x
719 - fixed #140 freeze of python dateutil library, since new version is python2.x
720 incompatible
720 incompatible
721 - setup-app will check for write permission in given path
721 - setup-app will check for write permission in given path
722 - cleaned up license info issue #149
722 - cleaned up license info issue #149
723 - fixes for issues #137,#116 and problems with unicode and accented characters.
723 - fixes for issues #137,#116 and problems with unicode and accented characters.
724 - fixes crashes on gravatar, when passed in email as unicode
724 - fixes crashes on gravatar, when passed in email as unicode
725 - fixed tooltip flickering problems
725 - fixed tooltip flickering problems
726 - fixed came_from redirection on windows
726 - fixed came_from redirection on windows
727 - fixed logging modules, and sql formatters
727 - fixed logging modules, and sql formatters
728 - windows fixes for os.kill issue #133
728 - windows fixes for os.kill issue #133
729 - fixes path splitting for windows issues #148
729 - fixes path splitting for windows issues #148
730 - fixed issue #143 wrong import on migration to 1.1.X
730 - fixed issue #143 wrong import on migration to 1.1.X
731 - fixed problems with displaying binary files, thanks to Thomas Waldmann
731 - fixed problems with displaying binary files, thanks to Thomas Waldmann
732 - removed name from archive files since it's breaking ui for long repo names
732 - removed name from archive files since it's breaking ui for long repo names
733 - fixed issue with archive headers sent to browser, thanks to Thomas Waldmann
733 - fixed issue with archive headers sent to browser, thanks to Thomas Waldmann
734 - fixed compatibility for 1024px displays, and larger dpi settings, thanks to
734 - fixed compatibility for 1024px displays, and larger dpi settings, thanks to
735 Thomas Waldmann
735 Thomas Waldmann
736 - fixed issue #166 summary pager was skipping 10 revisions on second page
736 - fixed issue #166 summary pager was skipping 10 revisions on second page
737
737
738
738
739 1.1.7 (**2011-03-23**)
739 1.1.7 (**2011-03-23**)
740 ----------------------
740 ----------------------
741
741
742 news
742 news
743 ++++
743 ++++
744
744
745 fixes
745 fixes
746 +++++
746 +++++
747
747
748 - fixed (again) #136 installation support for FreeBSD
748 - fixed (again) #136 installation support for FreeBSD
749
749
750
750
751 1.1.6 (**2011-03-21**)
751 1.1.6 (**2011-03-21**)
752 ----------------------
752 ----------------------
753
753
754 news
754 news
755 ++++
755 ++++
756
756
757 fixes
757 fixes
758 +++++
758 +++++
759
759
760 - fixed #136 installation support for FreeBSD
760 - fixed #136 installation support for FreeBSD
761 - RhodeCode will check for python version during installation
761 - RhodeCode will check for python version during installation
762
762
763 1.1.5 (**2011-03-17**)
763 1.1.5 (**2011-03-17**)
764 ----------------------
764 ----------------------
765
765
766 news
766 news
767 ++++
767 ++++
768
768
769 - basic windows support, by exchanging pybcrypt into sha256 for windows only
769 - basic windows support, by exchanging pybcrypt into sha256 for windows only
770 highly inspired by idea of mantis406
770 highly inspired by idea of mantis406
771
771
772 fixes
772 fixes
773 +++++
773 +++++
774
774
775 - fixed sorting by author in main page
775 - fixed sorting by author in main page
776 - fixed crashes with diffs on binary files
776 - fixed crashes with diffs on binary files
777 - fixed #131 problem with boolean values for LDAP
777 - fixed #131 problem with boolean values for LDAP
778 - fixed #122 mysql problems thanks to striker69
778 - fixed #122 mysql problems thanks to striker69
779 - fixed problem with errors on calling raw/raw_files/annotate functions
779 - fixed problem with errors on calling raw/raw_files/annotate functions
780 with unknown revisions
780 with unknown revisions
781 - fixed returned rawfiles attachment names with international character
781 - fixed returned rawfiles attachment names with international character
782 - cleaned out docs, big thanks to Jason Harris
782 - cleaned out docs, big thanks to Jason Harris
783
783
784 1.1.4 (**2011-02-19**)
784 1.1.4 (**2011-02-19**)
785 ----------------------
785 ----------------------
786
786
787 news
787 news
788 ++++
788 ++++
789
789
790 fixes
790 fixes
791 +++++
791 +++++
792
792
793 - fixed formencode import problem on settings page, that caused server crash
793 - fixed formencode import problem on settings page, that caused server crash
794 when that page was accessed as first after server start
794 when that page was accessed as first after server start
795 - journal fixes
795 - journal fixes
796 - fixed option to access repository just by entering http://server/<repo_name>
796 - fixed option to access repository just by entering http://server/<repo_name>
797
797
798 1.1.3 (**2011-02-16**)
798 1.1.3 (**2011-02-16**)
799 ----------------------
799 ----------------------
800
800
801 news
801 news
802 ++++
802 ++++
803
803
804 - implemented #102 allowing the '.' character in username
804 - implemented #102 allowing the '.' character in username
805 - added option to access repository just by entering http://server/<repo_name>
805 - added option to access repository just by entering http://server/<repo_name>
806 - celery task ignores result for better performance
806 - celery task ignores result for better performance
807
807
808 fixes
808 fixes
809 +++++
809 +++++
810
810
811 - fixed ehlo command and non auth mail servers on smtp_lib. Thanks to
811 - fixed ehlo command and non auth mail servers on smtp_lib. Thanks to
812 apollo13 and Johan Walles
812 apollo13 and Johan Walles
813 - small fixes in journal
813 - small fixes in journal
814 - fixed problems with getting setting for celery from .ini files
814 - fixed problems with getting setting for celery from .ini files
815 - registration, password reset and login boxes share the same title as main
815 - registration, password reset and login boxes share the same title as main
816 application now
816 application now
817 - fixed #113: to high permissions to fork repository
817 - fixed #113: to high permissions to fork repository
818 - fixed problem with '[' chars in commit messages in journal
818 - fixed problem with '[' chars in commit messages in journal
819 - removed issue with space inside renamed repository after deletion
819 - removed issue with space inside renamed repository after deletion
820 - db transaction fixes when filesystem repository creation failed
820 - db transaction fixes when filesystem repository creation failed
821 - fixed #106 relation issues on databases different than sqlite
821 - fixed #106 relation issues on databases different than sqlite
822 - fixed static files paths links to use of url() method
822 - fixed static files paths links to use of url() method
823
823
824 1.1.2 (**2011-01-12**)
824 1.1.2 (**2011-01-12**)
825 ----------------------
825 ----------------------
826
826
827 news
827 news
828 ++++
828 ++++
829
829
830
830
831 fixes
831 fixes
832 +++++
832 +++++
833
833
834 - fixes #98 protection against float division of percentage stats
834 - fixes #98 protection against float division of percentage stats
835 - fixed graph bug
835 - fixed graph bug
836 - forced webhelpers version since it was making troubles during installation
836 - forced webhelpers version since it was making troubles during installation
837
837
838 1.1.1 (**2011-01-06**)
838 1.1.1 (**2011-01-06**)
839 ----------------------
839 ----------------------
840
840
841 news
841 news
842 ++++
842 ++++
843
843
844 - added force https option into ini files for easier https usage (no need to
844 - added force https option into ini files for easier https usage (no need to
845 set server headers with this options)
845 set server headers with this options)
846 - small css updates
846 - small css updates
847
847
848 fixes
848 fixes
849 +++++
849 +++++
850
850
851 - fixed #96 redirect loop on files view on repositories without changesets
851 - fixed #96 redirect loop on files view on repositories without changesets
852 - fixed #97 unicode string passed into server header in special cases (mod_wsgi)
852 - fixed #97 unicode string passed into server header in special cases (mod_wsgi)
853 and server crashed with errors
853 and server crashed with errors
854 - fixed large tooltips problems on main page
854 - fixed large tooltips problems on main page
855 - fixed #92 whoosh indexer is more error proof
855 - fixed #92 whoosh indexer is more error proof
856
856
857 1.1.0 (**2010-12-18**)
857 1.1.0 (**2010-12-18**)
858 ----------------------
858 ----------------------
859
859
860 news
860 news
861 ++++
861 ++++
862
862
863 - rewrite of internals for vcs >=0.1.10
863 - rewrite of internals for vcs >=0.1.10
864 - uses mercurial 1.7 with dotencode disabled for maintaining compatibility
864 - uses mercurial 1.7 with dotencode disabled for maintaining compatibility
865 with older clients
865 with older clients
866 - anonymous access, authentication via ldap
866 - anonymous access, authentication via ldap
867 - performance upgrade for cached repos list - each repository has its own
867 - performance upgrade for cached repos list - each repository has its own
868 cache that's invalidated when needed.
868 cache that's invalidated when needed.
869 - performance upgrades on repositories with large amount of commits (20K+)
869 - performance upgrades on repositories with large amount of commits (20K+)
870 - main page quick filter for filtering repositories
870 - main page quick filter for filtering repositories
871 - user dashboards with ability to follow chosen repositories actions
871 - user dashboards with ability to follow chosen repositories actions
872 - sends email to admin on new user registration
872 - sends email to admin on new user registration
873 - added cache/statistics reset options into repository settings
873 - added cache/statistics reset options into repository settings
874 - more detailed action logger (based on hooks) with pushed changesets lists
874 - more detailed action logger (based on hooks) with pushed changesets lists
875 and options to disable those hooks from admin panel
875 and options to disable those hooks from admin panel
876 - introduced new enhanced changelog for merges that shows more accurate results
876 - introduced new enhanced changelog for merges that shows more accurate results
877 - new improved and faster code stats (based on pygments lexers mapping tables,
877 - new improved and faster code stats (based on pygments lexers mapping tables,
878 showing up to 10 trending sources for each repository. Additionally stats
878 showing up to 10 trending sources for each repository. Additionally stats
879 can be disabled in repository settings.
879 can be disabled in repository settings.
880 - gui optimizations, fixed application width to 1024px
880 - gui optimizations, fixed application width to 1024px
881 - added cut off (for large files/changesets) limit into config files
881 - added cut off (for large files/changesets) limit into config files
882 - whoosh, celeryd, upgrade moved to paster command
882 - whoosh, celeryd, upgrade moved to paster command
883 - other than sqlite database backends can be used
883 - other than sqlite database backends can be used
884
884
885 fixes
885 fixes
886 +++++
886 +++++
887
887
888 - fixes #61 forked repo was showing only after cache expired
888 - fixes #61 forked repo was showing only after cache expired
889 - fixes #76 no confirmation on user deletes
889 - fixes #76 no confirmation on user deletes
890 - fixes #66 Name field misspelled
890 - fixes #66 Name field misspelled
891 - fixes #72 block user removal when he owns repositories
891 - fixes #72 block user removal when he owns repositories
892 - fixes #69 added password confirmation fields
892 - fixes #69 added password confirmation fields
893 - fixes #87 RhodeCode crashes occasionally on updating repository owner
893 - fixes #87 RhodeCode crashes occasionally on updating repository owner
894 - fixes #82 broken annotations on files with more than 1 blank line at the end
894 - fixes #82 broken annotations on files with more than 1 blank line at the end
895 - a lot of fixes and tweaks for file browser
895 - a lot of fixes and tweaks for file browser
896 - fixed detached session issues
896 - fixed detached session issues
897 - fixed when user had no repos he would see all repos listed in my account
897 - fixed when user had no repos he would see all repos listed in my account
898 - fixed ui() instance bug when global hgrc settings was loaded for server
898 - fixed ui() instance bug when global hgrc settings was loaded for server
899 instance and all hgrc options were merged with our db ui() object
899 instance and all hgrc options were merged with our db ui() object
900 - numerous small bugfixes
900 - numerous small bugfixes
901
901
902 (special thanks for TkSoh for detailed feedback)
902 (special thanks for TkSoh for detailed feedback)
903
903
904
904
905 1.0.2 (**2010-11-12**)
905 1.0.2 (**2010-11-12**)
906 ----------------------
906 ----------------------
907
907
908 news
908 news
909 ++++
909 ++++
910
910
911 - tested under python2.7
911 - tested under python2.7
912 - bumped sqlalchemy and celery versions
912 - bumped sqlalchemy and celery versions
913
913
914 fixes
914 fixes
915 +++++
915 +++++
916
916
917 - fixed #59 missing graph.js
917 - fixed #59 missing graph.js
918 - fixed repo_size crash when repository had broken symlinks
918 - fixed repo_size crash when repository had broken symlinks
919 - fixed python2.5 crashes.
919 - fixed python2.5 crashes.
920
920
921
921
922 1.0.1 (**2010-11-10**)
922 1.0.1 (**2010-11-10**)
923 ----------------------
923 ----------------------
924
924
925 news
925 news
926 ++++
926 ++++
927
927
928 - small css updated
928 - small css updated
929
929
930 fixes
930 fixes
931 +++++
931 +++++
932
932
933 - fixed #53 python2.5 incompatible enumerate calls
933 - fixed #53 python2.5 incompatible enumerate calls
934 - fixed #52 disable mercurial extension for web
934 - fixed #52 disable mercurial extension for web
935 - fixed #51 deleting repositories don't delete it's dependent objects
935 - fixed #51 deleting repositories don't delete it's dependent objects
936
936
937
937
938 1.0.0 (**2010-11-02**)
938 1.0.0 (**2010-11-02**)
939 ----------------------
939 ----------------------
940
940
941 - security bugfix simplehg wasn't checking for permissions on commands
941 - security bugfix simplehg wasn't checking for permissions on commands
942 other than pull or push.
942 other than pull or push.
943 - fixed doubled messages after push or pull in admin journal
943 - fixed doubled messages after push or pull in admin journal
944 - templating and css corrections, fixed repo switcher on chrome, updated titles
944 - templating and css corrections, fixed repo switcher on chrome, updated titles
945 - admin menu accessible from options menu on repository view
945 - admin menu accessible from options menu on repository view
946 - permissions cached queries
946 - permissions cached queries
947
947
948 1.0.0rc4 (**2010-10-12**)
948 1.0.0rc4 (**2010-10-12**)
949 --------------------------
949 --------------------------
950
950
951 - fixed python2.5 missing simplejson imports (thanks to Jens BΓ€ckman)
951 - fixed python2.5 missing simplejson imports (thanks to Jens BΓ€ckman)
952 - removed cache_manager settings from sqlalchemy meta
952 - removed cache_manager settings from sqlalchemy meta
953 - added sqlalchemy cache settings to ini files
953 - added sqlalchemy cache settings to ini files
954 - validated password length and added second try of failure on paster setup-app
954 - validated password length and added second try of failure on paster setup-app
955 - fixed setup database destroy prompt even when there was no db
955 - fixed setup database destroy prompt even when there was no db
956
956
957
957
958 1.0.0rc3 (**2010-10-11**)
958 1.0.0rc3 (**2010-10-11**)
959 -------------------------
959 -------------------------
960
960
961 - fixed i18n during installation.
961 - fixed i18n during installation.
962
962
963 1.0.0rc2 (**2010-10-11**)
963 1.0.0rc2 (**2010-10-11**)
964 -------------------------
964 -------------------------
965
965
966 - Disabled dirsize in file browser, it's causing nasty bug when dir renames
966 - Disabled dirsize in file browser, it's causing nasty bug when dir renames
967 occure. After vcs is fixed it'll be put back again.
967 occure. After vcs is fixed it'll be put back again.
968 - templating/css rewrites, optimized css.
968 - templating/css rewrites, optimized css.
@@ -1,648 +1,648 b''
1 """
1 """
2 Routes configuration
2 Routes configuration
3
3
4 The more specific and detailed routes should be defined first so they
4 The more specific and detailed routes should be defined first so they
5 may take precedent over the more generic routes. For more information
5 may take precedent over the more generic routes. For more information
6 refer to the routes manual at http://routes.groovie.org/docs/
6 refer to the routes manual at http://routes.groovie.org/docs/
7 """
7 """
8 from __future__ import with_statement
8 from __future__ import with_statement
9 from routes import Mapper
9 from routes import Mapper
10
10
11 # prefix for non repository related links needs to be prefixed with `/`
11 # prefix for non repository related links needs to be prefixed with `/`
12 ADMIN_PREFIX = '/_admin'
12 ADMIN_PREFIX = '/_admin'
13
13
14
14
15 def make_map(config):
15 def make_map(config):
16 """Create, configure and return the routes Mapper"""
16 """Create, configure and return the routes Mapper"""
17 rmap = Mapper(directory=config['pylons.paths']['controllers'],
17 rmap = Mapper(directory=config['pylons.paths']['controllers'],
18 always_scan=config['debug'])
18 always_scan=config['debug'])
19 rmap.minimization = False
19 rmap.minimization = False
20 rmap.explicit = False
20 rmap.explicit = False
21
21
22 from rhodecode.lib.utils import is_valid_repo
22 from rhodecode.lib.utils import is_valid_repo
23 from rhodecode.lib.utils import is_valid_repos_group
23 from rhodecode.lib.utils import is_valid_repos_group
24
24
25 def check_repo(environ, match_dict):
25 def check_repo(environ, match_dict):
26 """
26 """
27 check for valid repository for proper 404 handling
27 check for valid repository for proper 404 handling
28
28
29 :param environ:
29 :param environ:
30 :param match_dict:
30 :param match_dict:
31 """
31 """
32 from rhodecode.model.db import Repository
32 from rhodecode.model.db import Repository
33 repo_name = match_dict.get('repo_name')
33 repo_name = match_dict.get('repo_name')
34
34
35 if match_dict.get('f_path'):
35 if match_dict.get('f_path'):
36 #fix for multiple initial slashes that causes errors
36 #fix for multiple initial slashes that causes errors
37 match_dict['f_path'] = match_dict['f_path'].lstrip('/')
37 match_dict['f_path'] = match_dict['f_path'].lstrip('/')
38
38
39 try:
39 try:
40 by_id = repo_name.split('_')
40 by_id = repo_name.split('_')
41 if len(by_id) == 2 and by_id[1].isdigit() and by_id[0] == '':
41 if len(by_id) == 2 and by_id[1].isdigit() and by_id[0] == '':
42 repo_name = Repository.get(by_id[1]).repo_name
42 repo_name = Repository.get(by_id[1]).repo_name
43 match_dict['repo_name'] = repo_name
43 match_dict['repo_name'] = repo_name
44 except:
44 except:
45 pass
45 pass
46
46
47 return is_valid_repo(repo_name, config['base_path'])
47 return is_valid_repo(repo_name, config['base_path'])
48
48
49 def check_group(environ, match_dict):
49 def check_group(environ, match_dict):
50 """
50 """
51 check for valid repositories group for proper 404 handling
51 check for valid repositories group for proper 404 handling
52
52
53 :param environ:
53 :param environ:
54 :param match_dict:
54 :param match_dict:
55 """
55 """
56 repos_group_name = match_dict.get('group_name')
56 repos_group_name = match_dict.get('group_name')
57 return is_valid_repos_group(repos_group_name, config['base_path'])
57 return is_valid_repos_group(repos_group_name, config['base_path'])
58
58
59 def check_int(environ, match_dict):
59 def check_int(environ, match_dict):
60 return match_dict.get('id').isdigit()
60 return match_dict.get('id').isdigit()
61
61
62 # The ErrorController route (handles 404/500 error pages); it should
62 # The ErrorController route (handles 404/500 error pages); it should
63 # likely stay at the top, ensuring it can always be resolved
63 # likely stay at the top, ensuring it can always be resolved
64 rmap.connect('/error/{action}', controller='error')
64 rmap.connect('/error/{action}', controller='error')
65 rmap.connect('/error/{action}/{id}', controller='error')
65 rmap.connect('/error/{action}/{id}', controller='error')
66
66
67 #==========================================================================
67 #==========================================================================
68 # CUSTOM ROUTES HERE
68 # CUSTOM ROUTES HERE
69 #==========================================================================
69 #==========================================================================
70
70
71 #MAIN PAGE
71 #MAIN PAGE
72 rmap.connect('home', '/', controller='home', action='index')
72 rmap.connect('home', '/', controller='home', action='index')
73 rmap.connect('repo_switcher', '/repos', controller='home',
73 rmap.connect('repo_switcher', '/repos', controller='home',
74 action='repo_switcher')
74 action='repo_switcher')
75 rmap.connect('branch_tag_switcher', '/branches-tags/{repo_name:.*?}',
75 rmap.connect('branch_tag_switcher', '/branches-tags/{repo_name:.*?}',
76 controller='home', action='branch_tag_switcher')
76 controller='home', action='branch_tag_switcher')
77 rmap.connect('bugtracker',
77 rmap.connect('bugtracker',
78 "http://bitbucket.org/marcinkuzminski/rhodecode/issues",
78 "http://bitbucket.org/marcinkuzminski/rhodecode/issues",
79 _static=True)
79 _static=True)
80 rmap.connect('rst_help',
80 rmap.connect('rst_help',
81 "http://docutils.sourceforge.net/docs/user/rst/quickref.html",
81 "http://docutils.sourceforge.net/docs/user/rst/quickref.html",
82 _static=True)
82 _static=True)
83 rmap.connect('rhodecode_official', "http://rhodecode.org", _static=True)
83 rmap.connect('rhodecode_official', "http://rhodecode.org", _static=True)
84
84
85 #ADMIN REPOSITORY REST ROUTES
85 #ADMIN REPOSITORY REST ROUTES
86 with rmap.submapper(path_prefix=ADMIN_PREFIX,
86 with rmap.submapper(path_prefix=ADMIN_PREFIX,
87 controller='admin/repos') as m:
87 controller='admin/repos') as m:
88 m.connect("repos", "/repos",
88 m.connect("repos", "/repos",
89 action="create", conditions=dict(method=["POST"]))
89 action="create", conditions=dict(method=["POST"]))
90 m.connect("repos", "/repos",
90 m.connect("repos", "/repos",
91 action="index", conditions=dict(method=["GET"]))
91 action="index", conditions=dict(method=["GET"]))
92 m.connect("formatted_repos", "/repos.{format}",
92 m.connect("formatted_repos", "/repos.{format}",
93 action="index",
93 action="index",
94 conditions=dict(method=["GET"]))
94 conditions=dict(method=["GET"]))
95 m.connect("new_repo", "/repos/new",
95 m.connect("new_repo", "/repos/new",
96 action="new", conditions=dict(method=["GET"]))
96 action="new", conditions=dict(method=["GET"]))
97 m.connect("formatted_new_repo", "/repos/new.{format}",
97 m.connect("formatted_new_repo", "/repos/new.{format}",
98 action="new", conditions=dict(method=["GET"]))
98 action="new", conditions=dict(method=["GET"]))
99 m.connect("/repos/{repo_name:.*?}",
99 m.connect("/repos/{repo_name:.*?}",
100 action="update", conditions=dict(method=["PUT"],
100 action="update", conditions=dict(method=["PUT"],
101 function=check_repo))
101 function=check_repo))
102 m.connect("/repos/{repo_name:.*?}",
102 m.connect("/repos/{repo_name:.*?}",
103 action="delete", conditions=dict(method=["DELETE"],
103 action="delete", conditions=dict(method=["DELETE"],
104 function=check_repo))
104 function=check_repo))
105 # no longer used:
105 # no longer used:
106 m.connect("edit_repo_admin", "/repos/{repo_name:.*?}/edit",
106 m.connect("edit_repo_admin", "/repos/{repo_name:.*?}/edit",
107 action="edit", conditions=dict(method=["GET"],
107 action="edit", conditions=dict(method=["GET"],
108 function=check_repo))
108 function=check_repo))
109 m.connect("formatted_edit_repo", "/repos/{repo_name:.*?}.{format}/edit",
109 m.connect("formatted_edit_repo", "/repos/{repo_name:.*?}.{format}/edit",
110 action="edit", conditions=dict(method=["GET"],
110 action="edit", conditions=dict(method=["GET"],
111 function=check_repo))
111 function=check_repo))
112 m.connect("repo", "/repos/{repo_name:.*?}",
112 m.connect("repo", "/repos/{repo_name:.*?}",
113 action="show", conditions=dict(method=["GET"],
113 action="show", conditions=dict(method=["GET"],
114 function=check_repo))
114 function=check_repo))
115 m.connect("formatted_repo", "/repos/{repo_name:.*?}.{format}",
115 m.connect("formatted_repo", "/repos/{repo_name:.*?}.{format}",
116 action="show", conditions=dict(method=["GET"],
116 action="show", conditions=dict(method=["GET"],
117 function=check_repo))
117 function=check_repo))
118 #ajax delete repo perm user
118 #ajax delete repo perm user
119 m.connect('delete_repo_user', "/repos_delete_user/{repo_name:.*?}",
119 m.connect('delete_repo_user', "/repos_delete_user/{repo_name:.*?}",
120 action="delete_perm_user",
120 action="delete_perm_user",
121 conditions=dict(method=["DELETE"], function=check_repo))
121 conditions=dict(method=["DELETE"], function=check_repo))
122
122
123 #ajax delete repo perm users_group
123 #ajax delete repo perm users_group
124 m.connect('delete_repo_users_group',
124 m.connect('delete_repo_users_group',
125 "/repos_delete_users_group/{repo_name:.*?}",
125 "/repos_delete_users_group/{repo_name:.*?}",
126 action="delete_perm_users_group",
126 action="delete_perm_users_group",
127 conditions=dict(method=["DELETE"], function=check_repo))
127 conditions=dict(method=["DELETE"], function=check_repo))
128
128
129 #settings actions
129 #settings actions
130 m.connect('repo_stats', "/repos_stats/{repo_name:.*?}",
130 m.connect('repo_stats', "/repos_stats/{repo_name:.*?}",
131 action="repo_stats", conditions=dict(method=["DELETE"],
131 action="repo_stats", conditions=dict(method=["DELETE"],
132 function=check_repo))
132 function=check_repo))
133 m.connect('repo_cache', "/repos_cache/{repo_name:.*?}",
133 m.connect('repo_cache', "/repos_cache/{repo_name:.*?}",
134 action="repo_cache", conditions=dict(method=["DELETE"],
134 action="repo_cache", conditions=dict(method=["DELETE"],
135 function=check_repo))
135 function=check_repo))
136 m.connect('repo_public_journal', "/repos_public_journal/{repo_name:.*?}",
136 m.connect('repo_public_journal', "/repos_public_journal/{repo_name:.*?}",
137 action="repo_public_journal", conditions=dict(method=["PUT"],
137 action="repo_public_journal", conditions=dict(method=["PUT"],
138 function=check_repo))
138 function=check_repo))
139 m.connect('repo_pull', "/repo_pull/{repo_name:.*?}",
139 m.connect('repo_pull', "/repo_pull/{repo_name:.*?}",
140 action="repo_pull", conditions=dict(method=["PUT"],
140 action="repo_pull", conditions=dict(method=["PUT"],
141 function=check_repo))
141 function=check_repo))
142 m.connect('repo_as_fork', "/repo_as_fork/{repo_name:.*?}",
142 m.connect('repo_as_fork', "/repo_as_fork/{repo_name:.*?}",
143 action="repo_as_fork", conditions=dict(method=["PUT"],
143 action="repo_as_fork", conditions=dict(method=["PUT"],
144 function=check_repo))
144 function=check_repo))
145 m.connect('repo_locking', "/repo_locking/{repo_name:.*?}",
145 m.connect('repo_locking', "/repo_locking/{repo_name:.*?}",
146 action="repo_locking", conditions=dict(method=["PUT"],
146 action="repo_locking", conditions=dict(method=["PUT"],
147 function=check_repo))
147 function=check_repo))
148 #repo fields
148 #repo fields
149 m.connect('create_repo_fields', "/repo_fields/{repo_name:.*?}/new",
149 m.connect('create_repo_fields', "/repo_fields/{repo_name:.*?}/new",
150 action="create_repo_field", conditions=dict(method=["PUT"],
150 action="create_repo_field", conditions=dict(method=["PUT"],
151 function=check_repo))
151 function=check_repo))
152
152
153 m.connect('delete_repo_fields', "/repo_fields/{repo_name:.*?}/{field_id}",
153 m.connect('delete_repo_fields', "/repo_fields/{repo_name:.*?}/{field_id}",
154 action="delete_repo_field", conditions=dict(method=["DELETE"],
154 action="delete_repo_field", conditions=dict(method=["DELETE"],
155 function=check_repo))
155 function=check_repo))
156
156
157 with rmap.submapper(path_prefix=ADMIN_PREFIX,
157 with rmap.submapper(path_prefix=ADMIN_PREFIX,
158 controller='admin/repos_groups') as m:
158 controller='admin/repos_groups') as m:
159 m.connect("repos_groups", "/repos_groups",
159 m.connect("repos_groups", "/repos_groups",
160 action="create", conditions=dict(method=["POST"]))
160 action="create", conditions=dict(method=["POST"]))
161 m.connect("repos_groups", "/repos_groups",
161 m.connect("repos_groups", "/repos_groups",
162 action="index", conditions=dict(method=["GET"]))
162 action="index", conditions=dict(method=["GET"]))
163 m.connect("formatted_repos_groups", "/repos_groups.{format}",
163 m.connect("formatted_repos_groups", "/repos_groups.{format}",
164 action="index", conditions=dict(method=["GET"]))
164 action="index", conditions=dict(method=["GET"]))
165 m.connect("new_repos_group", "/repos_groups/new",
165 m.connect("new_repos_group", "/repos_groups/new",
166 action="new", conditions=dict(method=["GET"]))
166 action="new", conditions=dict(method=["GET"]))
167 m.connect("formatted_new_repos_group", "/repos_groups/new.{format}",
167 m.connect("formatted_new_repos_group", "/repos_groups/new.{format}",
168 action="new", conditions=dict(method=["GET"]))
168 action="new", conditions=dict(method=["GET"]))
169 m.connect("update_repos_group", "/repos_groups/{group_name:.*?}",
169 m.connect("update_repos_group", "/repos_groups/{group_name:.*?}",
170 action="update", conditions=dict(method=["PUT"],
170 action="update", conditions=dict(method=["PUT"],
171 function=check_group))
171 function=check_group))
172 m.connect("delete_repos_group", "/repos_groups/{group_name:.*?}",
172 m.connect("delete_repos_group", "/repos_groups/{group_name:.*?}",
173 action="delete", conditions=dict(method=["DELETE"],
173 action="delete", conditions=dict(method=["DELETE"],
174 function=check_group))
174 function=check_group))
175 m.connect("edit_repos_group", "/repos_groups/{group_name:.*?}/edit",
175 m.connect("edit_repos_group", "/repos_groups/{group_name:.*?}/edit",
176 action="edit", conditions=dict(method=["GET"],))
176 action="edit", conditions=dict(method=["GET"],))
177 m.connect("formatted_edit_repos_group",
177 m.connect("formatted_edit_repos_group",
178 "/repos_groups/{group_name:.*?}.{format}/edit",
178 "/repos_groups/{group_name:.*?}.{format}/edit",
179 action="edit", conditions=dict(method=["GET"],
179 action="edit", conditions=dict(method=["GET"],
180 function=check_group))
180 function=check_group))
181 m.connect("repos_group", "/repos_groups/{group_name:.*?}",
181 m.connect("repos_group", "/repos_groups/{group_name:.*?}",
182 action="show", conditions=dict(method=["GET"],
182 action="show", conditions=dict(method=["GET"],
183 function=check_group))
183 function=check_group))
184 m.connect("formatted_repos_group", "/repos_groups/{group_name:.*?}.{format}",
184 m.connect("formatted_repos_group", "/repos_groups/{group_name:.*?}.{format}",
185 action="show", conditions=dict(method=["GET"],
185 action="show", conditions=dict(method=["GET"],
186 function=check_group))
186 function=check_group))
187 # ajax delete repos group perm user
187 # ajax delete repos group perm user
188 m.connect('delete_repos_group_user_perm',
188 m.connect('delete_repos_group_user_perm',
189 "/delete_repos_group_user_perm/{group_name:.*?}",
189 "/delete_repos_group_user_perm/{group_name:.*?}",
190 action="delete_repos_group_user_perm",
190 action="delete_repos_group_user_perm",
191 conditions=dict(method=["DELETE"], function=check_group))
191 conditions=dict(method=["DELETE"], function=check_group))
192
192
193 # ajax delete repos group perm users_group
193 # ajax delete repos group perm users_group
194 m.connect('delete_repos_group_users_group_perm',
194 m.connect('delete_repos_group_users_group_perm',
195 "/delete_repos_group_users_group_perm/{group_name:.*?}",
195 "/delete_repos_group_users_group_perm/{group_name:.*?}",
196 action="delete_repos_group_users_group_perm",
196 action="delete_repos_group_users_group_perm",
197 conditions=dict(method=["DELETE"], function=check_group))
197 conditions=dict(method=["DELETE"], function=check_group))
198
198
199 #ADMIN USER REST ROUTES
199 #ADMIN USER REST ROUTES
200 with rmap.submapper(path_prefix=ADMIN_PREFIX,
200 with rmap.submapper(path_prefix=ADMIN_PREFIX,
201 controller='admin/users') as m:
201 controller='admin/users') as m:
202 m.connect("users", "/users",
202 m.connect("users", "/users",
203 action="create", conditions=dict(method=["POST"]))
203 action="create", conditions=dict(method=["POST"]))
204 m.connect("users", "/users",
204 m.connect("users", "/users",
205 action="index", conditions=dict(method=["GET"]))
205 action="index", conditions=dict(method=["GET"]))
206 m.connect("formatted_users", "/users.{format}",
206 m.connect("formatted_users", "/users.{format}",
207 action="index", conditions=dict(method=["GET"]))
207 action="index", conditions=dict(method=["GET"]))
208 m.connect("new_user", "/users/new",
208 m.connect("new_user", "/users/new",
209 action="new", conditions=dict(method=["GET"]))
209 action="new", conditions=dict(method=["GET"]))
210 m.connect("formatted_new_user", "/users/new.{format}",
210 m.connect("formatted_new_user", "/users/new.{format}",
211 action="new", conditions=dict(method=["GET"]))
211 action="new", conditions=dict(method=["GET"]))
212 m.connect("update_user", "/users/{id}",
212 m.connect("update_user", "/users/{id}",
213 action="update", conditions=dict(method=["PUT"]))
213 action="update", conditions=dict(method=["PUT"]))
214 m.connect("delete_user", "/users/{id}",
214 m.connect("delete_user", "/users/{id}",
215 action="delete", conditions=dict(method=["DELETE"]))
215 action="delete", conditions=dict(method=["DELETE"]))
216 m.connect("edit_user", "/users/{id}/edit",
216 m.connect("edit_user", "/users/{id}/edit",
217 action="edit", conditions=dict(method=["GET"]))
217 action="edit", conditions=dict(method=["GET"]))
218 m.connect("formatted_edit_user",
218 m.connect("formatted_edit_user",
219 "/users/{id}.{format}/edit",
219 "/users/{id}.{format}/edit",
220 action="edit", conditions=dict(method=["GET"]))
220 action="edit", conditions=dict(method=["GET"]))
221 m.connect("user", "/users/{id}",
221 m.connect("user", "/users/{id}",
222 action="show", conditions=dict(method=["GET"]))
222 action="show", conditions=dict(method=["GET"]))
223 m.connect("formatted_user", "/users/{id}.{format}",
223 m.connect("formatted_user", "/users/{id}.{format}",
224 action="show", conditions=dict(method=["GET"]))
224 action="show", conditions=dict(method=["GET"]))
225
225
226 #EXTRAS USER ROUTES
226 #EXTRAS USER ROUTES
227 m.connect("user_perm", "/users_perm/{id}",
227 m.connect("user_perm", "/users_perm/{id}",
228 action="update_perm", conditions=dict(method=["PUT"]))
228 action="update_perm", conditions=dict(method=["PUT"]))
229 m.connect("user_emails", "/users_emails/{id}",
229 m.connect("user_emails", "/users_emails/{id}",
230 action="add_email", conditions=dict(method=["PUT"]))
230 action="add_email", conditions=dict(method=["PUT"]))
231 m.connect("user_emails_delete", "/users_emails/{id}",
231 m.connect("user_emails_delete", "/users_emails/{id}",
232 action="delete_email", conditions=dict(method=["DELETE"]))
232 action="delete_email", conditions=dict(method=["DELETE"]))
233 m.connect("user_ips", "/users_ips/{id}",
233 m.connect("user_ips", "/users_ips/{id}",
234 action="add_ip", conditions=dict(method=["PUT"]))
234 action="add_ip", conditions=dict(method=["PUT"]))
235 m.connect("user_ips_delete", "/users_ips/{id}",
235 m.connect("user_ips_delete", "/users_ips/{id}",
236 action="delete_ip", conditions=dict(method=["DELETE"]))
236 action="delete_ip", conditions=dict(method=["DELETE"]))
237
237
238 #ADMIN USERS GROUPS REST ROUTES
238 #ADMIN USER GROUPS REST ROUTES
239 with rmap.submapper(path_prefix=ADMIN_PREFIX,
239 with rmap.submapper(path_prefix=ADMIN_PREFIX,
240 controller='admin/users_groups') as m:
240 controller='admin/users_groups') as m:
241 m.connect("users_groups", "/users_groups",
241 m.connect("users_groups", "/users_groups",
242 action="create", conditions=dict(method=["POST"]))
242 action="create", conditions=dict(method=["POST"]))
243 m.connect("users_groups", "/users_groups",
243 m.connect("users_groups", "/users_groups",
244 action="index", conditions=dict(method=["GET"]))
244 action="index", conditions=dict(method=["GET"]))
245 m.connect("formatted_users_groups", "/users_groups.{format}",
245 m.connect("formatted_users_groups", "/users_groups.{format}",
246 action="index", conditions=dict(method=["GET"]))
246 action="index", conditions=dict(method=["GET"]))
247 m.connect("new_users_group", "/users_groups/new",
247 m.connect("new_users_group", "/users_groups/new",
248 action="new", conditions=dict(method=["GET"]))
248 action="new", conditions=dict(method=["GET"]))
249 m.connect("formatted_new_users_group", "/users_groups/new.{format}",
249 m.connect("formatted_new_users_group", "/users_groups/new.{format}",
250 action="new", conditions=dict(method=["GET"]))
250 action="new", conditions=dict(method=["GET"]))
251 m.connect("update_users_group", "/users_groups/{id}",
251 m.connect("update_users_group", "/users_groups/{id}",
252 action="update", conditions=dict(method=["PUT"]))
252 action="update", conditions=dict(method=["PUT"]))
253 m.connect("delete_users_group", "/users_groups/{id}",
253 m.connect("delete_users_group", "/users_groups/{id}",
254 action="delete", conditions=dict(method=["DELETE"]))
254 action="delete", conditions=dict(method=["DELETE"]))
255 m.connect("edit_users_group", "/users_groups/{id}/edit",
255 m.connect("edit_users_group", "/users_groups/{id}/edit",
256 action="edit", conditions=dict(method=["GET"]))
256 action="edit", conditions=dict(method=["GET"]))
257 m.connect("formatted_edit_users_group",
257 m.connect("formatted_edit_users_group",
258 "/users_groups/{id}.{format}/edit",
258 "/users_groups/{id}.{format}/edit",
259 action="edit", conditions=dict(method=["GET"]))
259 action="edit", conditions=dict(method=["GET"]))
260 m.connect("users_group", "/users_groups/{id}",
260 m.connect("users_group", "/users_groups/{id}",
261 action="show", conditions=dict(method=["GET"]))
261 action="show", conditions=dict(method=["GET"]))
262 m.connect("formatted_users_group", "/users_groups/{id}.{format}",
262 m.connect("formatted_users_group", "/users_groups/{id}.{format}",
263 action="show", conditions=dict(method=["GET"]))
263 action="show", conditions=dict(method=["GET"]))
264
264
265 #EXTRAS USER ROUTES
265 #EXTRAS USER ROUTES
266 m.connect("users_group_perm", "/users_groups_perm/{id}",
266 m.connect("users_group_perm", "/users_groups_perm/{id}",
267 action="update_perm", conditions=dict(method=["PUT"]))
267 action="update_perm", conditions=dict(method=["PUT"]))
268
268
269 #ADMIN GROUP REST ROUTES
269 #ADMIN GROUP REST ROUTES
270 rmap.resource('group', 'groups',
270 rmap.resource('group', 'groups',
271 controller='admin/groups', path_prefix=ADMIN_PREFIX)
271 controller='admin/groups', path_prefix=ADMIN_PREFIX)
272
272
273 #ADMIN PERMISSIONS REST ROUTES
273 #ADMIN PERMISSIONS REST ROUTES
274 rmap.resource('permission', 'permissions',
274 rmap.resource('permission', 'permissions',
275 controller='admin/permissions', path_prefix=ADMIN_PREFIX)
275 controller='admin/permissions', path_prefix=ADMIN_PREFIX)
276
276
277 #ADMIN DEFAULTS REST ROUTES
277 #ADMIN DEFAULTS REST ROUTES
278 rmap.resource('default', 'defaults',
278 rmap.resource('default', 'defaults',
279 controller='admin/defaults', path_prefix=ADMIN_PREFIX)
279 controller='admin/defaults', path_prefix=ADMIN_PREFIX)
280
280
281 ##ADMIN LDAP SETTINGS
281 ##ADMIN LDAP SETTINGS
282 rmap.connect('ldap_settings', '%s/ldap' % ADMIN_PREFIX,
282 rmap.connect('ldap_settings', '%s/ldap' % ADMIN_PREFIX,
283 controller='admin/ldap_settings', action='ldap_settings',
283 controller='admin/ldap_settings', action='ldap_settings',
284 conditions=dict(method=["POST"]))
284 conditions=dict(method=["POST"]))
285
285
286 rmap.connect('ldap_home', '%s/ldap' % ADMIN_PREFIX,
286 rmap.connect('ldap_home', '%s/ldap' % ADMIN_PREFIX,
287 controller='admin/ldap_settings')
287 controller='admin/ldap_settings')
288
288
289 #ADMIN SETTINGS REST ROUTES
289 #ADMIN SETTINGS REST ROUTES
290 with rmap.submapper(path_prefix=ADMIN_PREFIX,
290 with rmap.submapper(path_prefix=ADMIN_PREFIX,
291 controller='admin/settings') as m:
291 controller='admin/settings') as m:
292 m.connect("admin_settings", "/settings",
292 m.connect("admin_settings", "/settings",
293 action="create", conditions=dict(method=["POST"]))
293 action="create", conditions=dict(method=["POST"]))
294 m.connect("admin_settings", "/settings",
294 m.connect("admin_settings", "/settings",
295 action="index", conditions=dict(method=["GET"]))
295 action="index", conditions=dict(method=["GET"]))
296 m.connect("formatted_admin_settings", "/settings.{format}",
296 m.connect("formatted_admin_settings", "/settings.{format}",
297 action="index", conditions=dict(method=["GET"]))
297 action="index", conditions=dict(method=["GET"]))
298 m.connect("admin_new_setting", "/settings/new",
298 m.connect("admin_new_setting", "/settings/new",
299 action="new", conditions=dict(method=["GET"]))
299 action="new", conditions=dict(method=["GET"]))
300 m.connect("formatted_admin_new_setting", "/settings/new.{format}",
300 m.connect("formatted_admin_new_setting", "/settings/new.{format}",
301 action="new", conditions=dict(method=["GET"]))
301 action="new", conditions=dict(method=["GET"]))
302 m.connect("/settings/{setting_id}",
302 m.connect("/settings/{setting_id}",
303 action="update", conditions=dict(method=["PUT"]))
303 action="update", conditions=dict(method=["PUT"]))
304 m.connect("/settings/{setting_id}",
304 m.connect("/settings/{setting_id}",
305 action="delete", conditions=dict(method=["DELETE"]))
305 action="delete", conditions=dict(method=["DELETE"]))
306 m.connect("admin_edit_setting", "/settings/{setting_id}/edit",
306 m.connect("admin_edit_setting", "/settings/{setting_id}/edit",
307 action="edit", conditions=dict(method=["GET"]))
307 action="edit", conditions=dict(method=["GET"]))
308 m.connect("formatted_admin_edit_setting",
308 m.connect("formatted_admin_edit_setting",
309 "/settings/{setting_id}.{format}/edit",
309 "/settings/{setting_id}.{format}/edit",
310 action="edit", conditions=dict(method=["GET"]))
310 action="edit", conditions=dict(method=["GET"]))
311 m.connect("admin_setting", "/settings/{setting_id}",
311 m.connect("admin_setting", "/settings/{setting_id}",
312 action="show", conditions=dict(method=["GET"]))
312 action="show", conditions=dict(method=["GET"]))
313 m.connect("formatted_admin_setting", "/settings/{setting_id}.{format}",
313 m.connect("formatted_admin_setting", "/settings/{setting_id}.{format}",
314 action="show", conditions=dict(method=["GET"]))
314 action="show", conditions=dict(method=["GET"]))
315 m.connect("admin_settings_my_account", "/my_account",
315 m.connect("admin_settings_my_account", "/my_account",
316 action="my_account", conditions=dict(method=["GET"]))
316 action="my_account", conditions=dict(method=["GET"]))
317 m.connect("admin_settings_my_account_update", "/my_account_update",
317 m.connect("admin_settings_my_account_update", "/my_account_update",
318 action="my_account_update", conditions=dict(method=["PUT"]))
318 action="my_account_update", conditions=dict(method=["PUT"]))
319 m.connect("admin_settings_create_repository", "/create_repository",
319 m.connect("admin_settings_create_repository", "/create_repository",
320 action="create_repository", conditions=dict(method=["GET"]))
320 action="create_repository", conditions=dict(method=["GET"]))
321 m.connect("admin_settings_my_repos", "/my_account/repos",
321 m.connect("admin_settings_my_repos", "/my_account/repos",
322 action="my_account_my_repos", conditions=dict(method=["GET"]))
322 action="my_account_my_repos", conditions=dict(method=["GET"]))
323 m.connect("admin_settings_my_pullrequests", "/my_account/pull_requests",
323 m.connect("admin_settings_my_pullrequests", "/my_account/pull_requests",
324 action="my_account_my_pullrequests", conditions=dict(method=["GET"]))
324 action="my_account_my_pullrequests", conditions=dict(method=["GET"]))
325
325
326 #NOTIFICATION REST ROUTES
326 #NOTIFICATION REST ROUTES
327 with rmap.submapper(path_prefix=ADMIN_PREFIX,
327 with rmap.submapper(path_prefix=ADMIN_PREFIX,
328 controller='admin/notifications') as m:
328 controller='admin/notifications') as m:
329 m.connect("notifications", "/notifications",
329 m.connect("notifications", "/notifications",
330 action="create", conditions=dict(method=["POST"]))
330 action="create", conditions=dict(method=["POST"]))
331 m.connect("notifications", "/notifications",
331 m.connect("notifications", "/notifications",
332 action="index", conditions=dict(method=["GET"]))
332 action="index", conditions=dict(method=["GET"]))
333 m.connect("notifications_mark_all_read", "/notifications/mark_all_read",
333 m.connect("notifications_mark_all_read", "/notifications/mark_all_read",
334 action="mark_all_read", conditions=dict(method=["GET"]))
334 action="mark_all_read", conditions=dict(method=["GET"]))
335 m.connect("formatted_notifications", "/notifications.{format}",
335 m.connect("formatted_notifications", "/notifications.{format}",
336 action="index", conditions=dict(method=["GET"]))
336 action="index", conditions=dict(method=["GET"]))
337 m.connect("new_notification", "/notifications/new",
337 m.connect("new_notification", "/notifications/new",
338 action="new", conditions=dict(method=["GET"]))
338 action="new", conditions=dict(method=["GET"]))
339 m.connect("formatted_new_notification", "/notifications/new.{format}",
339 m.connect("formatted_new_notification", "/notifications/new.{format}",
340 action="new", conditions=dict(method=["GET"]))
340 action="new", conditions=dict(method=["GET"]))
341 m.connect("/notification/{notification_id}",
341 m.connect("/notification/{notification_id}",
342 action="update", conditions=dict(method=["PUT"]))
342 action="update", conditions=dict(method=["PUT"]))
343 m.connect("/notification/{notification_id}",
343 m.connect("/notification/{notification_id}",
344 action="delete", conditions=dict(method=["DELETE"]))
344 action="delete", conditions=dict(method=["DELETE"]))
345 m.connect("edit_notification", "/notification/{notification_id}/edit",
345 m.connect("edit_notification", "/notification/{notification_id}/edit",
346 action="edit", conditions=dict(method=["GET"]))
346 action="edit", conditions=dict(method=["GET"]))
347 m.connect("formatted_edit_notification",
347 m.connect("formatted_edit_notification",
348 "/notification/{notification_id}.{format}/edit",
348 "/notification/{notification_id}.{format}/edit",
349 action="edit", conditions=dict(method=["GET"]))
349 action="edit", conditions=dict(method=["GET"]))
350 m.connect("notification", "/notification/{notification_id}",
350 m.connect("notification", "/notification/{notification_id}",
351 action="show", conditions=dict(method=["GET"]))
351 action="show", conditions=dict(method=["GET"]))
352 m.connect("formatted_notification", "/notifications/{notification_id}.{format}",
352 m.connect("formatted_notification", "/notifications/{notification_id}.{format}",
353 action="show", conditions=dict(method=["GET"]))
353 action="show", conditions=dict(method=["GET"]))
354
354
355 #ADMIN MAIN PAGES
355 #ADMIN MAIN PAGES
356 with rmap.submapper(path_prefix=ADMIN_PREFIX,
356 with rmap.submapper(path_prefix=ADMIN_PREFIX,
357 controller='admin/admin') as m:
357 controller='admin/admin') as m:
358 m.connect('admin_home', '', action='index')
358 m.connect('admin_home', '', action='index')
359 m.connect('admin_add_repo', '/add_repo/{new_repo:[a-z0-9\. _-]*}',
359 m.connect('admin_add_repo', '/add_repo/{new_repo:[a-z0-9\. _-]*}',
360 action='add_repo')
360 action='add_repo')
361
361
362 #==========================================================================
362 #==========================================================================
363 # API V2
363 # API V2
364 #==========================================================================
364 #==========================================================================
365 with rmap.submapper(path_prefix=ADMIN_PREFIX,
365 with rmap.submapper(path_prefix=ADMIN_PREFIX,
366 controller='api/api') as m:
366 controller='api/api') as m:
367 m.connect('api', '/api')
367 m.connect('api', '/api')
368
368
369 #USER JOURNAL
369 #USER JOURNAL
370 rmap.connect('journal', '%s/journal' % ADMIN_PREFIX,
370 rmap.connect('journal', '%s/journal' % ADMIN_PREFIX,
371 controller='journal', action='index')
371 controller='journal', action='index')
372 rmap.connect('journal_rss', '%s/journal/rss' % ADMIN_PREFIX,
372 rmap.connect('journal_rss', '%s/journal/rss' % ADMIN_PREFIX,
373 controller='journal', action='journal_rss')
373 controller='journal', action='journal_rss')
374 rmap.connect('journal_atom', '%s/journal/atom' % ADMIN_PREFIX,
374 rmap.connect('journal_atom', '%s/journal/atom' % ADMIN_PREFIX,
375 controller='journal', action='journal_atom')
375 controller='journal', action='journal_atom')
376
376
377 rmap.connect('public_journal', '%s/public_journal' % ADMIN_PREFIX,
377 rmap.connect('public_journal', '%s/public_journal' % ADMIN_PREFIX,
378 controller='journal', action="public_journal")
378 controller='journal', action="public_journal")
379
379
380 rmap.connect('public_journal_rss', '%s/public_journal/rss' % ADMIN_PREFIX,
380 rmap.connect('public_journal_rss', '%s/public_journal/rss' % ADMIN_PREFIX,
381 controller='journal', action="public_journal_rss")
381 controller='journal', action="public_journal_rss")
382
382
383 rmap.connect('public_journal_rss_old', '%s/public_journal_rss' % ADMIN_PREFIX,
383 rmap.connect('public_journal_rss_old', '%s/public_journal_rss' % ADMIN_PREFIX,
384 controller='journal', action="public_journal_rss")
384 controller='journal', action="public_journal_rss")
385
385
386 rmap.connect('public_journal_atom',
386 rmap.connect('public_journal_atom',
387 '%s/public_journal/atom' % ADMIN_PREFIX, controller='journal',
387 '%s/public_journal/atom' % ADMIN_PREFIX, controller='journal',
388 action="public_journal_atom")
388 action="public_journal_atom")
389
389
390 rmap.connect('public_journal_atom_old',
390 rmap.connect('public_journal_atom_old',
391 '%s/public_journal_atom' % ADMIN_PREFIX, controller='journal',
391 '%s/public_journal_atom' % ADMIN_PREFIX, controller='journal',
392 action="public_journal_atom")
392 action="public_journal_atom")
393
393
394 rmap.connect('toggle_following', '%s/toggle_following' % ADMIN_PREFIX,
394 rmap.connect('toggle_following', '%s/toggle_following' % ADMIN_PREFIX,
395 controller='journal', action='toggle_following',
395 controller='journal', action='toggle_following',
396 conditions=dict(method=["POST"]))
396 conditions=dict(method=["POST"]))
397
397
398 #SEARCH
398 #SEARCH
399 rmap.connect('search', '%s/search' % ADMIN_PREFIX, controller='search',)
399 rmap.connect('search', '%s/search' % ADMIN_PREFIX, controller='search',)
400 rmap.connect('search_repo_admin', '%s/search/{repo_name:.*}' % ADMIN_PREFIX,
400 rmap.connect('search_repo_admin', '%s/search/{repo_name:.*}' % ADMIN_PREFIX,
401 controller='search',
401 controller='search',
402 conditions=dict(function=check_repo))
402 conditions=dict(function=check_repo))
403 rmap.connect('search_repo', '/{repo_name:.*?}/search',
403 rmap.connect('search_repo', '/{repo_name:.*?}/search',
404 controller='search',
404 controller='search',
405 conditions=dict(function=check_repo),
405 conditions=dict(function=check_repo),
406 )
406 )
407
407
408 #LOGIN/LOGOUT/REGISTER/SIGN IN
408 #LOGIN/LOGOUT/REGISTER/SIGN IN
409 rmap.connect('login_home', '%s/login' % ADMIN_PREFIX, controller='login')
409 rmap.connect('login_home', '%s/login' % ADMIN_PREFIX, controller='login')
410 rmap.connect('logout_home', '%s/logout' % ADMIN_PREFIX, controller='login',
410 rmap.connect('logout_home', '%s/logout' % ADMIN_PREFIX, controller='login',
411 action='logout')
411 action='logout')
412
412
413 rmap.connect('register', '%s/register' % ADMIN_PREFIX, controller='login',
413 rmap.connect('register', '%s/register' % ADMIN_PREFIX, controller='login',
414 action='register')
414 action='register')
415
415
416 rmap.connect('reset_password', '%s/password_reset' % ADMIN_PREFIX,
416 rmap.connect('reset_password', '%s/password_reset' % ADMIN_PREFIX,
417 controller='login', action='password_reset')
417 controller='login', action='password_reset')
418
418
419 rmap.connect('reset_password_confirmation',
419 rmap.connect('reset_password_confirmation',
420 '%s/password_reset_confirmation' % ADMIN_PREFIX,
420 '%s/password_reset_confirmation' % ADMIN_PREFIX,
421 controller='login', action='password_reset_confirmation')
421 controller='login', action='password_reset_confirmation')
422
422
423 #FEEDS
423 #FEEDS
424 rmap.connect('rss_feed_home', '/{repo_name:.*?}/feed/rss',
424 rmap.connect('rss_feed_home', '/{repo_name:.*?}/feed/rss',
425 controller='feed', action='rss',
425 controller='feed', action='rss',
426 conditions=dict(function=check_repo))
426 conditions=dict(function=check_repo))
427
427
428 rmap.connect('atom_feed_home', '/{repo_name:.*?}/feed/atom',
428 rmap.connect('atom_feed_home', '/{repo_name:.*?}/feed/atom',
429 controller='feed', action='atom',
429 controller='feed', action='atom',
430 conditions=dict(function=check_repo))
430 conditions=dict(function=check_repo))
431
431
432 #==========================================================================
432 #==========================================================================
433 # REPOSITORY ROUTES
433 # REPOSITORY ROUTES
434 #==========================================================================
434 #==========================================================================
435 rmap.connect('summary_home', '/{repo_name:.*?}',
435 rmap.connect('summary_home', '/{repo_name:.*?}',
436 controller='summary',
436 controller='summary',
437 conditions=dict(function=check_repo))
437 conditions=dict(function=check_repo))
438
438
439 rmap.connect('repo_size', '/{repo_name:.*?}/repo_size',
439 rmap.connect('repo_size', '/{repo_name:.*?}/repo_size',
440 controller='summary', action='repo_size',
440 controller='summary', action='repo_size',
441 conditions=dict(function=check_repo))
441 conditions=dict(function=check_repo))
442
442
443 rmap.connect('repos_group_home', '/{group_name:.*}',
443 rmap.connect('repos_group_home', '/{group_name:.*}',
444 controller='admin/repos_groups', action="show_by_name",
444 controller='admin/repos_groups', action="show_by_name",
445 conditions=dict(function=check_group))
445 conditions=dict(function=check_group))
446
446
447 rmap.connect('changeset_home', '/{repo_name:.*?}/changeset/{revision}',
447 rmap.connect('changeset_home', '/{repo_name:.*?}/changeset/{revision}',
448 controller='changeset', revision='tip',
448 controller='changeset', revision='tip',
449 conditions=dict(function=check_repo))
449 conditions=dict(function=check_repo))
450
450
451 rmap.connect("edit_repo", "/{repo_name:.*?}/edit",
451 rmap.connect("edit_repo", "/{repo_name:.*?}/edit",
452 controller='admin/repos', action="edit",
452 controller='admin/repos', action="edit",
453 conditions=dict(method=["GET"], function=check_repo)
453 conditions=dict(method=["GET"], function=check_repo)
454 )
454 )
455
455
456 #still working url for backward compat.
456 #still working url for backward compat.
457 rmap.connect('raw_changeset_home_depraced',
457 rmap.connect('raw_changeset_home_depraced',
458 '/{repo_name:.*?}/raw-changeset/{revision}',
458 '/{repo_name:.*?}/raw-changeset/{revision}',
459 controller='changeset', action='changeset_raw',
459 controller='changeset', action='changeset_raw',
460 revision='tip', conditions=dict(function=check_repo))
460 revision='tip', conditions=dict(function=check_repo))
461
461
462 ## new URLs
462 ## new URLs
463 rmap.connect('changeset_raw_home',
463 rmap.connect('changeset_raw_home',
464 '/{repo_name:.*?}/changeset-diff/{revision}',
464 '/{repo_name:.*?}/changeset-diff/{revision}',
465 controller='changeset', action='changeset_raw',
465 controller='changeset', action='changeset_raw',
466 revision='tip', conditions=dict(function=check_repo))
466 revision='tip', conditions=dict(function=check_repo))
467
467
468 rmap.connect('changeset_patch_home',
468 rmap.connect('changeset_patch_home',
469 '/{repo_name:.*?}/changeset-patch/{revision}',
469 '/{repo_name:.*?}/changeset-patch/{revision}',
470 controller='changeset', action='changeset_patch',
470 controller='changeset', action='changeset_patch',
471 revision='tip', conditions=dict(function=check_repo))
471 revision='tip', conditions=dict(function=check_repo))
472
472
473 rmap.connect('changeset_download_home',
473 rmap.connect('changeset_download_home',
474 '/{repo_name:.*?}/changeset-download/{revision}',
474 '/{repo_name:.*?}/changeset-download/{revision}',
475 controller='changeset', action='changeset_download',
475 controller='changeset', action='changeset_download',
476 revision='tip', conditions=dict(function=check_repo))
476 revision='tip', conditions=dict(function=check_repo))
477
477
478 rmap.connect('changeset_comment',
478 rmap.connect('changeset_comment',
479 '/{repo_name:.*?}/changeset/{revision}/comment',
479 '/{repo_name:.*?}/changeset/{revision}/comment',
480 controller='changeset', revision='tip', action='comment',
480 controller='changeset', revision='tip', action='comment',
481 conditions=dict(function=check_repo))
481 conditions=dict(function=check_repo))
482
482
483 rmap.connect('changeset_comment_delete',
483 rmap.connect('changeset_comment_delete',
484 '/{repo_name:.*?}/changeset/comment/{comment_id}/delete',
484 '/{repo_name:.*?}/changeset/comment/{comment_id}/delete',
485 controller='changeset', action='delete_comment',
485 controller='changeset', action='delete_comment',
486 conditions=dict(function=check_repo, method=["DELETE"]))
486 conditions=dict(function=check_repo, method=["DELETE"]))
487
487
488 rmap.connect('changeset_info', '/changeset_info/{repo_name:.*?}/{revision}',
488 rmap.connect('changeset_info', '/changeset_info/{repo_name:.*?}/{revision}',
489 controller='changeset', action='changeset_info')
489 controller='changeset', action='changeset_info')
490
490
491 rmap.connect('compare_url',
491 rmap.connect('compare_url',
492 '/{repo_name:.*?}/compare/{org_ref_type}@{org_ref:.*?}...{other_ref_type}@{other_ref:.*?}',
492 '/{repo_name:.*?}/compare/{org_ref_type}@{org_ref:.*?}...{other_ref_type}@{other_ref:.*?}',
493 controller='compare', action='index',
493 controller='compare', action='index',
494 conditions=dict(function=check_repo),
494 conditions=dict(function=check_repo),
495 requirements=dict(
495 requirements=dict(
496 org_ref_type='(branch|book|tag|rev|__other_ref_type__)',
496 org_ref_type='(branch|book|tag|rev|__other_ref_type__)',
497 other_ref_type='(branch|book|tag|rev|__org_ref_type__)')
497 other_ref_type='(branch|book|tag|rev|__org_ref_type__)')
498 )
498 )
499
499
500 rmap.connect('pullrequest_home',
500 rmap.connect('pullrequest_home',
501 '/{repo_name:.*?}/pull-request/new', controller='pullrequests',
501 '/{repo_name:.*?}/pull-request/new', controller='pullrequests',
502 action='index', conditions=dict(function=check_repo,
502 action='index', conditions=dict(function=check_repo,
503 method=["GET"]))
503 method=["GET"]))
504
504
505 rmap.connect('pullrequest',
505 rmap.connect('pullrequest',
506 '/{repo_name:.*?}/pull-request/new', controller='pullrequests',
506 '/{repo_name:.*?}/pull-request/new', controller='pullrequests',
507 action='create', conditions=dict(function=check_repo,
507 action='create', conditions=dict(function=check_repo,
508 method=["POST"]))
508 method=["POST"]))
509
509
510 rmap.connect('pullrequest_show',
510 rmap.connect('pullrequest_show',
511 '/{repo_name:.*?}/pull-request/{pull_request_id}',
511 '/{repo_name:.*?}/pull-request/{pull_request_id}',
512 controller='pullrequests',
512 controller='pullrequests',
513 action='show', conditions=dict(function=check_repo,
513 action='show', conditions=dict(function=check_repo,
514 method=["GET"]))
514 method=["GET"]))
515 rmap.connect('pullrequest_update',
515 rmap.connect('pullrequest_update',
516 '/{repo_name:.*?}/pull-request/{pull_request_id}',
516 '/{repo_name:.*?}/pull-request/{pull_request_id}',
517 controller='pullrequests',
517 controller='pullrequests',
518 action='update', conditions=dict(function=check_repo,
518 action='update', conditions=dict(function=check_repo,
519 method=["PUT"]))
519 method=["PUT"]))
520 rmap.connect('pullrequest_delete',
520 rmap.connect('pullrequest_delete',
521 '/{repo_name:.*?}/pull-request/{pull_request_id}',
521 '/{repo_name:.*?}/pull-request/{pull_request_id}',
522 controller='pullrequests',
522 controller='pullrequests',
523 action='delete', conditions=dict(function=check_repo,
523 action='delete', conditions=dict(function=check_repo,
524 method=["DELETE"]))
524 method=["DELETE"]))
525
525
526 rmap.connect('pullrequest_show_all',
526 rmap.connect('pullrequest_show_all',
527 '/{repo_name:.*?}/pull-request',
527 '/{repo_name:.*?}/pull-request',
528 controller='pullrequests',
528 controller='pullrequests',
529 action='show_all', conditions=dict(function=check_repo,
529 action='show_all', conditions=dict(function=check_repo,
530 method=["GET"]))
530 method=["GET"]))
531
531
532 rmap.connect('pullrequest_comment',
532 rmap.connect('pullrequest_comment',
533 '/{repo_name:.*?}/pull-request-comment/{pull_request_id}',
533 '/{repo_name:.*?}/pull-request-comment/{pull_request_id}',
534 controller='pullrequests',
534 controller='pullrequests',
535 action='comment', conditions=dict(function=check_repo,
535 action='comment', conditions=dict(function=check_repo,
536 method=["POST"]))
536 method=["POST"]))
537
537
538 rmap.connect('pullrequest_comment_delete',
538 rmap.connect('pullrequest_comment_delete',
539 '/{repo_name:.*?}/pull-request-comment/{comment_id}/delete',
539 '/{repo_name:.*?}/pull-request-comment/{comment_id}/delete',
540 controller='pullrequests', action='delete_comment',
540 controller='pullrequests', action='delete_comment',
541 conditions=dict(function=check_repo, method=["DELETE"]))
541 conditions=dict(function=check_repo, method=["DELETE"]))
542
542
543 rmap.connect('summary_home_summary', '/{repo_name:.*?}/summary',
543 rmap.connect('summary_home_summary', '/{repo_name:.*?}/summary',
544 controller='summary', conditions=dict(function=check_repo))
544 controller='summary', conditions=dict(function=check_repo))
545
545
546 rmap.connect('shortlog_home', '/{repo_name:.*?}/shortlog',
546 rmap.connect('shortlog_home', '/{repo_name:.*?}/shortlog',
547 controller='shortlog', conditions=dict(function=check_repo))
547 controller='shortlog', conditions=dict(function=check_repo))
548
548
549 rmap.connect('shortlog_file_home', '/{repo_name:.*?}/shortlog/{revision}/{f_path:.*}',
549 rmap.connect('shortlog_file_home', '/{repo_name:.*?}/shortlog/{revision}/{f_path:.*}',
550 controller='shortlog', f_path=None,
550 controller='shortlog', f_path=None,
551 conditions=dict(function=check_repo))
551 conditions=dict(function=check_repo))
552
552
553 rmap.connect('branches_home', '/{repo_name:.*?}/branches',
553 rmap.connect('branches_home', '/{repo_name:.*?}/branches',
554 controller='branches', conditions=dict(function=check_repo))
554 controller='branches', conditions=dict(function=check_repo))
555
555
556 rmap.connect('tags_home', '/{repo_name:.*?}/tags',
556 rmap.connect('tags_home', '/{repo_name:.*?}/tags',
557 controller='tags', conditions=dict(function=check_repo))
557 controller='tags', conditions=dict(function=check_repo))
558
558
559 rmap.connect('bookmarks_home', '/{repo_name:.*?}/bookmarks',
559 rmap.connect('bookmarks_home', '/{repo_name:.*?}/bookmarks',
560 controller='bookmarks', conditions=dict(function=check_repo))
560 controller='bookmarks', conditions=dict(function=check_repo))
561
561
562 rmap.connect('changelog_home', '/{repo_name:.*?}/changelog',
562 rmap.connect('changelog_home', '/{repo_name:.*?}/changelog',
563 controller='changelog', conditions=dict(function=check_repo))
563 controller='changelog', conditions=dict(function=check_repo))
564
564
565 rmap.connect('changelog_details', '/{repo_name:.*?}/changelog_details/{cs}',
565 rmap.connect('changelog_details', '/{repo_name:.*?}/changelog_details/{cs}',
566 controller='changelog', action='changelog_details',
566 controller='changelog', action='changelog_details',
567 conditions=dict(function=check_repo))
567 conditions=dict(function=check_repo))
568
568
569 rmap.connect('files_home', '/{repo_name:.*?}/files/{revision}/{f_path:.*}',
569 rmap.connect('files_home', '/{repo_name:.*?}/files/{revision}/{f_path:.*}',
570 controller='files', revision='tip', f_path='',
570 controller='files', revision='tip', f_path='',
571 conditions=dict(function=check_repo))
571 conditions=dict(function=check_repo))
572
572
573 rmap.connect('files_history_home',
573 rmap.connect('files_history_home',
574 '/{repo_name:.*?}/history/{revision}/{f_path:.*}',
574 '/{repo_name:.*?}/history/{revision}/{f_path:.*}',
575 controller='files', action='history', revision='tip', f_path='',
575 controller='files', action='history', revision='tip', f_path='',
576 conditions=dict(function=check_repo))
576 conditions=dict(function=check_repo))
577
577
578 rmap.connect('files_diff_home', '/{repo_name:.*?}/diff/{f_path:.*}',
578 rmap.connect('files_diff_home', '/{repo_name:.*?}/diff/{f_path:.*}',
579 controller='files', action='diff', revision='tip', f_path='',
579 controller='files', action='diff', revision='tip', f_path='',
580 conditions=dict(function=check_repo))
580 conditions=dict(function=check_repo))
581
581
582 rmap.connect('files_rawfile_home',
582 rmap.connect('files_rawfile_home',
583 '/{repo_name:.*?}/rawfile/{revision}/{f_path:.*}',
583 '/{repo_name:.*?}/rawfile/{revision}/{f_path:.*}',
584 controller='files', action='rawfile', revision='tip',
584 controller='files', action='rawfile', revision='tip',
585 f_path='', conditions=dict(function=check_repo))
585 f_path='', conditions=dict(function=check_repo))
586
586
587 rmap.connect('files_raw_home',
587 rmap.connect('files_raw_home',
588 '/{repo_name:.*?}/raw/{revision}/{f_path:.*}',
588 '/{repo_name:.*?}/raw/{revision}/{f_path:.*}',
589 controller='files', action='raw', revision='tip', f_path='',
589 controller='files', action='raw', revision='tip', f_path='',
590 conditions=dict(function=check_repo))
590 conditions=dict(function=check_repo))
591
591
592 rmap.connect('files_annotate_home',
592 rmap.connect('files_annotate_home',
593 '/{repo_name:.*?}/annotate/{revision}/{f_path:.*}',
593 '/{repo_name:.*?}/annotate/{revision}/{f_path:.*}',
594 controller='files', action='index', revision='tip',
594 controller='files', action='index', revision='tip',
595 f_path='', annotate=True, conditions=dict(function=check_repo))
595 f_path='', annotate=True, conditions=dict(function=check_repo))
596
596
597 rmap.connect('files_edit_home',
597 rmap.connect('files_edit_home',
598 '/{repo_name:.*?}/edit/{revision}/{f_path:.*}',
598 '/{repo_name:.*?}/edit/{revision}/{f_path:.*}',
599 controller='files', action='edit', revision='tip',
599 controller='files', action='edit', revision='tip',
600 f_path='', conditions=dict(function=check_repo))
600 f_path='', conditions=dict(function=check_repo))
601
601
602 rmap.connect('files_add_home',
602 rmap.connect('files_add_home',
603 '/{repo_name:.*?}/add/{revision}/{f_path:.*}',
603 '/{repo_name:.*?}/add/{revision}/{f_path:.*}',
604 controller='files', action='add', revision='tip',
604 controller='files', action='add', revision='tip',
605 f_path='', conditions=dict(function=check_repo))
605 f_path='', conditions=dict(function=check_repo))
606
606
607 rmap.connect('files_archive_home', '/{repo_name:.*?}/archive/{fname}',
607 rmap.connect('files_archive_home', '/{repo_name:.*?}/archive/{fname}',
608 controller='files', action='archivefile',
608 controller='files', action='archivefile',
609 conditions=dict(function=check_repo))
609 conditions=dict(function=check_repo))
610
610
611 rmap.connect('files_nodelist_home',
611 rmap.connect('files_nodelist_home',
612 '/{repo_name:.*?}/nodelist/{revision}/{f_path:.*}',
612 '/{repo_name:.*?}/nodelist/{revision}/{f_path:.*}',
613 controller='files', action='nodelist',
613 controller='files', action='nodelist',
614 conditions=dict(function=check_repo))
614 conditions=dict(function=check_repo))
615
615
616 rmap.connect('repo_settings_delete', '/{repo_name:.*?}/settings',
616 rmap.connect('repo_settings_delete', '/{repo_name:.*?}/settings',
617 controller='settings', action="delete",
617 controller='settings', action="delete",
618 conditions=dict(method=["DELETE"], function=check_repo))
618 conditions=dict(method=["DELETE"], function=check_repo))
619
619
620 rmap.connect('repo_settings_update', '/{repo_name:.*?}/settings',
620 rmap.connect('repo_settings_update', '/{repo_name:.*?}/settings',
621 controller='settings', action="update",
621 controller='settings', action="update",
622 conditions=dict(method=["PUT"], function=check_repo))
622 conditions=dict(method=["PUT"], function=check_repo))
623
623
624 rmap.connect('repo_settings_home', '/{repo_name:.*?}/settings',
624 rmap.connect('repo_settings_home', '/{repo_name:.*?}/settings',
625 controller='settings', action='index',
625 controller='settings', action='index',
626 conditions=dict(function=check_repo))
626 conditions=dict(function=check_repo))
627
627
628 rmap.connect('toggle_locking', "/{repo_name:.*?}/locking_toggle",
628 rmap.connect('toggle_locking', "/{repo_name:.*?}/locking_toggle",
629 controller='settings', action="toggle_locking",
629 controller='settings', action="toggle_locking",
630 conditions=dict(method=["GET"], function=check_repo))
630 conditions=dict(method=["GET"], function=check_repo))
631
631
632 rmap.connect('repo_fork_create_home', '/{repo_name:.*?}/fork',
632 rmap.connect('repo_fork_create_home', '/{repo_name:.*?}/fork',
633 controller='forks', action='fork_create',
633 controller='forks', action='fork_create',
634 conditions=dict(function=check_repo, method=["POST"]))
634 conditions=dict(function=check_repo, method=["POST"]))
635
635
636 rmap.connect('repo_fork_home', '/{repo_name:.*?}/fork',
636 rmap.connect('repo_fork_home', '/{repo_name:.*?}/fork',
637 controller='forks', action='fork',
637 controller='forks', action='fork',
638 conditions=dict(function=check_repo))
638 conditions=dict(function=check_repo))
639
639
640 rmap.connect('repo_forks_home', '/{repo_name:.*?}/forks',
640 rmap.connect('repo_forks_home', '/{repo_name:.*?}/forks',
641 controller='forks', action='forks',
641 controller='forks', action='forks',
642 conditions=dict(function=check_repo))
642 conditions=dict(function=check_repo))
643
643
644 rmap.connect('repo_followers_home', '/{repo_name:.*?}/followers',
644 rmap.connect('repo_followers_home', '/{repo_name:.*?}/followers',
645 controller='followers', action='followers',
645 controller='followers', action='followers',
646 conditions=dict(function=check_repo))
646 conditions=dict(function=check_repo))
647
647
648 return rmap
648 return rmap
@@ -1,536 +1,536 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.controllers.admin.repos
3 rhodecode.controllers.admin.repos
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 Repositories controller for RhodeCode
6 Repositories controller for RhodeCode
7
7
8 :created_on: Apr 7, 2010
8 :created_on: Apr 7, 2010
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software: you can redistribute it and/or modify
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
16 # (at your option) any later version.
17 #
17 #
18 # This program is distributed in the hope that it will be useful,
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # GNU General Public License for more details.
21 # GNU General Public License for more details.
22 #
22 #
23 # You should have received a copy of the GNU General Public License
23 # You should have received a copy of the GNU General Public License
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25
25
26 import logging
26 import logging
27 import traceback
27 import traceback
28 import formencode
28 import formencode
29 from formencode import htmlfill
29 from formencode import htmlfill
30
30
31 from webob.exc import HTTPInternalServerError, HTTPForbidden
31 from webob.exc import HTTPInternalServerError, HTTPForbidden
32 from pylons import request, session, tmpl_context as c, url
32 from pylons import request, session, tmpl_context as c, url
33 from pylons.controllers.util import redirect
33 from pylons.controllers.util import redirect
34 from pylons.i18n.translation import _
34 from pylons.i18n.translation import _
35 from sqlalchemy.exc import IntegrityError
35 from sqlalchemy.exc import IntegrityError
36
36
37 import rhodecode
37 import rhodecode
38 from rhodecode.lib import helpers as h
38 from rhodecode.lib import helpers as h
39 from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator, \
39 from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator, \
40 HasPermissionAnyDecorator, HasRepoPermissionAllDecorator, NotAnonymous,\
40 HasPermissionAnyDecorator, HasRepoPermissionAllDecorator, NotAnonymous,\
41 HasPermissionAny, HasReposGroupPermissionAny
41 HasPermissionAny, HasReposGroupPermissionAny
42 from rhodecode.lib.base import BaseRepoController, render
42 from rhodecode.lib.base import BaseRepoController, render
43 from rhodecode.lib.utils import invalidate_cache, action_logger, repo_name_slug
43 from rhodecode.lib.utils import invalidate_cache, action_logger, repo_name_slug
44 from rhodecode.lib.helpers import get_token
44 from rhodecode.lib.helpers import get_token
45 from rhodecode.model.meta import Session
45 from rhodecode.model.meta import Session
46 from rhodecode.model.db import User, Repository, UserFollowing, RepoGroup,\
46 from rhodecode.model.db import User, Repository, UserFollowing, RepoGroup,\
47 RhodeCodeSetting, RepositoryField
47 RhodeCodeSetting, RepositoryField
48 from rhodecode.model.forms import RepoForm, RepoFieldForm
48 from rhodecode.model.forms import RepoForm, RepoFieldForm
49 from rhodecode.model.scm import ScmModel, GroupList
49 from rhodecode.model.scm import ScmModel, GroupList
50 from rhodecode.model.repo import RepoModel
50 from rhodecode.model.repo import RepoModel
51 from rhodecode.lib.compat import json
51 from rhodecode.lib.compat import json
52 from sqlalchemy.sql.expression import func
52 from sqlalchemy.sql.expression import func
53
53
54 log = logging.getLogger(__name__)
54 log = logging.getLogger(__name__)
55
55
56
56
57 class ReposController(BaseRepoController):
57 class ReposController(BaseRepoController):
58 """
58 """
59 REST Controller styled on the Atom Publishing Protocol"""
59 REST Controller styled on the Atom Publishing Protocol"""
60 # To properly map this controller, ensure your config/routing.py
60 # To properly map this controller, ensure your config/routing.py
61 # file has a resource setup:
61 # file has a resource setup:
62 # map.resource('repo', 'repos')
62 # map.resource('repo', 'repos')
63
63
64 @LoginRequired()
64 @LoginRequired()
65 def __before__(self):
65 def __before__(self):
66 c.admin_user = session.get('admin_user')
66 c.admin_user = session.get('admin_user')
67 c.admin_username = session.get('admin_username')
67 c.admin_username = session.get('admin_username')
68 super(ReposController, self).__before__()
68 super(ReposController, self).__before__()
69
69
70 def __load_defaults(self):
70 def __load_defaults(self):
71 acl_groups = GroupList(RepoGroup.query().all(),
71 acl_groups = GroupList(RepoGroup.query().all(),
72 perm_set=['group.write', 'group.admin'])
72 perm_set=['group.write', 'group.admin'])
73 c.repo_groups = RepoGroup.groups_choices(groups=acl_groups)
73 c.repo_groups = RepoGroup.groups_choices(groups=acl_groups)
74 c.repo_groups_choices = map(lambda k: unicode(k[0]), c.repo_groups)
74 c.repo_groups_choices = map(lambda k: unicode(k[0]), c.repo_groups)
75
75
76 repo_model = RepoModel()
76 repo_model = RepoModel()
77 c.users_array = repo_model.get_users_js()
77 c.users_array = repo_model.get_users_js()
78 c.users_groups_array = repo_model.get_users_groups_js()
78 c.users_groups_array = repo_model.get_users_groups_js()
79 choices, c.landing_revs = ScmModel().get_repo_landing_revs()
79 choices, c.landing_revs = ScmModel().get_repo_landing_revs()
80 c.landing_revs_choices = choices
80 c.landing_revs_choices = choices
81
81
82 def __load_data(self, repo_name=None):
82 def __load_data(self, repo_name=None):
83 """
83 """
84 Load defaults settings for edit, and update
84 Load defaults settings for edit, and update
85
85
86 :param repo_name:
86 :param repo_name:
87 """
87 """
88 self.__load_defaults()
88 self.__load_defaults()
89
89
90 c.repo_info = db_repo = Repository.get_by_repo_name(repo_name)
90 c.repo_info = db_repo = Repository.get_by_repo_name(repo_name)
91 repo = db_repo.scm_instance
91 repo = db_repo.scm_instance
92
92
93 if c.repo_info is None:
93 if c.repo_info is None:
94 h.not_mapped_error(repo_name)
94 h.not_mapped_error(repo_name)
95 return redirect(url('repos'))
95 return redirect(url('repos'))
96
96
97 ##override defaults for exact repo info here git/hg etc
97 ##override defaults for exact repo info here git/hg etc
98 choices, c.landing_revs = ScmModel().get_repo_landing_revs(c.repo_info)
98 choices, c.landing_revs = ScmModel().get_repo_landing_revs(c.repo_info)
99 c.landing_revs_choices = choices
99 c.landing_revs_choices = choices
100
100
101 c.default_user_id = User.get_by_username('default').user_id
101 c.default_user_id = User.get_by_username('default').user_id
102 c.in_public_journal = UserFollowing.query()\
102 c.in_public_journal = UserFollowing.query()\
103 .filter(UserFollowing.user_id == c.default_user_id)\
103 .filter(UserFollowing.user_id == c.default_user_id)\
104 .filter(UserFollowing.follows_repository == c.repo_info).scalar()
104 .filter(UserFollowing.follows_repository == c.repo_info).scalar()
105
105
106 if c.repo_info.stats:
106 if c.repo_info.stats:
107 # this is on what revision we ended up so we add +1 for count
107 # this is on what revision we ended up so we add +1 for count
108 last_rev = c.repo_info.stats.stat_on_revision + 1
108 last_rev = c.repo_info.stats.stat_on_revision + 1
109 else:
109 else:
110 last_rev = 0
110 last_rev = 0
111 c.stats_revision = last_rev
111 c.stats_revision = last_rev
112
112
113 c.repo_last_rev = repo.count() if repo.revisions else 0
113 c.repo_last_rev = repo.count() if repo.revisions else 0
114
114
115 if last_rev == 0 or c.repo_last_rev == 0:
115 if last_rev == 0 or c.repo_last_rev == 0:
116 c.stats_percentage = 0
116 c.stats_percentage = 0
117 else:
117 else:
118 c.stats_percentage = '%.2f' % ((float((last_rev)) /
118 c.stats_percentage = '%.2f' % ((float((last_rev)) /
119 c.repo_last_rev) * 100)
119 c.repo_last_rev) * 100)
120
120
121 c.repo_fields = RepositoryField.query()\
121 c.repo_fields = RepositoryField.query()\
122 .filter(RepositoryField.repository == db_repo).all()
122 .filter(RepositoryField.repository == db_repo).all()
123
123
124 defaults = RepoModel()._get_defaults(repo_name)
124 defaults = RepoModel()._get_defaults(repo_name)
125
125
126 c.repos_list = [('', _('--REMOVE FORK--'))]
126 c.repos_list = [('', _('--REMOVE FORK--'))]
127 c.repos_list += [(x.repo_id, x.repo_name) for x in
127 c.repos_list += [(x.repo_id, x.repo_name) for x in
128 Repository.query().order_by(Repository.repo_name).all()
128 Repository.query().order_by(Repository.repo_name).all()
129 if x.repo_id != c.repo_info.repo_id]
129 if x.repo_id != c.repo_info.repo_id]
130
130
131 defaults['id_fork_of'] = db_repo.fork.repo_id if db_repo.fork else ''
131 defaults['id_fork_of'] = db_repo.fork.repo_id if db_repo.fork else ''
132 return defaults
132 return defaults
133
133
134 @HasPermissionAllDecorator('hg.admin')
134 @HasPermissionAllDecorator('hg.admin')
135 def index(self, format='html'):
135 def index(self, format='html'):
136 """GET /repos: All items in the collection"""
136 """GET /repos: All items in the collection"""
137 # url('repos')
137 # url('repos')
138
138
139 c.repos_list = Repository.query()\
139 c.repos_list = Repository.query()\
140 .order_by(func.lower(Repository.repo_name))\
140 .order_by(func.lower(Repository.repo_name))\
141 .all()
141 .all()
142
142
143 repos_data = RepoModel().get_repos_as_dict(repos_list=c.repos_list,
143 repos_data = RepoModel().get_repos_as_dict(repos_list=c.repos_list,
144 admin=True,
144 admin=True,
145 super_user_actions=True)
145 super_user_actions=True)
146 #json used to render the grid
146 #json used to render the grid
147 c.data = json.dumps(repos_data)
147 c.data = json.dumps(repos_data)
148
148
149 return render('admin/repos/repos.html')
149 return render('admin/repos/repos.html')
150
150
151 @NotAnonymous()
151 @NotAnonymous()
152 def create(self):
152 def create(self):
153 """
153 """
154 POST /repos: Create a new item"""
154 POST /repos: Create a new item"""
155 # url('repos')
155 # url('repos')
156
156
157 self.__load_defaults()
157 self.__load_defaults()
158 form_result = {}
158 form_result = {}
159 try:
159 try:
160 form_result = RepoForm(repo_groups=c.repo_groups_choices,
160 form_result = RepoForm(repo_groups=c.repo_groups_choices,
161 landing_revs=c.landing_revs_choices)()\
161 landing_revs=c.landing_revs_choices)()\
162 .to_python(dict(request.POST))
162 .to_python(dict(request.POST))
163
163
164 new_repo = RepoModel().create(form_result,
164 new_repo = RepoModel().create(form_result,
165 self.rhodecode_user.user_id)
165 self.rhodecode_user.user_id)
166 if form_result['clone_uri']:
166 if form_result['clone_uri']:
167 h.flash(_('created repository %s from %s') \
167 h.flash(_('created repository %s from %s') \
168 % (form_result['repo_name'], form_result['clone_uri']),
168 % (form_result['repo_name'], form_result['clone_uri']),
169 category='success')
169 category='success')
170 else:
170 else:
171 h.flash(_('created repository %s') % form_result['repo_name'],
171 h.flash(_('created repository %s') % form_result['repo_name'],
172 category='success')
172 category='success')
173
173
174 if request.POST.get('user_created'):
174 if request.POST.get('user_created'):
175 # created by regular non admin user
175 # created by regular non admin user
176 action_logger(self.rhodecode_user, 'user_created_repo',
176 action_logger(self.rhodecode_user, 'user_created_repo',
177 form_result['repo_name_full'], self.ip_addr,
177 form_result['repo_name_full'], self.ip_addr,
178 self.sa)
178 self.sa)
179 else:
179 else:
180 action_logger(self.rhodecode_user, 'admin_created_repo',
180 action_logger(self.rhodecode_user, 'admin_created_repo',
181 form_result['repo_name_full'], self.ip_addr,
181 form_result['repo_name_full'], self.ip_addr,
182 self.sa)
182 self.sa)
183 Session().commit()
183 Session().commit()
184 except formencode.Invalid, errors:
184 except formencode.Invalid, errors:
185 return htmlfill.render(
185 return htmlfill.render(
186 render('admin/repos/repo_add.html'),
186 render('admin/repos/repo_add.html'),
187 defaults=errors.value,
187 defaults=errors.value,
188 errors=errors.error_dict or {},
188 errors=errors.error_dict or {},
189 prefix_error=False,
189 prefix_error=False,
190 encoding="UTF-8")
190 encoding="UTF-8")
191
191
192 except Exception:
192 except Exception:
193 log.error(traceback.format_exc())
193 log.error(traceback.format_exc())
194 msg = _('error occurred during creation of repository %s') \
194 msg = _('error occurred during creation of repository %s') \
195 % form_result.get('repo_name')
195 % form_result.get('repo_name')
196 h.flash(msg, category='error')
196 h.flash(msg, category='error')
197 if c.rhodecode_user.is_admin:
197 if c.rhodecode_user.is_admin:
198 return redirect(url('repos'))
198 return redirect(url('repos'))
199 return redirect(url('home'))
199 return redirect(url('home'))
200 #redirect to our new repo !
200 #redirect to our new repo !
201 return redirect(url('summary_home', repo_name=new_repo.repo_name))
201 return redirect(url('summary_home', repo_name=new_repo.repo_name))
202
202
203 @HasPermissionAllDecorator('hg.admin')
203 @HasPermissionAllDecorator('hg.admin')
204 def new(self, format='html'):
204 def new(self, format='html'):
205 """
205 """
206 WARNING: this function is depracated see settings.create_repo !!
206 WARNING: this function is depracated see settings.create_repo !!
207
207
208 GET /repos/new: Form to create a new item
208 GET /repos/new: Form to create a new item
209 """
209 """
210
210
211 parent_group = request.GET.get('parent_group')
211 parent_group = request.GET.get('parent_group')
212 self.__load_defaults()
212 self.__load_defaults()
213 ## apply the defaults from defaults page
213 ## apply the defaults from defaults page
214 defaults = RhodeCodeSetting.get_default_repo_settings(strip_prefix=True)
214 defaults = RhodeCodeSetting.get_default_repo_settings(strip_prefix=True)
215 if parent_group:
215 if parent_group:
216 defaults.update({'repo_group': parent_group})
216 defaults.update({'repo_group': parent_group})
217
217
218 return htmlfill.render(
218 return htmlfill.render(
219 render('admin/repos/repo_add.html'),
219 render('admin/repos/repo_add.html'),
220 defaults=defaults,
220 defaults=defaults,
221 errors={},
221 errors={},
222 prefix_error=False,
222 prefix_error=False,
223 encoding="UTF-8"
223 encoding="UTF-8"
224 )
224 )
225
225
226 @HasPermissionAllDecorator('hg.admin')
226 @HasPermissionAllDecorator('hg.admin')
227 def update(self, repo_name):
227 def update(self, repo_name):
228 """
228 """
229 PUT /repos/repo_name: Update an existing item"""
229 PUT /repos/repo_name: Update an existing item"""
230 # Forms posted to this method should contain a hidden field:
230 # Forms posted to this method should contain a hidden field:
231 # <input type="hidden" name="_method" value="PUT" />
231 # <input type="hidden" name="_method" value="PUT" />
232 # Or using helpers:
232 # Or using helpers:
233 # h.form(url('repo', repo_name=ID),
233 # h.form(url('repo', repo_name=ID),
234 # method='put')
234 # method='put')
235 # url('repo', repo_name=ID)
235 # url('repo', repo_name=ID)
236 self.__load_defaults()
236 self.__load_defaults()
237 repo_model = RepoModel()
237 repo_model = RepoModel()
238 changed_name = repo_name
238 changed_name = repo_name
239 #override the choices with extracted revisions !
239 #override the choices with extracted revisions !
240 choices, c.landing_revs = ScmModel().get_repo_landing_revs(repo_name)
240 choices, c.landing_revs = ScmModel().get_repo_landing_revs(repo_name)
241 c.landing_revs_choices = choices
241 c.landing_revs_choices = choices
242
242
243 _form = RepoForm(edit=True, old_data={'repo_name': repo_name},
243 _form = RepoForm(edit=True, old_data={'repo_name': repo_name},
244 repo_groups=c.repo_groups_choices,
244 repo_groups=c.repo_groups_choices,
245 landing_revs=c.landing_revs_choices)()
245 landing_revs=c.landing_revs_choices)()
246 try:
246 try:
247 form_result = _form.to_python(dict(request.POST))
247 form_result = _form.to_python(dict(request.POST))
248 repo = repo_model.update(repo_name, **form_result)
248 repo = repo_model.update(repo_name, **form_result)
249 invalidate_cache('get_repo_cached_%s' % repo_name)
249 invalidate_cache('get_repo_cached_%s' % repo_name)
250 h.flash(_('Repository %s updated successfully') % repo_name,
250 h.flash(_('Repository %s updated successfully') % repo_name,
251 category='success')
251 category='success')
252 changed_name = repo.repo_name
252 changed_name = repo.repo_name
253 action_logger(self.rhodecode_user, 'admin_updated_repo',
253 action_logger(self.rhodecode_user, 'admin_updated_repo',
254 changed_name, self.ip_addr, self.sa)
254 changed_name, self.ip_addr, self.sa)
255 Session().commit()
255 Session().commit()
256 except formencode.Invalid, errors:
256 except formencode.Invalid, errors:
257 defaults = self.__load_data(repo_name)
257 defaults = self.__load_data(repo_name)
258 defaults.update(errors.value)
258 defaults.update(errors.value)
259 return htmlfill.render(
259 return htmlfill.render(
260 render('admin/repos/repo_edit.html'),
260 render('admin/repos/repo_edit.html'),
261 defaults=defaults,
261 defaults=defaults,
262 errors=errors.error_dict or {},
262 errors=errors.error_dict or {},
263 prefix_error=False,
263 prefix_error=False,
264 encoding="UTF-8")
264 encoding="UTF-8")
265
265
266 except Exception:
266 except Exception:
267 log.error(traceback.format_exc())
267 log.error(traceback.format_exc())
268 h.flash(_('error occurred during update of repository %s') \
268 h.flash(_('error occurred during update of repository %s') \
269 % repo_name, category='error')
269 % repo_name, category='error')
270 return redirect(url('edit_repo', repo_name=changed_name))
270 return redirect(url('edit_repo', repo_name=changed_name))
271
271
272 @HasPermissionAllDecorator('hg.admin')
272 @HasPermissionAllDecorator('hg.admin')
273 def delete(self, repo_name):
273 def delete(self, repo_name):
274 """
274 """
275 DELETE /repos/repo_name: Delete an existing item"""
275 DELETE /repos/repo_name: Delete an existing item"""
276 # Forms posted to this method should contain a hidden field:
276 # Forms posted to this method should contain a hidden field:
277 # <input type="hidden" name="_method" value="DELETE" />
277 # <input type="hidden" name="_method" value="DELETE" />
278 # Or using helpers:
278 # Or using helpers:
279 # h.form(url('repo', repo_name=ID),
279 # h.form(url('repo', repo_name=ID),
280 # method='delete')
280 # method='delete')
281 # url('repo', repo_name=ID)
281 # url('repo', repo_name=ID)
282
282
283 repo_model = RepoModel()
283 repo_model = RepoModel()
284 repo = repo_model.get_by_repo_name(repo_name)
284 repo = repo_model.get_by_repo_name(repo_name)
285 if not repo:
285 if not repo:
286 h.not_mapped_error(repo_name)
286 h.not_mapped_error(repo_name)
287 return redirect(url('repos'))
287 return redirect(url('repos'))
288 try:
288 try:
289 _forks = repo.forks.count()
289 _forks = repo.forks.count()
290 if _forks and request.POST.get('forks'):
290 if _forks and request.POST.get('forks'):
291 do = request.POST['forks']
291 do = request.POST['forks']
292 if do == 'detach_forks':
292 if do == 'detach_forks':
293 for r in repo.forks:
293 for r in repo.forks:
294 log.debug('Detaching fork %s from repo %s' % (r, repo))
294 log.debug('Detaching fork %s from repo %s' % (r, repo))
295 r.fork = None
295 r.fork = None
296 Session().add(r)
296 Session().add(r)
297 h.flash(_('detached %s forks') % _forks, category='success')
297 h.flash(_('detached %s forks') % _forks, category='success')
298 elif do == 'delete_forks':
298 elif do == 'delete_forks':
299 for r in repo.forks:
299 for r in repo.forks:
300 log.debug('Deleting fork %s of repo %s' % (r, repo))
300 log.debug('Deleting fork %s of repo %s' % (r, repo))
301 repo_model.delete(r)
301 repo_model.delete(r)
302 h.flash(_('deleted %s forks') % _forks, category='success')
302 h.flash(_('deleted %s forks') % _forks, category='success')
303 action_logger(self.rhodecode_user, 'admin_deleted_repo',
303 action_logger(self.rhodecode_user, 'admin_deleted_repo',
304 repo_name, self.ip_addr, self.sa)
304 repo_name, self.ip_addr, self.sa)
305 repo_model.delete(repo)
305 repo_model.delete(repo)
306 invalidate_cache('get_repo_cached_%s' % repo_name)
306 invalidate_cache('get_repo_cached_%s' % repo_name)
307 h.flash(_('deleted repository %s') % repo_name, category='success')
307 h.flash(_('deleted repository %s') % repo_name, category='success')
308 Session().commit()
308 Session().commit()
309 except IntegrityError, e:
309 except IntegrityError, e:
310 if e.message.find('repositories_fork_id_fkey') != -1:
310 if e.message.find('repositories_fork_id_fkey') != -1:
311 log.error(traceback.format_exc())
311 log.error(traceback.format_exc())
312 h.flash(_('Cannot delete %s it still contains attached '
312 h.flash(_('Cannot delete %s it still contains attached '
313 'forks') % repo_name,
313 'forks') % repo_name,
314 category='warning')
314 category='warning')
315 else:
315 else:
316 log.error(traceback.format_exc())
316 log.error(traceback.format_exc())
317 h.flash(_('An error occurred during '
317 h.flash(_('An error occurred during '
318 'deletion of %s') % repo_name,
318 'deletion of %s') % repo_name,
319 category='error')
319 category='error')
320
320
321 except Exception, e:
321 except Exception, e:
322 log.error(traceback.format_exc())
322 log.error(traceback.format_exc())
323 h.flash(_('An error occurred during deletion of %s') % repo_name,
323 h.flash(_('An error occurred during deletion of %s') % repo_name,
324 category='error')
324 category='error')
325
325
326 return redirect(url('repos'))
326 return redirect(url('repos'))
327
327
328 @HasRepoPermissionAllDecorator('repository.admin')
328 @HasRepoPermissionAllDecorator('repository.admin')
329 def delete_perm_user(self, repo_name):
329 def delete_perm_user(self, repo_name):
330 """
330 """
331 DELETE an existing repository permission user
331 DELETE an existing repository permission user
332
332
333 :param repo_name:
333 :param repo_name:
334 """
334 """
335 try:
335 try:
336 RepoModel().revoke_user_permission(repo=repo_name,
336 RepoModel().revoke_user_permission(repo=repo_name,
337 user=request.POST['user_id'])
337 user=request.POST['user_id'])
338 Session().commit()
338 Session().commit()
339 except Exception:
339 except Exception:
340 log.error(traceback.format_exc())
340 log.error(traceback.format_exc())
341 h.flash(_('An error occurred during deletion of repository user'),
341 h.flash(_('An error occurred during deletion of repository user'),
342 category='error')
342 category='error')
343 raise HTTPInternalServerError()
343 raise HTTPInternalServerError()
344
344
345 @HasRepoPermissionAllDecorator('repository.admin')
345 @HasRepoPermissionAllDecorator('repository.admin')
346 def delete_perm_users_group(self, repo_name):
346 def delete_perm_users_group(self, repo_name):
347 """
347 """
348 DELETE an existing repository permission users group
348 DELETE an existing repository permission user group
349
349
350 :param repo_name:
350 :param repo_name:
351 """
351 """
352
352
353 try:
353 try:
354 RepoModel().revoke_users_group_permission(
354 RepoModel().revoke_users_group_permission(
355 repo=repo_name, group_name=request.POST['users_group_id']
355 repo=repo_name, group_name=request.POST['users_group_id']
356 )
356 )
357 Session().commit()
357 Session().commit()
358 except Exception:
358 except Exception:
359 log.error(traceback.format_exc())
359 log.error(traceback.format_exc())
360 h.flash(_('An error occurred during deletion of repository'
360 h.flash(_('An error occurred during deletion of repository'
361 ' users groups'),
361 ' user groups'),
362 category='error')
362 category='error')
363 raise HTTPInternalServerError()
363 raise HTTPInternalServerError()
364
364
365 @HasPermissionAllDecorator('hg.admin')
365 @HasPermissionAllDecorator('hg.admin')
366 def repo_stats(self, repo_name):
366 def repo_stats(self, repo_name):
367 """
367 """
368 DELETE an existing repository statistics
368 DELETE an existing repository statistics
369
369
370 :param repo_name:
370 :param repo_name:
371 """
371 """
372
372
373 try:
373 try:
374 RepoModel().delete_stats(repo_name)
374 RepoModel().delete_stats(repo_name)
375 Session().commit()
375 Session().commit()
376 except Exception, e:
376 except Exception, e:
377 log.error(traceback.format_exc())
377 log.error(traceback.format_exc())
378 h.flash(_('An error occurred during deletion of repository stats'),
378 h.flash(_('An error occurred during deletion of repository stats'),
379 category='error')
379 category='error')
380 return redirect(url('edit_repo', repo_name=repo_name))
380 return redirect(url('edit_repo', repo_name=repo_name))
381
381
382 @HasPermissionAllDecorator('hg.admin')
382 @HasPermissionAllDecorator('hg.admin')
383 def repo_cache(self, repo_name):
383 def repo_cache(self, repo_name):
384 """
384 """
385 INVALIDATE existing repository cache
385 INVALIDATE existing repository cache
386
386
387 :param repo_name:
387 :param repo_name:
388 """
388 """
389
389
390 try:
390 try:
391 ScmModel().mark_for_invalidation(repo_name)
391 ScmModel().mark_for_invalidation(repo_name)
392 Session().commit()
392 Session().commit()
393 except Exception, e:
393 except Exception, e:
394 log.error(traceback.format_exc())
394 log.error(traceback.format_exc())
395 h.flash(_('An error occurred during cache invalidation'),
395 h.flash(_('An error occurred during cache invalidation'),
396 category='error')
396 category='error')
397 return redirect(url('edit_repo', repo_name=repo_name))
397 return redirect(url('edit_repo', repo_name=repo_name))
398
398
399 @HasPermissionAllDecorator('hg.admin')
399 @HasPermissionAllDecorator('hg.admin')
400 def repo_locking(self, repo_name):
400 def repo_locking(self, repo_name):
401 """
401 """
402 Unlock repository when it is locked !
402 Unlock repository when it is locked !
403
403
404 :param repo_name:
404 :param repo_name:
405 """
405 """
406
406
407 try:
407 try:
408 repo = Repository.get_by_repo_name(repo_name)
408 repo = Repository.get_by_repo_name(repo_name)
409 if request.POST.get('set_lock'):
409 if request.POST.get('set_lock'):
410 Repository.lock(repo, c.rhodecode_user.user_id)
410 Repository.lock(repo, c.rhodecode_user.user_id)
411 elif request.POST.get('set_unlock'):
411 elif request.POST.get('set_unlock'):
412 Repository.unlock(repo)
412 Repository.unlock(repo)
413 except Exception, e:
413 except Exception, e:
414 log.error(traceback.format_exc())
414 log.error(traceback.format_exc())
415 h.flash(_('An error occurred during unlocking'),
415 h.flash(_('An error occurred during unlocking'),
416 category='error')
416 category='error')
417 return redirect(url('edit_repo', repo_name=repo_name))
417 return redirect(url('edit_repo', repo_name=repo_name))
418
418
419 @HasPermissionAllDecorator('hg.admin')
419 @HasPermissionAllDecorator('hg.admin')
420 def repo_public_journal(self, repo_name):
420 def repo_public_journal(self, repo_name):
421 """
421 """
422 Set's this repository to be visible in public journal,
422 Set's this repository to be visible in public journal,
423 in other words assing default user to follow this repo
423 in other words assing default user to follow this repo
424
424
425 :param repo_name:
425 :param repo_name:
426 """
426 """
427
427
428 cur_token = request.POST.get('auth_token')
428 cur_token = request.POST.get('auth_token')
429 token = get_token()
429 token = get_token()
430 if cur_token == token:
430 if cur_token == token:
431 try:
431 try:
432 repo_id = Repository.get_by_repo_name(repo_name).repo_id
432 repo_id = Repository.get_by_repo_name(repo_name).repo_id
433 user_id = User.get_by_username('default').user_id
433 user_id = User.get_by_username('default').user_id
434 self.scm_model.toggle_following_repo(repo_id, user_id)
434 self.scm_model.toggle_following_repo(repo_id, user_id)
435 h.flash(_('Updated repository visibility in public journal'),
435 h.flash(_('Updated repository visibility in public journal'),
436 category='success')
436 category='success')
437 Session().commit()
437 Session().commit()
438 except:
438 except:
439 h.flash(_('An error occurred during setting this'
439 h.flash(_('An error occurred during setting this'
440 ' repository in public journal'),
440 ' repository in public journal'),
441 category='error')
441 category='error')
442
442
443 else:
443 else:
444 h.flash(_('Token mismatch'), category='error')
444 h.flash(_('Token mismatch'), category='error')
445 return redirect(url('edit_repo', repo_name=repo_name))
445 return redirect(url('edit_repo', repo_name=repo_name))
446
446
447 @HasPermissionAllDecorator('hg.admin')
447 @HasPermissionAllDecorator('hg.admin')
448 def repo_pull(self, repo_name):
448 def repo_pull(self, repo_name):
449 """
449 """
450 Runs task to update given repository with remote changes,
450 Runs task to update given repository with remote changes,
451 ie. make pull on remote location
451 ie. make pull on remote location
452
452
453 :param repo_name:
453 :param repo_name:
454 """
454 """
455 try:
455 try:
456 ScmModel().pull_changes(repo_name, self.rhodecode_user.username)
456 ScmModel().pull_changes(repo_name, self.rhodecode_user.username)
457 h.flash(_('Pulled from remote location'), category='success')
457 h.flash(_('Pulled from remote location'), category='success')
458 except Exception, e:
458 except Exception, e:
459 h.flash(_('An error occurred during pull from remote location'),
459 h.flash(_('An error occurred during pull from remote location'),
460 category='error')
460 category='error')
461
461
462 return redirect(url('edit_repo', repo_name=repo_name))
462 return redirect(url('edit_repo', repo_name=repo_name))
463
463
464 @HasPermissionAllDecorator('hg.admin')
464 @HasPermissionAllDecorator('hg.admin')
465 def repo_as_fork(self, repo_name):
465 def repo_as_fork(self, repo_name):
466 """
466 """
467 Mark given repository as a fork of another
467 Mark given repository as a fork of another
468
468
469 :param repo_name:
469 :param repo_name:
470 """
470 """
471 try:
471 try:
472 fork_id = request.POST.get('id_fork_of')
472 fork_id = request.POST.get('id_fork_of')
473 repo = ScmModel().mark_as_fork(repo_name, fork_id,
473 repo = ScmModel().mark_as_fork(repo_name, fork_id,
474 self.rhodecode_user.username)
474 self.rhodecode_user.username)
475 fork = repo.fork.repo_name if repo.fork else _('Nothing')
475 fork = repo.fork.repo_name if repo.fork else _('Nothing')
476 Session().commit()
476 Session().commit()
477 h.flash(_('Marked repo %s as fork of %s') % (repo_name, fork),
477 h.flash(_('Marked repo %s as fork of %s') % (repo_name, fork),
478 category='success')
478 category='success')
479 except Exception, e:
479 except Exception, e:
480 log.error(traceback.format_exc())
480 log.error(traceback.format_exc())
481 h.flash(_('An error occurred during this operation'),
481 h.flash(_('An error occurred during this operation'),
482 category='error')
482 category='error')
483
483
484 return redirect(url('edit_repo', repo_name=repo_name))
484 return redirect(url('edit_repo', repo_name=repo_name))
485
485
486 @HasPermissionAllDecorator('hg.admin')
486 @HasPermissionAllDecorator('hg.admin')
487 def show(self, repo_name, format='html'):
487 def show(self, repo_name, format='html'):
488 """GET /repos/repo_name: Show a specific item"""
488 """GET /repos/repo_name: Show a specific item"""
489 # url('repo', repo_name=ID)
489 # url('repo', repo_name=ID)
490
490
491 @HasPermissionAllDecorator('hg.admin')
491 @HasPermissionAllDecorator('hg.admin')
492 def edit(self, repo_name, format='html'):
492 def edit(self, repo_name, format='html'):
493 """GET /repos/repo_name/edit: Form to edit an existing item"""
493 """GET /repos/repo_name/edit: Form to edit an existing item"""
494 # url('edit_repo', repo_name=ID)
494 # url('edit_repo', repo_name=ID)
495 defaults = self.__load_data(repo_name)
495 defaults = self.__load_data(repo_name)
496
496
497 return htmlfill.render(
497 return htmlfill.render(
498 render('admin/repos/repo_edit.html'),
498 render('admin/repos/repo_edit.html'),
499 defaults=defaults,
499 defaults=defaults,
500 encoding="UTF-8",
500 encoding="UTF-8",
501 force_defaults=False
501 force_defaults=False
502 )
502 )
503
503
504 @HasPermissionAllDecorator('hg.admin')
504 @HasPermissionAllDecorator('hg.admin')
505 def create_repo_field(self, repo_name):
505 def create_repo_field(self, repo_name):
506 try:
506 try:
507 form_result = RepoFieldForm()().to_python(dict(request.POST))
507 form_result = RepoFieldForm()().to_python(dict(request.POST))
508 new_field = RepositoryField()
508 new_field = RepositoryField()
509 new_field.repository = Repository.get_by_repo_name(repo_name)
509 new_field.repository = Repository.get_by_repo_name(repo_name)
510 new_field.field_key = form_result['new_field_key']
510 new_field.field_key = form_result['new_field_key']
511 new_field.field_type = form_result['new_field_type'] # python type
511 new_field.field_type = form_result['new_field_type'] # python type
512 new_field.field_value = form_result['new_field_value'] # set initial blank value
512 new_field.field_value = form_result['new_field_value'] # set initial blank value
513 new_field.field_desc = form_result['new_field_desc']
513 new_field.field_desc = form_result['new_field_desc']
514 new_field.field_label = form_result['new_field_label']
514 new_field.field_label = form_result['new_field_label']
515 Session().add(new_field)
515 Session().add(new_field)
516 Session().commit()
516 Session().commit()
517
517
518 except Exception, e:
518 except Exception, e:
519 log.error(traceback.format_exc())
519 log.error(traceback.format_exc())
520 msg = _('An error occurred during creation of field')
520 msg = _('An error occurred during creation of field')
521 if isinstance(e, formencode.Invalid):
521 if isinstance(e, formencode.Invalid):
522 msg += ". " + e.msg
522 msg += ". " + e.msg
523 h.flash(msg, category='error')
523 h.flash(msg, category='error')
524 return redirect(url('edit_repo', repo_name=repo_name))
524 return redirect(url('edit_repo', repo_name=repo_name))
525
525
526 @HasPermissionAllDecorator('hg.admin')
526 @HasPermissionAllDecorator('hg.admin')
527 def delete_repo_field(self, repo_name, field_id):
527 def delete_repo_field(self, repo_name, field_id):
528 field = RepositoryField.get_or_404(field_id)
528 field = RepositoryField.get_or_404(field_id)
529 try:
529 try:
530 Session().delete(field)
530 Session().delete(field)
531 Session().commit()
531 Session().commit()
532 except Exception, e:
532 except Exception, e:
533 log.error(traceback.format_exc())
533 log.error(traceback.format_exc())
534 msg = _('An error occurred during removal of field')
534 msg = _('An error occurred during removal of field')
535 h.flash(msg, category='error')
535 h.flash(msg, category='error')
536 return redirect(url('edit_repo', repo_name=repo_name))
536 return redirect(url('edit_repo', repo_name=repo_name))
@@ -1,398 +1,398 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.controllers.admin.repos_groups
3 rhodecode.controllers.admin.repos_groups
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 Repositories groups controller for RhodeCode
6 Repository groups controller for RhodeCode
7
7
8 :created_on: Mar 23, 2010
8 :created_on: Mar 23, 2010
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software: you can redistribute it and/or modify
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
16 # (at your option) any later version.
17 #
17 #
18 # This program is distributed in the hope that it will be useful,
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # GNU General Public License for more details.
21 # GNU General Public License for more details.
22 #
22 #
23 # You should have received a copy of the GNU General Public License
23 # You should have received a copy of the GNU General Public License
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25
25
26 import logging
26 import logging
27 import traceback
27 import traceback
28 import formencode
28 import formencode
29
29
30 from formencode import htmlfill
30 from formencode import htmlfill
31
31
32 from pylons import request, tmpl_context as c, url
32 from pylons import request, tmpl_context as c, url
33 from pylons.controllers.util import abort, redirect
33 from pylons.controllers.util import abort, redirect
34 from pylons.i18n.translation import _
34 from pylons.i18n.translation import _
35
35
36 from sqlalchemy.exc import IntegrityError
36 from sqlalchemy.exc import IntegrityError
37
37
38 import rhodecode
38 import rhodecode
39 from rhodecode.lib import helpers as h
39 from rhodecode.lib import helpers as h
40 from rhodecode.lib.ext_json import json
40 from rhodecode.lib.ext_json import json
41 from rhodecode.lib.auth import LoginRequired, HasPermissionAnyDecorator,\
41 from rhodecode.lib.auth import LoginRequired, HasPermissionAnyDecorator,\
42 HasReposGroupPermissionAnyDecorator, HasReposGroupPermissionAll,\
42 HasReposGroupPermissionAnyDecorator, HasReposGroupPermissionAll,\
43 HasPermissionAll
43 HasPermissionAll
44 from rhodecode.lib.base import BaseController, render
44 from rhodecode.lib.base import BaseController, render
45 from rhodecode.model.db import RepoGroup, Repository
45 from rhodecode.model.db import RepoGroup, Repository
46 from rhodecode.model.repos_group import ReposGroupModel
46 from rhodecode.model.repos_group import ReposGroupModel
47 from rhodecode.model.forms import ReposGroupForm
47 from rhodecode.model.forms import ReposGroupForm
48 from rhodecode.model.meta import Session
48 from rhodecode.model.meta import Session
49 from rhodecode.model.repo import RepoModel
49 from rhodecode.model.repo import RepoModel
50 from webob.exc import HTTPInternalServerError, HTTPNotFound
50 from webob.exc import HTTPInternalServerError, HTTPNotFound
51 from rhodecode.lib.utils2 import str2bool, safe_int
51 from rhodecode.lib.utils2 import str2bool, safe_int
52 from sqlalchemy.sql.expression import func
52 from sqlalchemy.sql.expression import func
53 from rhodecode.model.scm import GroupList
53 from rhodecode.model.scm import GroupList
54
54
55 log = logging.getLogger(__name__)
55 log = logging.getLogger(__name__)
56
56
57
57
58 class ReposGroupsController(BaseController):
58 class ReposGroupsController(BaseController):
59 """REST Controller styled on the Atom Publishing Protocol"""
59 """REST Controller styled on the Atom Publishing Protocol"""
60 # To properly map this controller, ensure your config/routing.py
60 # To properly map this controller, ensure your config/routing.py
61 # file has a resource setup:
61 # file has a resource setup:
62 # map.resource('repos_group', 'repos_groups')
62 # map.resource('repos_group', 'repos_groups')
63
63
64 @LoginRequired()
64 @LoginRequired()
65 def __before__(self):
65 def __before__(self):
66 super(ReposGroupsController, self).__before__()
66 super(ReposGroupsController, self).__before__()
67
67
68 def __load_defaults(self, allow_empty_group=False, exclude_group_ids=[]):
68 def __load_defaults(self, allow_empty_group=False, exclude_group_ids=[]):
69 if HasPermissionAll('hg.admin')('group edit'):
69 if HasPermissionAll('hg.admin')('group edit'):
70 #we're global admin, we're ok and we can create TOP level groups
70 #we're global admin, we're ok and we can create TOP level groups
71 allow_empty_group = True
71 allow_empty_group = True
72
72
73 #override the choices for this form, we need to filter choices
73 #override the choices for this form, we need to filter choices
74 #and display only those we have ADMIN right
74 #and display only those we have ADMIN right
75 groups_with_admin_rights = GroupList(RepoGroup.query().all(),
75 groups_with_admin_rights = GroupList(RepoGroup.query().all(),
76 perm_set=['group.admin'])
76 perm_set=['group.admin'])
77 c.repo_groups = RepoGroup.groups_choices(groups=groups_with_admin_rights,
77 c.repo_groups = RepoGroup.groups_choices(groups=groups_with_admin_rights,
78 show_empty_group=allow_empty_group)
78 show_empty_group=allow_empty_group)
79 # exclude filtered ids
79 # exclude filtered ids
80 c.repo_groups = filter(lambda x: x[0] not in exclude_group_ids,
80 c.repo_groups = filter(lambda x: x[0] not in exclude_group_ids,
81 c.repo_groups)
81 c.repo_groups)
82 c.repo_groups_choices = map(lambda k: unicode(k[0]), c.repo_groups)
82 c.repo_groups_choices = map(lambda k: unicode(k[0]), c.repo_groups)
83 repo_model = RepoModel()
83 repo_model = RepoModel()
84 c.users_array = repo_model.get_users_js()
84 c.users_array = repo_model.get_users_js()
85 c.users_groups_array = repo_model.get_users_groups_js()
85 c.users_groups_array = repo_model.get_users_groups_js()
86
86
87 def __load_data(self, group_id):
87 def __load_data(self, group_id):
88 """
88 """
89 Load defaults settings for edit, and update
89 Load defaults settings for edit, and update
90
90
91 :param group_id:
91 :param group_id:
92 """
92 """
93 repo_group = RepoGroup.get_or_404(group_id)
93 repo_group = RepoGroup.get_or_404(group_id)
94 data = repo_group.get_dict()
94 data = repo_group.get_dict()
95 data['group_name'] = repo_group.name
95 data['group_name'] = repo_group.name
96
96
97 # fill repository users
97 # fill repository users
98 for p in repo_group.repo_group_to_perm:
98 for p in repo_group.repo_group_to_perm:
99 data.update({'u_perm_%s' % p.user.username:
99 data.update({'u_perm_%s' % p.user.username:
100 p.permission.permission_name})
100 p.permission.permission_name})
101
101
102 # fill repository groups
102 # fill repository groups
103 for p in repo_group.users_group_to_perm:
103 for p in repo_group.users_group_to_perm:
104 data.update({'g_perm_%s' % p.users_group.users_group_name:
104 data.update({'g_perm_%s' % p.users_group.users_group_name:
105 p.permission.permission_name})
105 p.permission.permission_name})
106
106
107 return data
107 return data
108
108
109 def _revoke_perms_on_yourself(self, form_result):
109 def _revoke_perms_on_yourself(self, form_result):
110 _up = filter(lambda u: c.rhodecode_user.username == u[0],
110 _up = filter(lambda u: c.rhodecode_user.username == u[0],
111 form_result['perms_updates'])
111 form_result['perms_updates'])
112 _new = filter(lambda u: c.rhodecode_user.username == u[0],
112 _new = filter(lambda u: c.rhodecode_user.username == u[0],
113 form_result['perms_new'])
113 form_result['perms_new'])
114 if _new and _new[0][1] != 'group.admin' or _up and _up[0][1] != 'group.admin':
114 if _new and _new[0][1] != 'group.admin' or _up and _up[0][1] != 'group.admin':
115 return True
115 return True
116 return False
116 return False
117
117
118 def index(self, format='html'):
118 def index(self, format='html'):
119 """GET /repos_groups: All items in the collection"""
119 """GET /repos_groups: All items in the collection"""
120 # url('repos_groups')
120 # url('repos_groups')
121 group_iter = GroupList(RepoGroup.query().all(), perm_set=['group.admin'])
121 group_iter = GroupList(RepoGroup.query().all(), perm_set=['group.admin'])
122 sk = lambda g: g.parents[0].group_name if g.parents else g.group_name
122 sk = lambda g: g.parents[0].group_name if g.parents else g.group_name
123 c.groups = sorted(group_iter, key=sk)
123 c.groups = sorted(group_iter, key=sk)
124 return render('admin/repos_groups/repos_groups_show.html')
124 return render('admin/repos_groups/repos_groups_show.html')
125
125
126 def create(self):
126 def create(self):
127 """POST /repos_groups: Create a new item"""
127 """POST /repos_groups: Create a new item"""
128 # url('repos_groups')
128 # url('repos_groups')
129
129
130 self.__load_defaults()
130 self.__load_defaults()
131
131
132 # permissions for can create group based on parent_id are checked
132 # permissions for can create group based on parent_id are checked
133 # here in the Form
133 # here in the Form
134 repos_group_form = ReposGroupForm(available_groups=
134 repos_group_form = ReposGroupForm(available_groups=
135 map(lambda k: unicode(k[0]), c.repo_groups))()
135 map(lambda k: unicode(k[0]), c.repo_groups))()
136 try:
136 try:
137 form_result = repos_group_form.to_python(dict(request.POST))
137 form_result = repos_group_form.to_python(dict(request.POST))
138 ReposGroupModel().create(
138 ReposGroupModel().create(
139 group_name=form_result['group_name'],
139 group_name=form_result['group_name'],
140 group_description=form_result['group_description'],
140 group_description=form_result['group_description'],
141 parent=form_result['group_parent_id'],
141 parent=form_result['group_parent_id'],
142 owner=self.rhodecode_user.user_id
142 owner=self.rhodecode_user.user_id
143 )
143 )
144 Session().commit()
144 Session().commit()
145 h.flash(_('created repos group %s') \
145 h.flash(_('created repos group %s') \
146 % form_result['group_name'], category='success')
146 % form_result['group_name'], category='success')
147 #TODO: in futureaction_logger(, '', '', '', self.sa)
147 #TODO: in futureaction_logger(, '', '', '', self.sa)
148 except formencode.Invalid, errors:
148 except formencode.Invalid, errors:
149 return htmlfill.render(
149 return htmlfill.render(
150 render('admin/repos_groups/repos_groups_add.html'),
150 render('admin/repos_groups/repos_groups_add.html'),
151 defaults=errors.value,
151 defaults=errors.value,
152 errors=errors.error_dict or {},
152 errors=errors.error_dict or {},
153 prefix_error=False,
153 prefix_error=False,
154 encoding="UTF-8")
154 encoding="UTF-8")
155 except Exception:
155 except Exception:
156 log.error(traceback.format_exc())
156 log.error(traceback.format_exc())
157 h.flash(_('error occurred during creation of repos group %s') \
157 h.flash(_('error occurred during creation of repos group %s') \
158 % request.POST.get('group_name'), category='error')
158 % request.POST.get('group_name'), category='error')
159 parent_group_id = form_result['group_parent_id']
159 parent_group_id = form_result['group_parent_id']
160 #TODO: maybe we should get back to the main view, not the admin one
160 #TODO: maybe we should get back to the main view, not the admin one
161 return redirect(url('repos_groups', parent_group=parent_group_id))
161 return redirect(url('repos_groups', parent_group=parent_group_id))
162
162
163 def new(self, format='html'):
163 def new(self, format='html'):
164 """GET /repos_groups/new: Form to create a new item"""
164 """GET /repos_groups/new: Form to create a new item"""
165 # url('new_repos_group')
165 # url('new_repos_group')
166 if HasPermissionAll('hg.admin')('group create'):
166 if HasPermissionAll('hg.admin')('group create'):
167 #we're global admin, we're ok and we can create TOP level groups
167 #we're global admin, we're ok and we can create TOP level groups
168 pass
168 pass
169 else:
169 else:
170 # we pass in parent group into creation form, thus we know
170 # we pass in parent group into creation form, thus we know
171 # what would be the group, we can check perms here !
171 # what would be the group, we can check perms here !
172 group_id = safe_int(request.GET.get('parent_group'))
172 group_id = safe_int(request.GET.get('parent_group'))
173 group = RepoGroup.get(group_id) if group_id else None
173 group = RepoGroup.get(group_id) if group_id else None
174 group_name = group.group_name if group else None
174 group_name = group.group_name if group else None
175 if HasReposGroupPermissionAll('group.admin')(group_name, 'group create'):
175 if HasReposGroupPermissionAll('group.admin')(group_name, 'group create'):
176 pass
176 pass
177 else:
177 else:
178 return abort(403)
178 return abort(403)
179
179
180 self.__load_defaults()
180 self.__load_defaults()
181 return render('admin/repos_groups/repos_groups_add.html')
181 return render('admin/repos_groups/repos_groups_add.html')
182
182
183 @HasReposGroupPermissionAnyDecorator('group.admin')
183 @HasReposGroupPermissionAnyDecorator('group.admin')
184 def update(self, group_name):
184 def update(self, group_name):
185 """PUT /repos_groups/group_name: Update an existing item"""
185 """PUT /repos_groups/group_name: Update an existing item"""
186 # Forms posted to this method should contain a hidden field:
186 # Forms posted to this method should contain a hidden field:
187 # <input type="hidden" name="_method" value="PUT" />
187 # <input type="hidden" name="_method" value="PUT" />
188 # Or using helpers:
188 # Or using helpers:
189 # h.form(url('repos_group', group_name=GROUP_NAME),
189 # h.form(url('repos_group', group_name=GROUP_NAME),
190 # method='put')
190 # method='put')
191 # url('repos_group', group_name=GROUP_NAME)
191 # url('repos_group', group_name=GROUP_NAME)
192
192
193 c.repos_group = ReposGroupModel()._get_repos_group(group_name)
193 c.repos_group = ReposGroupModel()._get_repos_group(group_name)
194 if HasPermissionAll('hg.admin')('group edit'):
194 if HasPermissionAll('hg.admin')('group edit'):
195 #we're global admin, we're ok and we can create TOP level groups
195 #we're global admin, we're ok and we can create TOP level groups
196 allow_empty_group = True
196 allow_empty_group = True
197 elif not c.repos_group.parent_group:
197 elif not c.repos_group.parent_group:
198 allow_empty_group = True
198 allow_empty_group = True
199 else:
199 else:
200 allow_empty_group = False
200 allow_empty_group = False
201 self.__load_defaults(allow_empty_group=allow_empty_group,
201 self.__load_defaults(allow_empty_group=allow_empty_group,
202 exclude_group_ids=[c.repos_group.group_id])
202 exclude_group_ids=[c.repos_group.group_id])
203
203
204 repos_group_form = ReposGroupForm(
204 repos_group_form = ReposGroupForm(
205 edit=True,
205 edit=True,
206 old_data=c.repos_group.get_dict(),
206 old_data=c.repos_group.get_dict(),
207 available_groups=c.repo_groups_choices,
207 available_groups=c.repo_groups_choices,
208 can_create_in_root=allow_empty_group,
208 can_create_in_root=allow_empty_group,
209 )()
209 )()
210 try:
210 try:
211 form_result = repos_group_form.to_python(dict(request.POST))
211 form_result = repos_group_form.to_python(dict(request.POST))
212 if not c.rhodecode_user.is_admin:
212 if not c.rhodecode_user.is_admin:
213 if self._revoke_perms_on_yourself(form_result):
213 if self._revoke_perms_on_yourself(form_result):
214 msg = _('Cannot revoke permission for yourself as admin')
214 msg = _('Cannot revoke permission for yourself as admin')
215 h.flash(msg, category='warning')
215 h.flash(msg, category='warning')
216 raise Exception('revoke admin permission on self')
216 raise Exception('revoke admin permission on self')
217
217
218 new_gr = ReposGroupModel().update(group_name, form_result)
218 new_gr = ReposGroupModel().update(group_name, form_result)
219 Session().commit()
219 Session().commit()
220 h.flash(_('updated repos group %s') \
220 h.flash(_('updated repos group %s') \
221 % form_result['group_name'], category='success')
221 % form_result['group_name'], category='success')
222 # we now have new name !
222 # we now have new name !
223 group_name = new_gr.group_name
223 group_name = new_gr.group_name
224 #TODO: in future action_logger(, '', '', '', self.sa)
224 #TODO: in future action_logger(, '', '', '', self.sa)
225 except formencode.Invalid, errors:
225 except formencode.Invalid, errors:
226
226
227 return htmlfill.render(
227 return htmlfill.render(
228 render('admin/repos_groups/repos_groups_edit.html'),
228 render('admin/repos_groups/repos_groups_edit.html'),
229 defaults=errors.value,
229 defaults=errors.value,
230 errors=errors.error_dict or {},
230 errors=errors.error_dict or {},
231 prefix_error=False,
231 prefix_error=False,
232 encoding="UTF-8")
232 encoding="UTF-8")
233 except Exception:
233 except Exception:
234 log.error(traceback.format_exc())
234 log.error(traceback.format_exc())
235 h.flash(_('error occurred during update of repos group %s') \
235 h.flash(_('error occurred during update of repos group %s') \
236 % request.POST.get('group_name'), category='error')
236 % request.POST.get('group_name'), category='error')
237
237
238 return redirect(url('edit_repos_group', group_name=group_name))
238 return redirect(url('edit_repos_group', group_name=group_name))
239
239
240 @HasReposGroupPermissionAnyDecorator('group.admin')
240 @HasReposGroupPermissionAnyDecorator('group.admin')
241 def delete(self, group_name):
241 def delete(self, group_name):
242 """DELETE /repos_groups/group_name: Delete an existing item"""
242 """DELETE /repos_groups/group_name: Delete an existing item"""
243 # Forms posted to this method should contain a hidden field:
243 # Forms posted to this method should contain a hidden field:
244 # <input type="hidden" name="_method" value="DELETE" />
244 # <input type="hidden" name="_method" value="DELETE" />
245 # Or using helpers:
245 # Or using helpers:
246 # h.form(url('repos_group', group_name=GROUP_NAME),
246 # h.form(url('repos_group', group_name=GROUP_NAME),
247 # method='delete')
247 # method='delete')
248 # url('repos_group', group_name=GROUP_NAME)
248 # url('repos_group', group_name=GROUP_NAME)
249
249
250 gr = c.repos_group = ReposGroupModel()._get_repos_group(group_name)
250 gr = c.repos_group = ReposGroupModel()._get_repos_group(group_name)
251 repos = gr.repositories.all()
251 repos = gr.repositories.all()
252 if repos:
252 if repos:
253 h.flash(_('This group contains %s repositores and cannot be '
253 h.flash(_('This group contains %s repositores and cannot be '
254 'deleted') % len(repos),
254 'deleted') % len(repos),
255 category='error')
255 category='error')
256 return redirect(url('repos_groups'))
256 return redirect(url('repos_groups'))
257
257
258 try:
258 try:
259 ReposGroupModel().delete(group_name)
259 ReposGroupModel().delete(group_name)
260 Session().commit()
260 Session().commit()
261 h.flash(_('removed repos group %s') % gr.group_name,
261 h.flash(_('removed repos group %s') % gr.group_name,
262 category='success')
262 category='success')
263 #TODO: in future action_logger(, '', '', '', self.sa)
263 #TODO: in future action_logger(, '', '', '', self.sa)
264 except IntegrityError, e:
264 except IntegrityError, e:
265 if str(e.message).find('groups_group_parent_id_fkey') != -1:
265 if str(e.message).find('groups_group_parent_id_fkey') != -1:
266 log.error(traceback.format_exc())
266 log.error(traceback.format_exc())
267 h.flash(_('Cannot delete this group it still contains '
267 h.flash(_('Cannot delete this group it still contains '
268 'subgroups'),
268 'subgroups'),
269 category='warning')
269 category='warning')
270 else:
270 else:
271 log.error(traceback.format_exc())
271 log.error(traceback.format_exc())
272 h.flash(_('error occurred during deletion of repos '
272 h.flash(_('error occurred during deletion of repos '
273 'group %s') % gr.group_name, category='error')
273 'group %s') % gr.group_name, category='error')
274
274
275 except Exception:
275 except Exception:
276 log.error(traceback.format_exc())
276 log.error(traceback.format_exc())
277 h.flash(_('error occurred during deletion of repos '
277 h.flash(_('error occurred during deletion of repos '
278 'group %s') % gr.group_name, category='error')
278 'group %s') % gr.group_name, category='error')
279
279
280 return redirect(url('repos_groups'))
280 return redirect(url('repos_groups'))
281
281
282 @HasReposGroupPermissionAnyDecorator('group.admin')
282 @HasReposGroupPermissionAnyDecorator('group.admin')
283 def delete_repos_group_user_perm(self, group_name):
283 def delete_repos_group_user_perm(self, group_name):
284 """
284 """
285 DELETE an existing repositories group permission user
285 DELETE an existing repository group permission user
286
286
287 :param group_name:
287 :param group_name:
288 """
288 """
289 try:
289 try:
290 if not c.rhodecode_user.is_admin:
290 if not c.rhodecode_user.is_admin:
291 if c.rhodecode_user.user_id == safe_int(request.POST['user_id']):
291 if c.rhodecode_user.user_id == safe_int(request.POST['user_id']):
292 msg = _('Cannot revoke permission for yourself as admin')
292 msg = _('Cannot revoke permission for yourself as admin')
293 h.flash(msg, category='warning')
293 h.flash(msg, category='warning')
294 raise Exception('revoke admin permission on self')
294 raise Exception('revoke admin permission on self')
295 recursive = str2bool(request.POST.get('recursive', False))
295 recursive = str2bool(request.POST.get('recursive', False))
296 ReposGroupModel().delete_permission(
296 ReposGroupModel().delete_permission(
297 repos_group=group_name, obj=request.POST['user_id'],
297 repos_group=group_name, obj=request.POST['user_id'],
298 obj_type='user', recursive=recursive
298 obj_type='user', recursive=recursive
299 )
299 )
300 Session().commit()
300 Session().commit()
301 except Exception:
301 except Exception:
302 log.error(traceback.format_exc())
302 log.error(traceback.format_exc())
303 h.flash(_('An error occurred during deletion of group user'),
303 h.flash(_('An error occurred during deletion of group user'),
304 category='error')
304 category='error')
305 raise HTTPInternalServerError()
305 raise HTTPInternalServerError()
306
306
307 @HasReposGroupPermissionAnyDecorator('group.admin')
307 @HasReposGroupPermissionAnyDecorator('group.admin')
308 def delete_repos_group_users_group_perm(self, group_name):
308 def delete_repos_group_users_group_perm(self, group_name):
309 """
309 """
310 DELETE an existing repositories group permission users group
310 DELETE an existing repository group permission user group
311
311
312 :param group_name:
312 :param group_name:
313 """
313 """
314
314
315 try:
315 try:
316 recursive = str2bool(request.POST.get('recursive', False))
316 recursive = str2bool(request.POST.get('recursive', False))
317 ReposGroupModel().delete_permission(
317 ReposGroupModel().delete_permission(
318 repos_group=group_name, obj=request.POST['users_group_id'],
318 repos_group=group_name, obj=request.POST['users_group_id'],
319 obj_type='users_group', recursive=recursive
319 obj_type='users_group', recursive=recursive
320 )
320 )
321 Session().commit()
321 Session().commit()
322 except Exception:
322 except Exception:
323 log.error(traceback.format_exc())
323 log.error(traceback.format_exc())
324 h.flash(_('An error occurred during deletion of group'
324 h.flash(_('An error occurred during deletion of group'
325 ' users groups'),
325 ' user groups'),
326 category='error')
326 category='error')
327 raise HTTPInternalServerError()
327 raise HTTPInternalServerError()
328
328
329 def show_by_name(self, group_name):
329 def show_by_name(self, group_name):
330 """
330 """
331 This is a proxy that does a lookup group_name -> id, and shows
331 This is a proxy that does a lookup group_name -> id, and shows
332 the group by id view instead
332 the group by id view instead
333 """
333 """
334 group_name = group_name.rstrip('/')
334 group_name = group_name.rstrip('/')
335 id_ = RepoGroup.get_by_group_name(group_name)
335 id_ = RepoGroup.get_by_group_name(group_name)
336 if id_:
336 if id_:
337 return self.show(id_.group_id)
337 return self.show(id_.group_id)
338 raise HTTPNotFound
338 raise HTTPNotFound
339
339
340 @HasReposGroupPermissionAnyDecorator('group.read', 'group.write',
340 @HasReposGroupPermissionAnyDecorator('group.read', 'group.write',
341 'group.admin')
341 'group.admin')
342 def show(self, group_name, format='html'):
342 def show(self, group_name, format='html'):
343 """GET /repos_groups/group_name: Show a specific item"""
343 """GET /repos_groups/group_name: Show a specific item"""
344 # url('repos_group', group_name=GROUP_NAME)
344 # url('repos_group', group_name=GROUP_NAME)
345
345
346 c.group = c.repos_group = ReposGroupModel()._get_repos_group(group_name)
346 c.group = c.repos_group = ReposGroupModel()._get_repos_group(group_name)
347 c.group_repos = c.group.repositories.all()
347 c.group_repos = c.group.repositories.all()
348
348
349 #overwrite our cached list with current filter
349 #overwrite our cached list with current filter
350 gr_filter = c.group_repos
350 gr_filter = c.group_repos
351 c.repo_cnt = 0
351 c.repo_cnt = 0
352
352
353 groups = RepoGroup.query().order_by(RepoGroup.group_name)\
353 groups = RepoGroup.query().order_by(RepoGroup.group_name)\
354 .filter(RepoGroup.group_parent_id == c.group.group_id).all()
354 .filter(RepoGroup.group_parent_id == c.group.group_id).all()
355 c.groups = self.scm_model.get_repos_groups(groups)
355 c.groups = self.scm_model.get_repos_groups(groups)
356
356
357 if c.visual.lightweight_dashboard is False:
357 if c.visual.lightweight_dashboard is False:
358 c.repos_list = self.scm_model.get_repos(all_repos=gr_filter)
358 c.repos_list = self.scm_model.get_repos(all_repos=gr_filter)
359 ## lightweight version of dashboard
359 ## lightweight version of dashboard
360 else:
360 else:
361 c.repos_list = Repository.query()\
361 c.repos_list = Repository.query()\
362 .filter(Repository.group_id == c.group.group_id)\
362 .filter(Repository.group_id == c.group.group_id)\
363 .order_by(func.lower(Repository.repo_name))\
363 .order_by(func.lower(Repository.repo_name))\
364 .all()
364 .all()
365
365
366 repos_data = RepoModel().get_repos_as_dict(repos_list=c.repos_list,
366 repos_data = RepoModel().get_repos_as_dict(repos_list=c.repos_list,
367 admin=False)
367 admin=False)
368 #json used to render the grid
368 #json used to render the grid
369 c.data = json.dumps(repos_data)
369 c.data = json.dumps(repos_data)
370
370
371 return render('admin/repos_groups/repos_groups.html')
371 return render('admin/repos_groups/repos_groups.html')
372
372
373 @HasReposGroupPermissionAnyDecorator('group.admin')
373 @HasReposGroupPermissionAnyDecorator('group.admin')
374 def edit(self, group_name, format='html'):
374 def edit(self, group_name, format='html'):
375 """GET /repos_groups/group_name/edit: Form to edit an existing item"""
375 """GET /repos_groups/group_name/edit: Form to edit an existing item"""
376 # url('edit_repos_group', group_name=GROUP_NAME)
376 # url('edit_repos_group', group_name=GROUP_NAME)
377
377
378 c.repos_group = ReposGroupModel()._get_repos_group(group_name)
378 c.repos_group = ReposGroupModel()._get_repos_group(group_name)
379 #we can only allow moving empty group if it's already a top-level
379 #we can only allow moving empty group if it's already a top-level
380 #group, ie has no parents, or we're admin
380 #group, ie has no parents, or we're admin
381 if HasPermissionAll('hg.admin')('group edit'):
381 if HasPermissionAll('hg.admin')('group edit'):
382 #we're global admin, we're ok and we can create TOP level groups
382 #we're global admin, we're ok and we can create TOP level groups
383 allow_empty_group = True
383 allow_empty_group = True
384 elif not c.repos_group.parent_group:
384 elif not c.repos_group.parent_group:
385 allow_empty_group = True
385 allow_empty_group = True
386 else:
386 else:
387 allow_empty_group = False
387 allow_empty_group = False
388
388
389 self.__load_defaults(allow_empty_group=allow_empty_group,
389 self.__load_defaults(allow_empty_group=allow_empty_group,
390 exclude_group_ids=[c.repos_group.group_id])
390 exclude_group_ids=[c.repos_group.group_id])
391 defaults = self.__load_data(c.repos_group.group_id)
391 defaults = self.__load_data(c.repos_group.group_id)
392
392
393 return htmlfill.render(
393 return htmlfill.render(
394 render('admin/repos_groups/repos_groups_edit.html'),
394 render('admin/repos_groups/repos_groups_edit.html'),
395 defaults=defaults,
395 defaults=defaults,
396 encoding="UTF-8",
396 encoding="UTF-8",
397 force_defaults=False
397 force_defaults=False
398 )
398 )
@@ -1,282 +1,282 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.controllers.admin.users_groups
3 rhodecode.controllers.admin.users_groups
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 Users Groups crud controller for pylons
6 User Groups crud controller for pylons
7
7
8 :created_on: Jan 25, 2011
8 :created_on: Jan 25, 2011
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software: you can redistribute it and/or modify
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
16 # (at your option) any later version.
17 #
17 #
18 # This program is distributed in the hope that it will be useful,
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # GNU General Public License for more details.
21 # GNU General Public License for more details.
22 #
22 #
23 # You should have received a copy of the GNU General Public License
23 # You should have received a copy of the GNU General Public License
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25
25
26 import logging
26 import logging
27 import traceback
27 import traceback
28 import formencode
28 import formencode
29
29
30 from formencode import htmlfill
30 from formencode import htmlfill
31 from pylons import request, session, tmpl_context as c, url, config
31 from pylons import request, session, tmpl_context as c, url, config
32 from pylons.controllers.util import abort, redirect
32 from pylons.controllers.util import abort, redirect
33 from pylons.i18n.translation import _
33 from pylons.i18n.translation import _
34
34
35 from rhodecode.lib import helpers as h
35 from rhodecode.lib import helpers as h
36 from rhodecode.lib.exceptions import UsersGroupsAssignedException
36 from rhodecode.lib.exceptions import UsersGroupsAssignedException
37 from rhodecode.lib.utils2 import safe_unicode, str2bool
37 from rhodecode.lib.utils2 import safe_unicode, str2bool
38 from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator
38 from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator
39 from rhodecode.lib.base import BaseController, render
39 from rhodecode.lib.base import BaseController, render
40
40
41 from rhodecode.model.users_group import UsersGroupModel
41 from rhodecode.model.users_group import UsersGroupModel
42
42
43 from rhodecode.model.db import User, UsersGroup, UsersGroupToPerm,\
43 from rhodecode.model.db import User, UsersGroup, UsersGroupToPerm,\
44 UsersGroupRepoToPerm, UsersGroupRepoGroupToPerm
44 UsersGroupRepoToPerm, UsersGroupRepoGroupToPerm
45 from rhodecode.model.forms import UsersGroupForm
45 from rhodecode.model.forms import UsersGroupForm
46 from rhodecode.model.meta import Session
46 from rhodecode.model.meta import Session
47 from rhodecode.lib.utils import action_logger
47 from rhodecode.lib.utils import action_logger
48 from sqlalchemy.orm import joinedload
48 from sqlalchemy.orm import joinedload
49
49
50 log = logging.getLogger(__name__)
50 log = logging.getLogger(__name__)
51
51
52
52
53 class UsersGroupsController(BaseController):
53 class UsersGroupsController(BaseController):
54 """REST Controller styled on the Atom Publishing Protocol"""
54 """REST Controller styled on the Atom Publishing Protocol"""
55 # To properly map this controller, ensure your config/routing.py
55 # To properly map this controller, ensure your config/routing.py
56 # file has a resource setup:
56 # file has a resource setup:
57 # map.resource('users_group', 'users_groups')
57 # map.resource('users_group', 'users_groups')
58
58
59 @LoginRequired()
59 @LoginRequired()
60 @HasPermissionAllDecorator('hg.admin')
60 @HasPermissionAllDecorator('hg.admin')
61 def __before__(self):
61 def __before__(self):
62 c.admin_user = session.get('admin_user')
62 c.admin_user = session.get('admin_user')
63 c.admin_username = session.get('admin_username')
63 c.admin_username = session.get('admin_username')
64 super(UsersGroupsController, self).__before__()
64 super(UsersGroupsController, self).__before__()
65 c.available_permissions = config['available_permissions']
65 c.available_permissions = config['available_permissions']
66
66
67 def index(self, format='html'):
67 def index(self, format='html'):
68 """GET /users_groups: All items in the collection"""
68 """GET /users_groups: All items in the collection"""
69 # url('users_groups')
69 # url('users_groups')
70 c.users_groups_list = UsersGroup().query().all()
70 c.users_groups_list = UsersGroup().query().all()
71 return render('admin/users_groups/users_groups.html')
71 return render('admin/users_groups/users_groups.html')
72
72
73 def create(self):
73 def create(self):
74 """POST /users_groups: Create a new item"""
74 """POST /users_groups: Create a new item"""
75 # url('users_groups')
75 # url('users_groups')
76
76
77 users_group_form = UsersGroupForm()()
77 users_group_form = UsersGroupForm()()
78 try:
78 try:
79 form_result = users_group_form.to_python(dict(request.POST))
79 form_result = users_group_form.to_python(dict(request.POST))
80 UsersGroupModel().create(name=form_result['users_group_name'],
80 UsersGroupModel().create(name=form_result['users_group_name'],
81 active=form_result['users_group_active'])
81 active=form_result['users_group_active'])
82 gr = form_result['users_group_name']
82 gr = form_result['users_group_name']
83 action_logger(self.rhodecode_user,
83 action_logger(self.rhodecode_user,
84 'admin_created_users_group:%s' % gr,
84 'admin_created_users_group:%s' % gr,
85 None, self.ip_addr, self.sa)
85 None, self.ip_addr, self.sa)
86 h.flash(_('created users group %s') % gr, category='success')
86 h.flash(_('created user group %s') % gr, category='success')
87 Session().commit()
87 Session().commit()
88 except formencode.Invalid, errors:
88 except formencode.Invalid, errors:
89 return htmlfill.render(
89 return htmlfill.render(
90 render('admin/users_groups/users_group_add.html'),
90 render('admin/users_groups/users_group_add.html'),
91 defaults=errors.value,
91 defaults=errors.value,
92 errors=errors.error_dict or {},
92 errors=errors.error_dict or {},
93 prefix_error=False,
93 prefix_error=False,
94 encoding="UTF-8")
94 encoding="UTF-8")
95 except Exception:
95 except Exception:
96 log.error(traceback.format_exc())
96 log.error(traceback.format_exc())
97 h.flash(_('error occurred during creation of users group %s') \
97 h.flash(_('error occurred during creation of user group %s') \
98 % request.POST.get('users_group_name'), category='error')
98 % request.POST.get('users_group_name'), category='error')
99
99
100 return redirect(url('users_groups'))
100 return redirect(url('users_groups'))
101
101
102 def new(self, format='html'):
102 def new(self, format='html'):
103 """GET /users_groups/new: Form to create a new item"""
103 """GET /users_groups/new: Form to create a new item"""
104 # url('new_users_group')
104 # url('new_users_group')
105 return render('admin/users_groups/users_group_add.html')
105 return render('admin/users_groups/users_group_add.html')
106
106
107 def _load_data(self, id):
107 def _load_data(self, id):
108 c.users_group.permissions = {
108 c.users_group.permissions = {
109 'repositories': {},
109 'repositories': {},
110 'repositories_groups': {}
110 'repositories_groups': {}
111 }
111 }
112
112
113 ugroup_repo_perms = UsersGroupRepoToPerm.query()\
113 ugroup_repo_perms = UsersGroupRepoToPerm.query()\
114 .options(joinedload(UsersGroupRepoToPerm.permission))\
114 .options(joinedload(UsersGroupRepoToPerm.permission))\
115 .options(joinedload(UsersGroupRepoToPerm.repository))\
115 .options(joinedload(UsersGroupRepoToPerm.repository))\
116 .filter(UsersGroupRepoToPerm.users_group_id == id)\
116 .filter(UsersGroupRepoToPerm.users_group_id == id)\
117 .all()
117 .all()
118
118
119 for gr in ugroup_repo_perms:
119 for gr in ugroup_repo_perms:
120 c.users_group.permissions['repositories'][gr.repository.repo_name] \
120 c.users_group.permissions['repositories'][gr.repository.repo_name] \
121 = gr.permission.permission_name
121 = gr.permission.permission_name
122
122
123 ugroup_group_perms = UsersGroupRepoGroupToPerm.query()\
123 ugroup_group_perms = UsersGroupRepoGroupToPerm.query()\
124 .options(joinedload(UsersGroupRepoGroupToPerm.permission))\
124 .options(joinedload(UsersGroupRepoGroupToPerm.permission))\
125 .options(joinedload(UsersGroupRepoGroupToPerm.group))\
125 .options(joinedload(UsersGroupRepoGroupToPerm.group))\
126 .filter(UsersGroupRepoGroupToPerm.users_group_id == id)\
126 .filter(UsersGroupRepoGroupToPerm.users_group_id == id)\
127 .all()
127 .all()
128
128
129 for gr in ugroup_group_perms:
129 for gr in ugroup_group_perms:
130 c.users_group.permissions['repositories_groups'][gr.group.group_name] \
130 c.users_group.permissions['repositories_groups'][gr.group.group_name] \
131 = gr.permission.permission_name
131 = gr.permission.permission_name
132
132
133 c.group_members_obj = [x.user for x in c.users_group.members]
133 c.group_members_obj = [x.user for x in c.users_group.members]
134 c.group_members = [(x.user_id, x.username) for x in
134 c.group_members = [(x.user_id, x.username) for x in
135 c.group_members_obj]
135 c.group_members_obj]
136 c.available_members = [(x.user_id, x.username) for x in
136 c.available_members = [(x.user_id, x.username) for x in
137 User.query().all()]
137 User.query().all()]
138
138
139 def update(self, id):
139 def update(self, id):
140 """PUT /users_groups/id: Update an existing item"""
140 """PUT /users_groups/id: Update an existing item"""
141 # Forms posted to this method should contain a hidden field:
141 # Forms posted to this method should contain a hidden field:
142 # <input type="hidden" name="_method" value="PUT" />
142 # <input type="hidden" name="_method" value="PUT" />
143 # Or using helpers:
143 # Or using helpers:
144 # h.form(url('users_group', id=ID),
144 # h.form(url('users_group', id=ID),
145 # method='put')
145 # method='put')
146 # url('users_group', id=ID)
146 # url('users_group', id=ID)
147
147
148 c.users_group = UsersGroup.get_or_404(id)
148 c.users_group = UsersGroup.get_or_404(id)
149 self._load_data(id)
149 self._load_data(id)
150
150
151 available_members = [safe_unicode(x[0]) for x in c.available_members]
151 available_members = [safe_unicode(x[0]) for x in c.available_members]
152
152
153 users_group_form = UsersGroupForm(edit=True,
153 users_group_form = UsersGroupForm(edit=True,
154 old_data=c.users_group.get_dict(),
154 old_data=c.users_group.get_dict(),
155 available_members=available_members)()
155 available_members=available_members)()
156
156
157 try:
157 try:
158 form_result = users_group_form.to_python(request.POST)
158 form_result = users_group_form.to_python(request.POST)
159 UsersGroupModel().update(c.users_group, form_result)
159 UsersGroupModel().update(c.users_group, form_result)
160 gr = form_result['users_group_name']
160 gr = form_result['users_group_name']
161 action_logger(self.rhodecode_user,
161 action_logger(self.rhodecode_user,
162 'admin_updated_users_group:%s' % gr,
162 'admin_updated_users_group:%s' % gr,
163 None, self.ip_addr, self.sa)
163 None, self.ip_addr, self.sa)
164 h.flash(_('updated users group %s') % gr, category='success')
164 h.flash(_('updated user group %s') % gr, category='success')
165 Session().commit()
165 Session().commit()
166 except formencode.Invalid, errors:
166 except formencode.Invalid, errors:
167 ug_model = UsersGroupModel()
167 ug_model = UsersGroupModel()
168 defaults = errors.value
168 defaults = errors.value
169 e = errors.error_dict or {}
169 e = errors.error_dict or {}
170 defaults.update({
170 defaults.update({
171 'create_repo_perm': ug_model.has_perm(id,
171 'create_repo_perm': ug_model.has_perm(id,
172 'hg.create.repository'),
172 'hg.create.repository'),
173 'fork_repo_perm': ug_model.has_perm(id,
173 'fork_repo_perm': ug_model.has_perm(id,
174 'hg.fork.repository'),
174 'hg.fork.repository'),
175 '_method': 'put'
175 '_method': 'put'
176 })
176 })
177
177
178 return htmlfill.render(
178 return htmlfill.render(
179 render('admin/users_groups/users_group_edit.html'),
179 render('admin/users_groups/users_group_edit.html'),
180 defaults=defaults,
180 defaults=defaults,
181 errors=e,
181 errors=e,
182 prefix_error=False,
182 prefix_error=False,
183 encoding="UTF-8")
183 encoding="UTF-8")
184 except Exception:
184 except Exception:
185 log.error(traceback.format_exc())
185 log.error(traceback.format_exc())
186 h.flash(_('error occurred during update of users group %s') \
186 h.flash(_('error occurred during update of user group %s') \
187 % request.POST.get('users_group_name'), category='error')
187 % request.POST.get('users_group_name'), category='error')
188
188
189 return redirect(url('edit_users_group', id=id))
189 return redirect(url('edit_users_group', id=id))
190
190
191 def delete(self, id):
191 def delete(self, id):
192 """DELETE /users_groups/id: Delete an existing item"""
192 """DELETE /users_groups/id: Delete an existing item"""
193 # Forms posted to this method should contain a hidden field:
193 # Forms posted to this method should contain a hidden field:
194 # <input type="hidden" name="_method" value="DELETE" />
194 # <input type="hidden" name="_method" value="DELETE" />
195 # Or using helpers:
195 # Or using helpers:
196 # h.form(url('users_group', id=ID),
196 # h.form(url('users_group', id=ID),
197 # method='delete')
197 # method='delete')
198 # url('users_group', id=ID)
198 # url('users_group', id=ID)
199 usr_gr = UsersGroup.get_or_404(id)
199 usr_gr = UsersGroup.get_or_404(id)
200 try:
200 try:
201 UsersGroupModel().delete(usr_gr)
201 UsersGroupModel().delete(usr_gr)
202 Session().commit()
202 Session().commit()
203 h.flash(_('successfully deleted users group'), category='success')
203 h.flash(_('successfully deleted user group'), category='success')
204 except UsersGroupsAssignedException, e:
204 except UsersGroupsAssignedException, e:
205 h.flash(e, category='error')
205 h.flash(e, category='error')
206 except Exception:
206 except Exception:
207 log.error(traceback.format_exc())
207 log.error(traceback.format_exc())
208 h.flash(_('An error occurred during deletion of users group'),
208 h.flash(_('An error occurred during deletion of user group'),
209 category='error')
209 category='error')
210 return redirect(url('users_groups'))
210 return redirect(url('users_groups'))
211
211
212 def show(self, id, format='html'):
212 def show(self, id, format='html'):
213 """GET /users_groups/id: Show a specific item"""
213 """GET /users_groups/id: Show a specific item"""
214 # url('users_group', id=ID)
214 # url('users_group', id=ID)
215
215
216 def edit(self, id, format='html'):
216 def edit(self, id, format='html'):
217 """GET /users_groups/id/edit: Form to edit an existing item"""
217 """GET /users_groups/id/edit: Form to edit an existing item"""
218 # url('edit_users_group', id=ID)
218 # url('edit_users_group', id=ID)
219
219
220 c.users_group = UsersGroup.get_or_404(id)
220 c.users_group = UsersGroup.get_or_404(id)
221 self._load_data(id)
221 self._load_data(id)
222
222
223 ug_model = UsersGroupModel()
223 ug_model = UsersGroupModel()
224 defaults = c.users_group.get_dict()
224 defaults = c.users_group.get_dict()
225 defaults.update({
225 defaults.update({
226 'create_repo_perm': ug_model.has_perm(c.users_group,
226 'create_repo_perm': ug_model.has_perm(c.users_group,
227 'hg.create.repository'),
227 'hg.create.repository'),
228 'fork_repo_perm': ug_model.has_perm(c.users_group,
228 'fork_repo_perm': ug_model.has_perm(c.users_group,
229 'hg.fork.repository'),
229 'hg.fork.repository'),
230 })
230 })
231
231
232 return htmlfill.render(
232 return htmlfill.render(
233 render('admin/users_groups/users_group_edit.html'),
233 render('admin/users_groups/users_group_edit.html'),
234 defaults=defaults,
234 defaults=defaults,
235 encoding="UTF-8",
235 encoding="UTF-8",
236 force_defaults=False
236 force_defaults=False
237 )
237 )
238
238
239 def update_perm(self, id):
239 def update_perm(self, id):
240 """PUT /users_perm/id: Update an existing item"""
240 """PUT /users_perm/id: Update an existing item"""
241 # url('users_group_perm', id=ID, method='put')
241 # url('users_group_perm', id=ID, method='put')
242
242
243 users_group = UsersGroup.get_or_404(id)
243 users_group = UsersGroup.get_or_404(id)
244 grant_create_perm = str2bool(request.POST.get('create_repo_perm'))
244 grant_create_perm = str2bool(request.POST.get('create_repo_perm'))
245 grant_fork_perm = str2bool(request.POST.get('fork_repo_perm'))
245 grant_fork_perm = str2bool(request.POST.get('fork_repo_perm'))
246 inherit_perms = str2bool(request.POST.get('inherit_default_permissions'))
246 inherit_perms = str2bool(request.POST.get('inherit_default_permissions'))
247
247
248 usersgroup_model = UsersGroupModel()
248 usersgroup_model = UsersGroupModel()
249
249
250 try:
250 try:
251 users_group.inherit_default_permissions = inherit_perms
251 users_group.inherit_default_permissions = inherit_perms
252 Session().add(users_group)
252 Session().add(users_group)
253
253
254 if grant_create_perm:
254 if grant_create_perm:
255 usersgroup_model.revoke_perm(id, 'hg.create.none')
255 usersgroup_model.revoke_perm(id, 'hg.create.none')
256 usersgroup_model.grant_perm(id, 'hg.create.repository')
256 usersgroup_model.grant_perm(id, 'hg.create.repository')
257 h.flash(_("Granted 'repository create' permission to users group"),
257 h.flash(_("Granted 'repository create' permission to user group"),
258 category='success')
258 category='success')
259 else:
259 else:
260 usersgroup_model.revoke_perm(id, 'hg.create.repository')
260 usersgroup_model.revoke_perm(id, 'hg.create.repository')
261 usersgroup_model.grant_perm(id, 'hg.create.none')
261 usersgroup_model.grant_perm(id, 'hg.create.none')
262 h.flash(_("Revoked 'repository create' permission to users group"),
262 h.flash(_("Revoked 'repository create' permission to user group"),
263 category='success')
263 category='success')
264
264
265 if grant_fork_perm:
265 if grant_fork_perm:
266 usersgroup_model.revoke_perm(id, 'hg.fork.none')
266 usersgroup_model.revoke_perm(id, 'hg.fork.none')
267 usersgroup_model.grant_perm(id, 'hg.fork.repository')
267 usersgroup_model.grant_perm(id, 'hg.fork.repository')
268 h.flash(_("Granted 'repository fork' permission to users group"),
268 h.flash(_("Granted 'repository fork' permission to user group"),
269 category='success')
269 category='success')
270 else:
270 else:
271 usersgroup_model.revoke_perm(id, 'hg.fork.repository')
271 usersgroup_model.revoke_perm(id, 'hg.fork.repository')
272 usersgroup_model.grant_perm(id, 'hg.fork.none')
272 usersgroup_model.grant_perm(id, 'hg.fork.none')
273 h.flash(_("Revoked 'repository fork' permission to users group"),
273 h.flash(_("Revoked 'repository fork' permission to user group"),
274 category='success')
274 category='success')
275
275
276 Session().commit()
276 Session().commit()
277 except Exception:
277 except Exception:
278 log.error(traceback.format_exc())
278 log.error(traceback.format_exc())
279 h.flash(_('An error occurred during permissions saving'),
279 h.flash(_('An error occurred during permissions saving'),
280 category='error')
280 category='error')
281
281
282 return redirect(url('edit_users_group', id=id))
282 return redirect(url('edit_users_group', id=id))
@@ -1,958 +1,958 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.controllers.api
3 rhodecode.controllers.api
4 ~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 API controller for RhodeCode
6 API controller for RhodeCode
7
7
8 :created_on: Aug 20, 2011
8 :created_on: Aug 20, 2011
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2011-2012 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2011-2012 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software; you can redistribute it and/or
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 traceback
28 import traceback
29 import logging
29 import logging
30 from pylons.controllers.util import abort
30 from pylons.controllers.util import abort
31
31
32 from rhodecode.controllers.api import JSONRPCController, JSONRPCError
32 from rhodecode.controllers.api import JSONRPCController, JSONRPCError
33 from rhodecode.lib.auth import PasswordGenerator, AuthUser, \
33 from rhodecode.lib.auth import PasswordGenerator, AuthUser, \
34 HasPermissionAllDecorator, HasPermissionAnyDecorator, \
34 HasPermissionAllDecorator, HasPermissionAnyDecorator, \
35 HasPermissionAnyApi, HasRepoPermissionAnyApi
35 HasPermissionAnyApi, HasRepoPermissionAnyApi
36 from rhodecode.lib.utils import map_groups, repo2db_mapper
36 from rhodecode.lib.utils import map_groups, repo2db_mapper
37 from rhodecode.model.meta import Session
37 from rhodecode.model.meta import Session
38 from rhodecode.model.scm import ScmModel
38 from rhodecode.model.scm import ScmModel
39 from rhodecode.model.repo import RepoModel
39 from rhodecode.model.repo import RepoModel
40 from rhodecode.model.user import UserModel
40 from rhodecode.model.user import UserModel
41 from rhodecode.model.users_group import UsersGroupModel
41 from rhodecode.model.users_group import UsersGroupModel
42 from rhodecode.model.permission import PermissionModel
42 from rhodecode.model.permission import PermissionModel
43 from rhodecode.model.db import Repository, RhodeCodeSetting, UserIpMap
43 from rhodecode.model.db import Repository, RhodeCodeSetting, UserIpMap
44
44
45 log = logging.getLogger(__name__)
45 log = logging.getLogger(__name__)
46
46
47
47
48 class OptionalAttr(object):
48 class OptionalAttr(object):
49 """
49 """
50 Special Optional Option that defines other attribute
50 Special Optional Option that defines other attribute
51 """
51 """
52 def __init__(self, attr_name):
52 def __init__(self, attr_name):
53 self.attr_name = attr_name
53 self.attr_name = attr_name
54
54
55 def __repr__(self):
55 def __repr__(self):
56 return '<OptionalAttr:%s>' % self.attr_name
56 return '<OptionalAttr:%s>' % self.attr_name
57
57
58 def __call__(self):
58 def __call__(self):
59 return self
59 return self
60 #alias
60 #alias
61 OAttr = OptionalAttr
61 OAttr = OptionalAttr
62
62
63
63
64 class Optional(object):
64 class Optional(object):
65 """
65 """
66 Defines an optional parameter::
66 Defines an optional parameter::
67
67
68 param = param.getval() if isinstance(param, Optional) else param
68 param = param.getval() if isinstance(param, Optional) else param
69 param = param() if isinstance(param, Optional) else param
69 param = param() if isinstance(param, Optional) else param
70
70
71 is equivalent of::
71 is equivalent of::
72
72
73 param = Optional.extract(param)
73 param = Optional.extract(param)
74
74
75 """
75 """
76 def __init__(self, type_):
76 def __init__(self, type_):
77 self.type_ = type_
77 self.type_ = type_
78
78
79 def __repr__(self):
79 def __repr__(self):
80 return '<Optional:%s>' % self.type_.__repr__()
80 return '<Optional:%s>' % self.type_.__repr__()
81
81
82 def __call__(self):
82 def __call__(self):
83 return self.getval()
83 return self.getval()
84
84
85 def getval(self):
85 def getval(self):
86 """
86 """
87 returns value from this Optional instance
87 returns value from this Optional instance
88 """
88 """
89 return self.type_
89 return self.type_
90
90
91 @classmethod
91 @classmethod
92 def extract(cls, val):
92 def extract(cls, val):
93 if isinstance(val, cls):
93 if isinstance(val, cls):
94 return val.getval()
94 return val.getval()
95 return val
95 return val
96
96
97
97
98 def get_user_or_error(userid):
98 def get_user_or_error(userid):
99 """
99 """
100 Get user by id or name or return JsonRPCError if not found
100 Get user by id or name or return JsonRPCError if not found
101
101
102 :param userid:
102 :param userid:
103 """
103 """
104 user = UserModel().get_user(userid)
104 user = UserModel().get_user(userid)
105 if user is None:
105 if user is None:
106 raise JSONRPCError("user `%s` does not exist" % userid)
106 raise JSONRPCError("user `%s` does not exist" % userid)
107 return user
107 return user
108
108
109
109
110 def get_repo_or_error(repoid):
110 def get_repo_or_error(repoid):
111 """
111 """
112 Get repo by id or name or return JsonRPCError if not found
112 Get repo by id or name or return JsonRPCError if not found
113
113
114 :param userid:
114 :param userid:
115 """
115 """
116 repo = RepoModel().get_repo(repoid)
116 repo = RepoModel().get_repo(repoid)
117 if repo is None:
117 if repo is None:
118 raise JSONRPCError('repository `%s` does not exist' % (repoid))
118 raise JSONRPCError('repository `%s` does not exist' % (repoid))
119 return repo
119 return repo
120
120
121
121
122 def get_users_group_or_error(usersgroupid):
122 def get_users_group_or_error(usersgroupid):
123 """
123 """
124 Get users group by id or name or return JsonRPCError if not found
124 Get user group by id or name or return JsonRPCError if not found
125
125
126 :param userid:
126 :param userid:
127 """
127 """
128 users_group = UsersGroupModel().get_group(usersgroupid)
128 users_group = UsersGroupModel().get_group(usersgroupid)
129 if users_group is None:
129 if users_group is None:
130 raise JSONRPCError('users group `%s` does not exist' % usersgroupid)
130 raise JSONRPCError('user group `%s` does not exist' % usersgroupid)
131 return users_group
131 return users_group
132
132
133
133
134 def get_perm_or_error(permid):
134 def get_perm_or_error(permid):
135 """
135 """
136 Get permission by id or name or return JsonRPCError if not found
136 Get permission by id or name or return JsonRPCError if not found
137
137
138 :param userid:
138 :param userid:
139 """
139 """
140 perm = PermissionModel().get_permission_by_name(permid)
140 perm = PermissionModel().get_permission_by_name(permid)
141 if perm is None:
141 if perm is None:
142 raise JSONRPCError('permission `%s` does not exist' % (permid))
142 raise JSONRPCError('permission `%s` does not exist' % (permid))
143 return perm
143 return perm
144
144
145
145
146 class ApiController(JSONRPCController):
146 class ApiController(JSONRPCController):
147 """
147 """
148 API Controller
148 API Controller
149
149
150
150
151 Each method needs to have USER as argument this is then based on given
151 Each method needs to have USER as argument this is then based on given
152 API_KEY propagated as instance of user object
152 API_KEY propagated as instance of user object
153
153
154 Preferably this should be first argument also
154 Preferably this should be first argument also
155
155
156
156
157 Each function should also **raise** JSONRPCError for any
157 Each function should also **raise** JSONRPCError for any
158 errors that happens
158 errors that happens
159
159
160 """
160 """
161
161
162 @HasPermissionAllDecorator('hg.admin')
162 @HasPermissionAllDecorator('hg.admin')
163 def pull(self, apiuser, repoid):
163 def pull(self, apiuser, repoid):
164 """
164 """
165 Dispatch pull action on given repo
165 Dispatch pull action on given repo
166
166
167 :param apiuser:
167 :param apiuser:
168 :param repoid:
168 :param repoid:
169 """
169 """
170
170
171 repo = get_repo_or_error(repoid)
171 repo = get_repo_or_error(repoid)
172
172
173 try:
173 try:
174 ScmModel().pull_changes(repo.repo_name,
174 ScmModel().pull_changes(repo.repo_name,
175 self.rhodecode_user.username)
175 self.rhodecode_user.username)
176 return 'Pulled from `%s`' % repo.repo_name
176 return 'Pulled from `%s`' % repo.repo_name
177 except Exception:
177 except Exception:
178 log.error(traceback.format_exc())
178 log.error(traceback.format_exc())
179 raise JSONRPCError(
179 raise JSONRPCError(
180 'Unable to pull changes from `%s`' % repo.repo_name
180 'Unable to pull changes from `%s`' % repo.repo_name
181 )
181 )
182
182
183 @HasPermissionAllDecorator('hg.admin')
183 @HasPermissionAllDecorator('hg.admin')
184 def rescan_repos(self, apiuser, remove_obsolete=Optional(False)):
184 def rescan_repos(self, apiuser, remove_obsolete=Optional(False)):
185 """
185 """
186 Dispatch rescan repositories action. If remove_obsolete is set
186 Dispatch rescan repositories action. If remove_obsolete is set
187 than also delete repos that are in database but not in the filesystem.
187 than also delete repos that are in database but not in the filesystem.
188 aka "clean zombies"
188 aka "clean zombies"
189
189
190 :param apiuser:
190 :param apiuser:
191 :param remove_obsolete:
191 :param remove_obsolete:
192 """
192 """
193
193
194 try:
194 try:
195 rm_obsolete = Optional.extract(remove_obsolete)
195 rm_obsolete = Optional.extract(remove_obsolete)
196 added, removed = repo2db_mapper(ScmModel().repo_scan(),
196 added, removed = repo2db_mapper(ScmModel().repo_scan(),
197 remove_obsolete=rm_obsolete)
197 remove_obsolete=rm_obsolete)
198 return {'added': added, 'removed': removed}
198 return {'added': added, 'removed': removed}
199 except Exception:
199 except Exception:
200 log.error(traceback.format_exc())
200 log.error(traceback.format_exc())
201 raise JSONRPCError(
201 raise JSONRPCError(
202 'Error occurred during rescan repositories action'
202 'Error occurred during rescan repositories action'
203 )
203 )
204
204
205 def invalidate_cache(self, apiuser, repoid):
205 def invalidate_cache(self, apiuser, repoid):
206 """
206 """
207 Dispatch cache invalidation action on given repo
207 Dispatch cache invalidation action on given repo
208
208
209 :param apiuser:
209 :param apiuser:
210 :param repoid:
210 :param repoid:
211 """
211 """
212 repo = get_repo_or_error(repoid)
212 repo = get_repo_or_error(repoid)
213 if HasPermissionAnyApi('hg.admin')(user=apiuser) is False:
213 if HasPermissionAnyApi('hg.admin')(user=apiuser) is False:
214 # check if we have admin permission for this repo !
214 # check if we have admin permission for this repo !
215 if HasRepoPermissionAnyApi('repository.admin',
215 if HasRepoPermissionAnyApi('repository.admin',
216 'repository.write')(user=apiuser,
216 'repository.write')(user=apiuser,
217 repo_name=repo.repo_name) is False:
217 repo_name=repo.repo_name) is False:
218 raise JSONRPCError('repository `%s` does not exist' % (repoid))
218 raise JSONRPCError('repository `%s` does not exist' % (repoid))
219
219
220 try:
220 try:
221 invalidated_keys = ScmModel().mark_for_invalidation(repo.repo_name)
221 invalidated_keys = ScmModel().mark_for_invalidation(repo.repo_name)
222 Session().commit()
222 Session().commit()
223 return ('Cache for repository `%s` was invalidated: '
223 return ('Cache for repository `%s` was invalidated: '
224 'invalidated cache keys: %s' % (repoid, invalidated_keys))
224 'invalidated cache keys: %s' % (repoid, invalidated_keys))
225 except Exception:
225 except Exception:
226 log.error(traceback.format_exc())
226 log.error(traceback.format_exc())
227 raise JSONRPCError(
227 raise JSONRPCError(
228 'Error occurred during cache invalidation action'
228 'Error occurred during cache invalidation action'
229 )
229 )
230
230
231 def lock(self, apiuser, repoid, locked, userid=Optional(OAttr('apiuser'))):
231 def lock(self, apiuser, repoid, locked, userid=Optional(OAttr('apiuser'))):
232 """
232 """
233 Set locking state on particular repository by given user, if
233 Set locking state on particular repository by given user, if
234 this command is runned by non-admin account userid is set to user
234 this command is runned by non-admin account userid is set to user
235 who is calling this method
235 who is calling this method
236
236
237 :param apiuser:
237 :param apiuser:
238 :param repoid:
238 :param repoid:
239 :param userid:
239 :param userid:
240 :param locked:
240 :param locked:
241 """
241 """
242 repo = get_repo_or_error(repoid)
242 repo = get_repo_or_error(repoid)
243 if HasPermissionAnyApi('hg.admin')(user=apiuser):
243 if HasPermissionAnyApi('hg.admin')(user=apiuser):
244 pass
244 pass
245 elif HasRepoPermissionAnyApi('repository.admin',
245 elif HasRepoPermissionAnyApi('repository.admin',
246 'repository.write')(user=apiuser,
246 'repository.write')(user=apiuser,
247 repo_name=repo.repo_name):
247 repo_name=repo.repo_name):
248 #make sure normal user does not pass someone else userid,
248 #make sure normal user does not pass someone else userid,
249 #he is not allowed to do that
249 #he is not allowed to do that
250 if not isinstance(userid, Optional) and userid != apiuser.user_id:
250 if not isinstance(userid, Optional) and userid != apiuser.user_id:
251 raise JSONRPCError(
251 raise JSONRPCError(
252 'userid is not the same as your user'
252 'userid is not the same as your user'
253 )
253 )
254 else:
254 else:
255 raise JSONRPCError('repository `%s` does not exist' % (repoid))
255 raise JSONRPCError('repository `%s` does not exist' % (repoid))
256
256
257 if isinstance(userid, Optional):
257 if isinstance(userid, Optional):
258 userid = apiuser.user_id
258 userid = apiuser.user_id
259 user = get_user_or_error(userid)
259 user = get_user_or_error(userid)
260 locked = bool(locked)
260 locked = bool(locked)
261 try:
261 try:
262 if locked:
262 if locked:
263 Repository.lock(repo, user.user_id)
263 Repository.lock(repo, user.user_id)
264 else:
264 else:
265 Repository.unlock(repo)
265 Repository.unlock(repo)
266
266
267 return ('User `%s` set lock state for repo `%s` to `%s`'
267 return ('User `%s` set lock state for repo `%s` to `%s`'
268 % (user.username, repo.repo_name, locked))
268 % (user.username, repo.repo_name, locked))
269 except Exception:
269 except Exception:
270 log.error(traceback.format_exc())
270 log.error(traceback.format_exc())
271 raise JSONRPCError(
271 raise JSONRPCError(
272 'Error occurred locking repository `%s`' % repo.repo_name
272 'Error occurred locking repository `%s`' % repo.repo_name
273 )
273 )
274
274
275 @HasPermissionAllDecorator('hg.admin')
275 @HasPermissionAllDecorator('hg.admin')
276 def show_ip(self, apiuser, userid):
276 def show_ip(self, apiuser, userid):
277 """
277 """
278 Shows IP address as seen from RhodeCode server, together with all
278 Shows IP address as seen from RhodeCode server, together with all
279 defined IP addresses for given user
279 defined IP addresses for given user
280
280
281 :param apiuser:
281 :param apiuser:
282 :param userid:
282 :param userid:
283 """
283 """
284 user = get_user_or_error(userid)
284 user = get_user_or_error(userid)
285 ips = UserIpMap.query().filter(UserIpMap.user == user).all()
285 ips = UserIpMap.query().filter(UserIpMap.user == user).all()
286 return dict(
286 return dict(
287 ip_addr_server=self.ip_addr,
287 ip_addr_server=self.ip_addr,
288 user_ips=ips
288 user_ips=ips
289 )
289 )
290
290
291 def get_user(self, apiuser, userid=Optional(OAttr('apiuser'))):
291 def get_user(self, apiuser, userid=Optional(OAttr('apiuser'))):
292 """"
292 """"
293 Get a user by username, or userid, if userid is given
293 Get a user by username, or userid, if userid is given
294
294
295 :param apiuser:
295 :param apiuser:
296 :param userid:
296 :param userid:
297 """
297 """
298 if HasPermissionAnyApi('hg.admin')(user=apiuser) is False:
298 if HasPermissionAnyApi('hg.admin')(user=apiuser) is False:
299 #make sure normal user does not pass someone else userid,
299 #make sure normal user does not pass someone else userid,
300 #he is not allowed to do that
300 #he is not allowed to do that
301 if not isinstance(userid, Optional) and userid != apiuser.user_id:
301 if not isinstance(userid, Optional) and userid != apiuser.user_id:
302 raise JSONRPCError(
302 raise JSONRPCError(
303 'userid is not the same as your user'
303 'userid is not the same as your user'
304 )
304 )
305
305
306 if isinstance(userid, Optional):
306 if isinstance(userid, Optional):
307 userid = apiuser.user_id
307 userid = apiuser.user_id
308
308
309 user = get_user_or_error(userid)
309 user = get_user_or_error(userid)
310 data = user.get_api_data()
310 data = user.get_api_data()
311 data['permissions'] = AuthUser(user_id=user.user_id).permissions
311 data['permissions'] = AuthUser(user_id=user.user_id).permissions
312 return data
312 return data
313
313
314 @HasPermissionAllDecorator('hg.admin')
314 @HasPermissionAllDecorator('hg.admin')
315 def get_users(self, apiuser):
315 def get_users(self, apiuser):
316 """"
316 """"
317 Get all users
317 Get all users
318
318
319 :param apiuser:
319 :param apiuser:
320 """
320 """
321
321
322 result = []
322 result = []
323 for user in UserModel().get_all():
323 for user in UserModel().get_all():
324 result.append(user.get_api_data())
324 result.append(user.get_api_data())
325 return result
325 return result
326
326
327 @HasPermissionAllDecorator('hg.admin')
327 @HasPermissionAllDecorator('hg.admin')
328 def create_user(self, apiuser, username, email, password,
328 def create_user(self, apiuser, username, email, password,
329 firstname=Optional(None), lastname=Optional(None),
329 firstname=Optional(None), lastname=Optional(None),
330 active=Optional(True), admin=Optional(False),
330 active=Optional(True), admin=Optional(False),
331 ldap_dn=Optional(None)):
331 ldap_dn=Optional(None)):
332 """
332 """
333 Create new user
333 Create new user
334
334
335 :param apiuser:
335 :param apiuser:
336 :param username:
336 :param username:
337 :param email:
337 :param email:
338 :param password:
338 :param password:
339 :param firstname:
339 :param firstname:
340 :param lastname:
340 :param lastname:
341 :param active:
341 :param active:
342 :param admin:
342 :param admin:
343 :param ldap_dn:
343 :param ldap_dn:
344 """
344 """
345
345
346 if UserModel().get_by_username(username):
346 if UserModel().get_by_username(username):
347 raise JSONRPCError("user `%s` already exist" % username)
347 raise JSONRPCError("user `%s` already exist" % username)
348
348
349 if UserModel().get_by_email(email, case_insensitive=True):
349 if UserModel().get_by_email(email, case_insensitive=True):
350 raise JSONRPCError("email `%s` already exist" % email)
350 raise JSONRPCError("email `%s` already exist" % email)
351
351
352 if Optional.extract(ldap_dn):
352 if Optional.extract(ldap_dn):
353 # generate temporary password if ldap_dn
353 # generate temporary password if ldap_dn
354 password = PasswordGenerator().gen_password(length=8)
354 password = PasswordGenerator().gen_password(length=8)
355
355
356 try:
356 try:
357 user = UserModel().create_or_update(
357 user = UserModel().create_or_update(
358 username=Optional.extract(username),
358 username=Optional.extract(username),
359 password=Optional.extract(password),
359 password=Optional.extract(password),
360 email=Optional.extract(email),
360 email=Optional.extract(email),
361 firstname=Optional.extract(firstname),
361 firstname=Optional.extract(firstname),
362 lastname=Optional.extract(lastname),
362 lastname=Optional.extract(lastname),
363 active=Optional.extract(active),
363 active=Optional.extract(active),
364 admin=Optional.extract(admin),
364 admin=Optional.extract(admin),
365 ldap_dn=Optional.extract(ldap_dn)
365 ldap_dn=Optional.extract(ldap_dn)
366 )
366 )
367 Session().commit()
367 Session().commit()
368 return dict(
368 return dict(
369 msg='created new user `%s`' % username,
369 msg='created new user `%s`' % username,
370 user=user.get_api_data()
370 user=user.get_api_data()
371 )
371 )
372 except Exception:
372 except Exception:
373 log.error(traceback.format_exc())
373 log.error(traceback.format_exc())
374 raise JSONRPCError('failed to create user `%s`' % username)
374 raise JSONRPCError('failed to create user `%s`' % username)
375
375
376 @HasPermissionAllDecorator('hg.admin')
376 @HasPermissionAllDecorator('hg.admin')
377 def update_user(self, apiuser, userid, username=Optional(None),
377 def update_user(self, apiuser, userid, username=Optional(None),
378 email=Optional(None), firstname=Optional(None),
378 email=Optional(None), firstname=Optional(None),
379 lastname=Optional(None), active=Optional(None),
379 lastname=Optional(None), active=Optional(None),
380 admin=Optional(None), ldap_dn=Optional(None),
380 admin=Optional(None), ldap_dn=Optional(None),
381 password=Optional(None)):
381 password=Optional(None)):
382 """
382 """
383 Updates given user
383 Updates given user
384
384
385 :param apiuser:
385 :param apiuser:
386 :param userid:
386 :param userid:
387 :param username:
387 :param username:
388 :param email:
388 :param email:
389 :param firstname:
389 :param firstname:
390 :param lastname:
390 :param lastname:
391 :param active:
391 :param active:
392 :param admin:
392 :param admin:
393 :param ldap_dn:
393 :param ldap_dn:
394 :param password:
394 :param password:
395 """
395 """
396
396
397 user = get_user_or_error(userid)
397 user = get_user_or_error(userid)
398
398
399 # call function and store only updated arguments
399 # call function and store only updated arguments
400 updates = {}
400 updates = {}
401
401
402 def store_update(attr, name):
402 def store_update(attr, name):
403 if not isinstance(attr, Optional):
403 if not isinstance(attr, Optional):
404 updates[name] = attr
404 updates[name] = attr
405
405
406 try:
406 try:
407
407
408 store_update(username, 'username')
408 store_update(username, 'username')
409 store_update(password, 'password')
409 store_update(password, 'password')
410 store_update(email, 'email')
410 store_update(email, 'email')
411 store_update(firstname, 'name')
411 store_update(firstname, 'name')
412 store_update(lastname, 'lastname')
412 store_update(lastname, 'lastname')
413 store_update(active, 'active')
413 store_update(active, 'active')
414 store_update(admin, 'admin')
414 store_update(admin, 'admin')
415 store_update(ldap_dn, 'ldap_dn')
415 store_update(ldap_dn, 'ldap_dn')
416
416
417 user = UserModel().update_user(user, **updates)
417 user = UserModel().update_user(user, **updates)
418 Session().commit()
418 Session().commit()
419 return dict(
419 return dict(
420 msg='updated user ID:%s %s' % (user.user_id, user.username),
420 msg='updated user ID:%s %s' % (user.user_id, user.username),
421 user=user.get_api_data()
421 user=user.get_api_data()
422 )
422 )
423 except Exception:
423 except Exception:
424 log.error(traceback.format_exc())
424 log.error(traceback.format_exc())
425 raise JSONRPCError('failed to update user `%s`' % userid)
425 raise JSONRPCError('failed to update user `%s`' % userid)
426
426
427 @HasPermissionAllDecorator('hg.admin')
427 @HasPermissionAllDecorator('hg.admin')
428 def delete_user(self, apiuser, userid):
428 def delete_user(self, apiuser, userid):
429 """"
429 """"
430 Deletes an user
430 Deletes an user
431
431
432 :param apiuser:
432 :param apiuser:
433 :param userid:
433 :param userid:
434 """
434 """
435 user = get_user_or_error(userid)
435 user = get_user_or_error(userid)
436
436
437 try:
437 try:
438 UserModel().delete(userid)
438 UserModel().delete(userid)
439 Session().commit()
439 Session().commit()
440 return dict(
440 return dict(
441 msg='deleted user ID:%s %s' % (user.user_id, user.username),
441 msg='deleted user ID:%s %s' % (user.user_id, user.username),
442 user=None
442 user=None
443 )
443 )
444 except Exception:
444 except Exception:
445 log.error(traceback.format_exc())
445 log.error(traceback.format_exc())
446 raise JSONRPCError('failed to delete ID:%s %s' % (user.user_id,
446 raise JSONRPCError('failed to delete ID:%s %s' % (user.user_id,
447 user.username))
447 user.username))
448
448
449 @HasPermissionAllDecorator('hg.admin')
449 @HasPermissionAllDecorator('hg.admin')
450 def get_users_group(self, apiuser, usersgroupid):
450 def get_users_group(self, apiuser, usersgroupid):
451 """"
451 """"
452 Get users group by name or id
452 Get user group by name or id
453
453
454 :param apiuser:
454 :param apiuser:
455 :param usersgroupid:
455 :param usersgroupid:
456 """
456 """
457 users_group = get_users_group_or_error(usersgroupid)
457 users_group = get_users_group_or_error(usersgroupid)
458
458
459 data = users_group.get_api_data()
459 data = users_group.get_api_data()
460
460
461 members = []
461 members = []
462 for user in users_group.members:
462 for user in users_group.members:
463 user = user.user
463 user = user.user
464 members.append(user.get_api_data())
464 members.append(user.get_api_data())
465 data['members'] = members
465 data['members'] = members
466 return data
466 return data
467
467
468 @HasPermissionAllDecorator('hg.admin')
468 @HasPermissionAllDecorator('hg.admin')
469 def get_users_groups(self, apiuser):
469 def get_users_groups(self, apiuser):
470 """"
470 """"
471 Get all users groups
471 Get all user groups
472
472
473 :param apiuser:
473 :param apiuser:
474 """
474 """
475
475
476 result = []
476 result = []
477 for users_group in UsersGroupModel().get_all():
477 for users_group in UsersGroupModel().get_all():
478 result.append(users_group.get_api_data())
478 result.append(users_group.get_api_data())
479 return result
479 return result
480
480
481 @HasPermissionAllDecorator('hg.admin')
481 @HasPermissionAllDecorator('hg.admin')
482 def create_users_group(self, apiuser, group_name, active=Optional(True)):
482 def create_users_group(self, apiuser, group_name, active=Optional(True)):
483 """
483 """
484 Creates an new usergroup
484 Creates an new usergroup
485
485
486 :param apiuser:
486 :param apiuser:
487 :param group_name:
487 :param group_name:
488 :param active:
488 :param active:
489 """
489 """
490
490
491 if UsersGroupModel().get_by_name(group_name):
491 if UsersGroupModel().get_by_name(group_name):
492 raise JSONRPCError("users group `%s` already exist" % group_name)
492 raise JSONRPCError("user group `%s` already exist" % group_name)
493
493
494 try:
494 try:
495 active = Optional.extract(active)
495 active = Optional.extract(active)
496 ug = UsersGroupModel().create(name=group_name, active=active)
496 ug = UsersGroupModel().create(name=group_name, active=active)
497 Session().commit()
497 Session().commit()
498 return dict(
498 return dict(
499 msg='created new users group `%s`' % group_name,
499 msg='created new user group `%s`' % group_name,
500 users_group=ug.get_api_data()
500 users_group=ug.get_api_data()
501 )
501 )
502 except Exception:
502 except Exception:
503 log.error(traceback.format_exc())
503 log.error(traceback.format_exc())
504 raise JSONRPCError('failed to create group `%s`' % group_name)
504 raise JSONRPCError('failed to create group `%s`' % group_name)
505
505
506 @HasPermissionAllDecorator('hg.admin')
506 @HasPermissionAllDecorator('hg.admin')
507 def add_user_to_users_group(self, apiuser, usersgroupid, userid):
507 def add_user_to_users_group(self, apiuser, usersgroupid, userid):
508 """"
508 """"
509 Add a user to a users group
509 Add a user to a user group
510
510
511 :param apiuser:
511 :param apiuser:
512 :param usersgroupid:
512 :param usersgroupid:
513 :param userid:
513 :param userid:
514 """
514 """
515 user = get_user_or_error(userid)
515 user = get_user_or_error(userid)
516 users_group = get_users_group_or_error(usersgroupid)
516 users_group = get_users_group_or_error(usersgroupid)
517
517
518 try:
518 try:
519 ugm = UsersGroupModel().add_user_to_group(users_group, user)
519 ugm = UsersGroupModel().add_user_to_group(users_group, user)
520 success = True if ugm != True else False
520 success = True if ugm != True else False
521 msg = 'added member `%s` to users group `%s`' % (
521 msg = 'added member `%s` to user group `%s`' % (
522 user.username, users_group.users_group_name
522 user.username, users_group.users_group_name
523 )
523 )
524 msg = msg if success else 'User is already in that group'
524 msg = msg if success else 'User is already in that group'
525 Session().commit()
525 Session().commit()
526
526
527 return dict(
527 return dict(
528 success=success,
528 success=success,
529 msg=msg
529 msg=msg
530 )
530 )
531 except Exception:
531 except Exception:
532 log.error(traceback.format_exc())
532 log.error(traceback.format_exc())
533 raise JSONRPCError(
533 raise JSONRPCError(
534 'failed to add member to users group `%s`' % (
534 'failed to add member to user group `%s`' % (
535 users_group.users_group_name
535 users_group.users_group_name
536 )
536 )
537 )
537 )
538
538
539 @HasPermissionAllDecorator('hg.admin')
539 @HasPermissionAllDecorator('hg.admin')
540 def remove_user_from_users_group(self, apiuser, usersgroupid, userid):
540 def remove_user_from_users_group(self, apiuser, usersgroupid, userid):
541 """
541 """
542 Remove user from a group
542 Remove user from a group
543
543
544 :param apiuser:
544 :param apiuser:
545 :param usersgroupid:
545 :param usersgroupid:
546 :param userid:
546 :param userid:
547 """
547 """
548 user = get_user_or_error(userid)
548 user = get_user_or_error(userid)
549 users_group = get_users_group_or_error(usersgroupid)
549 users_group = get_users_group_or_error(usersgroupid)
550
550
551 try:
551 try:
552 success = UsersGroupModel().remove_user_from_group(users_group,
552 success = UsersGroupModel().remove_user_from_group(users_group,
553 user)
553 user)
554 msg = 'removed member `%s` from users group `%s`' % (
554 msg = 'removed member `%s` from user group `%s`' % (
555 user.username, users_group.users_group_name
555 user.username, users_group.users_group_name
556 )
556 )
557 msg = msg if success else "User wasn't in group"
557 msg = msg if success else "User wasn't in group"
558 Session().commit()
558 Session().commit()
559 return dict(success=success, msg=msg)
559 return dict(success=success, msg=msg)
560 except Exception:
560 except Exception:
561 log.error(traceback.format_exc())
561 log.error(traceback.format_exc())
562 raise JSONRPCError(
562 raise JSONRPCError(
563 'failed to remove member from users group `%s`' % (
563 'failed to remove member from user group `%s`' % (
564 users_group.users_group_name
564 users_group.users_group_name
565 )
565 )
566 )
566 )
567
567
568 def get_repo(self, apiuser, repoid):
568 def get_repo(self, apiuser, repoid):
569 """"
569 """"
570 Get repository by name
570 Get repository by name
571
571
572 :param apiuser:
572 :param apiuser:
573 :param repoid:
573 :param repoid:
574 """
574 """
575 repo = get_repo_or_error(repoid)
575 repo = get_repo_or_error(repoid)
576
576
577 if HasPermissionAnyApi('hg.admin')(user=apiuser) is False:
577 if HasPermissionAnyApi('hg.admin')(user=apiuser) is False:
578 # check if we have admin permission for this repo !
578 # check if we have admin permission for this repo !
579 if HasRepoPermissionAnyApi('repository.admin')(user=apiuser,
579 if HasRepoPermissionAnyApi('repository.admin')(user=apiuser,
580 repo_name=repo.repo_name) is False:
580 repo_name=repo.repo_name) is False:
581 raise JSONRPCError('repository `%s` does not exist' % (repoid))
581 raise JSONRPCError('repository `%s` does not exist' % (repoid))
582
582
583 members = []
583 members = []
584 followers = []
584 followers = []
585 for user in repo.repo_to_perm:
585 for user in repo.repo_to_perm:
586 perm = user.permission.permission_name
586 perm = user.permission.permission_name
587 user = user.user
587 user = user.user
588 user_data = user.get_api_data()
588 user_data = user.get_api_data()
589 user_data['type'] = "user"
589 user_data['type'] = "user"
590 user_data['permission'] = perm
590 user_data['permission'] = perm
591 members.append(user_data)
591 members.append(user_data)
592
592
593 for users_group in repo.users_group_to_perm:
593 for users_group in repo.users_group_to_perm:
594 perm = users_group.permission.permission_name
594 perm = users_group.permission.permission_name
595 users_group = users_group.users_group
595 users_group = users_group.users_group
596 users_group_data = users_group.get_api_data()
596 users_group_data = users_group.get_api_data()
597 users_group_data['type'] = "users_group"
597 users_group_data['type'] = "users_group"
598 users_group_data['permission'] = perm
598 users_group_data['permission'] = perm
599 members.append(users_group_data)
599 members.append(users_group_data)
600
600
601 for user in repo.followers:
601 for user in repo.followers:
602 followers.append(user.user.get_api_data())
602 followers.append(user.user.get_api_data())
603
603
604 data = repo.get_api_data()
604 data = repo.get_api_data()
605 data['members'] = members
605 data['members'] = members
606 data['followers'] = followers
606 data['followers'] = followers
607 return data
607 return data
608
608
609 def get_repos(self, apiuser):
609 def get_repos(self, apiuser):
610 """"
610 """"
611 Get all repositories
611 Get all repositories
612
612
613 :param apiuser:
613 :param apiuser:
614 """
614 """
615 result = []
615 result = []
616 if HasPermissionAnyApi('hg.admin')(user=apiuser) is False:
616 if HasPermissionAnyApi('hg.admin')(user=apiuser) is False:
617 repos = RepoModel().get_all_user_repos(user=apiuser)
617 repos = RepoModel().get_all_user_repos(user=apiuser)
618 else:
618 else:
619 repos = RepoModel().get_all()
619 repos = RepoModel().get_all()
620
620
621 for repo in repos:
621 for repo in repos:
622 result.append(repo.get_api_data())
622 result.append(repo.get_api_data())
623 return result
623 return result
624
624
625 @HasPermissionAllDecorator('hg.admin')
625 @HasPermissionAllDecorator('hg.admin')
626 def get_repo_nodes(self, apiuser, repoid, revision, root_path,
626 def get_repo_nodes(self, apiuser, repoid, revision, root_path,
627 ret_type='all'):
627 ret_type='all'):
628 """
628 """
629 returns a list of nodes and it's children
629 returns a list of nodes and it's children
630 for a given path at given revision. It's possible to specify ret_type
630 for a given path at given revision. It's possible to specify ret_type
631 to show only files or dirs
631 to show only files or dirs
632
632
633 :param apiuser:
633 :param apiuser:
634 :param repoid: name or id of repository
634 :param repoid: name or id of repository
635 :param revision: revision for which listing should be done
635 :param revision: revision for which listing should be done
636 :param root_path: path from which start displaying
636 :param root_path: path from which start displaying
637 :param ret_type: return type 'all|files|dirs' nodes
637 :param ret_type: return type 'all|files|dirs' nodes
638 """
638 """
639 repo = get_repo_or_error(repoid)
639 repo = get_repo_or_error(repoid)
640 try:
640 try:
641 _d, _f = ScmModel().get_nodes(repo, revision, root_path,
641 _d, _f = ScmModel().get_nodes(repo, revision, root_path,
642 flat=False)
642 flat=False)
643 _map = {
643 _map = {
644 'all': _d + _f,
644 'all': _d + _f,
645 'files': _f,
645 'files': _f,
646 'dirs': _d,
646 'dirs': _d,
647 }
647 }
648 return _map[ret_type]
648 return _map[ret_type]
649 except KeyError:
649 except KeyError:
650 raise JSONRPCError('ret_type must be one of %s' % _map.keys())
650 raise JSONRPCError('ret_type must be one of %s' % _map.keys())
651 except Exception:
651 except Exception:
652 log.error(traceback.format_exc())
652 log.error(traceback.format_exc())
653 raise JSONRPCError(
653 raise JSONRPCError(
654 'failed to get repo: `%s` nodes' % repo.repo_name
654 'failed to get repo: `%s` nodes' % repo.repo_name
655 )
655 )
656
656
657 @HasPermissionAnyDecorator('hg.admin', 'hg.create.repository')
657 @HasPermissionAnyDecorator('hg.admin', 'hg.create.repository')
658 def create_repo(self, apiuser, repo_name, owner=Optional(OAttr('apiuser')),
658 def create_repo(self, apiuser, repo_name, owner=Optional(OAttr('apiuser')),
659 repo_type=Optional('hg'),
659 repo_type=Optional('hg'),
660 description=Optional(''), private=Optional(False),
660 description=Optional(''), private=Optional(False),
661 clone_uri=Optional(None), landing_rev=Optional('tip'),
661 clone_uri=Optional(None), landing_rev=Optional('tip'),
662 enable_statistics=Optional(False),
662 enable_statistics=Optional(False),
663 enable_locking=Optional(False),
663 enable_locking=Optional(False),
664 enable_downloads=Optional(False)):
664 enable_downloads=Optional(False)):
665 """
665 """
666 Create repository, if clone_url is given it makes a remote clone
666 Create repository, if clone_url is given it makes a remote clone
667 if repo_name is within a group name the groups will be created
667 if repo_name is within a group name the groups will be created
668 automatically if they aren't present
668 automatically if they aren't present
669
669
670 :param apiuser:
670 :param apiuser:
671 :param repo_name:
671 :param repo_name:
672 :param onwer:
672 :param onwer:
673 :param repo_type:
673 :param repo_type:
674 :param description:
674 :param description:
675 :param private:
675 :param private:
676 :param clone_uri:
676 :param clone_uri:
677 :param landing_rev:
677 :param landing_rev:
678 """
678 """
679 if HasPermissionAnyApi('hg.admin')(user=apiuser) is False:
679 if HasPermissionAnyApi('hg.admin')(user=apiuser) is False:
680 if not isinstance(owner, Optional):
680 if not isinstance(owner, Optional):
681 #forbid setting owner for non-admins
681 #forbid setting owner for non-admins
682 raise JSONRPCError(
682 raise JSONRPCError(
683 'Only RhodeCode admin can specify `owner` param'
683 'Only RhodeCode admin can specify `owner` param'
684 )
684 )
685 if isinstance(owner, Optional):
685 if isinstance(owner, Optional):
686 owner = apiuser.user_id
686 owner = apiuser.user_id
687
687
688 owner = get_user_or_error(owner)
688 owner = get_user_or_error(owner)
689
689
690 if RepoModel().get_by_repo_name(repo_name):
690 if RepoModel().get_by_repo_name(repo_name):
691 raise JSONRPCError("repo `%s` already exist" % repo_name)
691 raise JSONRPCError("repo `%s` already exist" % repo_name)
692
692
693 defs = RhodeCodeSetting.get_default_repo_settings(strip_prefix=True)
693 defs = RhodeCodeSetting.get_default_repo_settings(strip_prefix=True)
694 if isinstance(private, Optional):
694 if isinstance(private, Optional):
695 private = defs.get('repo_private') or Optional.extract(private)
695 private = defs.get('repo_private') or Optional.extract(private)
696 if isinstance(repo_type, Optional):
696 if isinstance(repo_type, Optional):
697 repo_type = defs.get('repo_type')
697 repo_type = defs.get('repo_type')
698 if isinstance(enable_statistics, Optional):
698 if isinstance(enable_statistics, Optional):
699 enable_statistics = defs.get('repo_enable_statistics')
699 enable_statistics = defs.get('repo_enable_statistics')
700 if isinstance(enable_locking, Optional):
700 if isinstance(enable_locking, Optional):
701 enable_locking = defs.get('repo_enable_locking')
701 enable_locking = defs.get('repo_enable_locking')
702 if isinstance(enable_downloads, Optional):
702 if isinstance(enable_downloads, Optional):
703 enable_downloads = defs.get('repo_enable_downloads')
703 enable_downloads = defs.get('repo_enable_downloads')
704
704
705 clone_uri = Optional.extract(clone_uri)
705 clone_uri = Optional.extract(clone_uri)
706 description = Optional.extract(description)
706 description = Optional.extract(description)
707 landing_rev = Optional.extract(landing_rev)
707 landing_rev = Optional.extract(landing_rev)
708
708
709 try:
709 try:
710 # create structure of groups and return the last group
710 # create structure of groups and return the last group
711 group = map_groups(repo_name)
711 group = map_groups(repo_name)
712
712
713 repo = RepoModel().create_repo(
713 repo = RepoModel().create_repo(
714 repo_name=repo_name,
714 repo_name=repo_name,
715 repo_type=repo_type,
715 repo_type=repo_type,
716 description=description,
716 description=description,
717 owner=owner,
717 owner=owner,
718 private=private,
718 private=private,
719 clone_uri=clone_uri,
719 clone_uri=clone_uri,
720 repos_group=group,
720 repos_group=group,
721 landing_rev=landing_rev,
721 landing_rev=landing_rev,
722 enable_statistics=enable_statistics,
722 enable_statistics=enable_statistics,
723 enable_downloads=enable_downloads,
723 enable_downloads=enable_downloads,
724 enable_locking=enable_locking
724 enable_locking=enable_locking
725 )
725 )
726
726
727 Session().commit()
727 Session().commit()
728 return dict(
728 return dict(
729 msg="Created new repository `%s`" % (repo.repo_name),
729 msg="Created new repository `%s`" % (repo.repo_name),
730 repo=repo.get_api_data()
730 repo=repo.get_api_data()
731 )
731 )
732 except Exception:
732 except Exception:
733 log.error(traceback.format_exc())
733 log.error(traceback.format_exc())
734 raise JSONRPCError('failed to create repository `%s`' % repo_name)
734 raise JSONRPCError('failed to create repository `%s`' % repo_name)
735
735
736 @HasPermissionAnyDecorator('hg.admin', 'hg.fork.repository')
736 @HasPermissionAnyDecorator('hg.admin', 'hg.fork.repository')
737 def fork_repo(self, apiuser, repoid, fork_name, owner=Optional(OAttr('apiuser')),
737 def fork_repo(self, apiuser, repoid, fork_name, owner=Optional(OAttr('apiuser')),
738 description=Optional(''), copy_permissions=Optional(False),
738 description=Optional(''), copy_permissions=Optional(False),
739 private=Optional(False), landing_rev=Optional('tip')):
739 private=Optional(False), landing_rev=Optional('tip')):
740 repo = get_repo_or_error(repoid)
740 repo = get_repo_or_error(repoid)
741 repo_name = repo.repo_name
741 repo_name = repo.repo_name
742
742
743 _repo = RepoModel().get_by_repo_name(fork_name)
743 _repo = RepoModel().get_by_repo_name(fork_name)
744 if _repo:
744 if _repo:
745 type_ = 'fork' if _repo.fork else 'repo'
745 type_ = 'fork' if _repo.fork else 'repo'
746 raise JSONRPCError("%s `%s` already exist" % (type_, fork_name))
746 raise JSONRPCError("%s `%s` already exist" % (type_, fork_name))
747
747
748 if HasPermissionAnyApi('hg.admin')(user=apiuser):
748 if HasPermissionAnyApi('hg.admin')(user=apiuser):
749 pass
749 pass
750 elif HasRepoPermissionAnyApi('repository.admin',
750 elif HasRepoPermissionAnyApi('repository.admin',
751 'repository.write',
751 'repository.write',
752 'repository.read')(user=apiuser,
752 'repository.read')(user=apiuser,
753 repo_name=repo.repo_name):
753 repo_name=repo.repo_name):
754 if not isinstance(owner, Optional):
754 if not isinstance(owner, Optional):
755 #forbid setting owner for non-admins
755 #forbid setting owner for non-admins
756 raise JSONRPCError(
756 raise JSONRPCError(
757 'Only RhodeCode admin can specify `owner` param'
757 'Only RhodeCode admin can specify `owner` param'
758 )
758 )
759 else:
759 else:
760 raise JSONRPCError('repository `%s` does not exist' % (repoid))
760 raise JSONRPCError('repository `%s` does not exist' % (repoid))
761
761
762 if isinstance(owner, Optional):
762 if isinstance(owner, Optional):
763 owner = apiuser.user_id
763 owner = apiuser.user_id
764
764
765 owner = get_user_or_error(owner)
765 owner = get_user_or_error(owner)
766
766
767 try:
767 try:
768 # create structure of groups and return the last group
768 # create structure of groups and return the last group
769 group = map_groups(fork_name)
769 group = map_groups(fork_name)
770
770
771 form_data = dict(
771 form_data = dict(
772 repo_name=fork_name,
772 repo_name=fork_name,
773 repo_name_full=fork_name,
773 repo_name_full=fork_name,
774 repo_group=group,
774 repo_group=group,
775 repo_type=repo.repo_type,
775 repo_type=repo.repo_type,
776 description=Optional.extract(description),
776 description=Optional.extract(description),
777 private=Optional.extract(private),
777 private=Optional.extract(private),
778 copy_permissions=Optional.extract(copy_permissions),
778 copy_permissions=Optional.extract(copy_permissions),
779 landing_rev=Optional.extract(landing_rev),
779 landing_rev=Optional.extract(landing_rev),
780 update_after_clone=False,
780 update_after_clone=False,
781 fork_parent_id=repo.repo_id,
781 fork_parent_id=repo.repo_id,
782 )
782 )
783 RepoModel().create_fork(form_data, cur_user=owner)
783 RepoModel().create_fork(form_data, cur_user=owner)
784 return dict(
784 return dict(
785 msg='Created fork of `%s` as `%s`' % (repo.repo_name,
785 msg='Created fork of `%s` as `%s`' % (repo.repo_name,
786 fork_name),
786 fork_name),
787 success=True # cannot return the repo data here since fork
787 success=True # cannot return the repo data here since fork
788 # cann be done async
788 # cann be done async
789 )
789 )
790 except Exception:
790 except Exception:
791 log.error(traceback.format_exc())
791 log.error(traceback.format_exc())
792 raise JSONRPCError(
792 raise JSONRPCError(
793 'failed to fork repository `%s` as `%s`' % (repo_name,
793 'failed to fork repository `%s` as `%s`' % (repo_name,
794 fork_name)
794 fork_name)
795 )
795 )
796
796
797 def delete_repo(self, apiuser, repoid):
797 def delete_repo(self, apiuser, repoid):
798 """
798 """
799 Deletes a given repository
799 Deletes a given repository
800
800
801 :param apiuser:
801 :param apiuser:
802 :param repoid:
802 :param repoid:
803 """
803 """
804 repo = get_repo_or_error(repoid)
804 repo = get_repo_or_error(repoid)
805
805
806 if HasPermissionAnyApi('hg.admin')(user=apiuser) is False:
806 if HasPermissionAnyApi('hg.admin')(user=apiuser) is False:
807 # check if we have admin permission for this repo !
807 # check if we have admin permission for this repo !
808 if HasRepoPermissionAnyApi('repository.admin')(user=apiuser,
808 if HasRepoPermissionAnyApi('repository.admin')(user=apiuser,
809 repo_name=repo.repo_name) is False:
809 repo_name=repo.repo_name) is False:
810 raise JSONRPCError('repository `%s` does not exist' % (repoid))
810 raise JSONRPCError('repository `%s` does not exist' % (repoid))
811
811
812 try:
812 try:
813 RepoModel().delete(repo)
813 RepoModel().delete(repo)
814 Session().commit()
814 Session().commit()
815 return dict(
815 return dict(
816 msg='Deleted repository `%s`' % repo.repo_name,
816 msg='Deleted repository `%s`' % repo.repo_name,
817 success=True
817 success=True
818 )
818 )
819 except Exception:
819 except Exception:
820 log.error(traceback.format_exc())
820 log.error(traceback.format_exc())
821 raise JSONRPCError(
821 raise JSONRPCError(
822 'failed to delete repository `%s`' % repo.repo_name
822 'failed to delete repository `%s`' % repo.repo_name
823 )
823 )
824
824
825 @HasPermissionAllDecorator('hg.admin')
825 @HasPermissionAllDecorator('hg.admin')
826 def grant_user_permission(self, apiuser, repoid, userid, perm):
826 def grant_user_permission(self, apiuser, repoid, userid, perm):
827 """
827 """
828 Grant permission for user on given repository, or update existing one
828 Grant permission for user on given repository, or update existing one
829 if found
829 if found
830
830
831 :param repoid:
831 :param repoid:
832 :param userid:
832 :param userid:
833 :param perm:
833 :param perm:
834 """
834 """
835 repo = get_repo_or_error(repoid)
835 repo = get_repo_or_error(repoid)
836 user = get_user_or_error(userid)
836 user = get_user_or_error(userid)
837 perm = get_perm_or_error(perm)
837 perm = get_perm_or_error(perm)
838
838
839 try:
839 try:
840
840
841 RepoModel().grant_user_permission(repo=repo, user=user, perm=perm)
841 RepoModel().grant_user_permission(repo=repo, user=user, perm=perm)
842
842
843 Session().commit()
843 Session().commit()
844 return dict(
844 return dict(
845 msg='Granted perm: `%s` for user: `%s` in repo: `%s`' % (
845 msg='Granted perm: `%s` for user: `%s` in repo: `%s`' % (
846 perm.permission_name, user.username, repo.repo_name
846 perm.permission_name, user.username, repo.repo_name
847 ),
847 ),
848 success=True
848 success=True
849 )
849 )
850 except Exception:
850 except Exception:
851 log.error(traceback.format_exc())
851 log.error(traceback.format_exc())
852 raise JSONRPCError(
852 raise JSONRPCError(
853 'failed to edit permission for user: `%s` in repo: `%s`' % (
853 'failed to edit permission for user: `%s` in repo: `%s`' % (
854 userid, repoid
854 userid, repoid
855 )
855 )
856 )
856 )
857
857
858 @HasPermissionAllDecorator('hg.admin')
858 @HasPermissionAllDecorator('hg.admin')
859 def revoke_user_permission(self, apiuser, repoid, userid):
859 def revoke_user_permission(self, apiuser, repoid, userid):
860 """
860 """
861 Revoke permission for user on given repository
861 Revoke permission for user on given repository
862
862
863 :param apiuser:
863 :param apiuser:
864 :param repoid:
864 :param repoid:
865 :param userid:
865 :param userid:
866 """
866 """
867
867
868 repo = get_repo_or_error(repoid)
868 repo = get_repo_or_error(repoid)
869 user = get_user_or_error(userid)
869 user = get_user_or_error(userid)
870 try:
870 try:
871
871
872 RepoModel().revoke_user_permission(repo=repo, user=user)
872 RepoModel().revoke_user_permission(repo=repo, user=user)
873
873
874 Session().commit()
874 Session().commit()
875 return dict(
875 return dict(
876 msg='Revoked perm for user: `%s` in repo: `%s`' % (
876 msg='Revoked perm for user: `%s` in repo: `%s`' % (
877 user.username, repo.repo_name
877 user.username, repo.repo_name
878 ),
878 ),
879 success=True
879 success=True
880 )
880 )
881 except Exception:
881 except Exception:
882 log.error(traceback.format_exc())
882 log.error(traceback.format_exc())
883 raise JSONRPCError(
883 raise JSONRPCError(
884 'failed to edit permission for user: `%s` in repo: `%s`' % (
884 'failed to edit permission for user: `%s` in repo: `%s`' % (
885 userid, repoid
885 userid, repoid
886 )
886 )
887 )
887 )
888
888
889 @HasPermissionAllDecorator('hg.admin')
889 @HasPermissionAllDecorator('hg.admin')
890 def grant_users_group_permission(self, apiuser, repoid, usersgroupid,
890 def grant_users_group_permission(self, apiuser, repoid, usersgroupid,
891 perm):
891 perm):
892 """
892 """
893 Grant permission for users group on given repository, or update
893 Grant permission for user group on given repository, or update
894 existing one if found
894 existing one if found
895
895
896 :param apiuser:
896 :param apiuser:
897 :param repoid:
897 :param repoid:
898 :param usersgroupid:
898 :param usersgroupid:
899 :param perm:
899 :param perm:
900 """
900 """
901 repo = get_repo_or_error(repoid)
901 repo = get_repo_or_error(repoid)
902 perm = get_perm_or_error(perm)
902 perm = get_perm_or_error(perm)
903 users_group = get_users_group_or_error(usersgroupid)
903 users_group = get_users_group_or_error(usersgroupid)
904
904
905 try:
905 try:
906 RepoModel().grant_users_group_permission(repo=repo,
906 RepoModel().grant_users_group_permission(repo=repo,
907 group_name=users_group,
907 group_name=users_group,
908 perm=perm)
908 perm=perm)
909
909
910 Session().commit()
910 Session().commit()
911 return dict(
911 return dict(
912 msg='Granted perm: `%s` for users group: `%s` in '
912 msg='Granted perm: `%s` for user group: `%s` in '
913 'repo: `%s`' % (
913 'repo: `%s`' % (
914 perm.permission_name, users_group.users_group_name,
914 perm.permission_name, users_group.users_group_name,
915 repo.repo_name
915 repo.repo_name
916 ),
916 ),
917 success=True
917 success=True
918 )
918 )
919 except Exception:
919 except Exception:
920 log.error(traceback.format_exc())
920 log.error(traceback.format_exc())
921 raise JSONRPCError(
921 raise JSONRPCError(
922 'failed to edit permission for users group: `%s` in '
922 'failed to edit permission for user group: `%s` in '
923 'repo: `%s`' % (
923 'repo: `%s`' % (
924 usersgroupid, repo.repo_name
924 usersgroupid, repo.repo_name
925 )
925 )
926 )
926 )
927
927
928 @HasPermissionAllDecorator('hg.admin')
928 @HasPermissionAllDecorator('hg.admin')
929 def revoke_users_group_permission(self, apiuser, repoid, usersgroupid):
929 def revoke_users_group_permission(self, apiuser, repoid, usersgroupid):
930 """
930 """
931 Revoke permission for users group on given repository
931 Revoke permission for user group on given repository
932
932
933 :param apiuser:
933 :param apiuser:
934 :param repoid:
934 :param repoid:
935 :param usersgroupid:
935 :param usersgroupid:
936 """
936 """
937 repo = get_repo_or_error(repoid)
937 repo = get_repo_or_error(repoid)
938 users_group = get_users_group_or_error(usersgroupid)
938 users_group = get_users_group_or_error(usersgroupid)
939
939
940 try:
940 try:
941 RepoModel().revoke_users_group_permission(repo=repo,
941 RepoModel().revoke_users_group_permission(repo=repo,
942 group_name=users_group)
942 group_name=users_group)
943
943
944 Session().commit()
944 Session().commit()
945 return dict(
945 return dict(
946 msg='Revoked perm for users group: `%s` in repo: `%s`' % (
946 msg='Revoked perm for user group: `%s` in repo: `%s`' % (
947 users_group.users_group_name, repo.repo_name
947 users_group.users_group_name, repo.repo_name
948 ),
948 ),
949 success=True
949 success=True
950 )
950 )
951 except Exception:
951 except Exception:
952 log.error(traceback.format_exc())
952 log.error(traceback.format_exc())
953 raise JSONRPCError(
953 raise JSONRPCError(
954 'failed to edit permission for users group: `%s` in '
954 'failed to edit permission for user group: `%s` in '
955 'repo: `%s`' % (
955 'repo: `%s`' % (
956 users_group.users_group_name, repo.repo_name
956 users_group.users_group_name, repo.repo_name
957 )
957 )
958 )
958 )
@@ -1,426 +1,426 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.model.user_group
3 rhodecode.model.user_group
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 users groups model for RhodeCode
6 repo group model for RhodeCode
7
7
8 :created_on: Jan 25, 2011
8 :created_on: Jan 25, 2011
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2011-2012 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2011-2012 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software: you can redistribute it and/or modify
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
16 # (at your option) any later version.
17 #
17 #
18 # This program is distributed in the hope that it will be useful,
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # GNU General Public License for more details.
21 # GNU General Public License for more details.
22 #
22 #
23 # You should have received a copy of the GNU General Public License
23 # You should have received a copy of the GNU General Public License
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25
25
26 import os
26 import os
27 import logging
27 import logging
28 import traceback
28 import traceback
29 import shutil
29 import shutil
30 import datetime
30 import datetime
31
31
32 from rhodecode.lib.utils2 import LazyProperty
32 from rhodecode.lib.utils2 import LazyProperty
33
33
34 from rhodecode.model import BaseModel
34 from rhodecode.model import BaseModel
35 from rhodecode.model.db import RepoGroup, RhodeCodeUi, UserRepoGroupToPerm, \
35 from rhodecode.model.db import RepoGroup, RhodeCodeUi, UserRepoGroupToPerm, \
36 User, Permission, UsersGroupRepoGroupToPerm, UsersGroup, Repository
36 User, Permission, UsersGroupRepoGroupToPerm, UsersGroup, Repository
37
37
38 log = logging.getLogger(__name__)
38 log = logging.getLogger(__name__)
39
39
40
40
41 class ReposGroupModel(BaseModel):
41 class ReposGroupModel(BaseModel):
42
42
43 cls = RepoGroup
43 cls = RepoGroup
44
44
45 def __get_users_group(self, users_group):
45 def __get_users_group(self, users_group):
46 return self._get_instance(UsersGroup, users_group,
46 return self._get_instance(UsersGroup, users_group,
47 callback=UsersGroup.get_by_group_name)
47 callback=UsersGroup.get_by_group_name)
48
48
49 def _get_repos_group(self, repos_group):
49 def _get_repos_group(self, repos_group):
50 return self._get_instance(RepoGroup, repos_group,
50 return self._get_instance(RepoGroup, repos_group,
51 callback=RepoGroup.get_by_group_name)
51 callback=RepoGroup.get_by_group_name)
52
52
53 @LazyProperty
53 @LazyProperty
54 def repos_path(self):
54 def repos_path(self):
55 """
55 """
56 Get's the repositories root path from database
56 Get's the repositories root path from database
57 """
57 """
58
58
59 q = RhodeCodeUi.get_by_key('/')
59 q = RhodeCodeUi.get_by_key('/')
60 return q.ui_value
60 return q.ui_value
61
61
62 def _create_default_perms(self, new_group):
62 def _create_default_perms(self, new_group):
63 # create default permission
63 # create default permission
64 repo_group_to_perm = UserRepoGroupToPerm()
64 repo_group_to_perm = UserRepoGroupToPerm()
65 default_perm = 'group.read'
65 default_perm = 'group.read'
66 for p in User.get_by_username('default').user_perms:
66 for p in User.get_by_username('default').user_perms:
67 if p.permission.permission_name.startswith('group.'):
67 if p.permission.permission_name.startswith('group.'):
68 default_perm = p.permission.permission_name
68 default_perm = p.permission.permission_name
69 break
69 break
70
70
71 repo_group_to_perm.permission_id = self.sa.query(Permission)\
71 repo_group_to_perm.permission_id = self.sa.query(Permission)\
72 .filter(Permission.permission_name == default_perm)\
72 .filter(Permission.permission_name == default_perm)\
73 .one().permission_id
73 .one().permission_id
74
74
75 repo_group_to_perm.group = new_group
75 repo_group_to_perm.group = new_group
76 repo_group_to_perm.user_id = User.get_by_username('default').user_id
76 repo_group_to_perm.user_id = User.get_by_username('default').user_id
77
77
78 self.sa.add(repo_group_to_perm)
78 self.sa.add(repo_group_to_perm)
79
79
80 def __create_group(self, group_name):
80 def __create_group(self, group_name):
81 """
81 """
82 makes repositories group on filesystem
82 makes repositories group on filesystem
83
83
84 :param repo_name:
84 :param repo_name:
85 :param parent_id:
85 :param parent_id:
86 """
86 """
87
87
88 create_path = os.path.join(self.repos_path, group_name)
88 create_path = os.path.join(self.repos_path, group_name)
89 log.debug('creating new group in %s' % create_path)
89 log.debug('creating new group in %s' % create_path)
90
90
91 if os.path.isdir(create_path):
91 if os.path.isdir(create_path):
92 raise Exception('That directory already exists !')
92 raise Exception('That directory already exists !')
93
93
94 os.makedirs(create_path)
94 os.makedirs(create_path)
95
95
96 def __rename_group(self, old, new):
96 def __rename_group(self, old, new):
97 """
97 """
98 Renames a group on filesystem
98 Renames a group on filesystem
99
99
100 :param group_name:
100 :param group_name:
101 """
101 """
102
102
103 if old == new:
103 if old == new:
104 log.debug('skipping group rename')
104 log.debug('skipping group rename')
105 return
105 return
106
106
107 log.debug('renaming repos group from %s to %s' % (old, new))
107 log.debug('renaming repos group from %s to %s' % (old, new))
108
108
109 old_path = os.path.join(self.repos_path, old)
109 old_path = os.path.join(self.repos_path, old)
110 new_path = os.path.join(self.repos_path, new)
110 new_path = os.path.join(self.repos_path, new)
111
111
112 log.debug('renaming repos paths from %s to %s' % (old_path, new_path))
112 log.debug('renaming repos paths from %s to %s' % (old_path, new_path))
113
113
114 if os.path.isdir(new_path):
114 if os.path.isdir(new_path):
115 raise Exception('Was trying to rename to already '
115 raise Exception('Was trying to rename to already '
116 'existing dir %s' % new_path)
116 'existing dir %s' % new_path)
117 shutil.move(old_path, new_path)
117 shutil.move(old_path, new_path)
118
118
119 def __delete_group(self, group, force_delete=False):
119 def __delete_group(self, group, force_delete=False):
120 """
120 """
121 Deletes a group from a filesystem
121 Deletes a group from a filesystem
122
122
123 :param group: instance of group from database
123 :param group: instance of group from database
124 :param force_delete: use shutil rmtree to remove all objects
124 :param force_delete: use shutil rmtree to remove all objects
125 """
125 """
126 paths = group.full_path.split(RepoGroup.url_sep())
126 paths = group.full_path.split(RepoGroup.url_sep())
127 paths = os.sep.join(paths)
127 paths = os.sep.join(paths)
128
128
129 rm_path = os.path.join(self.repos_path, paths)
129 rm_path = os.path.join(self.repos_path, paths)
130 log.info("Removing group %s" % (rm_path))
130 log.info("Removing group %s" % (rm_path))
131 # delete only if that path really exists
131 # delete only if that path really exists
132 if os.path.isdir(rm_path):
132 if os.path.isdir(rm_path):
133 if force_delete:
133 if force_delete:
134 shutil.rmtree(rm_path)
134 shutil.rmtree(rm_path)
135 else:
135 else:
136 #archive that group`
136 #archive that group`
137 _now = datetime.datetime.now()
137 _now = datetime.datetime.now()
138 _ms = str(_now.microsecond).rjust(6, '0')
138 _ms = str(_now.microsecond).rjust(6, '0')
139 _d = 'rm__%s_GROUP_%s' % (_now.strftime('%Y%m%d_%H%M%S_' + _ms),
139 _d = 'rm__%s_GROUP_%s' % (_now.strftime('%Y%m%d_%H%M%S_' + _ms),
140 group.name)
140 group.name)
141 shutil.move(rm_path, os.path.join(self.repos_path, _d))
141 shutil.move(rm_path, os.path.join(self.repos_path, _d))
142
142
143 def create(self, group_name, group_description, owner, parent=None, just_db=False):
143 def create(self, group_name, group_description, owner, parent=None, just_db=False):
144 try:
144 try:
145 new_repos_group = RepoGroup()
145 new_repos_group = RepoGroup()
146 new_repos_group.group_description = group_description or group_name
146 new_repos_group.group_description = group_description or group_name
147 new_repos_group.parent_group = self._get_repos_group(parent)
147 new_repos_group.parent_group = self._get_repos_group(parent)
148 new_repos_group.group_name = new_repos_group.get_new_name(group_name)
148 new_repos_group.group_name = new_repos_group.get_new_name(group_name)
149
149
150 self.sa.add(new_repos_group)
150 self.sa.add(new_repos_group)
151 self._create_default_perms(new_repos_group)
151 self._create_default_perms(new_repos_group)
152
152
153 #create an ADMIN permission for owner, later owner should go into
153 #create an ADMIN permission for owner, later owner should go into
154 #the owner field of groups
154 #the owner field of groups
155 self.grant_user_permission(repos_group=new_repos_group,
155 self.grant_user_permission(repos_group=new_repos_group,
156 user=owner, perm='group.admin')
156 user=owner, perm='group.admin')
157
157
158 if not just_db:
158 if not just_db:
159 # we need to flush here, in order to check if database won't
159 # we need to flush here, in order to check if database won't
160 # throw any exceptions, create filesystem dirs at the very end
160 # throw any exceptions, create filesystem dirs at the very end
161 self.sa.flush()
161 self.sa.flush()
162 self.__create_group(new_repos_group.group_name)
162 self.__create_group(new_repos_group.group_name)
163
163
164 return new_repos_group
164 return new_repos_group
165 except:
165 except:
166 log.error(traceback.format_exc())
166 log.error(traceback.format_exc())
167 raise
167 raise
168
168
169 def _update_permissions(self, repos_group, perms_new=None,
169 def _update_permissions(self, repos_group, perms_new=None,
170 perms_updates=None, recursive=False):
170 perms_updates=None, recursive=False):
171 from rhodecode.model.repo import RepoModel
171 from rhodecode.model.repo import RepoModel
172 if not perms_new:
172 if not perms_new:
173 perms_new = []
173 perms_new = []
174 if not perms_updates:
174 if not perms_updates:
175 perms_updates = []
175 perms_updates = []
176
176
177 def _set_perm_user(obj, user, perm):
177 def _set_perm_user(obj, user, perm):
178 if isinstance(obj, RepoGroup):
178 if isinstance(obj, RepoGroup):
179 ReposGroupModel().grant_user_permission(
179 ReposGroupModel().grant_user_permission(
180 repos_group=obj, user=user, perm=perm
180 repos_group=obj, user=user, perm=perm
181 )
181 )
182 elif isinstance(obj, Repository):
182 elif isinstance(obj, Repository):
183 #we do this ONLY IF repository is non-private
183 #we do this ONLY IF repository is non-private
184 if obj.private:
184 if obj.private:
185 return
185 return
186
186
187 # we set group permission but we have to switch to repo
187 # we set group permission but we have to switch to repo
188 # permission
188 # permission
189 perm = perm.replace('group.', 'repository.')
189 perm = perm.replace('group.', 'repository.')
190 RepoModel().grant_user_permission(
190 RepoModel().grant_user_permission(
191 repo=obj, user=user, perm=perm
191 repo=obj, user=user, perm=perm
192 )
192 )
193
193
194 def _set_perm_group(obj, users_group, perm):
194 def _set_perm_group(obj, users_group, perm):
195 if isinstance(obj, RepoGroup):
195 if isinstance(obj, RepoGroup):
196 ReposGroupModel().grant_users_group_permission(
196 ReposGroupModel().grant_users_group_permission(
197 repos_group=obj, group_name=users_group, perm=perm
197 repos_group=obj, group_name=users_group, perm=perm
198 )
198 )
199 elif isinstance(obj, Repository):
199 elif isinstance(obj, Repository):
200 # we set group permission but we have to switch to repo
200 # we set group permission but we have to switch to repo
201 # permission
201 # permission
202 perm = perm.replace('group.', 'repository.')
202 perm = perm.replace('group.', 'repository.')
203 RepoModel().grant_users_group_permission(
203 RepoModel().grant_users_group_permission(
204 repo=obj, group_name=users_group, perm=perm
204 repo=obj, group_name=users_group, perm=perm
205 )
205 )
206 updates = []
206 updates = []
207 log.debug('Now updating permissions for %s in recursive mode:%s'
207 log.debug('Now updating permissions for %s in recursive mode:%s'
208 % (repos_group, recursive))
208 % (repos_group, recursive))
209
209
210 for obj in repos_group.recursive_groups_and_repos():
210 for obj in repos_group.recursive_groups_and_repos():
211 #obj is an instance of a group or repositories in that group
211 #obj is an instance of a group or repositories in that group
212 if not recursive:
212 if not recursive:
213 obj = repos_group
213 obj = repos_group
214
214
215 # update permissions
215 # update permissions
216 for member, perm, member_type in perms_updates:
216 for member, perm, member_type in perms_updates:
217 ## set for user
217 ## set for user
218 if member_type == 'user':
218 if member_type == 'user':
219 # this updates also current one if found
219 # this updates also current one if found
220 _set_perm_user(obj, user=member, perm=perm)
220 _set_perm_user(obj, user=member, perm=perm)
221 ## set for users group
221 ## set for user group
222 else:
222 else:
223 _set_perm_group(obj, users_group=member, perm=perm)
223 _set_perm_group(obj, users_group=member, perm=perm)
224 # set new permissions
224 # set new permissions
225 for member, perm, member_type in perms_new:
225 for member, perm, member_type in perms_new:
226 if member_type == 'user':
226 if member_type == 'user':
227 _set_perm_user(obj, user=member, perm=perm)
227 _set_perm_user(obj, user=member, perm=perm)
228 else:
228 else:
229 _set_perm_group(obj, users_group=member, perm=perm)
229 _set_perm_group(obj, users_group=member, perm=perm)
230 updates.append(obj)
230 updates.append(obj)
231 #if it's not recursive call
231 #if it's not recursive call
232 # break the loop and don't proceed with other changes
232 # break the loop and don't proceed with other changes
233 if not recursive:
233 if not recursive:
234 break
234 break
235 return updates
235 return updates
236
236
237 def update(self, repos_group, form_data):
237 def update(self, repos_group, form_data):
238
238
239 try:
239 try:
240 repos_group = self._get_repos_group(repos_group)
240 repos_group = self._get_repos_group(repos_group)
241 recursive = form_data['recursive']
241 recursive = form_data['recursive']
242 # iterate over all members(if in recursive mode) of this groups and
242 # iterate over all members(if in recursive mode) of this groups and
243 # set the permissions !
243 # set the permissions !
244 # this can be potentially heavy operation
244 # this can be potentially heavy operation
245 self._update_permissions(repos_group, form_data['perms_new'],
245 self._update_permissions(repos_group, form_data['perms_new'],
246 form_data['perms_updates'], recursive)
246 form_data['perms_updates'], recursive)
247
247
248 old_path = repos_group.full_path
248 old_path = repos_group.full_path
249
249
250 # change properties
250 # change properties
251 repos_group.group_description = form_data['group_description']
251 repos_group.group_description = form_data['group_description']
252 repos_group.parent_group = RepoGroup.get(form_data['group_parent_id'])
252 repos_group.parent_group = RepoGroup.get(form_data['group_parent_id'])
253 repos_group.group_parent_id = form_data['group_parent_id']
253 repos_group.group_parent_id = form_data['group_parent_id']
254 repos_group.enable_locking = form_data['enable_locking']
254 repos_group.enable_locking = form_data['enable_locking']
255 repos_group.group_name = repos_group.get_new_name(form_data['group_name'])
255 repos_group.group_name = repos_group.get_new_name(form_data['group_name'])
256 new_path = repos_group.full_path
256 new_path = repos_group.full_path
257
257
258 self.sa.add(repos_group)
258 self.sa.add(repos_group)
259
259
260 # iterate over all members of this groups and set the locking !
260 # iterate over all members of this groups and set the locking !
261 # this can be potentially heavy operation
261 # this can be potentially heavy operation
262 for obj in repos_group.recursive_groups_and_repos():
262 for obj in repos_group.recursive_groups_and_repos():
263 #set the value from it's parent
263 #set the value from it's parent
264 obj.enable_locking = repos_group.enable_locking
264 obj.enable_locking = repos_group.enable_locking
265 self.sa.add(obj)
265 self.sa.add(obj)
266
266
267 # we need to get all repositories from this new group and
267 # we need to get all repositories from this new group and
268 # rename them accordingly to new group path
268 # rename them accordingly to new group path
269 for r in repos_group.repositories:
269 for r in repos_group.repositories:
270 r.repo_name = r.get_new_name(r.just_name)
270 r.repo_name = r.get_new_name(r.just_name)
271 self.sa.add(r)
271 self.sa.add(r)
272
272
273 self.__rename_group(old_path, new_path)
273 self.__rename_group(old_path, new_path)
274
274
275 return repos_group
275 return repos_group
276 except:
276 except:
277 log.error(traceback.format_exc())
277 log.error(traceback.format_exc())
278 raise
278 raise
279
279
280 def delete(self, repos_group, force_delete=False):
280 def delete(self, repos_group, force_delete=False):
281 repos_group = self._get_repos_group(repos_group)
281 repos_group = self._get_repos_group(repos_group)
282 try:
282 try:
283 self.sa.delete(repos_group)
283 self.sa.delete(repos_group)
284 self.__delete_group(repos_group, force_delete)
284 self.__delete_group(repos_group, force_delete)
285 except:
285 except:
286 log.error('Error removing repos_group %s' % repos_group)
286 log.error('Error removing repos_group %s' % repos_group)
287 raise
287 raise
288
288
289 def delete_permission(self, repos_group, obj, obj_type, recursive):
289 def delete_permission(self, repos_group, obj, obj_type, recursive):
290 """
290 """
291 Revokes permission for repos_group for given obj(user or users_group),
291 Revokes permission for repos_group for given obj(user or users_group),
292 obj_type can be user or users group
292 obj_type can be user or user group
293
293
294 :param repos_group:
294 :param repos_group:
295 :param obj: user or users group id
295 :param obj: user or user group id
296 :param obj_type: user or users group type
296 :param obj_type: user or user group type
297 :param recursive: recurse to all children of group
297 :param recursive: recurse to all children of group
298 """
298 """
299 from rhodecode.model.repo import RepoModel
299 from rhodecode.model.repo import RepoModel
300 repos_group = self._get_repos_group(repos_group)
300 repos_group = self._get_repos_group(repos_group)
301
301
302 for el in repos_group.recursive_groups_and_repos():
302 for el in repos_group.recursive_groups_and_repos():
303 if not recursive:
303 if not recursive:
304 # if we don't recurse set the permission on only the top level
304 # if we don't recurse set the permission on only the top level
305 # object
305 # object
306 el = repos_group
306 el = repos_group
307
307
308 if isinstance(el, RepoGroup):
308 if isinstance(el, RepoGroup):
309 if obj_type == 'user':
309 if obj_type == 'user':
310 ReposGroupModel().revoke_user_permission(el, user=obj)
310 ReposGroupModel().revoke_user_permission(el, user=obj)
311 elif obj_type == 'users_group':
311 elif obj_type == 'users_group':
312 ReposGroupModel().revoke_users_group_permission(el, group_name=obj)
312 ReposGroupModel().revoke_users_group_permission(el, group_name=obj)
313 else:
313 else:
314 raise Exception('undefined object type %s' % obj_type)
314 raise Exception('undefined object type %s' % obj_type)
315 elif isinstance(el, Repository):
315 elif isinstance(el, Repository):
316 if obj_type == 'user':
316 if obj_type == 'user':
317 RepoModel().revoke_user_permission(el, user=obj)
317 RepoModel().revoke_user_permission(el, user=obj)
318 elif obj_type == 'users_group':
318 elif obj_type == 'users_group':
319 RepoModel().revoke_users_group_permission(el, group_name=obj)
319 RepoModel().revoke_users_group_permission(el, group_name=obj)
320 else:
320 else:
321 raise Exception('undefined object type %s' % obj_type)
321 raise Exception('undefined object type %s' % obj_type)
322
322
323 #if it's not recursive call
323 #if it's not recursive call
324 # break the loop and don't proceed with other changes
324 # break the loop and don't proceed with other changes
325 if not recursive:
325 if not recursive:
326 break
326 break
327
327
328 def grant_user_permission(self, repos_group, user, perm):
328 def grant_user_permission(self, repos_group, user, perm):
329 """
329 """
330 Grant permission for user on given repositories group, or update
330 Grant permission for user on given repositories group, or update
331 existing one if found
331 existing one if found
332
332
333 :param repos_group: Instance of ReposGroup, repositories_group_id,
333 :param repos_group: Instance of ReposGroup, repositories_group_id,
334 or repositories_group name
334 or repositories_group name
335 :param user: Instance of User, user_id or username
335 :param user: Instance of User, user_id or username
336 :param perm: Instance of Permission, or permission_name
336 :param perm: Instance of Permission, or permission_name
337 """
337 """
338
338
339 repos_group = self._get_repos_group(repos_group)
339 repos_group = self._get_repos_group(repos_group)
340 user = self._get_user(user)
340 user = self._get_user(user)
341 permission = self._get_perm(perm)
341 permission = self._get_perm(perm)
342
342
343 # check if we have that permission already
343 # check if we have that permission already
344 obj = self.sa.query(UserRepoGroupToPerm)\
344 obj = self.sa.query(UserRepoGroupToPerm)\
345 .filter(UserRepoGroupToPerm.user == user)\
345 .filter(UserRepoGroupToPerm.user == user)\
346 .filter(UserRepoGroupToPerm.group == repos_group)\
346 .filter(UserRepoGroupToPerm.group == repos_group)\
347 .scalar()
347 .scalar()
348 if obj is None:
348 if obj is None:
349 # create new !
349 # create new !
350 obj = UserRepoGroupToPerm()
350 obj = UserRepoGroupToPerm()
351 obj.group = repos_group
351 obj.group = repos_group
352 obj.user = user
352 obj.user = user
353 obj.permission = permission
353 obj.permission = permission
354 self.sa.add(obj)
354 self.sa.add(obj)
355 log.debug('Granted perm %s to %s on %s' % (perm, user, repos_group))
355 log.debug('Granted perm %s to %s on %s' % (perm, user, repos_group))
356
356
357 def revoke_user_permission(self, repos_group, user):
357 def revoke_user_permission(self, repos_group, user):
358 """
358 """
359 Revoke permission for user on given repositories group
359 Revoke permission for user on given repositories group
360
360
361 :param repos_group: Instance of ReposGroup, repositories_group_id,
361 :param repos_group: Instance of ReposGroup, repositories_group_id,
362 or repositories_group name
362 or repositories_group name
363 :param user: Instance of User, user_id or username
363 :param user: Instance of User, user_id or username
364 """
364 """
365
365
366 repos_group = self._get_repos_group(repos_group)
366 repos_group = self._get_repos_group(repos_group)
367 user = self._get_user(user)
367 user = self._get_user(user)
368
368
369 obj = self.sa.query(UserRepoGroupToPerm)\
369 obj = self.sa.query(UserRepoGroupToPerm)\
370 .filter(UserRepoGroupToPerm.user == user)\
370 .filter(UserRepoGroupToPerm.user == user)\
371 .filter(UserRepoGroupToPerm.group == repos_group)\
371 .filter(UserRepoGroupToPerm.group == repos_group)\
372 .scalar()
372 .scalar()
373 if obj:
373 if obj:
374 self.sa.delete(obj)
374 self.sa.delete(obj)
375 log.debug('Revoked perm on %s on %s' % (repos_group, user))
375 log.debug('Revoked perm on %s on %s' % (repos_group, user))
376
376
377 def grant_users_group_permission(self, repos_group, group_name, perm):
377 def grant_users_group_permission(self, repos_group, group_name, perm):
378 """
378 """
379 Grant permission for users group on given repositories group, or update
379 Grant permission for user group on given repositories group, or update
380 existing one if found
380 existing one if found
381
381
382 :param repos_group: Instance of ReposGroup, repositories_group_id,
382 :param repos_group: Instance of ReposGroup, repositories_group_id,
383 or repositories_group name
383 or repositories_group name
384 :param group_name: Instance of UserGroup, users_group_id,
384 :param group_name: Instance of UserGroup, users_group_id,
385 or users group name
385 or user group name
386 :param perm: Instance of Permission, or permission_name
386 :param perm: Instance of Permission, or permission_name
387 """
387 """
388 repos_group = self._get_repos_group(repos_group)
388 repos_group = self._get_repos_group(repos_group)
389 group_name = self.__get_users_group(group_name)
389 group_name = self.__get_users_group(group_name)
390 permission = self._get_perm(perm)
390 permission = self._get_perm(perm)
391
391
392 # check if we have that permission already
392 # check if we have that permission already
393 obj = self.sa.query(UsersGroupRepoGroupToPerm)\
393 obj = self.sa.query(UsersGroupRepoGroupToPerm)\
394 .filter(UsersGroupRepoGroupToPerm.group == repos_group)\
394 .filter(UsersGroupRepoGroupToPerm.group == repos_group)\
395 .filter(UsersGroupRepoGroupToPerm.users_group == group_name)\
395 .filter(UsersGroupRepoGroupToPerm.users_group == group_name)\
396 .scalar()
396 .scalar()
397
397
398 if obj is None:
398 if obj is None:
399 # create new
399 # create new
400 obj = UsersGroupRepoGroupToPerm()
400 obj = UsersGroupRepoGroupToPerm()
401
401
402 obj.group = repos_group
402 obj.group = repos_group
403 obj.users_group = group_name
403 obj.users_group = group_name
404 obj.permission = permission
404 obj.permission = permission
405 self.sa.add(obj)
405 self.sa.add(obj)
406 log.debug('Granted perm %s to %s on %s' % (perm, group_name, repos_group))
406 log.debug('Granted perm %s to %s on %s' % (perm, group_name, repos_group))
407
407
408 def revoke_users_group_permission(self, repos_group, group_name):
408 def revoke_users_group_permission(self, repos_group, group_name):
409 """
409 """
410 Revoke permission for users group on given repositories group
410 Revoke permission for user group on given repositories group
411
411
412 :param repos_group: Instance of ReposGroup, repositories_group_id,
412 :param repos_group: Instance of ReposGroup, repositories_group_id,
413 or repositories_group name
413 or repositories_group name
414 :param group_name: Instance of UserGroup, users_group_id,
414 :param group_name: Instance of UserGroup, users_group_id,
415 or users group name
415 or user group name
416 """
416 """
417 repos_group = self._get_repos_group(repos_group)
417 repos_group = self._get_repos_group(repos_group)
418 group_name = self.__get_users_group(group_name)
418 group_name = self.__get_users_group(group_name)
419
419
420 obj = self.sa.query(UsersGroupRepoGroupToPerm)\
420 obj = self.sa.query(UsersGroupRepoGroupToPerm)\
421 .filter(UsersGroupRepoGroupToPerm.group == repos_group)\
421 .filter(UsersGroupRepoGroupToPerm.group == repos_group)\
422 .filter(UsersGroupRepoGroupToPerm.users_group == group_name)\
422 .filter(UsersGroupRepoGroupToPerm.users_group == group_name)\
423 .scalar()
423 .scalar()
424 if obj:
424 if obj:
425 self.sa.delete(obj)
425 self.sa.delete(obj)
426 log.debug('Revoked perm to %s on %s' % (repos_group, group_name))
426 log.debug('Revoked perm to %s on %s' % (repos_group, group_name))
@@ -1,763 +1,763 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.model.user
3 rhodecode.model.user
4 ~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~
5
5
6 users model for RhodeCode
6 users model for RhodeCode
7
7
8 :created_on: Apr 9, 2010
8 :created_on: Apr 9, 2010
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software: you can redistribute it and/or modify
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
16 # (at your option) any later version.
17 #
17 #
18 # This program is distributed in the hope that it will be useful,
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # GNU General Public License for more details.
21 # GNU General Public License for more details.
22 #
22 #
23 # You should have received a copy of the GNU General Public License
23 # You should have received a copy of the GNU General Public License
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25
25
26 import logging
26 import logging
27 import traceback
27 import traceback
28 import itertools
28 import itertools
29 import collections
29 import collections
30 from pylons import url
30 from pylons import url
31 from pylons.i18n.translation import _
31 from pylons.i18n.translation import _
32
32
33 from sqlalchemy.exc import DatabaseError
33 from sqlalchemy.exc import DatabaseError
34 from sqlalchemy.orm import joinedload
34 from sqlalchemy.orm import joinedload
35
35
36 from rhodecode.lib.utils2 import safe_unicode, generate_api_key
36 from rhodecode.lib.utils2 import safe_unicode, generate_api_key
37 from rhodecode.lib.caching_query import FromCache
37 from rhodecode.lib.caching_query import FromCache
38 from rhodecode.model import BaseModel
38 from rhodecode.model import BaseModel
39 from rhodecode.model.db import User, UserRepoToPerm, Repository, Permission, \
39 from rhodecode.model.db import User, UserRepoToPerm, Repository, Permission, \
40 UserToPerm, UsersGroupRepoToPerm, UsersGroupToPerm, UsersGroupMember, \
40 UserToPerm, UsersGroupRepoToPerm, UsersGroupToPerm, UsersGroupMember, \
41 Notification, RepoGroup, UserRepoGroupToPerm, UsersGroupRepoGroupToPerm, \
41 Notification, RepoGroup, UserRepoGroupToPerm, UsersGroupRepoGroupToPerm, \
42 UserEmailMap, UserIpMap
42 UserEmailMap, UserIpMap
43 from rhodecode.lib.exceptions import DefaultUserException, \
43 from rhodecode.lib.exceptions import DefaultUserException, \
44 UserOwnsReposException
44 UserOwnsReposException
45 from rhodecode.model.meta import Session
45 from rhodecode.model.meta import Session
46
46
47
47
48 log = logging.getLogger(__name__)
48 log = logging.getLogger(__name__)
49
49
50 PERM_WEIGHTS = Permission.PERM_WEIGHTS
50 PERM_WEIGHTS = Permission.PERM_WEIGHTS
51
51
52
52
53 class UserModel(BaseModel):
53 class UserModel(BaseModel):
54 cls = User
54 cls = User
55
55
56 def get(self, user_id, cache=False):
56 def get(self, user_id, cache=False):
57 user = self.sa.query(User)
57 user = self.sa.query(User)
58 if cache:
58 if cache:
59 user = user.options(FromCache("sql_cache_short",
59 user = user.options(FromCache("sql_cache_short",
60 "get_user_%s" % user_id))
60 "get_user_%s" % user_id))
61 return user.get(user_id)
61 return user.get(user_id)
62
62
63 def get_user(self, user):
63 def get_user(self, user):
64 return self._get_user(user)
64 return self._get_user(user)
65
65
66 def get_by_username(self, username, cache=False, case_insensitive=False):
66 def get_by_username(self, username, cache=False, case_insensitive=False):
67
67
68 if case_insensitive:
68 if case_insensitive:
69 user = self.sa.query(User).filter(User.username.ilike(username))
69 user = self.sa.query(User).filter(User.username.ilike(username))
70 else:
70 else:
71 user = self.sa.query(User)\
71 user = self.sa.query(User)\
72 .filter(User.username == username)
72 .filter(User.username == username)
73 if cache:
73 if cache:
74 user = user.options(FromCache("sql_cache_short",
74 user = user.options(FromCache("sql_cache_short",
75 "get_user_%s" % username))
75 "get_user_%s" % username))
76 return user.scalar()
76 return user.scalar()
77
77
78 def get_by_email(self, email, cache=False, case_insensitive=False):
78 def get_by_email(self, email, cache=False, case_insensitive=False):
79 return User.get_by_email(email, case_insensitive, cache)
79 return User.get_by_email(email, case_insensitive, cache)
80
80
81 def get_by_api_key(self, api_key, cache=False):
81 def get_by_api_key(self, api_key, cache=False):
82 return User.get_by_api_key(api_key, cache)
82 return User.get_by_api_key(api_key, cache)
83
83
84 def create(self, form_data):
84 def create(self, form_data):
85 from rhodecode.lib.auth import get_crypt_password
85 from rhodecode.lib.auth import get_crypt_password
86 try:
86 try:
87 new_user = User()
87 new_user = User()
88 for k, v in form_data.items():
88 for k, v in form_data.items():
89 if k == 'password':
89 if k == 'password':
90 v = get_crypt_password(v)
90 v = get_crypt_password(v)
91 if k == 'firstname':
91 if k == 'firstname':
92 k = 'name'
92 k = 'name'
93 setattr(new_user, k, v)
93 setattr(new_user, k, v)
94
94
95 new_user.api_key = generate_api_key(form_data['username'])
95 new_user.api_key = generate_api_key(form_data['username'])
96 self.sa.add(new_user)
96 self.sa.add(new_user)
97 return new_user
97 return new_user
98 except:
98 except:
99 log.error(traceback.format_exc())
99 log.error(traceback.format_exc())
100 raise
100 raise
101
101
102 def create_or_update(self, username, password, email, firstname='',
102 def create_or_update(self, username, password, email, firstname='',
103 lastname='', active=True, admin=False, ldap_dn=None):
103 lastname='', active=True, admin=False, ldap_dn=None):
104 """
104 """
105 Creates a new instance if not found, or updates current one
105 Creates a new instance if not found, or updates current one
106
106
107 :param username:
107 :param username:
108 :param password:
108 :param password:
109 :param email:
109 :param email:
110 :param active:
110 :param active:
111 :param firstname:
111 :param firstname:
112 :param lastname:
112 :param lastname:
113 :param active:
113 :param active:
114 :param admin:
114 :param admin:
115 :param ldap_dn:
115 :param ldap_dn:
116 """
116 """
117
117
118 from rhodecode.lib.auth import get_crypt_password
118 from rhodecode.lib.auth import get_crypt_password
119
119
120 log.debug('Checking for %s account in RhodeCode database' % username)
120 log.debug('Checking for %s account in RhodeCode database' % username)
121 user = User.get_by_username(username, case_insensitive=True)
121 user = User.get_by_username(username, case_insensitive=True)
122 if user is None:
122 if user is None:
123 log.debug('creating new user %s' % username)
123 log.debug('creating new user %s' % username)
124 new_user = User()
124 new_user = User()
125 edit = False
125 edit = False
126 else:
126 else:
127 log.debug('updating user %s' % username)
127 log.debug('updating user %s' % username)
128 new_user = user
128 new_user = user
129 edit = True
129 edit = True
130
130
131 try:
131 try:
132 new_user.username = username
132 new_user.username = username
133 new_user.admin = admin
133 new_user.admin = admin
134 # set password only if creating an user or password is changed
134 # set password only if creating an user or password is changed
135 if edit is False or user.password != password:
135 if edit is False or user.password != password:
136 new_user.password = get_crypt_password(password)
136 new_user.password = get_crypt_password(password)
137 new_user.api_key = generate_api_key(username)
137 new_user.api_key = generate_api_key(username)
138 new_user.email = email
138 new_user.email = email
139 new_user.active = active
139 new_user.active = active
140 new_user.ldap_dn = safe_unicode(ldap_dn) if ldap_dn else None
140 new_user.ldap_dn = safe_unicode(ldap_dn) if ldap_dn else None
141 new_user.name = firstname
141 new_user.name = firstname
142 new_user.lastname = lastname
142 new_user.lastname = lastname
143 self.sa.add(new_user)
143 self.sa.add(new_user)
144 return new_user
144 return new_user
145 except (DatabaseError,):
145 except (DatabaseError,):
146 log.error(traceback.format_exc())
146 log.error(traceback.format_exc())
147 raise
147 raise
148
148
149 def create_for_container_auth(self, username, attrs):
149 def create_for_container_auth(self, username, attrs):
150 """
150 """
151 Creates the given user if it's not already in the database
151 Creates the given user if it's not already in the database
152
152
153 :param username:
153 :param username:
154 :param attrs:
154 :param attrs:
155 """
155 """
156 if self.get_by_username(username, case_insensitive=True) is None:
156 if self.get_by_username(username, case_insensitive=True) is None:
157
157
158 # autogenerate email for container account without one
158 # autogenerate email for container account without one
159 generate_email = lambda usr: '%s@container_auth.account' % usr
159 generate_email = lambda usr: '%s@container_auth.account' % usr
160
160
161 try:
161 try:
162 new_user = User()
162 new_user = User()
163 new_user.username = username
163 new_user.username = username
164 new_user.password = None
164 new_user.password = None
165 new_user.api_key = generate_api_key(username)
165 new_user.api_key = generate_api_key(username)
166 new_user.email = attrs['email']
166 new_user.email = attrs['email']
167 new_user.active = attrs.get('active', True)
167 new_user.active = attrs.get('active', True)
168 new_user.name = attrs['name'] or generate_email(username)
168 new_user.name = attrs['name'] or generate_email(username)
169 new_user.lastname = attrs['lastname']
169 new_user.lastname = attrs['lastname']
170
170
171 self.sa.add(new_user)
171 self.sa.add(new_user)
172 return new_user
172 return new_user
173 except (DatabaseError,):
173 except (DatabaseError,):
174 log.error(traceback.format_exc())
174 log.error(traceback.format_exc())
175 self.sa.rollback()
175 self.sa.rollback()
176 raise
176 raise
177 log.debug('User %s already exists. Skipping creation of account'
177 log.debug('User %s already exists. Skipping creation of account'
178 ' for container auth.', username)
178 ' for container auth.', username)
179 return None
179 return None
180
180
181 def create_ldap(self, username, password, user_dn, attrs):
181 def create_ldap(self, username, password, user_dn, attrs):
182 """
182 """
183 Checks if user is in database, if not creates this user marked
183 Checks if user is in database, if not creates this user marked
184 as ldap user
184 as ldap user
185
185
186 :param username:
186 :param username:
187 :param password:
187 :param password:
188 :param user_dn:
188 :param user_dn:
189 :param attrs:
189 :param attrs:
190 """
190 """
191 from rhodecode.lib.auth import get_crypt_password
191 from rhodecode.lib.auth import get_crypt_password
192 log.debug('Checking for such ldap account in RhodeCode database')
192 log.debug('Checking for such ldap account in RhodeCode database')
193 if self.get_by_username(username, case_insensitive=True) is None:
193 if self.get_by_username(username, case_insensitive=True) is None:
194
194
195 # autogenerate email for ldap account without one
195 # autogenerate email for ldap account without one
196 generate_email = lambda usr: '%s@ldap.account' % usr
196 generate_email = lambda usr: '%s@ldap.account' % usr
197
197
198 try:
198 try:
199 new_user = User()
199 new_user = User()
200 username = username.lower()
200 username = username.lower()
201 # add ldap account always lowercase
201 # add ldap account always lowercase
202 new_user.username = username
202 new_user.username = username
203 new_user.password = get_crypt_password(password)
203 new_user.password = get_crypt_password(password)
204 new_user.api_key = generate_api_key(username)
204 new_user.api_key = generate_api_key(username)
205 new_user.email = attrs['email'] or generate_email(username)
205 new_user.email = attrs['email'] or generate_email(username)
206 new_user.active = attrs.get('active', True)
206 new_user.active = attrs.get('active', True)
207 new_user.ldap_dn = safe_unicode(user_dn)
207 new_user.ldap_dn = safe_unicode(user_dn)
208 new_user.name = attrs['name']
208 new_user.name = attrs['name']
209 new_user.lastname = attrs['lastname']
209 new_user.lastname = attrs['lastname']
210
210
211 self.sa.add(new_user)
211 self.sa.add(new_user)
212 return new_user
212 return new_user
213 except (DatabaseError,):
213 except (DatabaseError,):
214 log.error(traceback.format_exc())
214 log.error(traceback.format_exc())
215 self.sa.rollback()
215 self.sa.rollback()
216 raise
216 raise
217 log.debug('this %s user exists skipping creation of ldap account',
217 log.debug('this %s user exists skipping creation of ldap account',
218 username)
218 username)
219 return None
219 return None
220
220
221 def create_registration(self, form_data):
221 def create_registration(self, form_data):
222 from rhodecode.model.notification import NotificationModel
222 from rhodecode.model.notification import NotificationModel
223
223
224 try:
224 try:
225 form_data['admin'] = False
225 form_data['admin'] = False
226 new_user = self.create(form_data)
226 new_user = self.create(form_data)
227
227
228 self.sa.add(new_user)
228 self.sa.add(new_user)
229 self.sa.flush()
229 self.sa.flush()
230
230
231 # notification to admins
231 # notification to admins
232 subject = _('new user registration')
232 subject = _('new user registration')
233 body = ('New user registration\n'
233 body = ('New user registration\n'
234 '---------------------\n'
234 '---------------------\n'
235 '- Username: %s\n'
235 '- Username: %s\n'
236 '- Full Name: %s\n'
236 '- Full Name: %s\n'
237 '- Email: %s\n')
237 '- Email: %s\n')
238 body = body % (new_user.username, new_user.full_name,
238 body = body % (new_user.username, new_user.full_name,
239 new_user.email)
239 new_user.email)
240 edit_url = url('edit_user', id=new_user.user_id, qualified=True)
240 edit_url = url('edit_user', id=new_user.user_id, qualified=True)
241 kw = {'registered_user_url': edit_url}
241 kw = {'registered_user_url': edit_url}
242 NotificationModel().create(created_by=new_user, subject=subject,
242 NotificationModel().create(created_by=new_user, subject=subject,
243 body=body, recipients=None,
243 body=body, recipients=None,
244 type_=Notification.TYPE_REGISTRATION,
244 type_=Notification.TYPE_REGISTRATION,
245 email_kwargs=kw)
245 email_kwargs=kw)
246
246
247 except:
247 except:
248 log.error(traceback.format_exc())
248 log.error(traceback.format_exc())
249 raise
249 raise
250
250
251 def update(self, user_id, form_data, skip_attrs=[]):
251 def update(self, user_id, form_data, skip_attrs=[]):
252 from rhodecode.lib.auth import get_crypt_password
252 from rhodecode.lib.auth import get_crypt_password
253 try:
253 try:
254 user = self.get(user_id, cache=False)
254 user = self.get(user_id, cache=False)
255 if user.username == 'default':
255 if user.username == 'default':
256 raise DefaultUserException(
256 raise DefaultUserException(
257 _("You can't Edit this user since it's"
257 _("You can't Edit this user since it's"
258 " crucial for entire application"))
258 " crucial for entire application"))
259
259
260 for k, v in form_data.items():
260 for k, v in form_data.items():
261 if k in skip_attrs:
261 if k in skip_attrs:
262 continue
262 continue
263 if k == 'new_password' and v:
263 if k == 'new_password' and v:
264 user.password = get_crypt_password(v)
264 user.password = get_crypt_password(v)
265 user.api_key = generate_api_key(user.username)
265 user.api_key = generate_api_key(user.username)
266 else:
266 else:
267 if k == 'firstname':
267 if k == 'firstname':
268 k = 'name'
268 k = 'name'
269 setattr(user, k, v)
269 setattr(user, k, v)
270 self.sa.add(user)
270 self.sa.add(user)
271 except:
271 except:
272 log.error(traceback.format_exc())
272 log.error(traceback.format_exc())
273 raise
273 raise
274
274
275 def update_user(self, user, **kwargs):
275 def update_user(self, user, **kwargs):
276 from rhodecode.lib.auth import get_crypt_password
276 from rhodecode.lib.auth import get_crypt_password
277 try:
277 try:
278 user = self._get_user(user)
278 user = self._get_user(user)
279 if user.username == 'default':
279 if user.username == 'default':
280 raise DefaultUserException(
280 raise DefaultUserException(
281 _("You can't Edit this user since it's"
281 _("You can't Edit this user since it's"
282 " crucial for entire application")
282 " crucial for entire application")
283 )
283 )
284
284
285 for k, v in kwargs.items():
285 for k, v in kwargs.items():
286 if k == 'password' and v:
286 if k == 'password' and v:
287 v = get_crypt_password(v)
287 v = get_crypt_password(v)
288 user.api_key = generate_api_key(user.username)
288 user.api_key = generate_api_key(user.username)
289
289
290 setattr(user, k, v)
290 setattr(user, k, v)
291 self.sa.add(user)
291 self.sa.add(user)
292 return user
292 return user
293 except:
293 except:
294 log.error(traceback.format_exc())
294 log.error(traceback.format_exc())
295 raise
295 raise
296
296
297 def delete(self, user):
297 def delete(self, user):
298 user = self._get_user(user)
298 user = self._get_user(user)
299
299
300 try:
300 try:
301 if user.username == 'default':
301 if user.username == 'default':
302 raise DefaultUserException(
302 raise DefaultUserException(
303 _(u"You can't remove this user since it's"
303 _(u"You can't remove this user since it's"
304 " crucial for entire application")
304 " crucial for entire application")
305 )
305 )
306 if user.repositories:
306 if user.repositories:
307 repos = [x.repo_name for x in user.repositories]
307 repos = [x.repo_name for x in user.repositories]
308 raise UserOwnsReposException(
308 raise UserOwnsReposException(
309 _(u'user "%s" still owns %s repositories and cannot be '
309 _(u'user "%s" still owns %s repositories and cannot be '
310 'removed. Switch owners or remove those repositories. %s')
310 'removed. Switch owners or remove those repositories. %s')
311 % (user.username, len(repos), ', '.join(repos))
311 % (user.username, len(repos), ', '.join(repos))
312 )
312 )
313 self.sa.delete(user)
313 self.sa.delete(user)
314 except:
314 except:
315 log.error(traceback.format_exc())
315 log.error(traceback.format_exc())
316 raise
316 raise
317
317
318 def reset_password_link(self, data):
318 def reset_password_link(self, data):
319 from rhodecode.lib.celerylib import tasks, run_task
319 from rhodecode.lib.celerylib import tasks, run_task
320 from rhodecode.model.notification import EmailNotificationModel
320 from rhodecode.model.notification import EmailNotificationModel
321 user_email = data['email']
321 user_email = data['email']
322 try:
322 try:
323 user = User.get_by_email(user_email)
323 user = User.get_by_email(user_email)
324 if user:
324 if user:
325 log.debug('password reset user found %s' % user)
325 log.debug('password reset user found %s' % user)
326 link = url('reset_password_confirmation', key=user.api_key,
326 link = url('reset_password_confirmation', key=user.api_key,
327 qualified=True)
327 qualified=True)
328 reg_type = EmailNotificationModel.TYPE_PASSWORD_RESET
328 reg_type = EmailNotificationModel.TYPE_PASSWORD_RESET
329 body = EmailNotificationModel().get_email_tmpl(reg_type,
329 body = EmailNotificationModel().get_email_tmpl(reg_type,
330 **{'user': user.short_contact,
330 **{'user': user.short_contact,
331 'reset_url': link})
331 'reset_url': link})
332 log.debug('sending email')
332 log.debug('sending email')
333 run_task(tasks.send_email, user_email,
333 run_task(tasks.send_email, user_email,
334 _("password reset link"), body, body)
334 _("password reset link"), body, body)
335 log.info('send new password mail to %s' % user_email)
335 log.info('send new password mail to %s' % user_email)
336 else:
336 else:
337 log.debug("password reset email %s not found" % user_email)
337 log.debug("password reset email %s not found" % user_email)
338 except:
338 except:
339 log.error(traceback.format_exc())
339 log.error(traceback.format_exc())
340 return False
340 return False
341
341
342 return True
342 return True
343
343
344 def reset_password(self, data):
344 def reset_password(self, data):
345 from rhodecode.lib.celerylib import tasks, run_task
345 from rhodecode.lib.celerylib import tasks, run_task
346 from rhodecode.lib import auth
346 from rhodecode.lib import auth
347 user_email = data['email']
347 user_email = data['email']
348 try:
348 try:
349 try:
349 try:
350 user = User.get_by_email(user_email)
350 user = User.get_by_email(user_email)
351 new_passwd = auth.PasswordGenerator().gen_password(8,
351 new_passwd = auth.PasswordGenerator().gen_password(8,
352 auth.PasswordGenerator.ALPHABETS_BIG_SMALL)
352 auth.PasswordGenerator.ALPHABETS_BIG_SMALL)
353 if user:
353 if user:
354 user.password = auth.get_crypt_password(new_passwd)
354 user.password = auth.get_crypt_password(new_passwd)
355 user.api_key = auth.generate_api_key(user.username)
355 user.api_key = auth.generate_api_key(user.username)
356 Session().add(user)
356 Session().add(user)
357 Session().commit()
357 Session().commit()
358 log.info('change password for %s' % user_email)
358 log.info('change password for %s' % user_email)
359 if new_passwd is None:
359 if new_passwd is None:
360 raise Exception('unable to generate new password')
360 raise Exception('unable to generate new password')
361 except:
361 except:
362 log.error(traceback.format_exc())
362 log.error(traceback.format_exc())
363 Session().rollback()
363 Session().rollback()
364
364
365 run_task(tasks.send_email, user_email,
365 run_task(tasks.send_email, user_email,
366 _('Your new password'),
366 _('Your new password'),
367 _('Your new RhodeCode password:%s') % (new_passwd))
367 _('Your new RhodeCode password:%s') % (new_passwd))
368 log.info('send new password mail to %s' % user_email)
368 log.info('send new password mail to %s' % user_email)
369
369
370 except:
370 except:
371 log.error('Failed to update user password')
371 log.error('Failed to update user password')
372 log.error(traceback.format_exc())
372 log.error(traceback.format_exc())
373
373
374 return True
374 return True
375
375
376 def fill_data(self, auth_user, user_id=None, api_key=None):
376 def fill_data(self, auth_user, user_id=None, api_key=None):
377 """
377 """
378 Fetches auth_user by user_id,or api_key if present.
378 Fetches auth_user by user_id,or api_key if present.
379 Fills auth_user attributes with those taken from database.
379 Fills auth_user attributes with those taken from database.
380 Additionally set's is_authenitated if lookup fails
380 Additionally set's is_authenitated if lookup fails
381 present in database
381 present in database
382
382
383 :param auth_user: instance of user to set attributes
383 :param auth_user: instance of user to set attributes
384 :param user_id: user id to fetch by
384 :param user_id: user id to fetch by
385 :param api_key: api key to fetch by
385 :param api_key: api key to fetch by
386 """
386 """
387 if user_id is None and api_key is None:
387 if user_id is None and api_key is None:
388 raise Exception('You need to pass user_id or api_key')
388 raise Exception('You need to pass user_id or api_key')
389
389
390 try:
390 try:
391 if api_key:
391 if api_key:
392 dbuser = self.get_by_api_key(api_key)
392 dbuser = self.get_by_api_key(api_key)
393 else:
393 else:
394 dbuser = self.get(user_id)
394 dbuser = self.get(user_id)
395
395
396 if dbuser is not None and dbuser.active:
396 if dbuser is not None and dbuser.active:
397 log.debug('filling %s data' % dbuser)
397 log.debug('filling %s data' % dbuser)
398 for k, v in dbuser.get_dict().items():
398 for k, v in dbuser.get_dict().items():
399 setattr(auth_user, k, v)
399 setattr(auth_user, k, v)
400 else:
400 else:
401 return False
401 return False
402
402
403 except:
403 except:
404 log.error(traceback.format_exc())
404 log.error(traceback.format_exc())
405 auth_user.is_authenticated = False
405 auth_user.is_authenticated = False
406 return False
406 return False
407
407
408 return True
408 return True
409
409
410 def fill_perms(self, user, explicit=True, algo='higherwin'):
410 def fill_perms(self, user, explicit=True, algo='higherwin'):
411 """
411 """
412 Fills user permission attribute with permissions taken from database
412 Fills user permission attribute with permissions taken from database
413 works for permissions given for repositories, and for permissions that
413 works for permissions given for repositories, and for permissions that
414 are granted to groups
414 are granted to groups
415
415
416 :param user: user instance to fill his perms
416 :param user: user instance to fill his perms
417 :param explicit: In case there are permissions both for user and a group
417 :param explicit: In case there are permissions both for user and a group
418 that user is part of, explicit flag will defiine if user will
418 that user is part of, explicit flag will defiine if user will
419 explicitly override permissions from group, if it's False it will
419 explicitly override permissions from group, if it's False it will
420 make decision based on the algo
420 make decision based on the algo
421 :param algo: algorithm to decide what permission should be choose if
421 :param algo: algorithm to decide what permission should be choose if
422 it's multiple defined, eg user in two different groups. It also
422 it's multiple defined, eg user in two different groups. It also
423 decides if explicit flag is turned off how to specify the permission
423 decides if explicit flag is turned off how to specify the permission
424 for case when user is in a group + have defined separate permission
424 for case when user is in a group + have defined separate permission
425 """
425 """
426 RK = 'repositories'
426 RK = 'repositories'
427 GK = 'repositories_groups'
427 GK = 'repositories_groups'
428 GLOBAL = 'global'
428 GLOBAL = 'global'
429 user.permissions[RK] = {}
429 user.permissions[RK] = {}
430 user.permissions[GK] = {}
430 user.permissions[GK] = {}
431 user.permissions[GLOBAL] = set()
431 user.permissions[GLOBAL] = set()
432
432
433 def _choose_perm(new_perm, cur_perm):
433 def _choose_perm(new_perm, cur_perm):
434 new_perm_val = PERM_WEIGHTS[new_perm]
434 new_perm_val = PERM_WEIGHTS[new_perm]
435 cur_perm_val = PERM_WEIGHTS[cur_perm]
435 cur_perm_val = PERM_WEIGHTS[cur_perm]
436 if algo == 'higherwin':
436 if algo == 'higherwin':
437 if new_perm_val > cur_perm_val:
437 if new_perm_val > cur_perm_val:
438 return new_perm
438 return new_perm
439 return cur_perm
439 return cur_perm
440 elif algo == 'lowerwin':
440 elif algo == 'lowerwin':
441 if new_perm_val < cur_perm_val:
441 if new_perm_val < cur_perm_val:
442 return new_perm
442 return new_perm
443 return cur_perm
443 return cur_perm
444
444
445 #======================================================================
445 #======================================================================
446 # fetch default permissions
446 # fetch default permissions
447 #======================================================================
447 #======================================================================
448 default_user = User.get_by_username('default', cache=True)
448 default_user = User.get_by_username('default', cache=True)
449 default_user_id = default_user.user_id
449 default_user_id = default_user.user_id
450
450
451 default_repo_perms = Permission.get_default_perms(default_user_id)
451 default_repo_perms = Permission.get_default_perms(default_user_id)
452 default_repo_groups_perms = Permission.get_default_group_perms(default_user_id)
452 default_repo_groups_perms = Permission.get_default_group_perms(default_user_id)
453
453
454 if user.is_admin:
454 if user.is_admin:
455 #==================================================================
455 #==================================================================
456 # admin user have all default rights for repositories
456 # admin user have all default rights for repositories
457 # and groups set to admin
457 # and groups set to admin
458 #==================================================================
458 #==================================================================
459 user.permissions[GLOBAL].add('hg.admin')
459 user.permissions[GLOBAL].add('hg.admin')
460
460
461 # repositories
461 # repositories
462 for perm in default_repo_perms:
462 for perm in default_repo_perms:
463 r_k = perm.UserRepoToPerm.repository.repo_name
463 r_k = perm.UserRepoToPerm.repository.repo_name
464 p = 'repository.admin'
464 p = 'repository.admin'
465 user.permissions[RK][r_k] = p
465 user.permissions[RK][r_k] = p
466
466
467 # repositories groups
467 # repository groups
468 for perm in default_repo_groups_perms:
468 for perm in default_repo_groups_perms:
469 rg_k = perm.UserRepoGroupToPerm.group.group_name
469 rg_k = perm.UserRepoGroupToPerm.group.group_name
470 p = 'group.admin'
470 p = 'group.admin'
471 user.permissions[GK][rg_k] = p
471 user.permissions[GK][rg_k] = p
472 return user
472 return user
473
473
474 #==================================================================
474 #==================================================================
475 # SET DEFAULTS GLOBAL, REPOS, REPOS GROUPS
475 # SET DEFAULTS GLOBAL, REPOS, REPOS GROUPS
476 #==================================================================
476 #==================================================================
477 uid = user.user_id
477 uid = user.user_id
478
478
479 # default global permissions taken fron the default user
479 # default global permissions taken fron the default user
480 default_global_perms = self.sa.query(UserToPerm)\
480 default_global_perms = self.sa.query(UserToPerm)\
481 .filter(UserToPerm.user_id == default_user_id)
481 .filter(UserToPerm.user_id == default_user_id)
482
482
483 for perm in default_global_perms:
483 for perm in default_global_perms:
484 user.permissions[GLOBAL].add(perm.permission.permission_name)
484 user.permissions[GLOBAL].add(perm.permission.permission_name)
485
485
486 # defaults for repositories, taken from default user
486 # defaults for repositories, taken from default user
487 for perm in default_repo_perms:
487 for perm in default_repo_perms:
488 r_k = perm.UserRepoToPerm.repository.repo_name
488 r_k = perm.UserRepoToPerm.repository.repo_name
489 if perm.Repository.private and not (perm.Repository.user_id == uid):
489 if perm.Repository.private and not (perm.Repository.user_id == uid):
490 # disable defaults for private repos,
490 # disable defaults for private repos,
491 p = 'repository.none'
491 p = 'repository.none'
492 elif perm.Repository.user_id == uid:
492 elif perm.Repository.user_id == uid:
493 # set admin if owner
493 # set admin if owner
494 p = 'repository.admin'
494 p = 'repository.admin'
495 else:
495 else:
496 p = perm.Permission.permission_name
496 p = perm.Permission.permission_name
497
497
498 user.permissions[RK][r_k] = p
498 user.permissions[RK][r_k] = p
499
499
500 # defaults for repositories groups taken from default user permission
500 # defaults for repository groups taken from default user permission
501 # on given group
501 # on given group
502 for perm in default_repo_groups_perms:
502 for perm in default_repo_groups_perms:
503 rg_k = perm.UserRepoGroupToPerm.group.group_name
503 rg_k = perm.UserRepoGroupToPerm.group.group_name
504 p = perm.Permission.permission_name
504 p = perm.Permission.permission_name
505 user.permissions[GK][rg_k] = p
505 user.permissions[GK][rg_k] = p
506
506
507 #======================================================================
507 #======================================================================
508 # !! OVERRIDE GLOBALS !! with user permissions if any found
508 # !! OVERRIDE GLOBALS !! with user permissions if any found
509 #======================================================================
509 #======================================================================
510 # those can be configured from groups or users explicitly
510 # those can be configured from groups or users explicitly
511 _configurable = set(['hg.fork.none', 'hg.fork.repository',
511 _configurable = set(['hg.fork.none', 'hg.fork.repository',
512 'hg.create.none', 'hg.create.repository'])
512 'hg.create.none', 'hg.create.repository'])
513
513
514 # USER GROUPS comes first
514 # USER GROUPS comes first
515 # users group global permissions
515 # users group global permissions
516 user_perms_from_users_groups = self.sa.query(UsersGroupToPerm)\
516 user_perms_from_users_groups = self.sa.query(UsersGroupToPerm)\
517 .options(joinedload(UsersGroupToPerm.permission))\
517 .options(joinedload(UsersGroupToPerm.permission))\
518 .join((UsersGroupMember, UsersGroupToPerm.users_group_id ==
518 .join((UsersGroupMember, UsersGroupToPerm.users_group_id ==
519 UsersGroupMember.users_group_id))\
519 UsersGroupMember.users_group_id))\
520 .filter(UsersGroupMember.user_id == uid)\
520 .filter(UsersGroupMember.user_id == uid)\
521 .order_by(UsersGroupToPerm.users_group_id)\
521 .order_by(UsersGroupToPerm.users_group_id)\
522 .all()
522 .all()
523 #need to group here by groups since user can be in more than one group
523 #need to group here by groups since user can be in more than one group
524 _grouped = [[x, list(y)] for x, y in
524 _grouped = [[x, list(y)] for x, y in
525 itertools.groupby(user_perms_from_users_groups,
525 itertools.groupby(user_perms_from_users_groups,
526 lambda x:x.users_group)]
526 lambda x:x.users_group)]
527 for gr, perms in _grouped:
527 for gr, perms in _grouped:
528 # since user can be in multiple groups iterate over them and
528 # since user can be in multiple groups iterate over them and
529 # select the lowest permissions first (more explicit)
529 # select the lowest permissions first (more explicit)
530 ##TODO: do this^^
530 ##TODO: do this^^
531 if not gr.inherit_default_permissions:
531 if not gr.inherit_default_permissions:
532 # NEED TO IGNORE all configurable permissions and
532 # NEED TO IGNORE all configurable permissions and
533 # replace them with explicitly set
533 # replace them with explicitly set
534 user.permissions[GLOBAL] = user.permissions[GLOBAL]\
534 user.permissions[GLOBAL] = user.permissions[GLOBAL]\
535 .difference(_configurable)
535 .difference(_configurable)
536 for perm in perms:
536 for perm in perms:
537 user.permissions[GLOBAL].add(perm.permission.permission_name)
537 user.permissions[GLOBAL].add(perm.permission.permission_name)
538
538
539 # user specific global permissions
539 # user specific global permissions
540 user_perms = self.sa.query(UserToPerm)\
540 user_perms = self.sa.query(UserToPerm)\
541 .options(joinedload(UserToPerm.permission))\
541 .options(joinedload(UserToPerm.permission))\
542 .filter(UserToPerm.user_id == uid).all()
542 .filter(UserToPerm.user_id == uid).all()
543
543
544 if not user.inherit_default_permissions:
544 if not user.inherit_default_permissions:
545 # NEED TO IGNORE all configurable permissions and
545 # NEED TO IGNORE all configurable permissions and
546 # replace them with explicitly set
546 # replace them with explicitly set
547 user.permissions[GLOBAL] = user.permissions[GLOBAL]\
547 user.permissions[GLOBAL] = user.permissions[GLOBAL]\
548 .difference(_configurable)
548 .difference(_configurable)
549
549
550 for perm in user_perms:
550 for perm in user_perms:
551 user.permissions[GLOBAL].add(perm.permission.permission_name)
551 user.permissions[GLOBAL].add(perm.permission.permission_name)
552
552
553 #======================================================================
553 #======================================================================
554 # !! PERMISSIONS FOR REPOSITORIES !!
554 # !! PERMISSIONS FOR REPOSITORIES !!
555 #======================================================================
555 #======================================================================
556 #======================================================================
556 #======================================================================
557 # check if user is part of user groups for this repository and
557 # check if user is part of user groups for this repository and
558 # fill in his permission from it. _choose_perm decides of which
558 # fill in his permission from it. _choose_perm decides of which
559 # permission should be selected based on selected method
559 # permission should be selected based on selected method
560 #======================================================================
560 #======================================================================
561
561
562 # users group for repositories permissions
562 # users group for repositories permissions
563 user_repo_perms_from_users_groups = \
563 user_repo_perms_from_users_groups = \
564 self.sa.query(UsersGroupRepoToPerm, Permission, Repository,)\
564 self.sa.query(UsersGroupRepoToPerm, Permission, Repository,)\
565 .join((Repository, UsersGroupRepoToPerm.repository_id ==
565 .join((Repository, UsersGroupRepoToPerm.repository_id ==
566 Repository.repo_id))\
566 Repository.repo_id))\
567 .join((Permission, UsersGroupRepoToPerm.permission_id ==
567 .join((Permission, UsersGroupRepoToPerm.permission_id ==
568 Permission.permission_id))\
568 Permission.permission_id))\
569 .join((UsersGroupMember, UsersGroupRepoToPerm.users_group_id ==
569 .join((UsersGroupMember, UsersGroupRepoToPerm.users_group_id ==
570 UsersGroupMember.users_group_id))\
570 UsersGroupMember.users_group_id))\
571 .filter(UsersGroupMember.user_id == uid)\
571 .filter(UsersGroupMember.user_id == uid)\
572 .all()
572 .all()
573
573
574 multiple_counter = collections.defaultdict(int)
574 multiple_counter = collections.defaultdict(int)
575 for perm in user_repo_perms_from_users_groups:
575 for perm in user_repo_perms_from_users_groups:
576 r_k = perm.UsersGroupRepoToPerm.repository.repo_name
576 r_k = perm.UsersGroupRepoToPerm.repository.repo_name
577 multiple_counter[r_k] += 1
577 multiple_counter[r_k] += 1
578 p = perm.Permission.permission_name
578 p = perm.Permission.permission_name
579 cur_perm = user.permissions[RK][r_k]
579 cur_perm = user.permissions[RK][r_k]
580
580
581 if perm.Repository.user_id == uid:
581 if perm.Repository.user_id == uid:
582 # set admin if owner
582 # set admin if owner
583 p = 'repository.admin'
583 p = 'repository.admin'
584 else:
584 else:
585 if multiple_counter[r_k] > 1:
585 if multiple_counter[r_k] > 1:
586 p = _choose_perm(p, cur_perm)
586 p = _choose_perm(p, cur_perm)
587 user.permissions[RK][r_k] = p
587 user.permissions[RK][r_k] = p
588
588
589 # user explicit permissions for repositories, overrides any specified
589 # user explicit permissions for repositories, overrides any specified
590 # by the group permission
590 # by the group permission
591 user_repo_perms = \
591 user_repo_perms = \
592 self.sa.query(UserRepoToPerm, Permission, Repository)\
592 self.sa.query(UserRepoToPerm, Permission, Repository)\
593 .join((Repository, UserRepoToPerm.repository_id ==
593 .join((Repository, UserRepoToPerm.repository_id ==
594 Repository.repo_id))\
594 Repository.repo_id))\
595 .join((Permission, UserRepoToPerm.permission_id ==
595 .join((Permission, UserRepoToPerm.permission_id ==
596 Permission.permission_id))\
596 Permission.permission_id))\
597 .filter(UserRepoToPerm.user_id == uid)\
597 .filter(UserRepoToPerm.user_id == uid)\
598 .all()
598 .all()
599
599
600 for perm in user_repo_perms:
600 for perm in user_repo_perms:
601 r_k = perm.UserRepoToPerm.repository.repo_name
601 r_k = perm.UserRepoToPerm.repository.repo_name
602 cur_perm = user.permissions[RK][r_k]
602 cur_perm = user.permissions[RK][r_k]
603 # set admin if owner
603 # set admin if owner
604 if perm.Repository.user_id == uid:
604 if perm.Repository.user_id == uid:
605 p = 'repository.admin'
605 p = 'repository.admin'
606 else:
606 else:
607 p = perm.Permission.permission_name
607 p = perm.Permission.permission_name
608 if not explicit:
608 if not explicit:
609 p = _choose_perm(p, cur_perm)
609 p = _choose_perm(p, cur_perm)
610 user.permissions[RK][r_k] = p
610 user.permissions[RK][r_k] = p
611
611
612 #======================================================================
612 #======================================================================
613 # !! PERMISSIONS FOR REPOSITORIES GROUPS !!
613 # !! PERMISSIONS FOR REPOSITORY GROUPS !!
614 #======================================================================
614 #======================================================================
615 #======================================================================
615 #======================================================================
616 # check if user is part of user groups for this repository groups and
616 # check if user is part of user groups for this repository groups and
617 # fill in his permission from it. _choose_perm decides of which
617 # fill in his permission from it. _choose_perm decides of which
618 # permission should be selected based on selected method
618 # permission should be selected based on selected method
619 #======================================================================
619 #======================================================================
620 # users group for repo groups permissions
620 # users group for repo groups permissions
621 user_repo_group_perms_from_users_groups = \
621 user_repo_group_perms_from_users_groups = \
622 self.sa.query(UsersGroupRepoGroupToPerm, Permission, RepoGroup)\
622 self.sa.query(UsersGroupRepoGroupToPerm, Permission, RepoGroup)\
623 .join((RepoGroup, UsersGroupRepoGroupToPerm.group_id == RepoGroup.group_id))\
623 .join((RepoGroup, UsersGroupRepoGroupToPerm.group_id == RepoGroup.group_id))\
624 .join((Permission, UsersGroupRepoGroupToPerm.permission_id
624 .join((Permission, UsersGroupRepoGroupToPerm.permission_id
625 == Permission.permission_id))\
625 == Permission.permission_id))\
626 .join((UsersGroupMember, UsersGroupRepoGroupToPerm.users_group_id
626 .join((UsersGroupMember, UsersGroupRepoGroupToPerm.users_group_id
627 == UsersGroupMember.users_group_id))\
627 == UsersGroupMember.users_group_id))\
628 .filter(UsersGroupMember.user_id == uid)\
628 .filter(UsersGroupMember.user_id == uid)\
629 .all()
629 .all()
630
630
631 multiple_counter = collections.defaultdict(int)
631 multiple_counter = collections.defaultdict(int)
632 for perm in user_repo_group_perms_from_users_groups:
632 for perm in user_repo_group_perms_from_users_groups:
633 g_k = perm.UsersGroupRepoGroupToPerm.group.group_name
633 g_k = perm.UsersGroupRepoGroupToPerm.group.group_name
634 multiple_counter[g_k] += 1
634 multiple_counter[g_k] += 1
635 p = perm.Permission.permission_name
635 p = perm.Permission.permission_name
636 cur_perm = user.permissions[GK][g_k]
636 cur_perm = user.permissions[GK][g_k]
637 if multiple_counter[g_k] > 1:
637 if multiple_counter[g_k] > 1:
638 p = _choose_perm(p, cur_perm)
638 p = _choose_perm(p, cur_perm)
639 user.permissions[GK][g_k] = p
639 user.permissions[GK][g_k] = p
640
640
641 # user explicit permissions for repository groups
641 # user explicit permissions for repository groups
642 user_repo_groups_perms = \
642 user_repo_groups_perms = \
643 self.sa.query(UserRepoGroupToPerm, Permission, RepoGroup)\
643 self.sa.query(UserRepoGroupToPerm, Permission, RepoGroup)\
644 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
644 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
645 .join((Permission, UserRepoGroupToPerm.permission_id
645 .join((Permission, UserRepoGroupToPerm.permission_id
646 == Permission.permission_id))\
646 == Permission.permission_id))\
647 .filter(UserRepoGroupToPerm.user_id == uid)\
647 .filter(UserRepoGroupToPerm.user_id == uid)\
648 .all()
648 .all()
649
649
650 for perm in user_repo_groups_perms:
650 for perm in user_repo_groups_perms:
651 rg_k = perm.UserRepoGroupToPerm.group.group_name
651 rg_k = perm.UserRepoGroupToPerm.group.group_name
652 p = perm.Permission.permission_name
652 p = perm.Permission.permission_name
653 cur_perm = user.permissions[GK][rg_k]
653 cur_perm = user.permissions[GK][rg_k]
654 if not explicit:
654 if not explicit:
655 p = _choose_perm(p, cur_perm)
655 p = _choose_perm(p, cur_perm)
656 user.permissions[GK][rg_k] = p
656 user.permissions[GK][rg_k] = p
657
657
658 return user
658 return user
659
659
660 def has_perm(self, user, perm):
660 def has_perm(self, user, perm):
661 perm = self._get_perm(perm)
661 perm = self._get_perm(perm)
662 user = self._get_user(user)
662 user = self._get_user(user)
663
663
664 return UserToPerm.query().filter(UserToPerm.user == user)\
664 return UserToPerm.query().filter(UserToPerm.user == user)\
665 .filter(UserToPerm.permission == perm).scalar() is not None
665 .filter(UserToPerm.permission == perm).scalar() is not None
666
666
667 def grant_perm(self, user, perm):
667 def grant_perm(self, user, perm):
668 """
668 """
669 Grant user global permissions
669 Grant user global permissions
670
670
671 :param user:
671 :param user:
672 :param perm:
672 :param perm:
673 """
673 """
674 user = self._get_user(user)
674 user = self._get_user(user)
675 perm = self._get_perm(perm)
675 perm = self._get_perm(perm)
676 # if this permission is already granted skip it
676 # if this permission is already granted skip it
677 _perm = UserToPerm.query()\
677 _perm = UserToPerm.query()\
678 .filter(UserToPerm.user == user)\
678 .filter(UserToPerm.user == user)\
679 .filter(UserToPerm.permission == perm)\
679 .filter(UserToPerm.permission == perm)\
680 .scalar()
680 .scalar()
681 if _perm:
681 if _perm:
682 return
682 return
683 new = UserToPerm()
683 new = UserToPerm()
684 new.user = user
684 new.user = user
685 new.permission = perm
685 new.permission = perm
686 self.sa.add(new)
686 self.sa.add(new)
687
687
688 def revoke_perm(self, user, perm):
688 def revoke_perm(self, user, perm):
689 """
689 """
690 Revoke users global permissions
690 Revoke users global permissions
691
691
692 :param user:
692 :param user:
693 :param perm:
693 :param perm:
694 """
694 """
695 user = self._get_user(user)
695 user = self._get_user(user)
696 perm = self._get_perm(perm)
696 perm = self._get_perm(perm)
697
697
698 obj = UserToPerm.query()\
698 obj = UserToPerm.query()\
699 .filter(UserToPerm.user == user)\
699 .filter(UserToPerm.user == user)\
700 .filter(UserToPerm.permission == perm)\
700 .filter(UserToPerm.permission == perm)\
701 .scalar()
701 .scalar()
702 if obj:
702 if obj:
703 self.sa.delete(obj)
703 self.sa.delete(obj)
704
704
705 def add_extra_email(self, user, email):
705 def add_extra_email(self, user, email):
706 """
706 """
707 Adds email address to UserEmailMap
707 Adds email address to UserEmailMap
708
708
709 :param user:
709 :param user:
710 :param email:
710 :param email:
711 """
711 """
712 from rhodecode.model import forms
712 from rhodecode.model import forms
713 form = forms.UserExtraEmailForm()()
713 form = forms.UserExtraEmailForm()()
714 data = form.to_python(dict(email=email))
714 data = form.to_python(dict(email=email))
715 user = self._get_user(user)
715 user = self._get_user(user)
716
716
717 obj = UserEmailMap()
717 obj = UserEmailMap()
718 obj.user = user
718 obj.user = user
719 obj.email = data['email']
719 obj.email = data['email']
720 self.sa.add(obj)
720 self.sa.add(obj)
721 return obj
721 return obj
722
722
723 def delete_extra_email(self, user, email_id):
723 def delete_extra_email(self, user, email_id):
724 """
724 """
725 Removes email address from UserEmailMap
725 Removes email address from UserEmailMap
726
726
727 :param user:
727 :param user:
728 :param email_id:
728 :param email_id:
729 """
729 """
730 user = self._get_user(user)
730 user = self._get_user(user)
731 obj = UserEmailMap.query().get(email_id)
731 obj = UserEmailMap.query().get(email_id)
732 if obj:
732 if obj:
733 self.sa.delete(obj)
733 self.sa.delete(obj)
734
734
735 def add_extra_ip(self, user, ip):
735 def add_extra_ip(self, user, ip):
736 """
736 """
737 Adds ip address to UserIpMap
737 Adds ip address to UserIpMap
738
738
739 :param user:
739 :param user:
740 :param ip:
740 :param ip:
741 """
741 """
742 from rhodecode.model import forms
742 from rhodecode.model import forms
743 form = forms.UserExtraIpForm()()
743 form = forms.UserExtraIpForm()()
744 data = form.to_python(dict(ip=ip))
744 data = form.to_python(dict(ip=ip))
745 user = self._get_user(user)
745 user = self._get_user(user)
746
746
747 obj = UserIpMap()
747 obj = UserIpMap()
748 obj.user = user
748 obj.user = user
749 obj.ip_addr = data['ip']
749 obj.ip_addr = data['ip']
750 self.sa.add(obj)
750 self.sa.add(obj)
751 return obj
751 return obj
752
752
753 def delete_extra_ip(self, user, ip_id):
753 def delete_extra_ip(self, user, ip_id):
754 """
754 """
755 Removes ip address from UserIpMap
755 Removes ip address from UserIpMap
756
756
757 :param user:
757 :param user:
758 :param ip_id:
758 :param ip_id:
759 """
759 """
760 user = self._get_user(user)
760 user = self._get_user(user)
761 obj = UserIpMap.query().get(ip_id)
761 obj = UserIpMap.query().get(ip_id)
762 if obj:
762 if obj:
763 self.sa.delete(obj)
763 self.sa.delete(obj)
@@ -1,192 +1,192 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.model.users_group
3 rhodecode.model.users_group
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 users group model for RhodeCode
6 user group model for RhodeCode
7
7
8 :created_on: Oct 1, 2011
8 :created_on: Oct 1, 2011
9 :author: nvinot
9 :author: nvinot
10 :copyright: (C) 2011-2011 Nicolas Vinot <aeris@imirhil.fr>
10 :copyright: (C) 2011-2011 Nicolas Vinot <aeris@imirhil.fr>
11 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
12 :license: GPLv3, see COPYING for more details.
12 :license: GPLv3, see COPYING for more details.
13 """
13 """
14 # This program is free software: you can redistribute it and/or modify
14 # This program is free software: you can redistribute it and/or modify
15 # it under the terms of the GNU General Public License as published by
15 # it under the terms of the GNU General Public License as published by
16 # the Free Software Foundation, either version 3 of the License, or
16 # the Free Software Foundation, either version 3 of the License, or
17 # (at your option) any later version.
17 # (at your option) any later version.
18 #
18 #
19 # This program is distributed in the hope that it will be useful,
19 # This program is distributed in the hope that it will be useful,
20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 # GNU General Public License for more details.
22 # GNU General Public License for more details.
23 #
23 #
24 # You should have received a copy of the GNU General Public License
24 # You should have received a copy of the GNU General Public License
25 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 # along with this program. If not, see <http://www.gnu.org/licenses/>.
26
26
27 import logging
27 import logging
28 import traceback
28 import traceback
29
29
30 from rhodecode.model import BaseModel
30 from rhodecode.model import BaseModel
31 from rhodecode.model.db import UsersGroupMember, UsersGroup,\
31 from rhodecode.model.db import UsersGroupMember, UsersGroup,\
32 UsersGroupRepoToPerm, Permission, UsersGroupToPerm, User
32 UsersGroupRepoToPerm, Permission, UsersGroupToPerm, User
33 from rhodecode.lib.exceptions import UsersGroupsAssignedException
33 from rhodecode.lib.exceptions import UsersGroupsAssignedException
34
34
35 log = logging.getLogger(__name__)
35 log = logging.getLogger(__name__)
36
36
37
37
38 class UsersGroupModel(BaseModel):
38 class UsersGroupModel(BaseModel):
39
39
40 cls = UsersGroup
40 cls = UsersGroup
41
41
42 def __get_users_group(self, users_group):
42 def __get_users_group(self, users_group):
43 return self._get_instance(UsersGroup, users_group,
43 return self._get_instance(UsersGroup, users_group,
44 callback=UsersGroup.get_by_group_name)
44 callback=UsersGroup.get_by_group_name)
45
45
46 def get(self, users_group_id, cache=False):
46 def get(self, users_group_id, cache=False):
47 return UsersGroup.get(users_group_id)
47 return UsersGroup.get(users_group_id)
48
48
49 def get_group(self, users_group):
49 def get_group(self, users_group):
50 return self.__get_users_group(users_group)
50 return self.__get_users_group(users_group)
51
51
52 def get_by_name(self, name, cache=False, case_insensitive=False):
52 def get_by_name(self, name, cache=False, case_insensitive=False):
53 return UsersGroup.get_by_group_name(name, cache, case_insensitive)
53 return UsersGroup.get_by_group_name(name, cache, case_insensitive)
54
54
55 def create(self, name, active=True):
55 def create(self, name, active=True):
56 try:
56 try:
57 new = UsersGroup()
57 new = UsersGroup()
58 new.users_group_name = name
58 new.users_group_name = name
59 new.users_group_active = active
59 new.users_group_active = active
60 self.sa.add(new)
60 self.sa.add(new)
61 return new
61 return new
62 except:
62 except:
63 log.error(traceback.format_exc())
63 log.error(traceback.format_exc())
64 raise
64 raise
65
65
66 def update(self, users_group, form_data):
66 def update(self, users_group, form_data):
67
67
68 try:
68 try:
69 users_group = self.__get_users_group(users_group)
69 users_group = self.__get_users_group(users_group)
70
70
71 for k, v in form_data.items():
71 for k, v in form_data.items():
72 if k == 'users_group_members':
72 if k == 'users_group_members':
73 users_group.members = []
73 users_group.members = []
74 self.sa.flush()
74 self.sa.flush()
75 members_list = []
75 members_list = []
76 if v:
76 if v:
77 v = [v] if isinstance(v, basestring) else v
77 v = [v] if isinstance(v, basestring) else v
78 for u_id in set(v):
78 for u_id in set(v):
79 member = UsersGroupMember(users_group.users_group_id, u_id)
79 member = UsersGroupMember(users_group.users_group_id, u_id)
80 members_list.append(member)
80 members_list.append(member)
81 setattr(users_group, 'members', members_list)
81 setattr(users_group, 'members', members_list)
82 setattr(users_group, k, v)
82 setattr(users_group, k, v)
83
83
84 self.sa.add(users_group)
84 self.sa.add(users_group)
85 except:
85 except:
86 log.error(traceback.format_exc())
86 log.error(traceback.format_exc())
87 raise
87 raise
88
88
89 def delete(self, users_group, force=False):
89 def delete(self, users_group, force=False):
90 """
90 """
91 Deletes repos group, unless force flag is used
91 Deletes repos group, unless force flag is used
92 raises exception if there are members in that group, else deletes
92 raises exception if there are members in that group, else deletes
93 group and users
93 group and users
94
94
95 :param users_group:
95 :param users_group:
96 :param force:
96 :param force:
97 """
97 """
98 try:
98 try:
99 users_group = self.__get_users_group(users_group)
99 users_group = self.__get_users_group(users_group)
100
100
101 # check if this group is not assigned to repo
101 # check if this group is not assigned to repo
102 assigned_groups = UsersGroupRepoToPerm.query()\
102 assigned_groups = UsersGroupRepoToPerm.query()\
103 .filter(UsersGroupRepoToPerm.users_group == users_group).all()
103 .filter(UsersGroupRepoToPerm.users_group == users_group).all()
104
104
105 if assigned_groups and force is False:
105 if assigned_groups and force is False:
106 raise UsersGroupsAssignedException('RepoGroup assigned to %s' %
106 raise UsersGroupsAssignedException('RepoGroup assigned to %s' %
107 assigned_groups)
107 assigned_groups)
108
108
109 self.sa.delete(users_group)
109 self.sa.delete(users_group)
110 except:
110 except:
111 log.error(traceback.format_exc())
111 log.error(traceback.format_exc())
112 raise
112 raise
113
113
114 def add_user_to_group(self, users_group, user):
114 def add_user_to_group(self, users_group, user):
115 users_group = self.__get_users_group(users_group)
115 users_group = self.__get_users_group(users_group)
116 user = self._get_user(user)
116 user = self._get_user(user)
117
117
118 for m in users_group.members:
118 for m in users_group.members:
119 u = m.user
119 u = m.user
120 if u.user_id == user.user_id:
120 if u.user_id == user.user_id:
121 return True
121 return True
122
122
123 try:
123 try:
124 users_group_member = UsersGroupMember()
124 users_group_member = UsersGroupMember()
125 users_group_member.user = user
125 users_group_member.user = user
126 users_group_member.users_group = users_group
126 users_group_member.users_group = users_group
127
127
128 users_group.members.append(users_group_member)
128 users_group.members.append(users_group_member)
129 user.group_member.append(users_group_member)
129 user.group_member.append(users_group_member)
130
130
131 self.sa.add(users_group_member)
131 self.sa.add(users_group_member)
132 return users_group_member
132 return users_group_member
133 except:
133 except:
134 log.error(traceback.format_exc())
134 log.error(traceback.format_exc())
135 raise
135 raise
136
136
137 def remove_user_from_group(self, users_group, user):
137 def remove_user_from_group(self, users_group, user):
138 users_group = self.__get_users_group(users_group)
138 users_group = self.__get_users_group(users_group)
139 user = self._get_user(user)
139 user = self._get_user(user)
140
140
141 users_group_member = None
141 users_group_member = None
142 for m in users_group.members:
142 for m in users_group.members:
143 if m.user.user_id == user.user_id:
143 if m.user.user_id == user.user_id:
144 # Found this user's membership row
144 # Found this user's membership row
145 users_group_member = m
145 users_group_member = m
146 break
146 break
147
147
148 if users_group_member:
148 if users_group_member:
149 try:
149 try:
150 self.sa.delete(users_group_member)
150 self.sa.delete(users_group_member)
151 return True
151 return True
152 except:
152 except:
153 log.error(traceback.format_exc())
153 log.error(traceback.format_exc())
154 raise
154 raise
155 else:
155 else:
156 # User isn't in that group
156 # User isn't in that group
157 return False
157 return False
158
158
159 def has_perm(self, users_group, perm):
159 def has_perm(self, users_group, perm):
160 users_group = self.__get_users_group(users_group)
160 users_group = self.__get_users_group(users_group)
161 perm = self._get_perm(perm)
161 perm = self._get_perm(perm)
162
162
163 return UsersGroupToPerm.query()\
163 return UsersGroupToPerm.query()\
164 .filter(UsersGroupToPerm.users_group == users_group)\
164 .filter(UsersGroupToPerm.users_group == users_group)\
165 .filter(UsersGroupToPerm.permission == perm).scalar() is not None
165 .filter(UsersGroupToPerm.permission == perm).scalar() is not None
166
166
167 def grant_perm(self, users_group, perm):
167 def grant_perm(self, users_group, perm):
168 users_group = self.__get_users_group(users_group)
168 users_group = self.__get_users_group(users_group)
169 perm = self._get_perm(perm)
169 perm = self._get_perm(perm)
170
170
171 # if this permission is already granted skip it
171 # if this permission is already granted skip it
172 _perm = UsersGroupToPerm.query()\
172 _perm = UsersGroupToPerm.query()\
173 .filter(UsersGroupToPerm.users_group == users_group)\
173 .filter(UsersGroupToPerm.users_group == users_group)\
174 .filter(UsersGroupToPerm.permission == perm)\
174 .filter(UsersGroupToPerm.permission == perm)\
175 .scalar()
175 .scalar()
176 if _perm:
176 if _perm:
177 return
177 return
178
178
179 new = UsersGroupToPerm()
179 new = UsersGroupToPerm()
180 new.users_group = users_group
180 new.users_group = users_group
181 new.permission = perm
181 new.permission = perm
182 self.sa.add(new)
182 self.sa.add(new)
183
183
184 def revoke_perm(self, users_group, perm):
184 def revoke_perm(self, users_group, perm):
185 users_group = self.__get_users_group(users_group)
185 users_group = self.__get_users_group(users_group)
186 perm = self._get_perm(perm)
186 perm = self._get_perm(perm)
187
187
188 obj = UsersGroupToPerm.query()\
188 obj = UsersGroupToPerm.query()\
189 .filter(UsersGroupToPerm.users_group == users_group)\
189 .filter(UsersGroupToPerm.users_group == users_group)\
190 .filter(UsersGroupToPerm.permission == perm).scalar()
190 .filter(UsersGroupToPerm.permission == perm).scalar()
191 if obj:
191 if obj:
192 self.sa.delete(obj)
192 self.sa.delete(obj)
@@ -1,128 +1,128 b''
1 <table id="permissions_manage" class="noborder">
1 <table id="permissions_manage" class="noborder">
2 <tr>
2 <tr>
3 <td>${_('none')}</td>
3 <td>${_('none')}</td>
4 <td>${_('read')}</td>
4 <td>${_('read')}</td>
5 <td>${_('write')}</td>
5 <td>${_('write')}</td>
6 <td>${_('admin')}</td>
6 <td>${_('admin')}</td>
7 <td>${_('member')}</td>
7 <td>${_('member')}</td>
8 <td></td>
8 <td></td>
9 </tr>
9 </tr>
10 ## USERS
10 ## USERS
11 %for r2p in c.repo_info.repo_to_perm:
11 %for r2p in c.repo_info.repo_to_perm:
12 %if r2p.user.username =='default' and c.repo_info.private:
12 %if r2p.user.username =='default' and c.repo_info.private:
13 <tr>
13 <tr>
14 <td colspan="4">
14 <td colspan="4">
15 <span class="private_repo_msg">
15 <span class="private_repo_msg">
16 ${_('private repository')}
16 ${_('private repository')}
17 </span>
17 </span>
18 </td>
18 </td>
19 <td class="private_repo_msg"><img style="vertical-align:bottom" src="${h.url('/images/icons/user.png')}"/>${_('default')}</td>
19 <td class="private_repo_msg"><img style="vertical-align:bottom" src="${h.url('/images/icons/user.png')}"/>${_('default')}</td>
20 </tr>
20 </tr>
21 %else:
21 %else:
22 <tr id="id${id(r2p.user.username)}">
22 <tr id="id${id(r2p.user.username)}">
23 <td>${h.radio('u_perm_%s' % r2p.user.username,'repository.none')}</td>
23 <td>${h.radio('u_perm_%s' % r2p.user.username,'repository.none')}</td>
24 <td>${h.radio('u_perm_%s' % r2p.user.username,'repository.read')}</td>
24 <td>${h.radio('u_perm_%s' % r2p.user.username,'repository.read')}</td>
25 <td>${h.radio('u_perm_%s' % r2p.user.username,'repository.write')}</td>
25 <td>${h.radio('u_perm_%s' % r2p.user.username,'repository.write')}</td>
26 <td>${h.radio('u_perm_%s' % r2p.user.username,'repository.admin')}</td>
26 <td>${h.radio('u_perm_%s' % r2p.user.username,'repository.admin')}</td>
27 <td style="white-space: nowrap;">
27 <td style="white-space: nowrap;">
28 <img class="perm-gravatar" src="${h.gravatar_url(r2p.user.email,14)}"/>${r2p.user.username if r2p.user.username != 'default' else _('default')}
28 <img class="perm-gravatar" src="${h.gravatar_url(r2p.user.email,14)}"/>${r2p.user.username if r2p.user.username != 'default' else _('default')}
29 </td>
29 </td>
30 <td>
30 <td>
31 %if r2p.user.username !='default':
31 %if r2p.user.username !='default':
32 <span class="delete_icon action_button" onclick="ajaxActionUser(${r2p.user.user_id},'${'id%s'%id(r2p.user.username)}')">
32 <span class="delete_icon action_button" onclick="ajaxActionUser(${r2p.user.user_id},'${'id%s'%id(r2p.user.username)}')">
33 ${_('revoke')}
33 ${_('revoke')}
34 </span>
34 </span>
35 %endif
35 %endif
36 </td>
36 </td>
37 </tr>
37 </tr>
38 %endif
38 %endif
39 %endfor
39 %endfor
40
40
41 ## USERS GROUPS
41 ## USER GROUPS
42 %for g2p in c.repo_info.users_group_to_perm:
42 %for g2p in c.repo_info.users_group_to_perm:
43 <tr id="id${id(g2p.users_group.users_group_name)}">
43 <tr id="id${id(g2p.users_group.users_group_name)}">
44 <td>${h.radio('g_perm_%s' % g2p.users_group.users_group_name,'repository.none')}</td>
44 <td>${h.radio('g_perm_%s' % g2p.users_group.users_group_name,'repository.none')}</td>
45 <td>${h.radio('g_perm_%s' % g2p.users_group.users_group_name,'repository.read')}</td>
45 <td>${h.radio('g_perm_%s' % g2p.users_group.users_group_name,'repository.read')}</td>
46 <td>${h.radio('g_perm_%s' % g2p.users_group.users_group_name,'repository.write')}</td>
46 <td>${h.radio('g_perm_%s' % g2p.users_group.users_group_name,'repository.write')}</td>
47 <td>${h.radio('g_perm_%s' % g2p.users_group.users_group_name,'repository.admin')}</td>
47 <td>${h.radio('g_perm_%s' % g2p.users_group.users_group_name,'repository.admin')}</td>
48 <td style="white-space: nowrap;">
48 <td style="white-space: nowrap;">
49 <img class="perm-gravatar" src="${h.url('/images/icons/group.png')}"/>
49 <img class="perm-gravatar" src="${h.url('/images/icons/group.png')}"/>
50 %if h.HasPermissionAny('hg.admin')():
50 %if h.HasPermissionAny('hg.admin')():
51 <a href="${h.url('edit_users_group',id=g2p.users_group.users_group_id)}">${g2p.users_group.users_group_name}</a>
51 <a href="${h.url('edit_users_group',id=g2p.users_group.users_group_id)}">${g2p.users_group.users_group_name}</a>
52 %else:
52 %else:
53 ${g2p.users_group.users_group_name}
53 ${g2p.users_group.users_group_name}
54 %endif
54 %endif
55 </td>
55 </td>
56 <td>
56 <td>
57 <span class="delete_icon action_button" onclick="ajaxActionUsersGroup(${g2p.users_group.users_group_id},'${'id%s'%id(g2p.users_group.users_group_name)}')">
57 <span class="delete_icon action_button" onclick="ajaxActionUsersGroup(${g2p.users_group.users_group_id},'${'id%s'%id(g2p.users_group.users_group_name)}')">
58 ${_('revoke')}
58 ${_('revoke')}
59 </span>
59 </span>
60 </td>
60 </td>
61 </tr>
61 </tr>
62 %endfor
62 %endfor
63 <%
63 <%
64 _tmpl = h.literal("""' \
64 _tmpl = h.literal("""' \
65 <td><input type="radio" value="repository.none" name="perm_new_member_{0}" id="perm_new_member_{0}"></td> \
65 <td><input type="radio" value="repository.none" name="perm_new_member_{0}" id="perm_new_member_{0}"></td> \
66 <td><input type="radio" value="repository.read" name="perm_new_member_{0}" id="perm_new_member_{0}"></td> \
66 <td><input type="radio" value="repository.read" name="perm_new_member_{0}" id="perm_new_member_{0}"></td> \
67 <td><input type="radio" value="repository.write" name="perm_new_member_{0}" id="perm_new_member_{0}"></td> \
67 <td><input type="radio" value="repository.write" name="perm_new_member_{0}" id="perm_new_member_{0}"></td> \
68 <td><input type="radio" value="repository.admin" name="perm_new_member_{0}" id="perm_new_member_{0}"></td> \
68 <td><input type="radio" value="repository.admin" name="perm_new_member_{0}" id="perm_new_member_{0}"></td> \
69 <td class="ac"> \
69 <td class="ac"> \
70 <div class="perm_ac" id="perm_ac_{0}"> \
70 <div class="perm_ac" id="perm_ac_{0}"> \
71 <input class="yui-ac-input" id="perm_new_member_name_{0}" name="perm_new_member_name_{0}" value="" type="text"> \
71 <input class="yui-ac-input" id="perm_new_member_name_{0}" name="perm_new_member_name_{0}" value="" type="text"> \
72 <input id="perm_new_member_type_{0}" name="perm_new_member_type_{0}" value="" type="hidden"> \
72 <input id="perm_new_member_type_{0}" name="perm_new_member_type_{0}" value="" type="hidden"> \
73 <div id="perm_container_{0}"></div> \
73 <div id="perm_container_{0}"></div> \
74 </div> \
74 </div> \
75 </td> \
75 </td> \
76 <td></td>'""")
76 <td></td>'""")
77 %>
77 %>
78 ## ADD HERE DYNAMICALLY NEW INPUTS FROM THE '_tmpl'
78 ## ADD HERE DYNAMICALLY NEW INPUTS FROM THE '_tmpl'
79 <tr class="new_members last_new_member" id="add_perm_input"></tr>
79 <tr class="new_members last_new_member" id="add_perm_input"></tr>
80 <tr>
80 <tr>
81 <td colspan="6">
81 <td colspan="6">
82 <span id="add_perm" class="add_icon" style="cursor: pointer;">
82 <span id="add_perm" class="add_icon" style="cursor: pointer;">
83 ${_('Add another member')}
83 ${_('Add another member')}
84 </span>
84 </span>
85 </td>
85 </td>
86 </tr>
86 </tr>
87 </table>
87 </table>
88 <script type="text/javascript">
88 <script type="text/javascript">
89 function ajaxActionUser(user_id, field_id) {
89 function ajaxActionUser(user_id, field_id) {
90 var sUrl = "${h.url('delete_repo_user',repo_name=c.repo_name)}";
90 var sUrl = "${h.url('delete_repo_user',repo_name=c.repo_name)}";
91 var callback = {
91 var callback = {
92 success: function (o) {
92 success: function (o) {
93 var tr = YUD.get(String(field_id));
93 var tr = YUD.get(String(field_id));
94 tr.parentNode.removeChild(tr);
94 tr.parentNode.removeChild(tr);
95 },
95 },
96 failure: function (o) {
96 failure: function (o) {
97 alert("${_('Failed to remove user')}");
97 alert("${_('Failed to remove user')}");
98 },
98 },
99 };
99 };
100 var postData = '_method=delete&user_id=' + user_id;
100 var postData = '_method=delete&user_id=' + user_id;
101 var request = YAHOO.util.Connect.asyncRequest('POST', sUrl, callback, postData);
101 var request = YAHOO.util.Connect.asyncRequest('POST', sUrl, callback, postData);
102 };
102 };
103
103
104 function ajaxActionUsersGroup(users_group_id,field_id){
104 function ajaxActionUsersGroup(users_group_id,field_id){
105 var sUrl = "${h.url('delete_repo_users_group',repo_name=c.repo_name)}";
105 var sUrl = "${h.url('delete_repo_users_group',repo_name=c.repo_name)}";
106 var callback = {
106 var callback = {
107 success:function(o){
107 success:function(o){
108 var tr = YUD.get(String(field_id));
108 var tr = YUD.get(String(field_id));
109 tr.parentNode.removeChild(tr);
109 tr.parentNode.removeChild(tr);
110 },
110 },
111 failure:function(o){
111 failure:function(o){
112 alert("${_('Failed to remove users group')}");
112 alert("${_('Failed to remove user group')}");
113 },
113 },
114 };
114 };
115 var postData = '_method=delete&users_group_id='+users_group_id;
115 var postData = '_method=delete&users_group_id='+users_group_id;
116 var request = YAHOO.util.Connect.asyncRequest('POST', sUrl, callback, postData);
116 var request = YAHOO.util.Connect.asyncRequest('POST', sUrl, callback, postData);
117 };
117 };
118
118
119 YUE.onDOMReady(function () {
119 YUE.onDOMReady(function () {
120 if (!YUD.hasClass('perm_new_member_name', 'error')) {
120 if (!YUD.hasClass('perm_new_member_name', 'error')) {
121 YUD.setStyle('add_perm_input', 'display', 'none');
121 YUD.setStyle('add_perm_input', 'display', 'none');
122 }
122 }
123 YAHOO.util.Event.addListener('add_perm', 'click', function () {
123 YAHOO.util.Event.addListener('add_perm', 'click', function () {
124 addPermAction(${_tmpl}, ${c.users_array|n}, ${c.users_groups_array|n});
124 addPermAction(${_tmpl}, ${c.users_array|n}, ${c.users_groups_array|n});
125 });
125 });
126 });
126 });
127
127
128 </script>
128 </script>
@@ -1,133 +1,133 b''
1 <table id="permissions_manage" class="noborder">
1 <table id="permissions_manage" class="noborder">
2 <tr>
2 <tr>
3 <td>${_('none')}</td>
3 <td>${_('none')}</td>
4 <td>${_('read')}</td>
4 <td>${_('read')}</td>
5 <td>${_('write')}</td>
5 <td>${_('write')}</td>
6 <td>${_('admin')}</td>
6 <td>${_('admin')}</td>
7 <td>${_('member')}</td>
7 <td>${_('member')}</td>
8 <td></td>
8 <td></td>
9 </tr>
9 </tr>
10 ## USERS
10 ## USERS
11 %for r2p in c.repos_group.repo_group_to_perm:
11 %for r2p in c.repos_group.repo_group_to_perm:
12 ##forbid revoking permission from yourself
12 ##forbid revoking permission from yourself
13 <tr id="id${id(r2p.user.username)}">
13 <tr id="id${id(r2p.user.username)}">
14 %if c.rhodecode_user.user_id != r2p.user.user_id or c.rhodecode_user.is_admin:
14 %if c.rhodecode_user.user_id != r2p.user.user_id or c.rhodecode_user.is_admin:
15 <td>${h.radio('u_perm_%s' % r2p.user.username,'group.none')}</td>
15 <td>${h.radio('u_perm_%s' % r2p.user.username,'group.none')}</td>
16 <td>${h.radio('u_perm_%s' % r2p.user.username,'group.read')}</td>
16 <td>${h.radio('u_perm_%s' % r2p.user.username,'group.read')}</td>
17 <td>${h.radio('u_perm_%s' % r2p.user.username,'group.write')}</td>
17 <td>${h.radio('u_perm_%s' % r2p.user.username,'group.write')}</td>
18 <td>${h.radio('u_perm_%s' % r2p.user.username,'group.admin')}</td>
18 <td>${h.radio('u_perm_%s' % r2p.user.username,'group.admin')}</td>
19 <td style="white-space: nowrap;">
19 <td style="white-space: nowrap;">
20 <img class="perm-gravatar" src="${h.gravatar_url(r2p.user.email,14)}"/>${r2p.user.username if r2p.user.username != 'default' else _('default')}
20 <img class="perm-gravatar" src="${h.gravatar_url(r2p.user.email,14)}"/>${r2p.user.username if r2p.user.username != 'default' else _('default')}
21 </td>
21 </td>
22 <td>
22 <td>
23 %if r2p.user.username !='default':
23 %if r2p.user.username !='default':
24 <span class="delete_icon action_button" onclick="ajaxActionUser(${r2p.user.user_id},'${'id%s'%id(r2p.user.username)}')">
24 <span class="delete_icon action_button" onclick="ajaxActionUser(${r2p.user.user_id},'${'id%s'%id(r2p.user.username)}')">
25 ${_('revoke')}
25 ${_('revoke')}
26 </span>
26 </span>
27 %endif
27 %endif
28 </td>
28 </td>
29 %else:
29 %else:
30 <td>${h.radio('u_perm_%s' % r2p.user.username,'group.none', disabled="disabled")}</td>
30 <td>${h.radio('u_perm_%s' % r2p.user.username,'group.none', disabled="disabled")}</td>
31 <td>${h.radio('u_perm_%s' % r2p.user.username,'group.read', disabled="disabled")}</td>
31 <td>${h.radio('u_perm_%s' % r2p.user.username,'group.read', disabled="disabled")}</td>
32 <td>${h.radio('u_perm_%s' % r2p.user.username,'group.write', disabled="disabled")}</td>
32 <td>${h.radio('u_perm_%s' % r2p.user.username,'group.write', disabled="disabled")}</td>
33 <td>${h.radio('u_perm_%s' % r2p.user.username,'group.admin', disabled="disabled")}</td>
33 <td>${h.radio('u_perm_%s' % r2p.user.username,'group.admin', disabled="disabled")}</td>
34 <td style="white-space: nowrap;">
34 <td style="white-space: nowrap;">
35 <img class="perm-gravatar" src="${h.gravatar_url(r2p.user.email,14)}"/>${r2p.user.username if r2p.user.username != 'default' else _('default')}
35 <img class="perm-gravatar" src="${h.gravatar_url(r2p.user.email,14)}"/>${r2p.user.username if r2p.user.username != 'default' else _('default')}
36 </td>
36 </td>
37 <td>
37 <td>
38 </td>
38 </td>
39 %endif
39 %endif
40 </tr>
40 </tr>
41 %endfor
41 %endfor
42
42
43 ## USERS GROUPS
43 ## USER GROUPS
44 %for g2p in c.repos_group.users_group_to_perm:
44 %for g2p in c.repos_group.users_group_to_perm:
45 <tr id="id${id(g2p.users_group.users_group_name)}">
45 <tr id="id${id(g2p.users_group.users_group_name)}">
46 <td>${h.radio('g_perm_%s' % g2p.users_group.users_group_name,'group.none')}</td>
46 <td>${h.radio('g_perm_%s' % g2p.users_group.users_group_name,'group.none')}</td>
47 <td>${h.radio('g_perm_%s' % g2p.users_group.users_group_name,'group.read')}</td>
47 <td>${h.radio('g_perm_%s' % g2p.users_group.users_group_name,'group.read')}</td>
48 <td>${h.radio('g_perm_%s' % g2p.users_group.users_group_name,'group.write')}</td>
48 <td>${h.radio('g_perm_%s' % g2p.users_group.users_group_name,'group.write')}</td>
49 <td>${h.radio('g_perm_%s' % g2p.users_group.users_group_name,'group.admin')}</td>
49 <td>${h.radio('g_perm_%s' % g2p.users_group.users_group_name,'group.admin')}</td>
50 <td style="white-space: nowrap;">
50 <td style="white-space: nowrap;">
51 <img class="perm-gravatar" src="${h.url('/images/icons/group.png')}"/>${g2p.users_group.users_group_name}
51 <img class="perm-gravatar" src="${h.url('/images/icons/group.png')}"/>${g2p.users_group.users_group_name}
52 </td>
52 </td>
53 <td>
53 <td>
54 <span class="delete_icon action_button" onclick="ajaxActionUsersGroup(${g2p.users_group.users_group_id},'${'id%s'%id(g2p.users_group.users_group_name)}')">
54 <span class="delete_icon action_button" onclick="ajaxActionUsersGroup(${g2p.users_group.users_group_id},'${'id%s'%id(g2p.users_group.users_group_name)}')">
55 ${_('revoke')}
55 ${_('revoke')}
56 </span>
56 </span>
57 </td>
57 </td>
58 </tr>
58 </tr>
59 %endfor
59 %endfor
60 <%
60 <%
61 _tmpl = h.literal("""' \
61 _tmpl = h.literal("""' \
62 <td><input type="radio" value="group.none" name="perm_new_member_{0}" id="perm_new_member_{0}"></td> \
62 <td><input type="radio" value="group.none" name="perm_new_member_{0}" id="perm_new_member_{0}"></td> \
63 <td><input type="radio" value="group.read" name="perm_new_member_{0}" id="perm_new_member_{0}"></td> \
63 <td><input type="radio" value="group.read" name="perm_new_member_{0}" id="perm_new_member_{0}"></td> \
64 <td><input type="radio" value="group.write" name="perm_new_member_{0}" id="perm_new_member_{0}"></td> \
64 <td><input type="radio" value="group.write" name="perm_new_member_{0}" id="perm_new_member_{0}"></td> \
65 <td><input type="radio" value="group.admin" name="perm_new_member_{0}" id="perm_new_member_{0}"></td> \
65 <td><input type="radio" value="group.admin" name="perm_new_member_{0}" id="perm_new_member_{0}"></td> \
66 <td class="ac"> \
66 <td class="ac"> \
67 <div class="perm_ac" id="perm_ac_{0}"> \
67 <div class="perm_ac" id="perm_ac_{0}"> \
68 <input class="yui-ac-input" id="perm_new_member_name_{0}" name="perm_new_member_name_{0}" value="" type="text"> \
68 <input class="yui-ac-input" id="perm_new_member_name_{0}" name="perm_new_member_name_{0}" value="" type="text"> \
69 <input id="perm_new_member_type_{0}" name="perm_new_member_type_{0}" value="" type="hidden"> \
69 <input id="perm_new_member_type_{0}" name="perm_new_member_type_{0}" value="" type="hidden"> \
70 <div id="perm_container_{0}"></div> \
70 <div id="perm_container_{0}"></div> \
71 </div> \
71 </div> \
72 </td> \
72 </td> \
73 <td></td>'""")
73 <td></td>'""")
74 %>
74 %>
75 ## ADD HERE DYNAMICALLY NEW INPUTS FROM THE '_tmpl'
75 ## ADD HERE DYNAMICALLY NEW INPUTS FROM THE '_tmpl'
76 <tr class="new_members last_new_member" id="add_perm_input"></tr>
76 <tr class="new_members last_new_member" id="add_perm_input"></tr>
77 <tr>
77 <tr>
78 <td colspan="6">
78 <td colspan="6">
79 <span id="add_perm" class="add_icon" style="cursor: pointer;">
79 <span id="add_perm" class="add_icon" style="cursor: pointer;">
80 ${_('Add another member')}
80 ${_('Add another member')}
81 </span>
81 </span>
82 </td>
82 </td>
83 </tr>
83 </tr>
84 <tr>
84 <tr>
85 <td colspan="6">
85 <td colspan="6">
86 ${h.checkbox('recursive',value="True", label=_('apply to children'))}
86 ${h.checkbox('recursive',value="True", label=_('apply to children'))}
87 <span class="help-block">${_('Set or revoke permission to all children of that group, including non-private repositories and other groups')}</span>
87 <span class="help-block">${_('Set or revoke permission to all children of that group, including non-private repositories and other groups')}</span>
88 </td>
88 </td>
89 </tr>
89 </tr>
90 </table>
90 </table>
91 <script type="text/javascript">
91 <script type="text/javascript">
92 function ajaxActionUser(user_id, field_id) {
92 function ajaxActionUser(user_id, field_id) {
93 var sUrl = "${h.url('delete_repos_group_user_perm',group_name=c.repos_group.group_name)}";
93 var sUrl = "${h.url('delete_repos_group_user_perm',group_name=c.repos_group.group_name)}";
94 var callback = {
94 var callback = {
95 success: function (o) {
95 success: function (o) {
96 var tr = YUD.get(String(field_id));
96 var tr = YUD.get(String(field_id));
97 tr.parentNode.removeChild(tr);
97 tr.parentNode.removeChild(tr);
98 },
98 },
99 failure: function (o) {
99 failure: function (o) {
100 alert("${_('Failed to remove user')}");
100 alert("${_('Failed to remove user')}");
101 },
101 },
102 };
102 };
103 var recursive = YUD.get('recursive').checked;
103 var recursive = YUD.get('recursive').checked;
104 var postData = '_method=delete&recursive={0}&user_id={1}'.format(recursive,user_id);
104 var postData = '_method=delete&recursive={0}&user_id={1}'.format(recursive,user_id);
105 var request = YAHOO.util.Connect.asyncRequest('POST', sUrl, callback, postData);
105 var request = YAHOO.util.Connect.asyncRequest('POST', sUrl, callback, postData);
106 };
106 };
107
107
108 function ajaxActionUsersGroup(users_group_id,field_id){
108 function ajaxActionUsersGroup(users_group_id,field_id){
109 var sUrl = "${h.url('delete_repos_group_users_group_perm',group_name=c.repos_group.group_name)}";
109 var sUrl = "${h.url('delete_repos_group_users_group_perm',group_name=c.repos_group.group_name)}";
110 var callback = {
110 var callback = {
111 success:function(o){
111 success:function(o){
112 var tr = YUD.get(String(field_id));
112 var tr = YUD.get(String(field_id));
113 tr.parentNode.removeChild(tr);
113 tr.parentNode.removeChild(tr);
114 },
114 },
115 failure:function(o){
115 failure:function(o){
116 alert("${_('Failed to remove users group')}");
116 alert("${_('Failed to remove user group')}");
117 },
117 },
118 };
118 };
119 var recursive = YUD.get('recursive').checked;
119 var recursive = YUD.get('recursive').checked;
120 var postData = '_method=delete&recursive={0}&users_group_id={1}'.format(recursive,users_group_id);
120 var postData = '_method=delete&recursive={0}&users_group_id={1}'.format(recursive,users_group_id);
121 var request = YAHOO.util.Connect.asyncRequest('POST', sUrl, callback, postData);
121 var request = YAHOO.util.Connect.asyncRequest('POST', sUrl, callback, postData);
122 };
122 };
123
123
124 YUE.onDOMReady(function () {
124 YUE.onDOMReady(function () {
125 if (!YUD.hasClass('perm_new_member_name', 'error')) {
125 if (!YUD.hasClass('perm_new_member_name', 'error')) {
126 YUD.setStyle('add_perm_input', 'display', 'none');
126 YUD.setStyle('add_perm_input', 'display', 'none');
127 }
127 }
128 YAHOO.util.Event.addListener('add_perm', 'click', function () {
128 YAHOO.util.Event.addListener('add_perm', 'click', function () {
129 addPermAction(${_tmpl}, ${c.users_array|n}, ${c.users_groups_array|n});
129 addPermAction(${_tmpl}, ${c.users_array|n}, ${c.users_groups_array|n});
130 });
130 });
131 });
131 });
132
132
133 </script>
133 </script>
@@ -1,78 +1,78 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <%inherit file="/base/base.html"/>
2 <%inherit file="/base/base.html"/>
3
3
4 <%def name="title()">
4 <%def name="title()">
5 ${_('Repositories groups administration')} - ${c.rhodecode_name}
5 ${_('Repository groups administration')} - ${c.rhodecode_name}
6 </%def>
6 </%def>
7
7
8
8
9 <%def name="breadcrumbs_links()">
9 <%def name="breadcrumbs_links()">
10 ${h.link_to(_('Admin'),h.url('admin_home'))}
10 ${h.link_to(_('Admin'),h.url('admin_home'))}
11 &raquo;
11 &raquo;
12 ${_('repositories groups')}
12 ${_('repositories groups')}
13 </%def>
13 </%def>
14 <%def name="page_nav()">
14 <%def name="page_nav()">
15 ${self.menu('admin')}
15 ${self.menu('admin')}
16 </%def>
16 </%def>
17 <%def name="main()">
17 <%def name="main()">
18 <div class="box">
18 <div class="box">
19 <!-- box / title -->
19 <!-- box / title -->
20 <div class="title">
20 <div class="title">
21 ${self.breadcrumbs()}
21 ${self.breadcrumbs()}
22 <ul class="links">
22 <ul class="links">
23 <li>
23 <li>
24 %if h.HasPermissionAny('hg.admin')():
24 %if h.HasPermissionAny('hg.admin')():
25 <span>${h.link_to(_(u'Add group'),h.url('new_repos_group'))}</span>
25 <span>${h.link_to(_(u'Add group'),h.url('new_repos_group'))}</span>
26 %endif
26 %endif
27 </li>
27 </li>
28 </ul>
28 </ul>
29 </div>
29 </div>
30 <!-- end box / title -->
30 <!-- end box / title -->
31 <div class="table">
31 <div class="table">
32 % if c.groups:
32 % if c.groups:
33 <table class="table_disp">
33 <table class="table_disp">
34
34
35 <thead>
35 <thead>
36 <tr>
36 <tr>
37 <th class="left"><a href="#">${_('Group name')}</a></th>
37 <th class="left"><a href="#">${_('Group name')}</a></th>
38 <th class="left"><a href="#">${_('Description')}</a></th>
38 <th class="left"><a href="#">${_('Description')}</a></th>
39 <th class="left"><a href="#">${_('Number of toplevel repositories')}</a></th>
39 <th class="left"><a href="#">${_('Number of toplevel repositories')}</a></th>
40 <th class="left" colspan="2">${_('action')}</th>
40 <th class="left" colspan="2">${_('action')}</th>
41 </tr>
41 </tr>
42 </thead>
42 </thead>
43
43
44 ## REPO GROUPS
44 ## REPO GROUPS
45
45
46 % for gr in c.groups:
46 % for gr in c.groups:
47 <% gr_cn = gr.repositories.count() %>
47 <% gr_cn = gr.repositories.count() %>
48 <tr>
48 <tr>
49 <td>
49 <td>
50 <div style="white-space: nowrap">
50 <div style="white-space: nowrap">
51 <img class="icon" alt="${_('Repositories group')}" src="${h.url('/images/icons/database_link.png')}"/>
51 <img class="icon" alt="${_('Repository group')}" src="${h.url('/images/icons/database_link.png')}"/>
52 ${h.link_to(h.literal(' &raquo; '.join(map(h.safe_unicode,[g.name for g in gr.parents+[gr]]))), url('repos_group_home',group_name=gr.group_name))}
52 ${h.link_to(h.literal(' &raquo; '.join(map(h.safe_unicode,[g.name for g in gr.parents+[gr]]))), url('repos_group_home',group_name=gr.group_name))}
53 </div>
53 </div>
54 </td>
54 </td>
55 <td>${gr.group_description}</td>
55 <td>${gr.group_description}</td>
56 <td><b>${gr_cn}</b></td>
56 <td><b>${gr_cn}</b></td>
57 <td>
57 <td>
58 <a href="${h.url('edit_repos_group',group_name=gr.group_name)}" title="${_('edit')}">
58 <a href="${h.url('edit_repos_group',group_name=gr.group_name)}" title="${_('edit')}">
59 ${h.submit('edit_%s' % gr.group_name,_('edit'),class_="edit_icon action_button")}
59 ${h.submit('edit_%s' % gr.group_name,_('edit'),class_="edit_icon action_button")}
60 </a>
60 </a>
61 </td>
61 </td>
62 <td>
62 <td>
63 ${h.form(url('repos_group', group_name=gr.group_name),method='delete')}
63 ${h.form(url('repos_group', group_name=gr.group_name),method='delete')}
64 ${h.submit('remove_%s' % gr.name,_('delete'),class_="delete_icon action_button",onclick="return confirm('"+ungettext('Confirm to delete this group: %s with %s repository','Confirm to delete this group: %s with %s repositories',gr_cn) % (gr.name,gr_cn)+"');")}
64 ${h.submit('remove_%s' % gr.name,_('delete'),class_="delete_icon action_button",onclick="return confirm('"+ungettext('Confirm to delete this group: %s with %s repository','Confirm to delete this group: %s with %s repositories',gr_cn) % (gr.name,gr_cn)+"');")}
65 ${h.end_form()}
65 ${h.end_form()}
66 </td>
66 </td>
67 </tr>
67 </tr>
68 % endfor
68 % endfor
69
69
70 </table>
70 </table>
71 % else:
71 % else:
72 ${_('There are no repositories groups yet')}
72 ${_('There are no repository groups yet')}
73 % endif
73 % endif
74
74
75 </div>
75 </div>
76 </div>
76 </div>
77
77
78 </%def>
78 </%def>
@@ -1,55 +1,55 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <%inherit file="/base/base.html"/>
2 <%inherit file="/base/base.html"/>
3
3
4 <%def name="title()">
4 <%def name="title()">
5 ${_('Add users group')} - ${c.rhodecode_name}
5 ${_('Add user group')} - ${c.rhodecode_name}
6 </%def>
6 </%def>
7 <%def name="breadcrumbs_links()">
7 <%def name="breadcrumbs_links()">
8 ${h.link_to(_('Admin'),h.url('admin_home'))}
8 ${h.link_to(_('Admin'),h.url('admin_home'))}
9 &raquo;
9 &raquo;
10 ${h.link_to(_('Users groups'),h.url('users_groups'))}
10 ${h.link_to(_('User groups'),h.url('users_groups'))}
11 &raquo;
11 &raquo;
12 ${_('add new users group')}
12 ${_('add new user group')}
13 </%def>
13 </%def>
14
14
15 <%def name="page_nav()">
15 <%def name="page_nav()">
16 ${self.menu('admin')}
16 ${self.menu('admin')}
17 </%def>
17 </%def>
18
18
19 <%def name="main()">
19 <%def name="main()">
20 <div class="box">
20 <div class="box">
21 <!-- box / title -->
21 <!-- box / title -->
22 <div class="title">
22 <div class="title">
23 ${self.breadcrumbs()}
23 ${self.breadcrumbs()}
24 </div>
24 </div>
25 <!-- end box / title -->
25 <!-- end box / title -->
26 ${h.form(url('users_groups'))}
26 ${h.form(url('users_groups'))}
27 <div class="form">
27 <div class="form">
28 <!-- fields -->
28 <!-- fields -->
29 <div class="fields">
29 <div class="fields">
30 <div class="field">
30 <div class="field">
31 <div class="label">
31 <div class="label">
32 <label for="users_group_name">${_('Group name')}:</label>
32 <label for="users_group_name">${_('Group name')}:</label>
33 </div>
33 </div>
34 <div class="input">
34 <div class="input">
35 ${h.text('users_group_name',class_='small')}
35 ${h.text('users_group_name',class_='small')}
36 </div>
36 </div>
37 </div>
37 </div>
38
38
39 <div class="field">
39 <div class="field">
40 <div class="label label-checkbox">
40 <div class="label label-checkbox">
41 <label for="users_group_active">${_('Active')}:</label>
41 <label for="users_group_active">${_('Active')}:</label>
42 </div>
42 </div>
43 <div class="checkboxes">
43 <div class="checkboxes">
44 ${h.checkbox('users_group_active',value=True, checked='checked')}
44 ${h.checkbox('users_group_active',value=True, checked='checked')}
45 </div>
45 </div>
46 </div>
46 </div>
47
47
48 <div class="buttons">
48 <div class="buttons">
49 ${h.submit('save',_('save'),class_="ui-btn large")}
49 ${h.submit('save',_('save'),class_="ui-btn large")}
50 </div>
50 </div>
51 </div>
51 </div>
52 </div>
52 </div>
53 ${h.end_form()}
53 ${h.end_form()}
54 </div>
54 </div>
55 </%def>
55 </%def>
@@ -1,55 +1,55 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <%inherit file="/base/base.html"/>
2 <%inherit file="/base/base.html"/>
3
3
4 <%def name="title()">
4 <%def name="title()">
5 ${_('Users groups administration')} - ${c.rhodecode_name}
5 ${_('User groups administration')} - ${c.rhodecode_name}
6 </%def>
6 </%def>
7
7
8 <%def name="breadcrumbs_links()">
8 <%def name="breadcrumbs_links()">
9 ${h.link_to(_('Admin'),h.url('admin_home'))}
9 ${h.link_to(_('Admin'),h.url('admin_home'))}
10 &raquo;
10 &raquo;
11 ${_('users groups')}
11 ${_('user groups')}
12 </%def>
12 </%def>
13
13
14 <%def name="page_nav()">
14 <%def name="page_nav()">
15 ${self.menu('admin')}
15 ${self.menu('admin')}
16 </%def>
16 </%def>
17
17
18 <%def name="main()">
18 <%def name="main()">
19 <div class="box">
19 <div class="box">
20 <!-- box / title -->
20 <!-- box / title -->
21 <div class="title">
21 <div class="title">
22 ${self.breadcrumbs()}
22 ${self.breadcrumbs()}
23 <ul class="links">
23 <ul class="links">
24 <li>
24 <li>
25 <span>${h.link_to(_(u'Add new user group'),h.url('new_users_group'))}</span>
25 <span>${h.link_to(_(u'Add new user group'),h.url('new_users_group'))}</span>
26 </li>
26 </li>
27
27
28 </ul>
28 </ul>
29 </div>
29 </div>
30 <!-- end box / title -->
30 <!-- end box / title -->
31 <div class="table">
31 <div class="table">
32 <table class="table_disp">
32 <table class="table_disp">
33 <tr class="header">
33 <tr class="header">
34 <th class="left">${_('group name')}</th>
34 <th class="left">${_('group name')}</th>
35 <th class="left">${_('members')}</th>
35 <th class="left">${_('members')}</th>
36 <th class="left">${_('active')}</th>
36 <th class="left">${_('active')}</th>
37 <th class="left">${_('action')}</th>
37 <th class="left">${_('action')}</th>
38 </tr>
38 </tr>
39 %for cnt,u_group in enumerate(c.users_groups_list):
39 %for cnt,u_group in enumerate(c.users_groups_list):
40 <tr class="parity${cnt%2}">
40 <tr class="parity${cnt%2}">
41 <td>${h.link_to(u_group.users_group_name,h.url('edit_users_group', id=u_group.users_group_id))}</td>
41 <td>${h.link_to(u_group.users_group_name,h.url('edit_users_group', id=u_group.users_group_id))}</td>
42 <td><span class="tooltip" title="${h.tooltip(', '.join(map(h.safe_unicode,[x.user.username for x in u_group.members[:50]])))}">${len(u_group.members)}</span></td>
42 <td><span class="tooltip" title="${h.tooltip(', '.join(map(h.safe_unicode,[x.user.username for x in u_group.members[:50]])))}">${len(u_group.members)}</span></td>
43 <td>${h.bool2icon(u_group.users_group_active)}</td>
43 <td>${h.bool2icon(u_group.users_group_active)}</td>
44 <td>
44 <td>
45 ${h.form(url('users_group', id=u_group.users_group_id),method='delete')}
45 ${h.form(url('users_group', id=u_group.users_group_id),method='delete')}
46 ${h.submit('remove_',_('delete'),id="remove_group_%s" % u_group.users_group_id,
46 ${h.submit('remove_',_('delete'),id="remove_group_%s" % u_group.users_group_id,
47 class_="delete_icon action_button",onclick="return confirm('"+_('Confirm to delete this users group: %s') % u_group.users_group_name+"');")}
47 class_="delete_icon action_button",onclick="return confirm('"+_('Confirm to delete this user group: %s') % u_group.users_group_name+"');")}
48 ${h.end_form()}
48 ${h.end_form()}
49 </td>
49 </td>
50 </tr>
50 </tr>
51 %endfor
51 %endfor
52 </table>
52 </table>
53 </div>
53 </div>
54 </div>
54 </div>
55 </%def>
55 </%def>
@@ -1,390 +1,390 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <%inherit file="root.html"/>
2 <%inherit file="root.html"/>
3
3
4 <!-- HEADER -->
4 <!-- HEADER -->
5 <div id="header-dd"></div>
5 <div id="header-dd"></div>
6 <div id="header">
6 <div id="header">
7 <div id="header-inner" class="title">
7 <div id="header-inner" class="title">
8 <div id="logo">
8 <div id="logo">
9 <h1><a href="${h.url('home')}">${c.rhodecode_name}</a></h1>
9 <h1><a href="${h.url('home')}">${c.rhodecode_name}</a></h1>
10 </div>
10 </div>
11 <!-- MENU -->
11 <!-- MENU -->
12 ${self.page_nav()}
12 ${self.page_nav()}
13 <!-- END MENU -->
13 <!-- END MENU -->
14 ${self.body()}
14 ${self.body()}
15 </div>
15 </div>
16 </div>
16 </div>
17 <!-- END HEADER -->
17 <!-- END HEADER -->
18
18
19 <!-- CONTENT -->
19 <!-- CONTENT -->
20 <div id="content">
20 <div id="content">
21 <div class="flash_msg">
21 <div class="flash_msg">
22 <% messages = h.flash.pop_messages() %>
22 <% messages = h.flash.pop_messages() %>
23 % if messages:
23 % if messages:
24 <ul id="flash-messages">
24 <ul id="flash-messages">
25 % for message in messages:
25 % for message in messages:
26 <li class="${message.category}_msg">${message}</li>
26 <li class="${message.category}_msg">${message}</li>
27 % endfor
27 % endfor
28 </ul>
28 </ul>
29 % endif
29 % endif
30 </div>
30 </div>
31 <div id="main">
31 <div id="main">
32 ${next.main()}
32 ${next.main()}
33 </div>
33 </div>
34 </div>
34 </div>
35 <!-- END CONTENT -->
35 <!-- END CONTENT -->
36
36
37 <!-- FOOTER -->
37 <!-- FOOTER -->
38 <div id="footer">
38 <div id="footer">
39 <div id="footer-inner" class="title">
39 <div id="footer-inner" class="title">
40 <div>
40 <div>
41 <p class="footer-link">
41 <p class="footer-link">
42 <a href="${h.url('bugtracker')}">${_('Submit a bug')}</a>
42 <a href="${h.url('bugtracker')}">${_('Submit a bug')}</a>
43 </p>
43 </p>
44 <p class="footer-link-right">
44 <p class="footer-link-right">
45 <a href="${h.url('rhodecode_official')}">RhodeCode${'-%s' % c.rhodecode_instanceid if c.rhodecode_instanceid else ''}</a>
45 <a href="${h.url('rhodecode_official')}">RhodeCode${'-%s' % c.rhodecode_instanceid if c.rhodecode_instanceid else ''}</a>
46 ${c.rhodecode_version} &copy; 2010-${h.datetime.today().year} by Marcin Kuzminski
46 ${c.rhodecode_version} &copy; 2010-${h.datetime.today().year} by Marcin Kuzminski
47 </p>
47 </p>
48 </div>
48 </div>
49 </div>
49 </div>
50 </div>
50 </div>
51 <!-- END FOOTER -->
51 <!-- END FOOTER -->
52
52
53 ### MAKO DEFS ###
53 ### MAKO DEFS ###
54 <%def name="page_nav()">
54 <%def name="page_nav()">
55 ${self.menu()}
55 ${self.menu()}
56 </%def>
56 </%def>
57
57
58 <%def name="breadcrumbs()">
58 <%def name="breadcrumbs()">
59 <div class="breadcrumbs">
59 <div class="breadcrumbs">
60 ${self.breadcrumbs_links()}
60 ${self.breadcrumbs_links()}
61 </div>
61 </div>
62 </%def>
62 </%def>
63
63
64 <%def name="usermenu()">
64 <%def name="usermenu()">
65 ## USER MENU
65 ## USER MENU
66 <li>
66 <li>
67 <a class="menu_link" id="quick_login_link">
67 <a class="menu_link" id="quick_login_link">
68 <span class="icon" style="padding:5px 5px 0px 5px">
68 <span class="icon" style="padding:5px 5px 0px 5px">
69 <img src="${h.gravatar_url(c.rhodecode_user.email,20)}" alt="avatar">
69 <img src="${h.gravatar_url(c.rhodecode_user.email,20)}" alt="avatar">
70 </span>
70 </span>
71 %if c.rhodecode_user.username != 'default':
71 %if c.rhodecode_user.username != 'default':
72 <span class="menu_link_user">${c.rhodecode_user.username}</span>
72 <span class="menu_link_user">${c.rhodecode_user.username}</span>
73 %if c.unread_notifications != 0:
73 %if c.unread_notifications != 0:
74 <span class="menu_link_notifications">${c.unread_notifications}</span>
74 <span class="menu_link_notifications">${c.unread_notifications}</span>
75 %endif
75 %endif
76 %else:
76 %else:
77 <span>${_('Not logged in')}</span>
77 <span>${_('Not logged in')}</span>
78 %endif
78 %endif
79 </a>
79 </a>
80
80
81 <div class="user-menu">
81 <div class="user-menu">
82 <div id="quick_login">
82 <div id="quick_login">
83 %if c.rhodecode_user.username == 'default':
83 %if c.rhodecode_user.username == 'default':
84 <h4>${_('Login to your account')}</h4>
84 <h4>${_('Login to your account')}</h4>
85 ${h.form(h.url('login_home',came_from=h.url.current()))}
85 ${h.form(h.url('login_home',came_from=h.url.current()))}
86 <div class="form">
86 <div class="form">
87 <div class="fields">
87 <div class="fields">
88 <div class="field">
88 <div class="field">
89 <div class="label">
89 <div class="label">
90 <label for="username">${_('Username')}:</label>
90 <label for="username">${_('Username')}:</label>
91 </div>
91 </div>
92 <div class="input">
92 <div class="input">
93 ${h.text('username',class_='focus',size=40)}
93 ${h.text('username',class_='focus',size=40)}
94 </div>
94 </div>
95
95
96 </div>
96 </div>
97 <div class="field">
97 <div class="field">
98 <div class="label">
98 <div class="label">
99 <label for="password">${_('Password')}:</label>
99 <label for="password">${_('Password')}:</label>
100 </div>
100 </div>
101 <div class="input">
101 <div class="input">
102 ${h.password('password',class_='focus',size=40)}
102 ${h.password('password',class_='focus',size=40)}
103 </div>
103 </div>
104
104
105 </div>
105 </div>
106 <div class="buttons">
106 <div class="buttons">
107 <div class="password_forgoten">${h.link_to(_('Forgot password ?'),h.url('reset_password'))}</div>
107 <div class="password_forgoten">${h.link_to(_('Forgot password ?'),h.url('reset_password'))}</div>
108 <div class="register">
108 <div class="register">
109 %if h.HasPermissionAny('hg.admin', 'hg.register.auto_activate', 'hg.register.manual_activate')():
109 %if h.HasPermissionAny('hg.admin', 'hg.register.auto_activate', 'hg.register.manual_activate')():
110 ${h.link_to(_("Don't have an account ?"),h.url('register'))}
110 ${h.link_to(_("Don't have an account ?"),h.url('register'))}
111 %endif
111 %endif
112 </div>
112 </div>
113 <div class="submit">
113 <div class="submit">
114 ${h.submit('sign_in',_('Log In'),class_="ui-btn xsmall")}
114 ${h.submit('sign_in',_('Log In'),class_="ui-btn xsmall")}
115 </div>
115 </div>
116 </div>
116 </div>
117 </div>
117 </div>
118 </div>
118 </div>
119 ${h.end_form()}
119 ${h.end_form()}
120 %else:
120 %else:
121 <div class="links_left">
121 <div class="links_left">
122 <div class="full_name">${c.rhodecode_user.full_name_or_username}</div>
122 <div class="full_name">${c.rhodecode_user.full_name_or_username}</div>
123 <div class="email">${c.rhodecode_user.email}</div>
123 <div class="email">${c.rhodecode_user.email}</div>
124 <div class="big_gravatar"><img alt="gravatar" src="${h.gravatar_url(c.rhodecode_user.email,48)}" /></div>
124 <div class="big_gravatar"><img alt="gravatar" src="${h.gravatar_url(c.rhodecode_user.email,48)}" /></div>
125 <div class="notifications"><a href="${h.url('notifications')}">${_('Notifications')}</a></div>
125 <div class="notifications"><a href="${h.url('notifications')}">${_('Notifications')}</a></div>
126 <div class="unread"><a href="${h.url('notifications')}">${_('Unread')}: ${c.unread_notifications}</a></div>
126 <div class="unread"><a href="${h.url('notifications')}">${_('Unread')}: ${c.unread_notifications}</a></div>
127 </div>
127 </div>
128 <div class="links_right">
128 <div class="links_right">
129 <ol class="links">
129 <ol class="links">
130 <li>${h.link_to(_(u'Home'),h.url('home'))}</li>
130 <li>${h.link_to(_(u'Home'),h.url('home'))}</li>
131 <li>${h.link_to(_(u'Journal'),h.url('journal'))}</li>
131 <li>${h.link_to(_(u'Journal'),h.url('journal'))}</li>
132 <li>${h.link_to(_(u'My account'),h.url('admin_settings_my_account'))}</li>
132 <li>${h.link_to(_(u'My account'),h.url('admin_settings_my_account'))}</li>
133 <li class="logout">${h.link_to(_(u'Log Out'),h.url('logout_home'))}</li>
133 <li class="logout">${h.link_to(_(u'Log Out'),h.url('logout_home'))}</li>
134 </ol>
134 </ol>
135 </div>
135 </div>
136 %endif
136 %endif
137 </div>
137 </div>
138 </div>
138 </div>
139
139
140 </li>
140 </li>
141 </%def>
141 </%def>
142
142
143 <%def name="menu(current=None)">
143 <%def name="menu(current=None)">
144 <%
144 <%
145 def is_current(selected):
145 def is_current(selected):
146 if selected == current:
146 if selected == current:
147 return h.literal('class="current"')
147 return h.literal('class="current"')
148 %>
148 %>
149 <ul id="quick">
149 <ul id="quick">
150 <!-- repo switcher -->
150 <!-- repo switcher -->
151 <li ${is_current('home')}>
151 <li ${is_current('home')}>
152 <a class="menu_link" id="repo_switcher" title="${_('Switch repository')}" href="${h.url('home')}">
152 <a class="menu_link" id="repo_switcher" title="${_('Switch repository')}" href="${h.url('home')}">
153 <span class="icon">
153 <span class="icon">
154 <img src="${h.url('/images/icons/database.png')}" alt="${_('Products')}" />
154 <img src="${h.url('/images/icons/database.png')}" alt="${_('Products')}" />
155 </span>
155 </span>
156 <span>${_('Repositories')}</span>
156 <span>${_('Repositories')}</span>
157 </a>
157 </a>
158 <ul id="repo_switcher_list" class="repo_switcher">
158 <ul id="repo_switcher_list" class="repo_switcher">
159 <li>
159 <li>
160 <a href="#">${_('loading...')}</a>
160 <a href="#">${_('loading...')}</a>
161 </li>
161 </li>
162 </ul>
162 </ul>
163 </li>
163 </li>
164 ## we render this menu only not for those pages
164 ## we render this menu only not for those pages
165 %if current not in ['home','admin', 'search', 'journal']:
165 %if current not in ['home','admin', 'search', 'journal']:
166 ##REGULAR MENU
166 ##REGULAR MENU
167 <li ${is_current('summary')}>
167 <li ${is_current('summary')}>
168 <a class="menu_link" title="${_('Summary page')}" href="${h.url('summary_home',repo_name=c.repo_name)}">
168 <a class="menu_link" title="${_('Summary page')}" href="${h.url('summary_home',repo_name=c.repo_name)}">
169 <span class="icon">
169 <span class="icon">
170 <img src="${h.url('/images/icons/clipboard_16.png')}" alt="${_('Summary')}" />
170 <img src="${h.url('/images/icons/clipboard_16.png')}" alt="${_('Summary')}" />
171 </span>
171 </span>
172 <span>${_('Summary')}</span>
172 <span>${_('Summary')}</span>
173 </a>
173 </a>
174 </li>
174 </li>
175 <li ${is_current('changelog')}>
175 <li ${is_current('changelog')}>
176 <a class="menu_link" title="${_('Changeset list')}" href="${h.url('changelog_home',repo_name=c.repo_name)}">
176 <a class="menu_link" title="${_('Changeset list')}" href="${h.url('changelog_home',repo_name=c.repo_name)}">
177 <span class="icon">
177 <span class="icon">
178 <img src="${h.url('/images/icons/time.png')}" alt="${_('Changelog')}" />
178 <img src="${h.url('/images/icons/time.png')}" alt="${_('Changelog')}" />
179 </span>
179 </span>
180 <span>${_('Changelog')}</span>
180 <span>${_('Changelog')}</span>
181 </a>
181 </a>
182 </li>
182 </li>
183 <li ${is_current('switch_to')}>
183 <li ${is_current('switch_to')}>
184 <a class="menu_link" id="branch_tag_switcher" title="${_('Switch to')}" href="#">
184 <a class="menu_link" id="branch_tag_switcher" title="${_('Switch to')}" href="#">
185 <span class="icon">
185 <span class="icon">
186 <img src="${h.url('/images/icons/arrow_switch.png')}" alt="${_('Switch to')}" />
186 <img src="${h.url('/images/icons/arrow_switch.png')}" alt="${_('Switch to')}" />
187 </span>
187 </span>
188 <span>${_('Switch to')}</span>
188 <span>${_('Switch to')}</span>
189 </a>
189 </a>
190 <ul id="switch_to_list" class="switch_to">
190 <ul id="switch_to_list" class="switch_to">
191 <li><a href="#">${_('loading...')}</a></li>
191 <li><a href="#">${_('loading...')}</a></li>
192 </ul>
192 </ul>
193 </li>
193 </li>
194 <li ${is_current('files')}>
194 <li ${is_current('files')}>
195 <a class="menu_link" title="${_('Show repository content')}" href="${h.url('files_home',repo_name=c.repo_name)}">
195 <a class="menu_link" title="${_('Show repository content')}" href="${h.url('files_home',repo_name=c.repo_name)}">
196 <span class="icon">
196 <span class="icon">
197 <img src="${h.url('/images/icons/file.png')}" alt="${_('Files')}" />
197 <img src="${h.url('/images/icons/file.png')}" alt="${_('Files')}" />
198 </span>
198 </span>
199 <span>${_('Files')}</span>
199 <span>${_('Files')}</span>
200 </a>
200 </a>
201 </li>
201 </li>
202 <li ${is_current('options')}>
202 <li ${is_current('options')}>
203 <a class="menu_link" title="${_('Options')}" href="#">
203 <a class="menu_link" title="${_('Options')}" href="#">
204 <span class="icon">
204 <span class="icon">
205 <img src="${h.url('/images/icons/table_gear.png')}" alt="${_('Admin')}" />
205 <img src="${h.url('/images/icons/table_gear.png')}" alt="${_('Admin')}" />
206 </span>
206 </span>
207 <span>${_('Options')}</span>
207 <span>${_('Options')}</span>
208 </a>
208 </a>
209 <ul>
209 <ul>
210 %if h.HasRepoPermissionAll('repository.admin')(c.repo_name):
210 %if h.HasRepoPermissionAll('repository.admin')(c.repo_name):
211 %if h.HasPermissionAll('hg.admin')('access settings on repository'):
211 %if h.HasPermissionAll('hg.admin')('access settings on repository'):
212 <li>${h.link_to(_('repository settings'),h.url('edit_repo',repo_name=c.repo_name),class_='settings')}</li>
212 <li>${h.link_to(_('repository settings'),h.url('edit_repo',repo_name=c.repo_name),class_='settings')}</li>
213 %else:
213 %else:
214 <li>${h.link_to(_('repository settings'),h.url('repo_settings_home',repo_name=c.repo_name),class_='settings')}</li>
214 <li>${h.link_to(_('repository settings'),h.url('repo_settings_home',repo_name=c.repo_name),class_='settings')}</li>
215 %endif
215 %endif
216 %endif
216 %endif
217
217
218 <li>${h.link_to(_('fork'),h.url('repo_fork_home',repo_name=c.repo_name),class_='fork')}</li>
218 <li>${h.link_to(_('fork'),h.url('repo_fork_home',repo_name=c.repo_name),class_='fork')}</li>
219 %if h.is_hg(c.rhodecode_repo):
219 %if h.is_hg(c.rhodecode_repo):
220 <li>${h.link_to(_('open new pull request'),h.url('pullrequest_home',repo_name=c.repo_name),class_='pull_request')}</li>
220 <li>${h.link_to(_('open new pull request'),h.url('pullrequest_home',repo_name=c.repo_name),class_='pull_request')}</li>
221 %endif
221 %endif
222 %if c.rhodecode_db_repo.fork:
222 %if c.rhodecode_db_repo.fork:
223 <li>${h.link_to(_('compare fork'),h.url('compare_url',repo_name=c.rhodecode_db_repo.fork.repo_name,org_ref_type='branch',org_ref='default',other_repo=c.repo_name,other_ref_type='branch',other_ref=request.GET.get('branch') or 'default'),class_='compare_request')}</li>
223 <li>${h.link_to(_('compare fork'),h.url('compare_url',repo_name=c.rhodecode_db_repo.fork.repo_name,org_ref_type='branch',org_ref='default',other_repo=c.repo_name,other_ref_type='branch',other_ref=request.GET.get('branch') or 'default'),class_='compare_request')}</li>
224 %endif
224 %endif
225 <li>${h.link_to(_('lightweight changelog'),h.url('shortlog_home',repo_name=c.repo_name),class_='shortlog')}</li>
225 <li>${h.link_to(_('lightweight changelog'),h.url('shortlog_home',repo_name=c.repo_name),class_='shortlog')}</li>
226 <li>${h.link_to(_('search'),h.url('search_repo',repo_name=c.repo_name),class_='search')}</li>
226 <li>${h.link_to(_('search'),h.url('search_repo',repo_name=c.repo_name),class_='search')}</li>
227
227
228 %if h.HasRepoPermissionAny('repository.write','repository.admin')(c.repo_name) and c.rhodecode_db_repo.enable_locking:
228 %if h.HasRepoPermissionAny('repository.write','repository.admin')(c.repo_name) and c.rhodecode_db_repo.enable_locking:
229 %if c.rhodecode_db_repo.locked[0]:
229 %if c.rhodecode_db_repo.locked[0]:
230 <li>${h.link_to(_('unlock'), h.url('toggle_locking',repo_name=c.repo_name),class_='locking_del')}</li>
230 <li>${h.link_to(_('unlock'), h.url('toggle_locking',repo_name=c.repo_name),class_='locking_del')}</li>
231 %else:
231 %else:
232 <li>${h.link_to(_('lock'), h.url('toggle_locking',repo_name=c.repo_name),class_='locking_add')}</li>
232 <li>${h.link_to(_('lock'), h.url('toggle_locking',repo_name=c.repo_name),class_='locking_add')}</li>
233 %endif
233 %endif
234 %endif
234 %endif
235
235
236 % if h.HasPermissionAll('hg.admin')('access admin main page'):
236 % if h.HasPermissionAll('hg.admin')('access admin main page'):
237 <li>
237 <li>
238 ${h.link_to(_('admin'),h.url('admin_home'),class_='admin')}
238 ${h.link_to(_('admin'),h.url('admin_home'),class_='admin')}
239 <%def name="admin_menu()">
239 <%def name="admin_menu()">
240 <ul>
240 <ul>
241 <li>${h.link_to(_('admin journal'),h.url('admin_home'),class_='journal')}</li>
241 <li>${h.link_to(_('admin journal'),h.url('admin_home'),class_='journal')}</li>
242 <li>${h.link_to(_('repositories'),h.url('repos'),class_='repos')}</li>
242 <li>${h.link_to(_('repositories'),h.url('repos'),class_='repos')}</li>
243 <li>${h.link_to(_('repositories groups'),h.url('repos_groups'),class_='repos_groups')}</li>
243 <li>${h.link_to(_('repository groups'),h.url('repos_groups'),class_='repos_groups')}</li>
244 <li>${h.link_to(_('users'),h.url('users'),class_='users')}</li>
244 <li>${h.link_to(_('users'),h.url('users'),class_='users')}</li>
245 <li>${h.link_to(_('users groups'),h.url('users_groups'),class_='groups')}</li>
245 <li>${h.link_to(_('user groups'),h.url('users_groups'),class_='groups')}</li>
246 <li>${h.link_to(_('permissions'),h.url('edit_permission',id='default'),class_='permissions')}</li>
246 <li>${h.link_to(_('permissions'),h.url('edit_permission',id='default'),class_='permissions')}</li>
247 <li>${h.link_to(_('ldap'),h.url('ldap_home'),class_='ldap')}</li>
247 <li>${h.link_to(_('ldap'),h.url('ldap_home'),class_='ldap')}</li>
248 <li>${h.link_to(_('defaults'),h.url('defaults'),class_='defaults')}</li>
248 <li>${h.link_to(_('defaults'),h.url('defaults'),class_='defaults')}</li>
249 <li class="last">${h.link_to(_('settings'),h.url('admin_settings'),class_='settings')}</li>
249 <li class="last">${h.link_to(_('settings'),h.url('admin_settings'),class_='settings')}</li>
250 </ul>
250 </ul>
251 </%def>
251 </%def>
252 ## ADMIN MENU
252 ## ADMIN MENU
253 ${admin_menu()}
253 ${admin_menu()}
254 </li>
254 </li>
255 ## if you're a admin of any groups, show admin menu for it
255 ## if you're a admin of any groups, show admin menu for it
256 % elif c.rhodecode_user.groups_admin:
256 % elif c.rhodecode_user.groups_admin:
257 <li>
257 <li>
258 ${h.link_to(_('admin'),h.url('admin_home'),class_='admin')}
258 ${h.link_to(_('admin'),h.url('admin_home'),class_='admin')}
259 <%def name="admin_menu_simple()">
259 <%def name="admin_menu_simple()">
260 <ul>
260 <ul>
261 <li>${h.link_to(_('repositories groups'),h.url('repos_groups'),class_='repos_groups')}</li>
261 <li>${h.link_to(_('repository groups'),h.url('repos_groups'),class_='repos_groups')}</li>
262 </ul>
262 </ul>
263 </%def>
263 </%def>
264 ## ADMIN MENU
264 ## ADMIN MENU
265 ${admin_menu_simple()}
265 ${admin_menu_simple()}
266 </li>
266 </li>
267 % endif
267 % endif
268 </ul>
268 </ul>
269 </li>
269 </li>
270 <li>
270 <li>
271 <a class="menu_link" title="${_('Followers')}" href="${h.url('repo_followers_home',repo_name=c.repo_name)}">
271 <a class="menu_link" title="${_('Followers')}" href="${h.url('repo_followers_home',repo_name=c.repo_name)}">
272 <span class="icon_short">
272 <span class="icon_short">
273 <img src="${h.url('/images/icons/heart.png')}" alt="${_('Followers')}" />
273 <img src="${h.url('/images/icons/heart.png')}" alt="${_('Followers')}" />
274 </span>
274 </span>
275 <span id="current_followers_count" class="short">${c.repository_followers}</span>
275 <span id="current_followers_count" class="short">${c.repository_followers}</span>
276 </a>
276 </a>
277 </li>
277 </li>
278 <li>
278 <li>
279 <a class="menu_link" title="${_('Forks')}" href="${h.url('repo_forks_home',repo_name=c.repo_name)}">
279 <a class="menu_link" title="${_('Forks')}" href="${h.url('repo_forks_home',repo_name=c.repo_name)}">
280 <span class="icon_short">
280 <span class="icon_short">
281 <img src="${h.url('/images/icons/arrow_divide.png')}" alt="${_('Forks')}" />
281 <img src="${h.url('/images/icons/arrow_divide.png')}" alt="${_('Forks')}" />
282 </span>
282 </span>
283 <span class="short">${c.repository_forks}</span>
283 <span class="short">${c.repository_forks}</span>
284 </a>
284 </a>
285 </li>
285 </li>
286 <li>
286 <li>
287 <a class="menu_link" title="${_('Pull requests')}" href="${h.url('pullrequest_show_all',repo_name=c.repo_name)}">
287 <a class="menu_link" title="${_('Pull requests')}" href="${h.url('pullrequest_show_all',repo_name=c.repo_name)}">
288 <span class="icon_short">
288 <span class="icon_short">
289 <img src="${h.url('/images/icons/arrow_join.png')}" alt="${_('Pull requests')}" />
289 <img src="${h.url('/images/icons/arrow_join.png')}" alt="${_('Pull requests')}" />
290 </span>
290 </span>
291 <span class="short">${c.repository_pull_requests}</span>
291 <span class="short">${c.repository_pull_requests}</span>
292 </a>
292 </a>
293 </li>
293 </li>
294 ${usermenu()}
294 ${usermenu()}
295 <script type="text/javascript">
295 <script type="text/javascript">
296 YUE.on('branch_tag_switcher','mouseover',function(){
296 YUE.on('branch_tag_switcher','mouseover',function(){
297 var loaded = YUD.hasClass('branch_tag_switcher','loaded');
297 var loaded = YUD.hasClass('branch_tag_switcher','loaded');
298 if(!loaded){
298 if(!loaded){
299 YUD.addClass('branch_tag_switcher','loaded');
299 YUD.addClass('branch_tag_switcher','loaded');
300 ypjax("${h.url('branch_tag_switcher',repo_name=c.repo_name)}",'switch_to_list',
300 ypjax("${h.url('branch_tag_switcher',repo_name=c.repo_name)}",'switch_to_list',
301 function(o){},
301 function(o){},
302 function(o){YUD.removeClass('branch_tag_switcher','loaded');}
302 function(o){YUD.removeClass('branch_tag_switcher','loaded');}
303 ,null);
303 ,null);
304 }
304 }
305 return false;
305 return false;
306 });
306 });
307 </script>
307 </script>
308 %else:
308 %else:
309 ##ROOT MENU
309 ##ROOT MENU
310 %if c.rhodecode_user.username != 'default':
310 %if c.rhodecode_user.username != 'default':
311 <li ${is_current('journal')}>
311 <li ${is_current('journal')}>
312 <a class="menu_link" title="${_('Show recent activity')}" href="${h.url('journal')}">
312 <a class="menu_link" title="${_('Show recent activity')}" href="${h.url('journal')}">
313 <span class="icon">
313 <span class="icon">
314 <img src="${h.url('/images/icons/book.png')}" alt="${_('Journal')}" />
314 <img src="${h.url('/images/icons/book.png')}" alt="${_('Journal')}" />
315 </span>
315 </span>
316 <span>${_('Journal')}</span>
316 <span>${_('Journal')}</span>
317 </a>
317 </a>
318 </li>
318 </li>
319 %else:
319 %else:
320 <li ${is_current('journal')}>
320 <li ${is_current('journal')}>
321 <a class="menu_link" title="${_('Public journal')}" href="${h.url('public_journal')}">
321 <a class="menu_link" title="${_('Public journal')}" href="${h.url('public_journal')}">
322 <span class="icon">
322 <span class="icon">
323 <img src="${h.url('/images/icons/book.png')}" alt="${_('Public journal')}" />
323 <img src="${h.url('/images/icons/book.png')}" alt="${_('Public journal')}" />
324 </span>
324 </span>
325 <span>${_('Public journal')}</span>
325 <span>${_('Public journal')}</span>
326 </a>
326 </a>
327 </li>
327 </li>
328 %endif
328 %endif
329 <li ${is_current('search')}>
329 <li ${is_current('search')}>
330 <a class="menu_link" title="${_('Search in repositories')}" href="${h.url('search')}">
330 <a class="menu_link" title="${_('Search in repositories')}" href="${h.url('search')}">
331 <span class="icon">
331 <span class="icon">
332 <img src="${h.url('/images/icons/search_16.png')}" alt="${_('Search')}" />
332 <img src="${h.url('/images/icons/search_16.png')}" alt="${_('Search')}" />
333 </span>
333 </span>
334 <span>${_('Search')}</span>
334 <span>${_('Search')}</span>
335 </a>
335 </a>
336 </li>
336 </li>
337 % if h.HasPermissionAll('hg.admin')('access admin main page'):
337 % if h.HasPermissionAll('hg.admin')('access admin main page'):
338 <li ${is_current('admin')}>
338 <li ${is_current('admin')}>
339 <a class="menu_link" title="${_('Admin')}" href="${h.url('admin_home')}">
339 <a class="menu_link" title="${_('Admin')}" href="${h.url('admin_home')}">
340 <span class="icon">
340 <span class="icon">
341 <img src="${h.url('/images/icons/cog_edit.png')}" alt="${_('Admin')}" />
341 <img src="${h.url('/images/icons/cog_edit.png')}" alt="${_('Admin')}" />
342 </span>
342 </span>
343 <span>${_('Admin')}</span>
343 <span>${_('Admin')}</span>
344 </a>
344 </a>
345 ${admin_menu()}
345 ${admin_menu()}
346 </li>
346 </li>
347 % elif c.rhodecode_user.groups_admin:
347 % elif c.rhodecode_user.groups_admin:
348 <li ${is_current('admin')}>
348 <li ${is_current('admin')}>
349 <a class="menu_link" title="${_('Admin')}" href="${h.url('admin_home')}">
349 <a class="menu_link" title="${_('Admin')}" href="${h.url('admin_home')}">
350 <span class="icon">
350 <span class="icon">
351 <img src="${h.url('/images/icons/cog_edit.png')}" alt="${_('Admin')}" />
351 <img src="${h.url('/images/icons/cog_edit.png')}" alt="${_('Admin')}" />
352 </span>
352 </span>
353 <span>${_('Admin')}</span>
353 <span>${_('Admin')}</span>
354 </a>
354 </a>
355 ${admin_menu_simple()}
355 ${admin_menu_simple()}
356 </li>
356 </li>
357 % endif
357 % endif
358 ${usermenu()}
358 ${usermenu()}
359 %endif
359 %endif
360 <script type="text/javascript">
360 <script type="text/javascript">
361 YUE.on('repo_switcher','mouseover',function(){
361 YUE.on('repo_switcher','mouseover',function(){
362 var target = 'q_filter_rs';
362 var target = 'q_filter_rs';
363 var qfilter_activate = function(){
363 var qfilter_activate = function(){
364 var nodes = YUQ('ul#repo_switcher_list li a.repo_name');
364 var nodes = YUQ('ul#repo_switcher_list li a.repo_name');
365 var func = function(node){
365 var func = function(node){
366 return node.parentNode;
366 return node.parentNode;
367 }
367 }
368 q_filter(target,nodes,func);
368 q_filter(target,nodes,func);
369 }
369 }
370
370
371 var loaded = YUD.hasClass('repo_switcher','loaded');
371 var loaded = YUD.hasClass('repo_switcher','loaded');
372 if(!loaded){
372 if(!loaded){
373 YUD.addClass('repo_switcher','loaded');
373 YUD.addClass('repo_switcher','loaded');
374 ypjax("${h.url('repo_switcher')}",'repo_switcher_list',
374 ypjax("${h.url('repo_switcher')}",'repo_switcher_list',
375 function(o){qfilter_activate();YUD.get(target).focus()},
375 function(o){qfilter_activate();YUD.get(target).focus()},
376 function(o){YUD.removeClass('repo_switcher','loaded');}
376 function(o){YUD.removeClass('repo_switcher','loaded');}
377 ,null);
377 ,null);
378 }else{
378 }else{
379 YUD.get(target).focus();
379 YUD.get(target).focus();
380 }
380 }
381 return false;
381 return false;
382 });
382 });
383
383
384 YUE.on('header-dd', 'click',function(e){
384 YUE.on('header-dd', 'click',function(e){
385 YUD.addClass('header-inner', 'hover');
385 YUD.addClass('header-inner', 'hover');
386 YUD.addClass('content', 'hover');
386 YUD.addClass('content', 'hover');
387 });
387 });
388
388
389 </script>
389 </script>
390 </%def>
390 </%def>
@@ -1,51 +1,51 b''
1 from rhodecode.tests import *
1 from rhodecode.tests import *
2
2
3
3
4 class TestReposGroupsController(TestController):
4 class TestReposGroupsController(TestController):
5
5
6 def test_index(self):
6 def test_index(self):
7 self.log_user()
7 self.log_user()
8 response = self.app.get(url('repos_groups'))
8 response = self.app.get(url('repos_groups'))
9 response.mustcontain('There are no repositories groups yet')
9 response.mustcontain('There are no repository groups yet')
10
10
11 # def test_index_as_xml(self):
11 # def test_index_as_xml(self):
12 # response = self.app.get(url('formatted_repos_groups', format='xml'))
12 # response = self.app.get(url('formatted_repos_groups', format='xml'))
13 #
13 #
14 # def test_create(self):
14 # def test_create(self):
15 # response = self.app.post(url('repos_groups'))
15 # response = self.app.post(url('repos_groups'))
16
16
17 def test_new(self):
17 def test_new(self):
18 self.log_user()
18 self.log_user()
19 response = self.app.get(url('new_repos_group'))
19 response = self.app.get(url('new_repos_group'))
20
20
21 def test_new_by_regular_user(self):
21 def test_new_by_regular_user(self):
22 self.log_user(TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)
22 self.log_user(TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)
23 response = self.app.get(url('new_repos_group'), status=403)
23 response = self.app.get(url('new_repos_group'), status=403)
24 #
24 #
25 # def test_new_as_xml(self):
25 # def test_new_as_xml(self):
26 # response = self.app.get(url('formatted_new_repos_group', format='xml'))
26 # response = self.app.get(url('formatted_new_repos_group', format='xml'))
27 #
27 #
28 # def test_update(self):
28 # def test_update(self):
29 # response = self.app.put(url('repos_group', group_name=1))
29 # response = self.app.put(url('repos_group', group_name=1))
30 #
30 #
31 # def test_update_browser_fakeout(self):
31 # def test_update_browser_fakeout(self):
32 # response = self.app.post(url('repos_group', group_name=1), params=dict(_method='put'))
32 # response = self.app.post(url('repos_group', group_name=1), params=dict(_method='put'))
33 #
33 #
34 # def test_delete(self):
34 # def test_delete(self):
35 # self.log_user()
35 # self.log_user()
36 # response = self.app.delete(url('repos_group', group_name=1))
36 # response = self.app.delete(url('repos_group', group_name=1))
37 #
37 #
38 # def test_delete_browser_fakeout(self):
38 # def test_delete_browser_fakeout(self):
39 # response = self.app.post(url('repos_group', group_name=1), params=dict(_method='delete'))
39 # response = self.app.post(url('repos_group', group_name=1), params=dict(_method='delete'))
40 #
40 #
41 # def test_show(self):
41 # def test_show(self):
42 # response = self.app.get(url('repos_group', group_name=1))
42 # response = self.app.get(url('repos_group', group_name=1))
43 #
43 #
44 # def test_show_as_xml(self):
44 # def test_show_as_xml(self):
45 # response = self.app.get(url('formatted_repos_group', group_name=1, format='xml'))
45 # response = self.app.get(url('formatted_repos_group', group_name=1, format='xml'))
46 #
46 #
47 # def test_edit(self):
47 # def test_edit(self):
48 # response = self.app.get(url('edit_repos_group', group_name=1))
48 # response = self.app.get(url('edit_repos_group', group_name=1))
49 #
49 #
50 # def test_edit_as_xml(self):
50 # def test_edit_as_xml(self):
51 # response = self.app.get(url('formatted_edit_repos_group', group_name=1, format='xml'))
51 # response = self.app.get(url('formatted_edit_repos_group', group_name=1, format='xml'))
General Comments 0
You need to be logged in to leave comments. Login now