##// END OF EJS Templates
Group management delegation:...
marcink -
r3222:b4daef4c beta
parent child Browse files
Show More
@@ -1,626 +1,625 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
58 return is_valid_repos_group(repos_group_name, config['base_path'])
57 return is_valid_repos_group(repos_group_name, config['base_path'])
59
58
60 def check_int(environ, match_dict):
59 def check_int(environ, match_dict):
61 return match_dict.get('id').isdigit()
60 return match_dict.get('id').isdigit()
62
61
63 # The ErrorController route (handles 404/500 error pages); it should
62 # The ErrorController route (handles 404/500 error pages); it should
64 # likely stay at the top, ensuring it can always be resolved
63 # likely stay at the top, ensuring it can always be resolved
65 rmap.connect('/error/{action}', controller='error')
64 rmap.connect('/error/{action}', controller='error')
66 rmap.connect('/error/{action}/{id}', controller='error')
65 rmap.connect('/error/{action}/{id}', controller='error')
67
66
68 #==========================================================================
67 #==========================================================================
69 # CUSTOM ROUTES HERE
68 # CUSTOM ROUTES HERE
70 #==========================================================================
69 #==========================================================================
71
70
72 #MAIN PAGE
71 #MAIN PAGE
73 rmap.connect('home', '/', controller='home', action='index')
72 rmap.connect('home', '/', controller='home', action='index')
74 rmap.connect('repo_switcher', '/repos', controller='home',
73 rmap.connect('repo_switcher', '/repos', controller='home',
75 action='repo_switcher')
74 action='repo_switcher')
76 rmap.connect('branch_tag_switcher', '/branches-tags/{repo_name:.*?}',
75 rmap.connect('branch_tag_switcher', '/branches-tags/{repo_name:.*?}',
77 controller='home', action='branch_tag_switcher')
76 controller='home', action='branch_tag_switcher')
78 rmap.connect('bugtracker',
77 rmap.connect('bugtracker',
79 "http://bitbucket.org/marcinkuzminski/rhodecode/issues",
78 "http://bitbucket.org/marcinkuzminski/rhodecode/issues",
80 _static=True)
79 _static=True)
81 rmap.connect('rst_help',
80 rmap.connect('rst_help',
82 "http://docutils.sourceforge.net/docs/user/rst/quickref.html",
81 "http://docutils.sourceforge.net/docs/user/rst/quickref.html",
83 _static=True)
82 _static=True)
84 rmap.connect('rhodecode_official', "http://rhodecode.org", _static=True)
83 rmap.connect('rhodecode_official', "http://rhodecode.org", _static=True)
85
84
86 #ADMIN REPOSITORY REST ROUTES
85 #ADMIN REPOSITORY REST ROUTES
87 with rmap.submapper(path_prefix=ADMIN_PREFIX,
86 with rmap.submapper(path_prefix=ADMIN_PREFIX,
88 controller='admin/repos') as m:
87 controller='admin/repos') as m:
89 m.connect("repos", "/repos",
88 m.connect("repos", "/repos",
90 action="create", conditions=dict(method=["POST"]))
89 action="create", conditions=dict(method=["POST"]))
91 m.connect("repos", "/repos",
90 m.connect("repos", "/repos",
92 action="index", conditions=dict(method=["GET"]))
91 action="index", conditions=dict(method=["GET"]))
93 m.connect("formatted_repos", "/repos.{format}",
92 m.connect("formatted_repos", "/repos.{format}",
94 action="index",
93 action="index",
95 conditions=dict(method=["GET"]))
94 conditions=dict(method=["GET"]))
96 m.connect("new_repo", "/repos/new",
95 m.connect("new_repo", "/repos/new",
97 action="new", conditions=dict(method=["GET"]))
96 action="new", conditions=dict(method=["GET"]))
98 m.connect("formatted_new_repo", "/repos/new.{format}",
97 m.connect("formatted_new_repo", "/repos/new.{format}",
99 action="new", conditions=dict(method=["GET"]))
98 action="new", conditions=dict(method=["GET"]))
100 m.connect("/repos/{repo_name:.*?}",
99 m.connect("/repos/{repo_name:.*?}",
101 action="update", conditions=dict(method=["PUT"],
100 action="update", conditions=dict(method=["PUT"],
102 function=check_repo))
101 function=check_repo))
103 m.connect("/repos/{repo_name:.*?}",
102 m.connect("/repos/{repo_name:.*?}",
104 action="delete", conditions=dict(method=["DELETE"],
103 action="delete", conditions=dict(method=["DELETE"],
105 function=check_repo))
104 function=check_repo))
106 m.connect("edit_repo", "/repos/{repo_name:.*?}/edit",
105 m.connect("edit_repo", "/repos/{repo_name:.*?}/edit",
107 action="edit", conditions=dict(method=["GET"],
106 action="edit", conditions=dict(method=["GET"],
108 function=check_repo))
107 function=check_repo))
109 m.connect("formatted_edit_repo", "/repos/{repo_name:.*?}.{format}/edit",
108 m.connect("formatted_edit_repo", "/repos/{repo_name:.*?}.{format}/edit",
110 action="edit", conditions=dict(method=["GET"],
109 action="edit", conditions=dict(method=["GET"],
111 function=check_repo))
110 function=check_repo))
112 m.connect("repo", "/repos/{repo_name:.*?}",
111 m.connect("repo", "/repos/{repo_name:.*?}",
113 action="show", conditions=dict(method=["GET"],
112 action="show", conditions=dict(method=["GET"],
114 function=check_repo))
113 function=check_repo))
115 m.connect("formatted_repo", "/repos/{repo_name:.*?}.{format}",
114 m.connect("formatted_repo", "/repos/{repo_name:.*?}.{format}",
116 action="show", conditions=dict(method=["GET"],
115 action="show", conditions=dict(method=["GET"],
117 function=check_repo))
116 function=check_repo))
118 #ajax delete repo perm user
117 #ajax delete repo perm user
119 m.connect('delete_repo_user', "/repos_delete_user/{repo_name:.*?}",
118 m.connect('delete_repo_user', "/repos_delete_user/{repo_name:.*?}",
120 action="delete_perm_user",
119 action="delete_perm_user",
121 conditions=dict(method=["DELETE"], function=check_repo))
120 conditions=dict(method=["DELETE"], function=check_repo))
122
121
123 #ajax delete repo perm users_group
122 #ajax delete repo perm users_group
124 m.connect('delete_repo_users_group',
123 m.connect('delete_repo_users_group',
125 "/repos_delete_users_group/{repo_name:.*?}",
124 "/repos_delete_users_group/{repo_name:.*?}",
126 action="delete_perm_users_group",
125 action="delete_perm_users_group",
127 conditions=dict(method=["DELETE"], function=check_repo))
126 conditions=dict(method=["DELETE"], function=check_repo))
128
127
129 #settings actions
128 #settings actions
130 m.connect('repo_stats', "/repos_stats/{repo_name:.*?}",
129 m.connect('repo_stats', "/repos_stats/{repo_name:.*?}",
131 action="repo_stats", conditions=dict(method=["DELETE"],
130 action="repo_stats", conditions=dict(method=["DELETE"],
132 function=check_repo))
131 function=check_repo))
133 m.connect('repo_cache', "/repos_cache/{repo_name:.*?}",
132 m.connect('repo_cache', "/repos_cache/{repo_name:.*?}",
134 action="repo_cache", conditions=dict(method=["DELETE"],
133 action="repo_cache", conditions=dict(method=["DELETE"],
135 function=check_repo))
134 function=check_repo))
136 m.connect('repo_public_journal', "/repos_public_journal/{repo_name:.*?}",
135 m.connect('repo_public_journal', "/repos_public_journal/{repo_name:.*?}",
137 action="repo_public_journal", conditions=dict(method=["PUT"],
136 action="repo_public_journal", conditions=dict(method=["PUT"],
138 function=check_repo))
137 function=check_repo))
139 m.connect('repo_pull', "/repo_pull/{repo_name:.*?}",
138 m.connect('repo_pull', "/repo_pull/{repo_name:.*?}",
140 action="repo_pull", conditions=dict(method=["PUT"],
139 action="repo_pull", conditions=dict(method=["PUT"],
141 function=check_repo))
140 function=check_repo))
142 m.connect('repo_as_fork', "/repo_as_fork/{repo_name:.*?}",
141 m.connect('repo_as_fork', "/repo_as_fork/{repo_name:.*?}",
143 action="repo_as_fork", conditions=dict(method=["PUT"],
142 action="repo_as_fork", conditions=dict(method=["PUT"],
144 function=check_repo))
143 function=check_repo))
145 m.connect('repo_locking', "/repo_locking/{repo_name:.*?}",
144 m.connect('repo_locking', "/repo_locking/{repo_name:.*?}",
146 action="repo_locking", conditions=dict(method=["PUT"],
145 action="repo_locking", conditions=dict(method=["PUT"],
147 function=check_repo))
146 function=check_repo))
148
147
149 with rmap.submapper(path_prefix=ADMIN_PREFIX,
148 with rmap.submapper(path_prefix=ADMIN_PREFIX,
150 controller='admin/repos_groups') as m:
149 controller='admin/repos_groups') as m:
151 m.connect("repos_groups", "/repos_groups",
150 m.connect("repos_groups", "/repos_groups",
152 action="create", conditions=dict(method=["POST"]))
151 action="create", conditions=dict(method=["POST"]))
153 m.connect("repos_groups", "/repos_groups",
152 m.connect("repos_groups", "/repos_groups",
154 action="index", conditions=dict(method=["GET"]))
153 action="index", conditions=dict(method=["GET"]))
155 m.connect("formatted_repos_groups", "/repos_groups.{format}",
154 m.connect("formatted_repos_groups", "/repos_groups.{format}",
156 action="index", conditions=dict(method=["GET"]))
155 action="index", conditions=dict(method=["GET"]))
157 m.connect("new_repos_group", "/repos_groups/new",
156 m.connect("new_repos_group", "/repos_groups/new",
158 action="new", conditions=dict(method=["GET"]))
157 action="new", conditions=dict(method=["GET"]))
159 m.connect("formatted_new_repos_group", "/repos_groups/new.{format}",
158 m.connect("formatted_new_repos_group", "/repos_groups/new.{format}",
160 action="new", conditions=dict(method=["GET"]))
159 action="new", conditions=dict(method=["GET"]))
161 m.connect("update_repos_group", "/repos_groups/{id}",
160 m.connect("update_repos_group", "/repos_groups/{group_name:.*?}",
162 action="update", conditions=dict(method=["PUT"],
161 action="update", conditions=dict(method=["PUT"],
163 function=check_int))
162 function=check_group))
164 m.connect("delete_repos_group", "/repos_groups/{id}",
163 m.connect("delete_repos_group", "/repos_groups/{group_name:.*?}",
165 action="delete", conditions=dict(method=["DELETE"],
164 action="delete", conditions=dict(method=["DELETE"],
166 function=check_int))
165 function=check_group))
167 m.connect("edit_repos_group", "/repos_groups/{id:.*?}/edit",
166 m.connect("edit_repos_group", "/repos_groups/{group_name:.*?}/edit",
168 action="edit", conditions=dict(method=["GET"],))
167 action="edit", conditions=dict(method=["GET"],))
169 m.connect("formatted_edit_repos_group",
168 m.connect("formatted_edit_repos_group",
170 "/repos_groups/{id}.{format}/edit",
169 "/repos_groups/{group_name:.*?}.{format}/edit",
171 action="edit", conditions=dict(method=["GET"],
170 action="edit", conditions=dict(method=["GET"],
172 function=check_int))
171 function=check_group))
173 m.connect("repos_group", "/repos_groups/{id}",
172 m.connect("repos_group", "/repos_groups/{group_name:.*?}",
174 action="show", conditions=dict(method=["GET"],
173 action="show", conditions=dict(method=["GET"],
175 function=check_int))
174 function=check_group))
176 m.connect("formatted_repos_group", "/repos_groups/{id}.{format}",
175 m.connect("formatted_repos_group", "/repos_groups/{group_name:.*?}.{format}",
177 action="show", conditions=dict(method=["GET"],
176 action="show", conditions=dict(method=["GET"],
178 function=check_int))
177 function=check_group))
179 # ajax delete repos group perm user
178 # ajax delete repos group perm user
180 m.connect('delete_repos_group_user_perm',
179 m.connect('delete_repos_group_user_perm',
181 "/delete_repos_group_user_perm/{group_name:.*}",
180 "/delete_repos_group_user_perm/{group_name:.*?}",
182 action="delete_repos_group_user_perm",
181 action="delete_repos_group_user_perm",
183 conditions=dict(method=["DELETE"], function=check_group))
182 conditions=dict(method=["DELETE"], function=check_group))
184
183
185 # ajax delete repos group perm users_group
184 # ajax delete repos group perm users_group
186 m.connect('delete_repos_group_users_group_perm',
185 m.connect('delete_repos_group_users_group_perm',
187 "/delete_repos_group_users_group_perm/{group_name:.*}",
186 "/delete_repos_group_users_group_perm/{group_name:.*?}",
188 action="delete_repos_group_users_group_perm",
187 action="delete_repos_group_users_group_perm",
189 conditions=dict(method=["DELETE"], function=check_group))
188 conditions=dict(method=["DELETE"], function=check_group))
190
189
191 #ADMIN USER REST ROUTES
190 #ADMIN USER REST ROUTES
192 with rmap.submapper(path_prefix=ADMIN_PREFIX,
191 with rmap.submapper(path_prefix=ADMIN_PREFIX,
193 controller='admin/users') as m:
192 controller='admin/users') as m:
194 m.connect("users", "/users",
193 m.connect("users", "/users",
195 action="create", conditions=dict(method=["POST"]))
194 action="create", conditions=dict(method=["POST"]))
196 m.connect("users", "/users",
195 m.connect("users", "/users",
197 action="index", conditions=dict(method=["GET"]))
196 action="index", conditions=dict(method=["GET"]))
198 m.connect("formatted_users", "/users.{format}",
197 m.connect("formatted_users", "/users.{format}",
199 action="index", conditions=dict(method=["GET"]))
198 action="index", conditions=dict(method=["GET"]))
200 m.connect("new_user", "/users/new",
199 m.connect("new_user", "/users/new",
201 action="new", conditions=dict(method=["GET"]))
200 action="new", conditions=dict(method=["GET"]))
202 m.connect("formatted_new_user", "/users/new.{format}",
201 m.connect("formatted_new_user", "/users/new.{format}",
203 action="new", conditions=dict(method=["GET"]))
202 action="new", conditions=dict(method=["GET"]))
204 m.connect("update_user", "/users/{id}",
203 m.connect("update_user", "/users/{id}",
205 action="update", conditions=dict(method=["PUT"]))
204 action="update", conditions=dict(method=["PUT"]))
206 m.connect("delete_user", "/users/{id}",
205 m.connect("delete_user", "/users/{id}",
207 action="delete", conditions=dict(method=["DELETE"]))
206 action="delete", conditions=dict(method=["DELETE"]))
208 m.connect("edit_user", "/users/{id}/edit",
207 m.connect("edit_user", "/users/{id}/edit",
209 action="edit", conditions=dict(method=["GET"]))
208 action="edit", conditions=dict(method=["GET"]))
210 m.connect("formatted_edit_user",
209 m.connect("formatted_edit_user",
211 "/users/{id}.{format}/edit",
210 "/users/{id}.{format}/edit",
212 action="edit", conditions=dict(method=["GET"]))
211 action="edit", conditions=dict(method=["GET"]))
213 m.connect("user", "/users/{id}",
212 m.connect("user", "/users/{id}",
214 action="show", conditions=dict(method=["GET"]))
213 action="show", conditions=dict(method=["GET"]))
215 m.connect("formatted_user", "/users/{id}.{format}",
214 m.connect("formatted_user", "/users/{id}.{format}",
216 action="show", conditions=dict(method=["GET"]))
215 action="show", conditions=dict(method=["GET"]))
217
216
218 #EXTRAS USER ROUTES
217 #EXTRAS USER ROUTES
219 m.connect("user_perm", "/users_perm/{id}",
218 m.connect("user_perm", "/users_perm/{id}",
220 action="update_perm", conditions=dict(method=["PUT"]))
219 action="update_perm", conditions=dict(method=["PUT"]))
221 m.connect("user_emails", "/users_emails/{id}",
220 m.connect("user_emails", "/users_emails/{id}",
222 action="add_email", conditions=dict(method=["PUT"]))
221 action="add_email", conditions=dict(method=["PUT"]))
223 m.connect("user_emails_delete", "/users_emails/{id}",
222 m.connect("user_emails_delete", "/users_emails/{id}",
224 action="delete_email", conditions=dict(method=["DELETE"]))
223 action="delete_email", conditions=dict(method=["DELETE"]))
225 m.connect("user_ips", "/users_ips/{id}",
224 m.connect("user_ips", "/users_ips/{id}",
226 action="add_ip", conditions=dict(method=["PUT"]))
225 action="add_ip", conditions=dict(method=["PUT"]))
227 m.connect("user_ips_delete", "/users_ips/{id}",
226 m.connect("user_ips_delete", "/users_ips/{id}",
228 action="delete_ip", conditions=dict(method=["DELETE"]))
227 action="delete_ip", conditions=dict(method=["DELETE"]))
229
228
230 #ADMIN USERS GROUPS REST ROUTES
229 #ADMIN USERS GROUPS REST ROUTES
231 with rmap.submapper(path_prefix=ADMIN_PREFIX,
230 with rmap.submapper(path_prefix=ADMIN_PREFIX,
232 controller='admin/users_groups') as m:
231 controller='admin/users_groups') as m:
233 m.connect("users_groups", "/users_groups",
232 m.connect("users_groups", "/users_groups",
234 action="create", conditions=dict(method=["POST"]))
233 action="create", conditions=dict(method=["POST"]))
235 m.connect("users_groups", "/users_groups",
234 m.connect("users_groups", "/users_groups",
236 action="index", conditions=dict(method=["GET"]))
235 action="index", conditions=dict(method=["GET"]))
237 m.connect("formatted_users_groups", "/users_groups.{format}",
236 m.connect("formatted_users_groups", "/users_groups.{format}",
238 action="index", conditions=dict(method=["GET"]))
237 action="index", conditions=dict(method=["GET"]))
239 m.connect("new_users_group", "/users_groups/new",
238 m.connect("new_users_group", "/users_groups/new",
240 action="new", conditions=dict(method=["GET"]))
239 action="new", conditions=dict(method=["GET"]))
241 m.connect("formatted_new_users_group", "/users_groups/new.{format}",
240 m.connect("formatted_new_users_group", "/users_groups/new.{format}",
242 action="new", conditions=dict(method=["GET"]))
241 action="new", conditions=dict(method=["GET"]))
243 m.connect("update_users_group", "/users_groups/{id}",
242 m.connect("update_users_group", "/users_groups/{id}",
244 action="update", conditions=dict(method=["PUT"]))
243 action="update", conditions=dict(method=["PUT"]))
245 m.connect("delete_users_group", "/users_groups/{id}",
244 m.connect("delete_users_group", "/users_groups/{id}",
246 action="delete", conditions=dict(method=["DELETE"]))
245 action="delete", conditions=dict(method=["DELETE"]))
247 m.connect("edit_users_group", "/users_groups/{id}/edit",
246 m.connect("edit_users_group", "/users_groups/{id}/edit",
248 action="edit", conditions=dict(method=["GET"]))
247 action="edit", conditions=dict(method=["GET"]))
249 m.connect("formatted_edit_users_group",
248 m.connect("formatted_edit_users_group",
250 "/users_groups/{id}.{format}/edit",
249 "/users_groups/{id}.{format}/edit",
251 action="edit", conditions=dict(method=["GET"]))
250 action="edit", conditions=dict(method=["GET"]))
252 m.connect("users_group", "/users_groups/{id}",
251 m.connect("users_group", "/users_groups/{id}",
253 action="show", conditions=dict(method=["GET"]))
252 action="show", conditions=dict(method=["GET"]))
254 m.connect("formatted_users_group", "/users_groups/{id}.{format}",
253 m.connect("formatted_users_group", "/users_groups/{id}.{format}",
255 action="show", conditions=dict(method=["GET"]))
254 action="show", conditions=dict(method=["GET"]))
256
255
257 #EXTRAS USER ROUTES
256 #EXTRAS USER ROUTES
258 m.connect("users_group_perm", "/users_groups_perm/{id}",
257 m.connect("users_group_perm", "/users_groups_perm/{id}",
259 action="update_perm", conditions=dict(method=["PUT"]))
258 action="update_perm", conditions=dict(method=["PUT"]))
260
259
261 #ADMIN GROUP REST ROUTES
260 #ADMIN GROUP REST ROUTES
262 rmap.resource('group', 'groups',
261 rmap.resource('group', 'groups',
263 controller='admin/groups', path_prefix=ADMIN_PREFIX)
262 controller='admin/groups', path_prefix=ADMIN_PREFIX)
264
263
265 #ADMIN PERMISSIONS REST ROUTES
264 #ADMIN PERMISSIONS REST ROUTES
266 rmap.resource('permission', 'permissions',
265 rmap.resource('permission', 'permissions',
267 controller='admin/permissions', path_prefix=ADMIN_PREFIX)
266 controller='admin/permissions', path_prefix=ADMIN_PREFIX)
268
267
269 #ADMIN DEFAULTS REST ROUTES
268 #ADMIN DEFAULTS REST ROUTES
270 rmap.resource('default', 'defaults',
269 rmap.resource('default', 'defaults',
271 controller='admin/defaults', path_prefix=ADMIN_PREFIX)
270 controller='admin/defaults', path_prefix=ADMIN_PREFIX)
272
271
273 ##ADMIN LDAP SETTINGS
272 ##ADMIN LDAP SETTINGS
274 rmap.connect('ldap_settings', '%s/ldap' % ADMIN_PREFIX,
273 rmap.connect('ldap_settings', '%s/ldap' % ADMIN_PREFIX,
275 controller='admin/ldap_settings', action='ldap_settings',
274 controller='admin/ldap_settings', action='ldap_settings',
276 conditions=dict(method=["POST"]))
275 conditions=dict(method=["POST"]))
277
276
278 rmap.connect('ldap_home', '%s/ldap' % ADMIN_PREFIX,
277 rmap.connect('ldap_home', '%s/ldap' % ADMIN_PREFIX,
279 controller='admin/ldap_settings')
278 controller='admin/ldap_settings')
280
279
281 #ADMIN SETTINGS REST ROUTES
280 #ADMIN SETTINGS REST ROUTES
282 with rmap.submapper(path_prefix=ADMIN_PREFIX,
281 with rmap.submapper(path_prefix=ADMIN_PREFIX,
283 controller='admin/settings') as m:
282 controller='admin/settings') as m:
284 m.connect("admin_settings", "/settings",
283 m.connect("admin_settings", "/settings",
285 action="create", conditions=dict(method=["POST"]))
284 action="create", conditions=dict(method=["POST"]))
286 m.connect("admin_settings", "/settings",
285 m.connect("admin_settings", "/settings",
287 action="index", conditions=dict(method=["GET"]))
286 action="index", conditions=dict(method=["GET"]))
288 m.connect("formatted_admin_settings", "/settings.{format}",
287 m.connect("formatted_admin_settings", "/settings.{format}",
289 action="index", conditions=dict(method=["GET"]))
288 action="index", conditions=dict(method=["GET"]))
290 m.connect("admin_new_setting", "/settings/new",
289 m.connect("admin_new_setting", "/settings/new",
291 action="new", conditions=dict(method=["GET"]))
290 action="new", conditions=dict(method=["GET"]))
292 m.connect("formatted_admin_new_setting", "/settings/new.{format}",
291 m.connect("formatted_admin_new_setting", "/settings/new.{format}",
293 action="new", conditions=dict(method=["GET"]))
292 action="new", conditions=dict(method=["GET"]))
294 m.connect("/settings/{setting_id}",
293 m.connect("/settings/{setting_id}",
295 action="update", conditions=dict(method=["PUT"]))
294 action="update", conditions=dict(method=["PUT"]))
296 m.connect("/settings/{setting_id}",
295 m.connect("/settings/{setting_id}",
297 action="delete", conditions=dict(method=["DELETE"]))
296 action="delete", conditions=dict(method=["DELETE"]))
298 m.connect("admin_edit_setting", "/settings/{setting_id}/edit",
297 m.connect("admin_edit_setting", "/settings/{setting_id}/edit",
299 action="edit", conditions=dict(method=["GET"]))
298 action="edit", conditions=dict(method=["GET"]))
300 m.connect("formatted_admin_edit_setting",
299 m.connect("formatted_admin_edit_setting",
301 "/settings/{setting_id}.{format}/edit",
300 "/settings/{setting_id}.{format}/edit",
302 action="edit", conditions=dict(method=["GET"]))
301 action="edit", conditions=dict(method=["GET"]))
303 m.connect("admin_setting", "/settings/{setting_id}",
302 m.connect("admin_setting", "/settings/{setting_id}",
304 action="show", conditions=dict(method=["GET"]))
303 action="show", conditions=dict(method=["GET"]))
305 m.connect("formatted_admin_setting", "/settings/{setting_id}.{format}",
304 m.connect("formatted_admin_setting", "/settings/{setting_id}.{format}",
306 action="show", conditions=dict(method=["GET"]))
305 action="show", conditions=dict(method=["GET"]))
307 m.connect("admin_settings_my_account", "/my_account",
306 m.connect("admin_settings_my_account", "/my_account",
308 action="my_account", conditions=dict(method=["GET"]))
307 action="my_account", conditions=dict(method=["GET"]))
309 m.connect("admin_settings_my_account_update", "/my_account_update",
308 m.connect("admin_settings_my_account_update", "/my_account_update",
310 action="my_account_update", conditions=dict(method=["PUT"]))
309 action="my_account_update", conditions=dict(method=["PUT"]))
311 m.connect("admin_settings_create_repository", "/create_repository",
310 m.connect("admin_settings_create_repository", "/create_repository",
312 action="create_repository", conditions=dict(method=["GET"]))
311 action="create_repository", conditions=dict(method=["GET"]))
313 m.connect("admin_settings_my_repos", "/my_account/repos",
312 m.connect("admin_settings_my_repos", "/my_account/repos",
314 action="my_account_my_repos", conditions=dict(method=["GET"]))
313 action="my_account_my_repos", conditions=dict(method=["GET"]))
315 m.connect("admin_settings_my_pullrequests", "/my_account/pull_requests",
314 m.connect("admin_settings_my_pullrequests", "/my_account/pull_requests",
316 action="my_account_my_pullrequests", conditions=dict(method=["GET"]))
315 action="my_account_my_pullrequests", conditions=dict(method=["GET"]))
317
316
318 #NOTIFICATION REST ROUTES
317 #NOTIFICATION REST ROUTES
319 with rmap.submapper(path_prefix=ADMIN_PREFIX,
318 with rmap.submapper(path_prefix=ADMIN_PREFIX,
320 controller='admin/notifications') as m:
319 controller='admin/notifications') as m:
321 m.connect("notifications", "/notifications",
320 m.connect("notifications", "/notifications",
322 action="create", conditions=dict(method=["POST"]))
321 action="create", conditions=dict(method=["POST"]))
323 m.connect("notifications", "/notifications",
322 m.connect("notifications", "/notifications",
324 action="index", conditions=dict(method=["GET"]))
323 action="index", conditions=dict(method=["GET"]))
325 m.connect("notifications_mark_all_read", "/notifications/mark_all_read",
324 m.connect("notifications_mark_all_read", "/notifications/mark_all_read",
326 action="mark_all_read", conditions=dict(method=["GET"]))
325 action="mark_all_read", conditions=dict(method=["GET"]))
327 m.connect("formatted_notifications", "/notifications.{format}",
326 m.connect("formatted_notifications", "/notifications.{format}",
328 action="index", conditions=dict(method=["GET"]))
327 action="index", conditions=dict(method=["GET"]))
329 m.connect("new_notification", "/notifications/new",
328 m.connect("new_notification", "/notifications/new",
330 action="new", conditions=dict(method=["GET"]))
329 action="new", conditions=dict(method=["GET"]))
331 m.connect("formatted_new_notification", "/notifications/new.{format}",
330 m.connect("formatted_new_notification", "/notifications/new.{format}",
332 action="new", conditions=dict(method=["GET"]))
331 action="new", conditions=dict(method=["GET"]))
333 m.connect("/notification/{notification_id}",
332 m.connect("/notification/{notification_id}",
334 action="update", conditions=dict(method=["PUT"]))
333 action="update", conditions=dict(method=["PUT"]))
335 m.connect("/notification/{notification_id}",
334 m.connect("/notification/{notification_id}",
336 action="delete", conditions=dict(method=["DELETE"]))
335 action="delete", conditions=dict(method=["DELETE"]))
337 m.connect("edit_notification", "/notification/{notification_id}/edit",
336 m.connect("edit_notification", "/notification/{notification_id}/edit",
338 action="edit", conditions=dict(method=["GET"]))
337 action="edit", conditions=dict(method=["GET"]))
339 m.connect("formatted_edit_notification",
338 m.connect("formatted_edit_notification",
340 "/notification/{notification_id}.{format}/edit",
339 "/notification/{notification_id}.{format}/edit",
341 action="edit", conditions=dict(method=["GET"]))
340 action="edit", conditions=dict(method=["GET"]))
342 m.connect("notification", "/notification/{notification_id}",
341 m.connect("notification", "/notification/{notification_id}",
343 action="show", conditions=dict(method=["GET"]))
342 action="show", conditions=dict(method=["GET"]))
344 m.connect("formatted_notification", "/notifications/{notification_id}.{format}",
343 m.connect("formatted_notification", "/notifications/{notification_id}.{format}",
345 action="show", conditions=dict(method=["GET"]))
344 action="show", conditions=dict(method=["GET"]))
346
345
347 #ADMIN MAIN PAGES
346 #ADMIN MAIN PAGES
348 with rmap.submapper(path_prefix=ADMIN_PREFIX,
347 with rmap.submapper(path_prefix=ADMIN_PREFIX,
349 controller='admin/admin') as m:
348 controller='admin/admin') as m:
350 m.connect('admin_home', '', action='index')
349 m.connect('admin_home', '', action='index')
351 m.connect('admin_add_repo', '/add_repo/{new_repo:[a-z0-9\. _-]*}',
350 m.connect('admin_add_repo', '/add_repo/{new_repo:[a-z0-9\. _-]*}',
352 action='add_repo')
351 action='add_repo')
353
352
354 #==========================================================================
353 #==========================================================================
355 # API V2
354 # API V2
356 #==========================================================================
355 #==========================================================================
357 with rmap.submapper(path_prefix=ADMIN_PREFIX,
356 with rmap.submapper(path_prefix=ADMIN_PREFIX,
358 controller='api/api') as m:
357 controller='api/api') as m:
359 m.connect('api', '/api')
358 m.connect('api', '/api')
360
359
361 #USER JOURNAL
360 #USER JOURNAL
362 rmap.connect('journal', '%s/journal' % ADMIN_PREFIX,
361 rmap.connect('journal', '%s/journal' % ADMIN_PREFIX,
363 controller='journal', action='index')
362 controller='journal', action='index')
364 rmap.connect('journal_rss', '%s/journal/rss' % ADMIN_PREFIX,
363 rmap.connect('journal_rss', '%s/journal/rss' % ADMIN_PREFIX,
365 controller='journal', action='journal_rss')
364 controller='journal', action='journal_rss')
366 rmap.connect('journal_atom', '%s/journal/atom' % ADMIN_PREFIX,
365 rmap.connect('journal_atom', '%s/journal/atom' % ADMIN_PREFIX,
367 controller='journal', action='journal_atom')
366 controller='journal', action='journal_atom')
368
367
369 rmap.connect('public_journal', '%s/public_journal' % ADMIN_PREFIX,
368 rmap.connect('public_journal', '%s/public_journal' % ADMIN_PREFIX,
370 controller='journal', action="public_journal")
369 controller='journal', action="public_journal")
371
370
372 rmap.connect('public_journal_rss', '%s/public_journal/rss' % ADMIN_PREFIX,
371 rmap.connect('public_journal_rss', '%s/public_journal/rss' % ADMIN_PREFIX,
373 controller='journal', action="public_journal_rss")
372 controller='journal', action="public_journal_rss")
374
373
375 rmap.connect('public_journal_rss_old', '%s/public_journal_rss' % ADMIN_PREFIX,
374 rmap.connect('public_journal_rss_old', '%s/public_journal_rss' % ADMIN_PREFIX,
376 controller='journal', action="public_journal_rss")
375 controller='journal', action="public_journal_rss")
377
376
378 rmap.connect('public_journal_atom',
377 rmap.connect('public_journal_atom',
379 '%s/public_journal/atom' % ADMIN_PREFIX, controller='journal',
378 '%s/public_journal/atom' % ADMIN_PREFIX, controller='journal',
380 action="public_journal_atom")
379 action="public_journal_atom")
381
380
382 rmap.connect('public_journal_atom_old',
381 rmap.connect('public_journal_atom_old',
383 '%s/public_journal_atom' % ADMIN_PREFIX, controller='journal',
382 '%s/public_journal_atom' % ADMIN_PREFIX, controller='journal',
384 action="public_journal_atom")
383 action="public_journal_atom")
385
384
386 rmap.connect('toggle_following', '%s/toggle_following' % ADMIN_PREFIX,
385 rmap.connect('toggle_following', '%s/toggle_following' % ADMIN_PREFIX,
387 controller='journal', action='toggle_following',
386 controller='journal', action='toggle_following',
388 conditions=dict(method=["POST"]))
387 conditions=dict(method=["POST"]))
389
388
390 #SEARCH
389 #SEARCH
391 rmap.connect('search', '%s/search' % ADMIN_PREFIX, controller='search',)
390 rmap.connect('search', '%s/search' % ADMIN_PREFIX, controller='search',)
392 rmap.connect('search_repo', '%s/search/{search_repo:.*}' % ADMIN_PREFIX,
391 rmap.connect('search_repo', '%s/search/{search_repo:.*}' % ADMIN_PREFIX,
393 controller='search')
392 controller='search')
394
393
395 #LOGIN/LOGOUT/REGISTER/SIGN IN
394 #LOGIN/LOGOUT/REGISTER/SIGN IN
396 rmap.connect('login_home', '%s/login' % ADMIN_PREFIX, controller='login')
395 rmap.connect('login_home', '%s/login' % ADMIN_PREFIX, controller='login')
397 rmap.connect('logout_home', '%s/logout' % ADMIN_PREFIX, controller='login',
396 rmap.connect('logout_home', '%s/logout' % ADMIN_PREFIX, controller='login',
398 action='logout')
397 action='logout')
399
398
400 rmap.connect('register', '%s/register' % ADMIN_PREFIX, controller='login',
399 rmap.connect('register', '%s/register' % ADMIN_PREFIX, controller='login',
401 action='register')
400 action='register')
402
401
403 rmap.connect('reset_password', '%s/password_reset' % ADMIN_PREFIX,
402 rmap.connect('reset_password', '%s/password_reset' % ADMIN_PREFIX,
404 controller='login', action='password_reset')
403 controller='login', action='password_reset')
405
404
406 rmap.connect('reset_password_confirmation',
405 rmap.connect('reset_password_confirmation',
407 '%s/password_reset_confirmation' % ADMIN_PREFIX,
406 '%s/password_reset_confirmation' % ADMIN_PREFIX,
408 controller='login', action='password_reset_confirmation')
407 controller='login', action='password_reset_confirmation')
409
408
410 #FEEDS
409 #FEEDS
411 rmap.connect('rss_feed_home', '/{repo_name:.*?}/feed/rss',
410 rmap.connect('rss_feed_home', '/{repo_name:.*?}/feed/rss',
412 controller='feed', action='rss',
411 controller='feed', action='rss',
413 conditions=dict(function=check_repo))
412 conditions=dict(function=check_repo))
414
413
415 rmap.connect('atom_feed_home', '/{repo_name:.*?}/feed/atom',
414 rmap.connect('atom_feed_home', '/{repo_name:.*?}/feed/atom',
416 controller='feed', action='atom',
415 controller='feed', action='atom',
417 conditions=dict(function=check_repo))
416 conditions=dict(function=check_repo))
418
417
419 #==========================================================================
418 #==========================================================================
420 # REPOSITORY ROUTES
419 # REPOSITORY ROUTES
421 #==========================================================================
420 #==========================================================================
422 rmap.connect('summary_home', '/{repo_name:.*?}',
421 rmap.connect('summary_home', '/{repo_name:.*?}',
423 controller='summary',
422 controller='summary',
424 conditions=dict(function=check_repo))
423 conditions=dict(function=check_repo))
425
424
426 rmap.connect('repos_group_home', '/{group_name:.*}',
425 rmap.connect('repos_group_home', '/{group_name:.*}',
427 controller='admin/repos_groups', action="show_by_name",
426 controller='admin/repos_groups', action="show_by_name",
428 conditions=dict(function=check_group))
427 conditions=dict(function=check_group))
429
428
430 rmap.connect('changeset_home', '/{repo_name:.*?}/changeset/{revision}',
429 rmap.connect('changeset_home', '/{repo_name:.*?}/changeset/{revision}',
431 controller='changeset', revision='tip',
430 controller='changeset', revision='tip',
432 conditions=dict(function=check_repo))
431 conditions=dict(function=check_repo))
433
432
434 #still working url for backward compat.
433 #still working url for backward compat.
435 rmap.connect('raw_changeset_home_depraced',
434 rmap.connect('raw_changeset_home_depraced',
436 '/{repo_name:.*?}/raw-changeset/{revision}',
435 '/{repo_name:.*?}/raw-changeset/{revision}',
437 controller='changeset', action='changeset_raw',
436 controller='changeset', action='changeset_raw',
438 revision='tip', conditions=dict(function=check_repo))
437 revision='tip', conditions=dict(function=check_repo))
439
438
440 ## new URLs
439 ## new URLs
441 rmap.connect('changeset_raw_home',
440 rmap.connect('changeset_raw_home',
442 '/{repo_name:.*?}/changeset-diff/{revision}',
441 '/{repo_name:.*?}/changeset-diff/{revision}',
443 controller='changeset', action='changeset_raw',
442 controller='changeset', action='changeset_raw',
444 revision='tip', conditions=dict(function=check_repo))
443 revision='tip', conditions=dict(function=check_repo))
445
444
446 rmap.connect('changeset_patch_home',
445 rmap.connect('changeset_patch_home',
447 '/{repo_name:.*?}/changeset-patch/{revision}',
446 '/{repo_name:.*?}/changeset-patch/{revision}',
448 controller='changeset', action='changeset_patch',
447 controller='changeset', action='changeset_patch',
449 revision='tip', conditions=dict(function=check_repo))
448 revision='tip', conditions=dict(function=check_repo))
450
449
451 rmap.connect('changeset_download_home',
450 rmap.connect('changeset_download_home',
452 '/{repo_name:.*?}/changeset-download/{revision}',
451 '/{repo_name:.*?}/changeset-download/{revision}',
453 controller='changeset', action='changeset_download',
452 controller='changeset', action='changeset_download',
454 revision='tip', conditions=dict(function=check_repo))
453 revision='tip', conditions=dict(function=check_repo))
455
454
456 rmap.connect('changeset_comment',
455 rmap.connect('changeset_comment',
457 '/{repo_name:.*?}/changeset/{revision}/comment',
456 '/{repo_name:.*?}/changeset/{revision}/comment',
458 controller='changeset', revision='tip', action='comment',
457 controller='changeset', revision='tip', action='comment',
459 conditions=dict(function=check_repo))
458 conditions=dict(function=check_repo))
460
459
461 rmap.connect('changeset_comment_delete',
460 rmap.connect('changeset_comment_delete',
462 '/{repo_name:.*?}/changeset/comment/{comment_id}/delete',
461 '/{repo_name:.*?}/changeset/comment/{comment_id}/delete',
463 controller='changeset', action='delete_comment',
462 controller='changeset', action='delete_comment',
464 conditions=dict(function=check_repo, method=["DELETE"]))
463 conditions=dict(function=check_repo, method=["DELETE"]))
465
464
466 rmap.connect('changeset_info', '/changeset_info/{repo_name:.*?}/{revision}',
465 rmap.connect('changeset_info', '/changeset_info/{repo_name:.*?}/{revision}',
467 controller='changeset', action='changeset_info')
466 controller='changeset', action='changeset_info')
468
467
469 rmap.connect('compare_url',
468 rmap.connect('compare_url',
470 '/{repo_name:.*?}/compare/{org_ref_type}@{org_ref:.*?}...{other_ref_type}@{other_ref:.*?}',
469 '/{repo_name:.*?}/compare/{org_ref_type}@{org_ref:.*?}...{other_ref_type}@{other_ref:.*?}',
471 controller='compare', action='index',
470 controller='compare', action='index',
472 conditions=dict(function=check_repo),
471 conditions=dict(function=check_repo),
473 requirements=dict(
472 requirements=dict(
474 org_ref_type='(branch|book|tag|rev|org_ref_type)',
473 org_ref_type='(branch|book|tag|rev|org_ref_type)',
475 other_ref_type='(branch|book|tag|rev|other_ref_type)')
474 other_ref_type='(branch|book|tag|rev|other_ref_type)')
476 )
475 )
477
476
478 rmap.connect('pullrequest_home',
477 rmap.connect('pullrequest_home',
479 '/{repo_name:.*?}/pull-request/new', controller='pullrequests',
478 '/{repo_name:.*?}/pull-request/new', controller='pullrequests',
480 action='index', conditions=dict(function=check_repo,
479 action='index', conditions=dict(function=check_repo,
481 method=["GET"]))
480 method=["GET"]))
482
481
483 rmap.connect('pullrequest',
482 rmap.connect('pullrequest',
484 '/{repo_name:.*?}/pull-request/new', controller='pullrequests',
483 '/{repo_name:.*?}/pull-request/new', controller='pullrequests',
485 action='create', conditions=dict(function=check_repo,
484 action='create', conditions=dict(function=check_repo,
486 method=["POST"]))
485 method=["POST"]))
487
486
488 rmap.connect('pullrequest_show',
487 rmap.connect('pullrequest_show',
489 '/{repo_name:.*?}/pull-request/{pull_request_id}',
488 '/{repo_name:.*?}/pull-request/{pull_request_id}',
490 controller='pullrequests',
489 controller='pullrequests',
491 action='show', conditions=dict(function=check_repo,
490 action='show', conditions=dict(function=check_repo,
492 method=["GET"]))
491 method=["GET"]))
493 rmap.connect('pullrequest_update',
492 rmap.connect('pullrequest_update',
494 '/{repo_name:.*?}/pull-request/{pull_request_id}',
493 '/{repo_name:.*?}/pull-request/{pull_request_id}',
495 controller='pullrequests',
494 controller='pullrequests',
496 action='update', conditions=dict(function=check_repo,
495 action='update', conditions=dict(function=check_repo,
497 method=["PUT"]))
496 method=["PUT"]))
498 rmap.connect('pullrequest_delete',
497 rmap.connect('pullrequest_delete',
499 '/{repo_name:.*?}/pull-request/{pull_request_id}',
498 '/{repo_name:.*?}/pull-request/{pull_request_id}',
500 controller='pullrequests',
499 controller='pullrequests',
501 action='delete', conditions=dict(function=check_repo,
500 action='delete', conditions=dict(function=check_repo,
502 method=["DELETE"]))
501 method=["DELETE"]))
503
502
504 rmap.connect('pullrequest_show_all',
503 rmap.connect('pullrequest_show_all',
505 '/{repo_name:.*?}/pull-request',
504 '/{repo_name:.*?}/pull-request',
506 controller='pullrequests',
505 controller='pullrequests',
507 action='show_all', conditions=dict(function=check_repo,
506 action='show_all', conditions=dict(function=check_repo,
508 method=["GET"]))
507 method=["GET"]))
509
508
510 rmap.connect('pullrequest_comment',
509 rmap.connect('pullrequest_comment',
511 '/{repo_name:.*?}/pull-request-comment/{pull_request_id}',
510 '/{repo_name:.*?}/pull-request-comment/{pull_request_id}',
512 controller='pullrequests',
511 controller='pullrequests',
513 action='comment', conditions=dict(function=check_repo,
512 action='comment', conditions=dict(function=check_repo,
514 method=["POST"]))
513 method=["POST"]))
515
514
516 rmap.connect('pullrequest_comment_delete',
515 rmap.connect('pullrequest_comment_delete',
517 '/{repo_name:.*?}/pull-request-comment/{comment_id}/delete',
516 '/{repo_name:.*?}/pull-request-comment/{comment_id}/delete',
518 controller='pullrequests', action='delete_comment',
517 controller='pullrequests', action='delete_comment',
519 conditions=dict(function=check_repo, method=["DELETE"]))
518 conditions=dict(function=check_repo, method=["DELETE"]))
520
519
521 rmap.connect('summary_home', '/{repo_name:.*?}/summary',
520 rmap.connect('summary_home', '/{repo_name:.*?}/summary',
522 controller='summary', conditions=dict(function=check_repo))
521 controller='summary', conditions=dict(function=check_repo))
523
522
524 rmap.connect('shortlog_home', '/{repo_name:.*?}/shortlog',
523 rmap.connect('shortlog_home', '/{repo_name:.*?}/shortlog',
525 controller='shortlog', conditions=dict(function=check_repo))
524 controller='shortlog', conditions=dict(function=check_repo))
526
525
527 rmap.connect('shortlog_file_home', '/{repo_name:.*?}/shortlog/{revision}/{f_path:.*}',
526 rmap.connect('shortlog_file_home', '/{repo_name:.*?}/shortlog/{revision}/{f_path:.*}',
528 controller='shortlog', f_path=None,
527 controller='shortlog', f_path=None,
529 conditions=dict(function=check_repo))
528 conditions=dict(function=check_repo))
530
529
531 rmap.connect('branches_home', '/{repo_name:.*?}/branches',
530 rmap.connect('branches_home', '/{repo_name:.*?}/branches',
532 controller='branches', conditions=dict(function=check_repo))
531 controller='branches', conditions=dict(function=check_repo))
533
532
534 rmap.connect('tags_home', '/{repo_name:.*?}/tags',
533 rmap.connect('tags_home', '/{repo_name:.*?}/tags',
535 controller='tags', conditions=dict(function=check_repo))
534 controller='tags', conditions=dict(function=check_repo))
536
535
537 rmap.connect('bookmarks_home', '/{repo_name:.*?}/bookmarks',
536 rmap.connect('bookmarks_home', '/{repo_name:.*?}/bookmarks',
538 controller='bookmarks', conditions=dict(function=check_repo))
537 controller='bookmarks', conditions=dict(function=check_repo))
539
538
540 rmap.connect('changelog_home', '/{repo_name:.*?}/changelog',
539 rmap.connect('changelog_home', '/{repo_name:.*?}/changelog',
541 controller='changelog', conditions=dict(function=check_repo))
540 controller='changelog', conditions=dict(function=check_repo))
542
541
543 rmap.connect('changelog_details', '/{repo_name:.*?}/changelog_details/{cs}',
542 rmap.connect('changelog_details', '/{repo_name:.*?}/changelog_details/{cs}',
544 controller='changelog', action='changelog_details',
543 controller='changelog', action='changelog_details',
545 conditions=dict(function=check_repo))
544 conditions=dict(function=check_repo))
546
545
547 rmap.connect('files_home', '/{repo_name:.*?}/files/{revision}/{f_path:.*}',
546 rmap.connect('files_home', '/{repo_name:.*?}/files/{revision}/{f_path:.*}',
548 controller='files', revision='tip', f_path='',
547 controller='files', revision='tip', f_path='',
549 conditions=dict(function=check_repo))
548 conditions=dict(function=check_repo))
550
549
551 rmap.connect('files_history_home',
550 rmap.connect('files_history_home',
552 '/{repo_name:.*?}/history/{revision}/{f_path:.*}',
551 '/{repo_name:.*?}/history/{revision}/{f_path:.*}',
553 controller='files', action='history', revision='tip', f_path='',
552 controller='files', action='history', revision='tip', f_path='',
554 conditions=dict(function=check_repo))
553 conditions=dict(function=check_repo))
555
554
556 rmap.connect('files_diff_home', '/{repo_name:.*?}/diff/{f_path:.*}',
555 rmap.connect('files_diff_home', '/{repo_name:.*?}/diff/{f_path:.*}',
557 controller='files', action='diff', revision='tip', f_path='',
556 controller='files', action='diff', revision='tip', f_path='',
558 conditions=dict(function=check_repo))
557 conditions=dict(function=check_repo))
559
558
560 rmap.connect('files_rawfile_home',
559 rmap.connect('files_rawfile_home',
561 '/{repo_name:.*?}/rawfile/{revision}/{f_path:.*}',
560 '/{repo_name:.*?}/rawfile/{revision}/{f_path:.*}',
562 controller='files', action='rawfile', revision='tip',
561 controller='files', action='rawfile', revision='tip',
563 f_path='', conditions=dict(function=check_repo))
562 f_path='', conditions=dict(function=check_repo))
564
563
565 rmap.connect('files_raw_home',
564 rmap.connect('files_raw_home',
566 '/{repo_name:.*?}/raw/{revision}/{f_path:.*}',
565 '/{repo_name:.*?}/raw/{revision}/{f_path:.*}',
567 controller='files', action='raw', revision='tip', f_path='',
566 controller='files', action='raw', revision='tip', f_path='',
568 conditions=dict(function=check_repo))
567 conditions=dict(function=check_repo))
569
568
570 rmap.connect('files_annotate_home',
569 rmap.connect('files_annotate_home',
571 '/{repo_name:.*?}/annotate/{revision}/{f_path:.*}',
570 '/{repo_name:.*?}/annotate/{revision}/{f_path:.*}',
572 controller='files', action='index', revision='tip',
571 controller='files', action='index', revision='tip',
573 f_path='', annotate=True, conditions=dict(function=check_repo))
572 f_path='', annotate=True, conditions=dict(function=check_repo))
574
573
575 rmap.connect('files_edit_home',
574 rmap.connect('files_edit_home',
576 '/{repo_name:.*?}/edit/{revision}/{f_path:.*}',
575 '/{repo_name:.*?}/edit/{revision}/{f_path:.*}',
577 controller='files', action='edit', revision='tip',
576 controller='files', action='edit', revision='tip',
578 f_path='', conditions=dict(function=check_repo))
577 f_path='', conditions=dict(function=check_repo))
579
578
580 rmap.connect('files_add_home',
579 rmap.connect('files_add_home',
581 '/{repo_name:.*?}/add/{revision}/{f_path:.*}',
580 '/{repo_name:.*?}/add/{revision}/{f_path:.*}',
582 controller='files', action='add', revision='tip',
581 controller='files', action='add', revision='tip',
583 f_path='', conditions=dict(function=check_repo))
582 f_path='', conditions=dict(function=check_repo))
584
583
585 rmap.connect('files_archive_home', '/{repo_name:.*?}/archive/{fname}',
584 rmap.connect('files_archive_home', '/{repo_name:.*?}/archive/{fname}',
586 controller='files', action='archivefile',
585 controller='files', action='archivefile',
587 conditions=dict(function=check_repo))
586 conditions=dict(function=check_repo))
588
587
589 rmap.connect('files_nodelist_home',
588 rmap.connect('files_nodelist_home',
590 '/{repo_name:.*?}/nodelist/{revision}/{f_path:.*}',
589 '/{repo_name:.*?}/nodelist/{revision}/{f_path:.*}',
591 controller='files', action='nodelist',
590 controller='files', action='nodelist',
592 conditions=dict(function=check_repo))
591 conditions=dict(function=check_repo))
593
592
594 rmap.connect('repo_settings_delete', '/{repo_name:.*?}/settings',
593 rmap.connect('repo_settings_delete', '/{repo_name:.*?}/settings',
595 controller='settings', action="delete",
594 controller='settings', action="delete",
596 conditions=dict(method=["DELETE"], function=check_repo))
595 conditions=dict(method=["DELETE"], function=check_repo))
597
596
598 rmap.connect('repo_settings_update', '/{repo_name:.*?}/settings',
597 rmap.connect('repo_settings_update', '/{repo_name:.*?}/settings',
599 controller='settings', action="update",
598 controller='settings', action="update",
600 conditions=dict(method=["PUT"], function=check_repo))
599 conditions=dict(method=["PUT"], function=check_repo))
601
600
602 rmap.connect('repo_settings_home', '/{repo_name:.*?}/settings',
601 rmap.connect('repo_settings_home', '/{repo_name:.*?}/settings',
603 controller='settings', action='index',
602 controller='settings', action='index',
604 conditions=dict(function=check_repo))
603 conditions=dict(function=check_repo))
605
604
606 rmap.connect('toggle_locking', "/{repo_name:.*?}/locking_toggle",
605 rmap.connect('toggle_locking', "/{repo_name:.*?}/locking_toggle",
607 controller='settings', action="toggle_locking",
606 controller='settings', action="toggle_locking",
608 conditions=dict(method=["GET"], function=check_repo))
607 conditions=dict(method=["GET"], function=check_repo))
609
608
610 rmap.connect('repo_fork_create_home', '/{repo_name:.*?}/fork',
609 rmap.connect('repo_fork_create_home', '/{repo_name:.*?}/fork',
611 controller='forks', action='fork_create',
610 controller='forks', action='fork_create',
612 conditions=dict(function=check_repo, method=["POST"]))
611 conditions=dict(function=check_repo, method=["POST"]))
613
612
614 rmap.connect('repo_fork_home', '/{repo_name:.*?}/fork',
613 rmap.connect('repo_fork_home', '/{repo_name:.*?}/fork',
615 controller='forks', action='fork',
614 controller='forks', action='fork',
616 conditions=dict(function=check_repo))
615 conditions=dict(function=check_repo))
617
616
618 rmap.connect('repo_forks_home', '/{repo_name:.*?}/forks',
617 rmap.connect('repo_forks_home', '/{repo_name:.*?}/forks',
619 controller='forks', action='forks',
618 controller='forks', action='forks',
620 conditions=dict(function=check_repo))
619 conditions=dict(function=check_repo))
621
620
622 rmap.connect('repo_followers_home', '/{repo_name:.*?}/followers',
621 rmap.connect('repo_followers_home', '/{repo_name:.*?}/followers',
623 controller='followers', action='followers',
622 controller='followers', action='followers',
624 conditions=dict(function=check_repo))
623 conditions=dict(function=check_repo))
625
624
626 return rmap
625 return rmap
@@ -1,330 +1,378 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 Repositories 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 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
42 HasReposGroupPermissionAnyDecorator, HasReposGroupPermissionAll,\
43 HasPermissionAll
43 from rhodecode.lib.base import BaseController, render
44 from rhodecode.lib.base import BaseController, render
44 from rhodecode.model.db import RepoGroup, Repository
45 from rhodecode.model.db import RepoGroup, Repository
45 from rhodecode.model.repos_group import ReposGroupModel
46 from rhodecode.model.repos_group import ReposGroupModel
46 from rhodecode.model.forms import ReposGroupForm
47 from rhodecode.model.forms import ReposGroupForm
47 from rhodecode.model.meta import Session
48 from rhodecode.model.meta import Session
48 from rhodecode.model.repo import RepoModel
49 from rhodecode.model.repo import RepoModel
49 from webob.exc import HTTPInternalServerError, HTTPNotFound
50 from webob.exc import HTTPInternalServerError, HTTPNotFound
50 from rhodecode.lib.utils2 import str2bool
51 from rhodecode.lib.utils2 import str2bool, safe_int
51 from sqlalchemy.sql.expression import func
52 from sqlalchemy.sql.expression import func
53 from rhodecode.model.scm import GroupList
52
54
53 log = logging.getLogger(__name__)
55 log = logging.getLogger(__name__)
54
56
55
57
56 class ReposGroupsController(BaseController):
58 class ReposGroupsController(BaseController):
57 """REST Controller styled on the Atom Publishing Protocol"""
59 """REST Controller styled on the Atom Publishing Protocol"""
58 # To properly map this controller, ensure your config/routing.py
60 # To properly map this controller, ensure your config/routing.py
59 # file has a resource setup:
61 # file has a resource setup:
60 # map.resource('repos_group', 'repos_groups')
62 # map.resource('repos_group', 'repos_groups')
61
63
62 @LoginRequired()
64 @LoginRequired()
63 def __before__(self):
65 def __before__(self):
64 super(ReposGroupsController, self).__before__()
66 super(ReposGroupsController, self).__before__()
65
67
66 def __load_defaults(self):
68 def __load_defaults(self, allow_empty_group=False, exclude_group_ids=[]):
67 c.repo_groups = RepoGroup.groups_choices()
69 if HasPermissionAll('hg.admin')('group edit'):
70 #we're global admin, we're ok and we can create TOP level groups
71 allow_empty_group = True
72
73 #override the choices for this form, we need to filter choices
74 #and display only those we have ADMIN right
75 groups_with_admin_rights = GroupList(RepoGroup.query().all(),
76 perm_set=['group.admin'])
77 c.repo_groups = RepoGroup.groups_choices(groups=groups_with_admin_rights,
78 show_empty_group=allow_empty_group)
79 # exclude filtered ids
80 c.repo_groups = filter(lambda x: x[0] not in exclude_group_ids,
81 c.repo_groups)
68 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)
69
70 repo_model = RepoModel()
83 repo_model = RepoModel()
71 c.users_array = repo_model.get_users_js()
84 c.users_array = repo_model.get_users_js()
72 c.users_groups_array = repo_model.get_users_groups_js()
85 c.users_groups_array = repo_model.get_users_groups_js()
73
86
74 def __load_data(self, group_id):
87 def __load_data(self, group_id):
75 """
88 """
76 Load defaults settings for edit, and update
89 Load defaults settings for edit, and update
77
90
78 :param group_id:
91 :param group_id:
79 """
92 """
80 self.__load_defaults()
81 repo_group = RepoGroup.get_or_404(group_id)
93 repo_group = RepoGroup.get_or_404(group_id)
82 data = repo_group.get_dict()
94 data = repo_group.get_dict()
83 data['group_name'] = repo_group.name
95 data['group_name'] = repo_group.name
84
96
85 # fill repository users
97 # fill repository users
86 for p in repo_group.repo_group_to_perm:
98 for p in repo_group.repo_group_to_perm:
87 data.update({'u_perm_%s' % p.user.username:
99 data.update({'u_perm_%s' % p.user.username:
88 p.permission.permission_name})
100 p.permission.permission_name})
89
101
90 # fill repository groups
102 # fill repository groups
91 for p in repo_group.users_group_to_perm:
103 for p in repo_group.users_group_to_perm:
92 data.update({'g_perm_%s' % p.users_group.users_group_name:
104 data.update({'g_perm_%s' % p.users_group.users_group_name:
93 p.permission.permission_name})
105 p.permission.permission_name})
94
106
95 return data
107 return data
96
108
97 @HasPermissionAnyDecorator('hg.admin')
98 def index(self, format='html'):
109 def index(self, format='html'):
99 """GET /repos_groups: All items in the collection"""
110 """GET /repos_groups: All items in the collection"""
100 # url('repos_groups')
111 # url('repos_groups')
112 group_iter = GroupList(RepoGroup.query().all(), perm_set=['group.admin'])
101 sk = lambda g: g.parents[0].group_name if g.parents else g.group_name
113 sk = lambda g: g.parents[0].group_name if g.parents else g.group_name
102 c.groups = sorted(RepoGroup.query().all(), key=sk)
114 c.groups = sorted(group_iter, key=sk)
103 return render('admin/repos_groups/repos_groups_show.html')
115 return render('admin/repos_groups/repos_groups_show.html')
104
116
105 @HasPermissionAnyDecorator('hg.admin')
106 def create(self):
117 def create(self):
107 """POST /repos_groups: Create a new item"""
118 """POST /repos_groups: Create a new item"""
108 # url('repos_groups')
119 # url('repos_groups')
120
109 self.__load_defaults()
121 self.__load_defaults()
110 repos_group_form = ReposGroupForm(available_groups =
122
111 c.repo_groups_choices)()
123 # permissions for can create group based on parent_id are checked
124 # here in the Form
125 repos_group_form = ReposGroupForm(available_groups=
126 map(lambda k: unicode(k[0]), c.repo_groups))()
112 try:
127 try:
113 form_result = repos_group_form.to_python(dict(request.POST))
128 form_result = repos_group_form.to_python(dict(request.POST))
114 ReposGroupModel().create(
129 ReposGroupModel().create(
115 group_name=form_result['group_name'],
130 group_name=form_result['group_name'],
116 group_description=form_result['group_description'],
131 group_description=form_result['group_description'],
117 parent=form_result['group_parent_id']
132 parent=form_result['group_parent_id'],
133 owner=self.rhodecode_user.user_id
118 )
134 )
119 Session().commit()
135 Session().commit()
120 h.flash(_('created repos group %s') \
136 h.flash(_('created repos group %s') \
121 % form_result['group_name'], category='success')
137 % form_result['group_name'], category='success')
122 #TODO: in futureaction_logger(, '', '', '', self.sa)
138 #TODO: in futureaction_logger(, '', '', '', self.sa)
123 except formencode.Invalid, errors:
139 except formencode.Invalid, errors:
124
125 return htmlfill.render(
140 return htmlfill.render(
126 render('admin/repos_groups/repos_groups_add.html'),
141 render('admin/repos_groups/repos_groups_add.html'),
127 defaults=errors.value,
142 defaults=errors.value,
128 errors=errors.error_dict or {},
143 errors=errors.error_dict or {},
129 prefix_error=False,
144 prefix_error=False,
130 encoding="UTF-8")
145 encoding="UTF-8")
131 except Exception:
146 except Exception:
132 log.error(traceback.format_exc())
147 log.error(traceback.format_exc())
133 h.flash(_('error occurred during creation of repos group %s') \
148 h.flash(_('error occurred during creation of repos group %s') \
134 % request.POST.get('group_name'), category='error')
149 % request.POST.get('group_name'), category='error')
150 parent_group_id = form_result['group_parent_id']
151 #TODO: maybe we should get back to the main view, not the admin one
152 return redirect(url('repos_groups', parent_group=parent_group_id))
135
153
136 return redirect(url('repos_groups'))
137
138 @HasPermissionAnyDecorator('hg.admin')
139 def new(self, format='html'):
154 def new(self, format='html'):
140 """GET /repos_groups/new: Form to create a new item"""
155 """GET /repos_groups/new: Form to create a new item"""
141 # url('new_repos_group')
156 # url('new_repos_group')
157 if HasPermissionAll('hg.admin')('group create'):
158 #we're global admin, we're ok and we can create TOP level groups
159 pass
160 else:
161 # we pass in parent group into creation form, thus we know
162 # what would be the group, we can check perms here !
163 group_id = safe_int(request.GET.get('parent_group'))
164 group = RepoGroup.get(group_id) if group_id else None
165 group_name = group.group_name if group else None
166 if HasReposGroupPermissionAll('group.admin')(group_name, 'group create'):
167 pass
168 else:
169 return abort(403)
170
142 self.__load_defaults()
171 self.__load_defaults()
143 return render('admin/repos_groups/repos_groups_add.html')
172 return render('admin/repos_groups/repos_groups_add.html')
144
173
145 @HasPermissionAnyDecorator('hg.admin')
174 @HasReposGroupPermissionAnyDecorator('group.admin')
146 def update(self, id):
175 def update(self, group_name):
147 """PUT /repos_groups/id: Update an existing item"""
176 """PUT /repos_groups/group_name: Update an existing item"""
148 # Forms posted to this method should contain a hidden field:
177 # Forms posted to this method should contain a hidden field:
149 # <input type="hidden" name="_method" value="PUT" />
178 # <input type="hidden" name="_method" value="PUT" />
150 # Or using helpers:
179 # Or using helpers:
151 # h.form(url('repos_group', id=ID),
180 # h.form(url('repos_group', group_name=GROUP_NAME),
152 # method='put')
181 # method='put')
153 # url('repos_group', id=ID)
182 # url('repos_group', group_name=GROUP_NAME)
154
183
155 self.__load_defaults()
184 c.repos_group = ReposGroupModel()._get_repos_group(group_name)
156 c.repos_group = RepoGroup.get(id)
185 if HasPermissionAll('hg.admin')('group edit'):
186 #we're global admin, we're ok and we can create TOP level groups
187 allow_empty_group = True
188 elif not c.repos_group.parent_group:
189 allow_empty_group = True
190 else:
191 allow_empty_group = False
192 self.__load_defaults(allow_empty_group=allow_empty_group,
193 exclude_group_ids=[c.repos_group.group_id])
157
194
158 repos_group_form = ReposGroupForm(
195 repos_group_form = ReposGroupForm(
159 edit=True,
196 edit=True,
160 old_data=c.repos_group.get_dict(),
197 old_data=c.repos_group.get_dict(),
161 available_groups=c.repo_groups_choices
198 available_groups=c.repo_groups_choices,
199 can_create_in_root=allow_empty_group,
162 )()
200 )()
163 try:
201 try:
164 form_result = repos_group_form.to_python(dict(request.POST))
202 form_result = repos_group_form.to_python(dict(request.POST))
165 ReposGroupModel().update(id, form_result)
203 new_gr = ReposGroupModel().update(group_name, form_result)
166 Session().commit()
204 Session().commit()
167 h.flash(_('updated repos group %s') \
205 h.flash(_('updated repos group %s') \
168 % form_result['group_name'], category='success')
206 % form_result['group_name'], category='success')
207 # we now have new name !
208 group_name = new_gr.group_name
169 #TODO: in future action_logger(, '', '', '', self.sa)
209 #TODO: in future action_logger(, '', '', '', self.sa)
170 except formencode.Invalid, errors:
210 except formencode.Invalid, errors:
171
211
172 return htmlfill.render(
212 return htmlfill.render(
173 render('admin/repos_groups/repos_groups_edit.html'),
213 render('admin/repos_groups/repos_groups_edit.html'),
174 defaults=errors.value,
214 defaults=errors.value,
175 errors=errors.error_dict or {},
215 errors=errors.error_dict or {},
176 prefix_error=False,
216 prefix_error=False,
177 encoding="UTF-8")
217 encoding="UTF-8")
178 except Exception:
218 except Exception:
179 log.error(traceback.format_exc())
219 log.error(traceback.format_exc())
180 h.flash(_('error occurred during update of repos group %s') \
220 h.flash(_('error occurred during update of repos group %s') \
181 % request.POST.get('group_name'), category='error')
221 % request.POST.get('group_name'), category='error')
182
222
183 return redirect(url('edit_repos_group', id=id))
223 return redirect(url('edit_repos_group', group_name=group_name))
184
224
185 @HasPermissionAnyDecorator('hg.admin')
225 @HasReposGroupPermissionAnyDecorator('group.admin')
186 def delete(self, id):
226 def delete(self, group_name):
187 """DELETE /repos_groups/id: Delete an existing item"""
227 """DELETE /repos_groups/group_name: Delete an existing item"""
188 # Forms posted to this method should contain a hidden field:
228 # Forms posted to this method should contain a hidden field:
189 # <input type="hidden" name="_method" value="DELETE" />
229 # <input type="hidden" name="_method" value="DELETE" />
190 # Or using helpers:
230 # Or using helpers:
191 # h.form(url('repos_group', id=ID),
231 # h.form(url('repos_group', group_name=GROUP_NAME),
192 # method='delete')
232 # method='delete')
193 # url('repos_group', id=ID)
233 # url('repos_group', group_name=GROUP_NAME)
194
234
195 gr = RepoGroup.get(id)
235 gr = c.repos_group = ReposGroupModel()._get_repos_group(group_name)
196 repos = gr.repositories.all()
236 repos = gr.repositories.all()
197 if repos:
237 if repos:
198 h.flash(_('This group contains %s repositores and cannot be '
238 h.flash(_('This group contains %s repositores and cannot be '
199 'deleted') % len(repos),
239 'deleted') % len(repos),
200 category='error')
240 category='error')
201 return redirect(url('repos_groups'))
241 return redirect(url('repos_groups'))
202
242
203 try:
243 try:
204 ReposGroupModel().delete(id)
244 ReposGroupModel().delete(group_name)
205 Session().commit()
245 Session().commit()
206 h.flash(_('removed repos group %s') % gr.group_name,
246 h.flash(_('removed repos group %s') % gr.group_name,
207 category='success')
247 category='success')
208 #TODO: in future action_logger(, '', '', '', self.sa)
248 #TODO: in future action_logger(, '', '', '', self.sa)
209 except IntegrityError, e:
249 except IntegrityError, e:
210 if str(e.message).find('groups_group_parent_id_fkey') != -1:
250 if str(e.message).find('groups_group_parent_id_fkey') != -1:
211 log.error(traceback.format_exc())
251 log.error(traceback.format_exc())
212 h.flash(_('Cannot delete this group it still contains '
252 h.flash(_('Cannot delete this group it still contains '
213 'subgroups'),
253 'subgroups'),
214 category='warning')
254 category='warning')
215 else:
255 else:
216 log.error(traceback.format_exc())
256 log.error(traceback.format_exc())
217 h.flash(_('error occurred during deletion of repos '
257 h.flash(_('error occurred during deletion of repos '
218 'group %s') % gr.group_name, category='error')
258 'group %s') % gr.group_name, category='error')
219
259
220 except Exception:
260 except Exception:
221 log.error(traceback.format_exc())
261 log.error(traceback.format_exc())
222 h.flash(_('error occurred during deletion of repos '
262 h.flash(_('error occurred during deletion of repos '
223 'group %s') % gr.group_name, category='error')
263 'group %s') % gr.group_name, category='error')
224
264
225 return redirect(url('repos_groups'))
265 return redirect(url('repos_groups'))
226
266
227 @HasReposGroupPermissionAnyDecorator('group.admin')
267 @HasReposGroupPermissionAnyDecorator('group.admin')
228 def delete_repos_group_user_perm(self, group_name):
268 def delete_repos_group_user_perm(self, group_name):
229 """
269 """
230 DELETE an existing repositories group permission user
270 DELETE an existing repositories group permission user
231
271
232 :param group_name:
272 :param group_name:
233 """
273 """
234 try:
274 try:
235 recursive = str2bool(request.POST.get('recursive', False))
275 recursive = str2bool(request.POST.get('recursive', False))
236 ReposGroupModel().delete_permission(
276 ReposGroupModel().delete_permission(
237 repos_group=group_name, obj=request.POST['user_id'],
277 repos_group=group_name, obj=request.POST['user_id'],
238 obj_type='user', recursive=recursive
278 obj_type='user', recursive=recursive
239 )
279 )
240 Session().commit()
280 Session().commit()
241 except Exception:
281 except Exception:
242 log.error(traceback.format_exc())
282 log.error(traceback.format_exc())
243 h.flash(_('An error occurred during deletion of group user'),
283 h.flash(_('An error occurred during deletion of group user'),
244 category='error')
284 category='error')
245 raise HTTPInternalServerError()
285 raise HTTPInternalServerError()
246
286
247 @HasReposGroupPermissionAnyDecorator('group.admin')
287 @HasReposGroupPermissionAnyDecorator('group.admin')
248 def delete_repos_group_users_group_perm(self, group_name):
288 def delete_repos_group_users_group_perm(self, group_name):
249 """
289 """
250 DELETE an existing repositories group permission users group
290 DELETE an existing repositories group permission users group
251
291
252 :param group_name:
292 :param group_name:
253 """
293 """
254
294
255 try:
295 try:
256 recursive = str2bool(request.POST.get('recursive', False))
296 recursive = str2bool(request.POST.get('recursive', False))
257 ReposGroupModel().delete_permission(
297 ReposGroupModel().delete_permission(
258 repos_group=group_name, obj=request.POST['users_group_id'],
298 repos_group=group_name, obj=request.POST['users_group_id'],
259 obj_type='users_group', recursive=recursive
299 obj_type='users_group', recursive=recursive
260 )
300 )
261 Session().commit()
301 Session().commit()
262 except Exception:
302 except Exception:
263 log.error(traceback.format_exc())
303 log.error(traceback.format_exc())
264 h.flash(_('An error occurred during deletion of group'
304 h.flash(_('An error occurred during deletion of group'
265 ' users groups'),
305 ' users groups'),
266 category='error')
306 category='error')
267 raise HTTPInternalServerError()
307 raise HTTPInternalServerError()
268
308
269 def show_by_name(self, group_name):
309 def show_by_name(self, group_name):
270 """
310 """
271 This is a proxy that does a lookup group_name -> id, and shows
311 This is a proxy that does a lookup group_name -> id, and shows
272 the group by id view instead
312 the group by id view instead
273 """
313 """
274 group_name = group_name.rstrip('/')
314 group_name = group_name.rstrip('/')
275 id_ = RepoGroup.get_by_group_name(group_name)
315 id_ = RepoGroup.get_by_group_name(group_name)
276 if id_:
316 if id_:
277 return self.show(id_.group_id)
317 return self.show(id_.group_id)
278 raise HTTPNotFound
318 raise HTTPNotFound
279
319
280 @HasReposGroupPermissionAnyDecorator('group.read', 'group.write',
320 @HasReposGroupPermissionAnyDecorator('group.read', 'group.write',
281 'group.admin')
321 'group.admin')
282 def show(self, id, format='html'):
322 def show(self, group_name, format='html'):
283 """GET /repos_groups/id: Show a specific item"""
323 """GET /repos_groups/group_name: Show a specific item"""
284 # url('repos_group', id=ID)
324 # url('repos_group', group_name=GROUP_NAME)
285
325
286 c.group = RepoGroup.get_or_404(id)
326 c.group = c.repos_group = ReposGroupModel()._get_repos_group(group_name)
287 c.group_repos = c.group.repositories.all()
327 c.group_repos = c.group.repositories.all()
288
328
289 #overwrite our cached list with current filter
329 #overwrite our cached list with current filter
290 gr_filter = c.group_repos
330 gr_filter = c.group_repos
291 c.repo_cnt = 0
331 c.repo_cnt = 0
292
332
293 groups = RepoGroup.query().order_by(RepoGroup.group_name)\
333 groups = RepoGroup.query().order_by(RepoGroup.group_name)\
294 .filter(RepoGroup.group_parent_id == id).all()
334 .filter(RepoGroup.group_parent_id == c.group.group_id).all()
295 c.groups = self.scm_model.get_repos_groups(groups)
335 c.groups = self.scm_model.get_repos_groups(groups)
296
336
297 if c.visual.lightweight_dashboard is False:
337 if c.visual.lightweight_dashboard is False:
298 c.repos_list = self.scm_model.get_repos(all_repos=gr_filter)
338 c.repos_list = self.scm_model.get_repos(all_repos=gr_filter)
299 ## lightweight version of dashboard
339 ## lightweight version of dashboard
300 else:
340 else:
301 c.repos_list = Repository.query()\
341 c.repos_list = Repository.query()\
302 .filter(Repository.group_id == id)\
342 .filter(Repository.group_id == c.group.group_id)\
303 .order_by(func.lower(Repository.repo_name))\
343 .order_by(func.lower(Repository.repo_name))\
304 .all()
344 .all()
305
345
306 repos_data = RepoModel().get_repos_as_dict(repos_list=c.repos_list,
346 repos_data = RepoModel().get_repos_as_dict(repos_list=c.repos_list,
307 admin=False)
347 admin=False)
308 #json used to render the grid
348 #json used to render the grid
309 c.data = json.dumps(repos_data)
349 c.data = json.dumps(repos_data)
310
350
311 return render('admin/repos_groups/repos_groups.html')
351 return render('admin/repos_groups/repos_groups.html')
312
352
313 @HasPermissionAnyDecorator('hg.admin')
353 @HasReposGroupPermissionAnyDecorator('group.admin')
314 def edit(self, id, format='html'):
354 def edit(self, group_name, format='html'):
315 """GET /repos_groups/id/edit: Form to edit an existing item"""
355 """GET /repos_groups/group_name/edit: Form to edit an existing item"""
316 # url('edit_repos_group', id=ID)
356 # url('edit_repos_group', group_name=GROUP_NAME)
317
357
318 c.repos_group = ReposGroupModel()._get_repos_group(id)
358 c.repos_group = ReposGroupModel()._get_repos_group(group_name)
319 defaults = self.__load_data(c.repos_group.group_id)
359 #we can only allow moving empty group if it's already a top-level
360 #group, ie has no parents, or we're admin
361 if HasPermissionAll('hg.admin')('group edit'):
362 #we're global admin, we're ok and we can create TOP level groups
363 allow_empty_group = True
364 elif not c.repos_group.parent_group:
365 allow_empty_group = True
366 else:
367 allow_empty_group = False
320
368
321 # we need to exclude this group from the group list for editing
369 self.__load_defaults(allow_empty_group=allow_empty_group,
322 c.repo_groups = filter(lambda x: x[0] != c.repos_group.group_id,
370 exclude_group_ids=[c.repos_group.group_id])
323 c.repo_groups)
371 defaults = self.__load_data(c.repos_group.group_id)
324
372
325 return htmlfill.render(
373 return htmlfill.render(
326 render('admin/repos_groups/repos_groups_edit.html'),
374 render('admin/repos_groups/repos_groups_edit.html'),
327 defaults=defaults,
375 defaults=defaults,
328 encoding="UTF-8",
376 encoding="UTF-8",
329 force_defaults=False
377 force_defaults=False
330 )
378 )
@@ -1,1008 +1,1008 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.lib.auth
3 rhodecode.lib.auth
4 ~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~
5
5
6 authentication and permission libraries
6 authentication and permission libraries
7
7
8 :created_on: Apr 4, 2010
8 :created_on: Apr 4, 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 random
26 import random
27 import logging
27 import logging
28 import traceback
28 import traceback
29 import hashlib
29 import hashlib
30
30
31 from tempfile import _RandomNameSequence
31 from tempfile import _RandomNameSequence
32 from decorator import decorator
32 from decorator import decorator
33
33
34 from pylons import config, url, request
34 from pylons import config, url, request
35 from pylons.controllers.util import abort, redirect
35 from pylons.controllers.util import abort, redirect
36 from pylons.i18n.translation import _
36 from pylons.i18n.translation import _
37 from sqlalchemy.orm.exc import ObjectDeletedError
37 from sqlalchemy.orm.exc import ObjectDeletedError
38
38
39 from rhodecode import __platform__, is_windows, is_unix
39 from rhodecode import __platform__, is_windows, is_unix
40 from rhodecode.model.meta import Session
40 from rhodecode.model.meta import Session
41
41
42 from rhodecode.lib.utils2 import str2bool, safe_unicode
42 from rhodecode.lib.utils2 import str2bool, safe_unicode
43 from rhodecode.lib.exceptions import LdapPasswordError, LdapUsernameError
43 from rhodecode.lib.exceptions import LdapPasswordError, LdapUsernameError
44 from rhodecode.lib.utils import get_repo_slug, get_repos_group_slug
44 from rhodecode.lib.utils import get_repo_slug, get_repos_group_slug
45 from rhodecode.lib.auth_ldap import AuthLdap
45 from rhodecode.lib.auth_ldap import AuthLdap
46
46
47 from rhodecode.model import meta
47 from rhodecode.model import meta
48 from rhodecode.model.user import UserModel
48 from rhodecode.model.user import UserModel
49 from rhodecode.model.db import Permission, RhodeCodeSetting, User, UserIpMap
49 from rhodecode.model.db import Permission, RhodeCodeSetting, User, UserIpMap
50 from rhodecode.lib.caching_query import FromCache
50 from rhodecode.lib.caching_query import FromCache
51
51
52 log = logging.getLogger(__name__)
52 log = logging.getLogger(__name__)
53
53
54
54
55 class PasswordGenerator(object):
55 class PasswordGenerator(object):
56 """
56 """
57 This is a simple class for generating password from different sets of
57 This is a simple class for generating password from different sets of
58 characters
58 characters
59 usage::
59 usage::
60
60
61 passwd_gen = PasswordGenerator()
61 passwd_gen = PasswordGenerator()
62 #print 8-letter password containing only big and small letters
62 #print 8-letter password containing only big and small letters
63 of alphabet
63 of alphabet
64 passwd_gen.gen_password(8, passwd_gen.ALPHABETS_BIG_SMALL)
64 passwd_gen.gen_password(8, passwd_gen.ALPHABETS_BIG_SMALL)
65 """
65 """
66 ALPHABETS_NUM = r'''1234567890'''
66 ALPHABETS_NUM = r'''1234567890'''
67 ALPHABETS_SMALL = r'''qwertyuiopasdfghjklzxcvbnm'''
67 ALPHABETS_SMALL = r'''qwertyuiopasdfghjklzxcvbnm'''
68 ALPHABETS_BIG = r'''QWERTYUIOPASDFGHJKLZXCVBNM'''
68 ALPHABETS_BIG = r'''QWERTYUIOPASDFGHJKLZXCVBNM'''
69 ALPHABETS_SPECIAL = r'''`-=[]\;',./~!@#$%^&*()_+{}|:"<>?'''
69 ALPHABETS_SPECIAL = r'''`-=[]\;',./~!@#$%^&*()_+{}|:"<>?'''
70 ALPHABETS_FULL = ALPHABETS_BIG + ALPHABETS_SMALL \
70 ALPHABETS_FULL = ALPHABETS_BIG + ALPHABETS_SMALL \
71 + ALPHABETS_NUM + ALPHABETS_SPECIAL
71 + ALPHABETS_NUM + ALPHABETS_SPECIAL
72 ALPHABETS_ALPHANUM = ALPHABETS_BIG + ALPHABETS_SMALL + ALPHABETS_NUM
72 ALPHABETS_ALPHANUM = ALPHABETS_BIG + ALPHABETS_SMALL + ALPHABETS_NUM
73 ALPHABETS_BIG_SMALL = ALPHABETS_BIG + ALPHABETS_SMALL
73 ALPHABETS_BIG_SMALL = ALPHABETS_BIG + ALPHABETS_SMALL
74 ALPHABETS_ALPHANUM_BIG = ALPHABETS_BIG + ALPHABETS_NUM
74 ALPHABETS_ALPHANUM_BIG = ALPHABETS_BIG + ALPHABETS_NUM
75 ALPHABETS_ALPHANUM_SMALL = ALPHABETS_SMALL + ALPHABETS_NUM
75 ALPHABETS_ALPHANUM_SMALL = ALPHABETS_SMALL + ALPHABETS_NUM
76
76
77 def __init__(self, passwd=''):
77 def __init__(self, passwd=''):
78 self.passwd = passwd
78 self.passwd = passwd
79
79
80 def gen_password(self, length, type_=None):
80 def gen_password(self, length, type_=None):
81 if type_ is None:
81 if type_ is None:
82 type_ = self.ALPHABETS_FULL
82 type_ = self.ALPHABETS_FULL
83 self.passwd = ''.join([random.choice(type_) for _ in xrange(length)])
83 self.passwd = ''.join([random.choice(type_) for _ in xrange(length)])
84 return self.passwd
84 return self.passwd
85
85
86
86
87 class RhodeCodeCrypto(object):
87 class RhodeCodeCrypto(object):
88
88
89 @classmethod
89 @classmethod
90 def hash_string(cls, str_):
90 def hash_string(cls, str_):
91 """
91 """
92 Cryptographic function used for password hashing based on pybcrypt
92 Cryptographic function used for password hashing based on pybcrypt
93 or pycrypto in windows
93 or pycrypto in windows
94
94
95 :param password: password to hash
95 :param password: password to hash
96 """
96 """
97 if is_windows:
97 if is_windows:
98 from hashlib import sha256
98 from hashlib import sha256
99 return sha256(str_).hexdigest()
99 return sha256(str_).hexdigest()
100 elif is_unix:
100 elif is_unix:
101 import bcrypt
101 import bcrypt
102 return bcrypt.hashpw(str_, bcrypt.gensalt(10))
102 return bcrypt.hashpw(str_, bcrypt.gensalt(10))
103 else:
103 else:
104 raise Exception('Unknown or unsupported platform %s' \
104 raise Exception('Unknown or unsupported platform %s' \
105 % __platform__)
105 % __platform__)
106
106
107 @classmethod
107 @classmethod
108 def hash_check(cls, password, hashed):
108 def hash_check(cls, password, hashed):
109 """
109 """
110 Checks matching password with it's hashed value, runs different
110 Checks matching password with it's hashed value, runs different
111 implementation based on platform it runs on
111 implementation based on platform it runs on
112
112
113 :param password: password
113 :param password: password
114 :param hashed: password in hashed form
114 :param hashed: password in hashed form
115 """
115 """
116
116
117 if is_windows:
117 if is_windows:
118 from hashlib import sha256
118 from hashlib import sha256
119 return sha256(password).hexdigest() == hashed
119 return sha256(password).hexdigest() == hashed
120 elif is_unix:
120 elif is_unix:
121 import bcrypt
121 import bcrypt
122 return bcrypt.hashpw(password, hashed) == hashed
122 return bcrypt.hashpw(password, hashed) == hashed
123 else:
123 else:
124 raise Exception('Unknown or unsupported platform %s' \
124 raise Exception('Unknown or unsupported platform %s' \
125 % __platform__)
125 % __platform__)
126
126
127
127
128 def get_crypt_password(password):
128 def get_crypt_password(password):
129 return RhodeCodeCrypto.hash_string(password)
129 return RhodeCodeCrypto.hash_string(password)
130
130
131
131
132 def check_password(password, hashed):
132 def check_password(password, hashed):
133 return RhodeCodeCrypto.hash_check(password, hashed)
133 return RhodeCodeCrypto.hash_check(password, hashed)
134
134
135
135
136 def generate_api_key(str_, salt=None):
136 def generate_api_key(str_, salt=None):
137 """
137 """
138 Generates API KEY from given string
138 Generates API KEY from given string
139
139
140 :param str_:
140 :param str_:
141 :param salt:
141 :param salt:
142 """
142 """
143
143
144 if salt is None:
144 if salt is None:
145 salt = _RandomNameSequence().next()
145 salt = _RandomNameSequence().next()
146
146
147 return hashlib.sha1(str_ + salt).hexdigest()
147 return hashlib.sha1(str_ + salt).hexdigest()
148
148
149
149
150 def authfunc(environ, username, password):
150 def authfunc(environ, username, password):
151 """
151 """
152 Dummy authentication wrapper function used in Mercurial and Git for
152 Dummy authentication wrapper function used in Mercurial and Git for
153 access control.
153 access control.
154
154
155 :param environ: needed only for using in Basic auth
155 :param environ: needed only for using in Basic auth
156 """
156 """
157 return authenticate(username, password)
157 return authenticate(username, password)
158
158
159
159
160 def authenticate(username, password):
160 def authenticate(username, password):
161 """
161 """
162 Authentication function used for access control,
162 Authentication function used for access control,
163 firstly checks for db authentication then if ldap is enabled for ldap
163 firstly checks for db authentication then if ldap is enabled for ldap
164 authentication, also creates ldap user if not in database
164 authentication, also creates ldap user if not in database
165
165
166 :param username: username
166 :param username: username
167 :param password: password
167 :param password: password
168 """
168 """
169
169
170 user_model = UserModel()
170 user_model = UserModel()
171 user = User.get_by_username(username)
171 user = User.get_by_username(username)
172
172
173 log.debug('Authenticating user using RhodeCode account')
173 log.debug('Authenticating user using RhodeCode account')
174 if user is not None and not user.ldap_dn:
174 if user is not None and not user.ldap_dn:
175 if user.active:
175 if user.active:
176 if user.username == 'default' and user.active:
176 if user.username == 'default' and user.active:
177 log.info('user %s authenticated correctly as anonymous user' %
177 log.info('user %s authenticated correctly as anonymous user' %
178 username)
178 username)
179 return True
179 return True
180
180
181 elif user.username == username and check_password(password,
181 elif user.username == username and check_password(password,
182 user.password):
182 user.password):
183 log.info('user %s authenticated correctly' % username)
183 log.info('user %s authenticated correctly' % username)
184 return True
184 return True
185 else:
185 else:
186 log.warning('user %s tried auth but is disabled' % username)
186 log.warning('user %s tried auth but is disabled' % username)
187
187
188 else:
188 else:
189 log.debug('Regular authentication failed')
189 log.debug('Regular authentication failed')
190 user_obj = User.get_by_username(username, case_insensitive=True)
190 user_obj = User.get_by_username(username, case_insensitive=True)
191
191
192 if user_obj is not None and not user_obj.ldap_dn:
192 if user_obj is not None and not user_obj.ldap_dn:
193 log.debug('this user already exists as non ldap')
193 log.debug('this user already exists as non ldap')
194 return False
194 return False
195
195
196 ldap_settings = RhodeCodeSetting.get_ldap_settings()
196 ldap_settings = RhodeCodeSetting.get_ldap_settings()
197 #======================================================================
197 #======================================================================
198 # FALLBACK TO LDAP AUTH IF ENABLE
198 # FALLBACK TO LDAP AUTH IF ENABLE
199 #======================================================================
199 #======================================================================
200 if str2bool(ldap_settings.get('ldap_active')):
200 if str2bool(ldap_settings.get('ldap_active')):
201 log.debug("Authenticating user using ldap")
201 log.debug("Authenticating user using ldap")
202 kwargs = {
202 kwargs = {
203 'server': ldap_settings.get('ldap_host', ''),
203 'server': ldap_settings.get('ldap_host', ''),
204 'base_dn': ldap_settings.get('ldap_base_dn', ''),
204 'base_dn': ldap_settings.get('ldap_base_dn', ''),
205 'port': ldap_settings.get('ldap_port'),
205 'port': ldap_settings.get('ldap_port'),
206 'bind_dn': ldap_settings.get('ldap_dn_user'),
206 'bind_dn': ldap_settings.get('ldap_dn_user'),
207 'bind_pass': ldap_settings.get('ldap_dn_pass'),
207 'bind_pass': ldap_settings.get('ldap_dn_pass'),
208 'tls_kind': ldap_settings.get('ldap_tls_kind'),
208 'tls_kind': ldap_settings.get('ldap_tls_kind'),
209 'tls_reqcert': ldap_settings.get('ldap_tls_reqcert'),
209 'tls_reqcert': ldap_settings.get('ldap_tls_reqcert'),
210 'ldap_filter': ldap_settings.get('ldap_filter'),
210 'ldap_filter': ldap_settings.get('ldap_filter'),
211 'search_scope': ldap_settings.get('ldap_search_scope'),
211 'search_scope': ldap_settings.get('ldap_search_scope'),
212 'attr_login': ldap_settings.get('ldap_attr_login'),
212 'attr_login': ldap_settings.get('ldap_attr_login'),
213 'ldap_version': 3,
213 'ldap_version': 3,
214 }
214 }
215 log.debug('Checking for ldap authentication')
215 log.debug('Checking for ldap authentication')
216 try:
216 try:
217 aldap = AuthLdap(**kwargs)
217 aldap = AuthLdap(**kwargs)
218 (user_dn, ldap_attrs) = aldap.authenticate_ldap(username,
218 (user_dn, ldap_attrs) = aldap.authenticate_ldap(username,
219 password)
219 password)
220 log.debug('Got ldap DN response %s' % user_dn)
220 log.debug('Got ldap DN response %s' % user_dn)
221
221
222 get_ldap_attr = lambda k: ldap_attrs.get(ldap_settings\
222 get_ldap_attr = lambda k: ldap_attrs.get(ldap_settings\
223 .get(k), [''])[0]
223 .get(k), [''])[0]
224
224
225 user_attrs = {
225 user_attrs = {
226 'name': safe_unicode(get_ldap_attr('ldap_attr_firstname')),
226 'name': safe_unicode(get_ldap_attr('ldap_attr_firstname')),
227 'lastname': safe_unicode(get_ldap_attr('ldap_attr_lastname')),
227 'lastname': safe_unicode(get_ldap_attr('ldap_attr_lastname')),
228 'email': get_ldap_attr('ldap_attr_email'),
228 'email': get_ldap_attr('ldap_attr_email'),
229 }
229 }
230
230
231 # don't store LDAP password since we don't need it. Override
231 # don't store LDAP password since we don't need it. Override
232 # with some random generated password
232 # with some random generated password
233 _password = PasswordGenerator().gen_password(length=8)
233 _password = PasswordGenerator().gen_password(length=8)
234 # create this user on the fly if it doesn't exist in rhodecode
234 # create this user on the fly if it doesn't exist in rhodecode
235 # database
235 # database
236 if user_model.create_ldap(username, _password, user_dn,
236 if user_model.create_ldap(username, _password, user_dn,
237 user_attrs):
237 user_attrs):
238 log.info('created new ldap user %s' % username)
238 log.info('created new ldap user %s' % username)
239
239
240 Session().commit()
240 Session().commit()
241 return True
241 return True
242 except (LdapUsernameError, LdapPasswordError,):
242 except (LdapUsernameError, LdapPasswordError,):
243 pass
243 pass
244 except (Exception,):
244 except (Exception,):
245 log.error(traceback.format_exc())
245 log.error(traceback.format_exc())
246 pass
246 pass
247 return False
247 return False
248
248
249
249
250 def login_container_auth(username):
250 def login_container_auth(username):
251 user = User.get_by_username(username)
251 user = User.get_by_username(username)
252 if user is None:
252 if user is None:
253 user_attrs = {
253 user_attrs = {
254 'name': username,
254 'name': username,
255 'lastname': None,
255 'lastname': None,
256 'email': None,
256 'email': None,
257 }
257 }
258 user = UserModel().create_for_container_auth(username, user_attrs)
258 user = UserModel().create_for_container_auth(username, user_attrs)
259 if not user:
259 if not user:
260 return None
260 return None
261 log.info('User %s was created by container authentication' % username)
261 log.info('User %s was created by container authentication' % username)
262
262
263 if not user.active:
263 if not user.active:
264 return None
264 return None
265
265
266 user.update_lastlogin()
266 user.update_lastlogin()
267 Session().commit()
267 Session().commit()
268
268
269 log.debug('User %s is now logged in by container authentication',
269 log.debug('User %s is now logged in by container authentication',
270 user.username)
270 user.username)
271 return user
271 return user
272
272
273
273
274 def get_container_username(environ, config, clean_username=False):
274 def get_container_username(environ, config, clean_username=False):
275 """
275 """
276 Get's the container_auth username (or email). It tries to get username
276 Get's the container_auth username (or email). It tries to get username
277 from REMOTE_USER if container_auth_enabled is enabled, if that fails
277 from REMOTE_USER if container_auth_enabled is enabled, if that fails
278 it tries to get username from HTTP_X_FORWARDED_USER if proxypass_auth_enabled
278 it tries to get username from HTTP_X_FORWARDED_USER if proxypass_auth_enabled
279 is enabled. clean_username extracts the username from this data if it's
279 is enabled. clean_username extracts the username from this data if it's
280 having @ in it.
280 having @ in it.
281
281
282 :param environ:
282 :param environ:
283 :param config:
283 :param config:
284 :param clean_username:
284 :param clean_username:
285 """
285 """
286 username = None
286 username = None
287
287
288 if str2bool(config.get('container_auth_enabled', False)):
288 if str2bool(config.get('container_auth_enabled', False)):
289 from paste.httpheaders import REMOTE_USER
289 from paste.httpheaders import REMOTE_USER
290 username = REMOTE_USER(environ)
290 username = REMOTE_USER(environ)
291 log.debug('extracted REMOTE_USER:%s' % (username))
291 log.debug('extracted REMOTE_USER:%s' % (username))
292
292
293 if not username and str2bool(config.get('proxypass_auth_enabled', False)):
293 if not username and str2bool(config.get('proxypass_auth_enabled', False)):
294 username = environ.get('HTTP_X_FORWARDED_USER')
294 username = environ.get('HTTP_X_FORWARDED_USER')
295 log.debug('extracted HTTP_X_FORWARDED_USER:%s' % (username))
295 log.debug('extracted HTTP_X_FORWARDED_USER:%s' % (username))
296
296
297 if username and clean_username:
297 if username and clean_username:
298 # Removing realm and domain from username
298 # Removing realm and domain from username
299 username = username.partition('@')[0]
299 username = username.partition('@')[0]
300 username = username.rpartition('\\')[2]
300 username = username.rpartition('\\')[2]
301 log.debug('Received username %s from container' % username)
301 log.debug('Received username %s from container' % username)
302
302
303 return username
303 return username
304
304
305
305
306 class CookieStoreWrapper(object):
306 class CookieStoreWrapper(object):
307
307
308 def __init__(self, cookie_store):
308 def __init__(self, cookie_store):
309 self.cookie_store = cookie_store
309 self.cookie_store = cookie_store
310
310
311 def __repr__(self):
311 def __repr__(self):
312 return 'CookieStore<%s>' % (self.cookie_store)
312 return 'CookieStore<%s>' % (self.cookie_store)
313
313
314 def get(self, key, other=None):
314 def get(self, key, other=None):
315 if isinstance(self.cookie_store, dict):
315 if isinstance(self.cookie_store, dict):
316 return self.cookie_store.get(key, other)
316 return self.cookie_store.get(key, other)
317 elif isinstance(self.cookie_store, AuthUser):
317 elif isinstance(self.cookie_store, AuthUser):
318 return self.cookie_store.__dict__.get(key, other)
318 return self.cookie_store.__dict__.get(key, other)
319
319
320
320
321 class AuthUser(object):
321 class AuthUser(object):
322 """
322 """
323 A simple object that handles all attributes of user in RhodeCode
323 A simple object that handles all attributes of user in RhodeCode
324
324
325 It does lookup based on API key,given user, or user present in session
325 It does lookup based on API key,given user, or user present in session
326 Then it fills all required information for such user. It also checks if
326 Then it fills all required information for such user. It also checks if
327 anonymous access is enabled and if so, it returns default user as logged
327 anonymous access is enabled and if so, it returns default user as logged
328 in
328 in
329 """
329 """
330
330
331 def __init__(self, user_id=None, api_key=None, username=None, ip_addr=None):
331 def __init__(self, user_id=None, api_key=None, username=None, ip_addr=None):
332
332
333 self.user_id = user_id
333 self.user_id = user_id
334 self.api_key = None
334 self.api_key = None
335 self.username = username
335 self.username = username
336 self.ip_addr = ip_addr
336 self.ip_addr = ip_addr
337
337
338 self.name = ''
338 self.name = ''
339 self.lastname = ''
339 self.lastname = ''
340 self.email = ''
340 self.email = ''
341 self.is_authenticated = False
341 self.is_authenticated = False
342 self.admin = False
342 self.admin = False
343 self.inherit_default_permissions = False
343 self.inherit_default_permissions = False
344 self.permissions = {}
344 self.permissions = {}
345 self._api_key = api_key
345 self._api_key = api_key
346 self.propagate_data()
346 self.propagate_data()
347 self._instance = None
347 self._instance = None
348
348
349 def propagate_data(self):
349 def propagate_data(self):
350 user_model = UserModel()
350 user_model = UserModel()
351 self.anonymous_user = User.get_by_username('default', cache=True)
351 self.anonymous_user = User.get_by_username('default', cache=True)
352 is_user_loaded = False
352 is_user_loaded = False
353
353
354 # try go get user by api key
354 # try go get user by api key
355 if self._api_key and self._api_key != self.anonymous_user.api_key:
355 if self._api_key and self._api_key != self.anonymous_user.api_key:
356 log.debug('Auth User lookup by API KEY %s' % self._api_key)
356 log.debug('Auth User lookup by API KEY %s' % self._api_key)
357 is_user_loaded = user_model.fill_data(self, api_key=self._api_key)
357 is_user_loaded = user_model.fill_data(self, api_key=self._api_key)
358 # lookup by userid
358 # lookup by userid
359 elif (self.user_id is not None and
359 elif (self.user_id is not None and
360 self.user_id != self.anonymous_user.user_id):
360 self.user_id != self.anonymous_user.user_id):
361 log.debug('Auth User lookup by USER ID %s' % self.user_id)
361 log.debug('Auth User lookup by USER ID %s' % self.user_id)
362 is_user_loaded = user_model.fill_data(self, user_id=self.user_id)
362 is_user_loaded = user_model.fill_data(self, user_id=self.user_id)
363 # lookup by username
363 # lookup by username
364 elif self.username and \
364 elif self.username and \
365 str2bool(config.get('container_auth_enabled', False)):
365 str2bool(config.get('container_auth_enabled', False)):
366
366
367 log.debug('Auth User lookup by USER NAME %s' % self.username)
367 log.debug('Auth User lookup by USER NAME %s' % self.username)
368 dbuser = login_container_auth(self.username)
368 dbuser = login_container_auth(self.username)
369 if dbuser is not None:
369 if dbuser is not None:
370 log.debug('filling all attributes to object')
370 log.debug('filling all attributes to object')
371 for k, v in dbuser.get_dict().items():
371 for k, v in dbuser.get_dict().items():
372 setattr(self, k, v)
372 setattr(self, k, v)
373 self.set_authenticated()
373 self.set_authenticated()
374 is_user_loaded = True
374 is_user_loaded = True
375 else:
375 else:
376 log.debug('No data in %s that could been used to log in' % self)
376 log.debug('No data in %s that could been used to log in' % self)
377
377
378 if not is_user_loaded:
378 if not is_user_loaded:
379 # if we cannot authenticate user try anonymous
379 # if we cannot authenticate user try anonymous
380 if self.anonymous_user.active is True:
380 if self.anonymous_user.active is True:
381 user_model.fill_data(self, user_id=self.anonymous_user.user_id)
381 user_model.fill_data(self, user_id=self.anonymous_user.user_id)
382 # then we set this user is logged in
382 # then we set this user is logged in
383 self.is_authenticated = True
383 self.is_authenticated = True
384 else:
384 else:
385 self.user_id = None
385 self.user_id = None
386 self.username = None
386 self.username = None
387 self.is_authenticated = False
387 self.is_authenticated = False
388
388
389 if not self.username:
389 if not self.username:
390 self.username = 'None'
390 self.username = 'None'
391
391
392 log.debug('Auth User is now %s' % self)
392 log.debug('Auth User is now %s' % self)
393 user_model.fill_perms(self)
393 user_model.fill_perms(self)
394
394
395 @property
395 @property
396 def is_admin(self):
396 def is_admin(self):
397 return self.admin
397 return self.admin
398
398
399 @property
399 @property
400 def ip_allowed(self):
400 def ip_allowed(self):
401 """
401 """
402 Checks if ip_addr used in constructor is allowed from defined list of
402 Checks if ip_addr used in constructor is allowed from defined list of
403 allowed ip_addresses for user
403 allowed ip_addresses for user
404
404
405 :returns: boolean, True if ip is in allowed ip range
405 :returns: boolean, True if ip is in allowed ip range
406 """
406 """
407 #check IP
407 #check IP
408 allowed_ips = AuthUser.get_allowed_ips(self.user_id, cache=True)
408 allowed_ips = AuthUser.get_allowed_ips(self.user_id, cache=True)
409 if check_ip_access(source_ip=self.ip_addr, allowed_ips=allowed_ips):
409 if check_ip_access(source_ip=self.ip_addr, allowed_ips=allowed_ips):
410 log.debug('IP:%s is in range of %s' % (self.ip_addr, allowed_ips))
410 log.debug('IP:%s is in range of %s' % (self.ip_addr, allowed_ips))
411 return True
411 return True
412 else:
412 else:
413 log.info('Access for IP:%s forbidden, '
413 log.info('Access for IP:%s forbidden, '
414 'not in %s' % (self.ip_addr, allowed_ips))
414 'not in %s' % (self.ip_addr, allowed_ips))
415 return False
415 return False
416
416
417 def __repr__(self):
417 def __repr__(self):
418 return "<AuthUser('id:%s:%s|%s')>" % (self.user_id, self.username,
418 return "<AuthUser('id:%s:%s|%s')>" % (self.user_id, self.username,
419 self.is_authenticated)
419 self.is_authenticated)
420
420
421 def set_authenticated(self, authenticated=True):
421 def set_authenticated(self, authenticated=True):
422 if self.user_id != self.anonymous_user.user_id:
422 if self.user_id != self.anonymous_user.user_id:
423 self.is_authenticated = authenticated
423 self.is_authenticated = authenticated
424
424
425 def get_cookie_store(self):
425 def get_cookie_store(self):
426 return {'username': self.username,
426 return {'username': self.username,
427 'user_id': self.user_id,
427 'user_id': self.user_id,
428 'is_authenticated': self.is_authenticated}
428 'is_authenticated': self.is_authenticated}
429
429
430 @classmethod
430 @classmethod
431 def from_cookie_store(cls, cookie_store):
431 def from_cookie_store(cls, cookie_store):
432 """
432 """
433 Creates AuthUser from a cookie store
433 Creates AuthUser from a cookie store
434
434
435 :param cls:
435 :param cls:
436 :param cookie_store:
436 :param cookie_store:
437 """
437 """
438 user_id = cookie_store.get('user_id')
438 user_id = cookie_store.get('user_id')
439 username = cookie_store.get('username')
439 username = cookie_store.get('username')
440 api_key = cookie_store.get('api_key')
440 api_key = cookie_store.get('api_key')
441 return AuthUser(user_id, api_key, username)
441 return AuthUser(user_id, api_key, username)
442
442
443 @classmethod
443 @classmethod
444 def get_allowed_ips(cls, user_id, cache=False):
444 def get_allowed_ips(cls, user_id, cache=False):
445 _set = set()
445 _set = set()
446 user_ips = UserIpMap.query().filter(UserIpMap.user_id == user_id)
446 user_ips = UserIpMap.query().filter(UserIpMap.user_id == user_id)
447 if cache:
447 if cache:
448 user_ips = user_ips.options(FromCache("sql_cache_short",
448 user_ips = user_ips.options(FromCache("sql_cache_short",
449 "get_user_ips_%s" % user_id))
449 "get_user_ips_%s" % user_id))
450 for ip in user_ips:
450 for ip in user_ips:
451 try:
451 try:
452 _set.add(ip.ip_addr)
452 _set.add(ip.ip_addr)
453 except ObjectDeletedError:
453 except ObjectDeletedError:
454 # since we use heavy caching sometimes it happens that we get
454 # since we use heavy caching sometimes it happens that we get
455 # deleted objects here, we just skip them
455 # deleted objects here, we just skip them
456 pass
456 pass
457 return _set or set(['0.0.0.0/0', '::/0'])
457 return _set or set(['0.0.0.0/0', '::/0'])
458
458
459
459
460 def set_available_permissions(config):
460 def set_available_permissions(config):
461 """
461 """
462 This function will propagate pylons globals with all available defined
462 This function will propagate pylons globals with all available defined
463 permission given in db. We don't want to check each time from db for new
463 permission given in db. We don't want to check each time from db for new
464 permissions since adding a new permission also requires application restart
464 permissions since adding a new permission also requires application restart
465 ie. to decorate new views with the newly created permission
465 ie. to decorate new views with the newly created permission
466
466
467 :param config: current pylons config instance
467 :param config: current pylons config instance
468
468
469 """
469 """
470 log.info('getting information about all available permissions')
470 log.info('getting information about all available permissions')
471 try:
471 try:
472 sa = meta.Session
472 sa = meta.Session
473 all_perms = sa.query(Permission).all()
473 all_perms = sa.query(Permission).all()
474 except Exception:
474 except Exception:
475 pass
475 pass
476 finally:
476 finally:
477 meta.Session.remove()
477 meta.Session.remove()
478
478
479 config['available_permissions'] = [x.permission_name for x in all_perms]
479 config['available_permissions'] = [x.permission_name for x in all_perms]
480
480
481
481
482 #==============================================================================
482 #==============================================================================
483 # CHECK DECORATORS
483 # CHECK DECORATORS
484 #==============================================================================
484 #==============================================================================
485 class LoginRequired(object):
485 class LoginRequired(object):
486 """
486 """
487 Must be logged in to execute this function else
487 Must be logged in to execute this function else
488 redirect to login page
488 redirect to login page
489
489
490 :param api_access: if enabled this checks only for valid auth token
490 :param api_access: if enabled this checks only for valid auth token
491 and grants access based on valid token
491 and grants access based on valid token
492 """
492 """
493
493
494 def __init__(self, api_access=False):
494 def __init__(self, api_access=False):
495 self.api_access = api_access
495 self.api_access = api_access
496
496
497 def __call__(self, func):
497 def __call__(self, func):
498 return decorator(self.__wrapper, func)
498 return decorator(self.__wrapper, func)
499
499
500 def __wrapper(self, func, *fargs, **fkwargs):
500 def __wrapper(self, func, *fargs, **fkwargs):
501 cls = fargs[0]
501 cls = fargs[0]
502 user = cls.rhodecode_user
502 user = cls.rhodecode_user
503 loc = "%s:%s" % (cls.__class__.__name__, func.__name__)
503 loc = "%s:%s" % (cls.__class__.__name__, func.__name__)
504
504
505 #check IP
505 #check IP
506 ip_access_ok = True
506 ip_access_ok = True
507 if not user.ip_allowed:
507 if not user.ip_allowed:
508 from rhodecode.lib import helpers as h
508 from rhodecode.lib import helpers as h
509 h.flash(h.literal(_('IP %s not allowed' % (user.ip_addr))),
509 h.flash(h.literal(_('IP %s not allowed' % (user.ip_addr))),
510 category='warning')
510 category='warning')
511 ip_access_ok = False
511 ip_access_ok = False
512
512
513 api_access_ok = False
513 api_access_ok = False
514 if self.api_access:
514 if self.api_access:
515 log.debug('Checking API KEY access for %s' % cls)
515 log.debug('Checking API KEY access for %s' % cls)
516 if user.api_key == request.GET.get('api_key'):
516 if user.api_key == request.GET.get('api_key'):
517 api_access_ok = True
517 api_access_ok = True
518 else:
518 else:
519 log.debug("API KEY token not valid")
519 log.debug("API KEY token not valid")
520
520
521 log.debug('Checking if %s is authenticated @ %s' % (user.username, loc))
521 log.debug('Checking if %s is authenticated @ %s' % (user.username, loc))
522 if (user.is_authenticated or api_access_ok) and ip_access_ok:
522 if (user.is_authenticated or api_access_ok) and ip_access_ok:
523 reason = 'RegularAuth' if user.is_authenticated else 'APIAuth'
523 reason = 'RegularAuth' if user.is_authenticated else 'APIAuth'
524 log.info('user %s is authenticated and granted access to %s '
524 log.info('user %s is authenticated and granted access to %s '
525 'using %s' % (user.username, loc, reason)
525 'using %s' % (user.username, loc, reason)
526 )
526 )
527 return func(*fargs, **fkwargs)
527 return func(*fargs, **fkwargs)
528 else:
528 else:
529 log.warn('user %s NOT authenticated on func: %s' % (
529 log.warn('user %s NOT authenticated on func: %s' % (
530 user, loc)
530 user, loc)
531 )
531 )
532 p = url.current()
532 p = url.current()
533
533
534 log.debug('redirecting to login page with %s' % p)
534 log.debug('redirecting to login page with %s' % p)
535 return redirect(url('login_home', came_from=p))
535 return redirect(url('login_home', came_from=p))
536
536
537
537
538 class NotAnonymous(object):
538 class NotAnonymous(object):
539 """
539 """
540 Must be logged in to execute this function else
540 Must be logged in to execute this function else
541 redirect to login page"""
541 redirect to login page"""
542
542
543 def __call__(self, func):
543 def __call__(self, func):
544 return decorator(self.__wrapper, func)
544 return decorator(self.__wrapper, func)
545
545
546 def __wrapper(self, func, *fargs, **fkwargs):
546 def __wrapper(self, func, *fargs, **fkwargs):
547 cls = fargs[0]
547 cls = fargs[0]
548 self.user = cls.rhodecode_user
548 self.user = cls.rhodecode_user
549
549
550 log.debug('Checking if user is not anonymous @%s' % cls)
550 log.debug('Checking if user is not anonymous @%s' % cls)
551
551
552 anonymous = self.user.username == 'default'
552 anonymous = self.user.username == 'default'
553
553
554 if anonymous:
554 if anonymous:
555 p = url.current()
555 p = url.current()
556
556
557 import rhodecode.lib.helpers as h
557 import rhodecode.lib.helpers as h
558 h.flash(_('You need to be a registered user to '
558 h.flash(_('You need to be a registered user to '
559 'perform this action'),
559 'perform this action'),
560 category='warning')
560 category='warning')
561 return redirect(url('login_home', came_from=p))
561 return redirect(url('login_home', came_from=p))
562 else:
562 else:
563 return func(*fargs, **fkwargs)
563 return func(*fargs, **fkwargs)
564
564
565
565
566 class PermsDecorator(object):
566 class PermsDecorator(object):
567 """Base class for controller decorators"""
567 """Base class for controller decorators"""
568
568
569 def __init__(self, *required_perms):
569 def __init__(self, *required_perms):
570 available_perms = config['available_permissions']
570 available_perms = config['available_permissions']
571 for perm in required_perms:
571 for perm in required_perms:
572 if perm not in available_perms:
572 if perm not in available_perms:
573 raise Exception("'%s' permission is not defined" % perm)
573 raise Exception("'%s' permission is not defined" % perm)
574 self.required_perms = set(required_perms)
574 self.required_perms = set(required_perms)
575 self.user_perms = None
575 self.user_perms = None
576
576
577 def __call__(self, func):
577 def __call__(self, func):
578 return decorator(self.__wrapper, func)
578 return decorator(self.__wrapper, func)
579
579
580 def __wrapper(self, func, *fargs, **fkwargs):
580 def __wrapper(self, func, *fargs, **fkwargs):
581 cls = fargs[0]
581 cls = fargs[0]
582 self.user = cls.rhodecode_user
582 self.user = cls.rhodecode_user
583 self.user_perms = self.user.permissions
583 self.user_perms = self.user.permissions
584 log.debug('checking %s permissions %s for %s %s',
584 log.debug('checking %s permissions %s for %s %s',
585 self.__class__.__name__, self.required_perms, cls, self.user)
585 self.__class__.__name__, self.required_perms, cls, self.user)
586
586
587 if self.check_permissions():
587 if self.check_permissions():
588 log.debug('Permission granted for %s %s' % (cls, self.user))
588 log.debug('Permission granted for %s %s' % (cls, self.user))
589 return func(*fargs, **fkwargs)
589 return func(*fargs, **fkwargs)
590
590
591 else:
591 else:
592 log.debug('Permission denied for %s %s' % (cls, self.user))
592 log.debug('Permission denied for %s %s' % (cls, self.user))
593 anonymous = self.user.username == 'default'
593 anonymous = self.user.username == 'default'
594
594
595 if anonymous:
595 if anonymous:
596 p = url.current()
596 p = url.current()
597
597
598 import rhodecode.lib.helpers as h
598 import rhodecode.lib.helpers as h
599 h.flash(_('You need to be a signed in to '
599 h.flash(_('You need to be a signed in to '
600 'view this page'),
600 'view this page'),
601 category='warning')
601 category='warning')
602 return redirect(url('login_home', came_from=p))
602 return redirect(url('login_home', came_from=p))
603
603
604 else:
604 else:
605 # redirect with forbidden ret code
605 # redirect with forbidden ret code
606 return abort(403)
606 return abort(403)
607
607
608 def check_permissions(self):
608 def check_permissions(self):
609 """Dummy function for overriding"""
609 """Dummy function for overriding"""
610 raise Exception('You have to write this function in child class')
610 raise Exception('You have to write this function in child class')
611
611
612
612
613 class HasPermissionAllDecorator(PermsDecorator):
613 class HasPermissionAllDecorator(PermsDecorator):
614 """
614 """
615 Checks for access permission for all given predicates. All of them
615 Checks for access permission for all given predicates. All of them
616 have to be meet in order to fulfill the request
616 have to be meet in order to fulfill the request
617 """
617 """
618
618
619 def check_permissions(self):
619 def check_permissions(self):
620 if self.required_perms.issubset(self.user_perms.get('global')):
620 if self.required_perms.issubset(self.user_perms.get('global')):
621 return True
621 return True
622 return False
622 return False
623
623
624
624
625 class HasPermissionAnyDecorator(PermsDecorator):
625 class HasPermissionAnyDecorator(PermsDecorator):
626 """
626 """
627 Checks for access permission for any of given predicates. In order to
627 Checks for access permission for any of given predicates. In order to
628 fulfill the request any of predicates must be meet
628 fulfill the request any of predicates must be meet
629 """
629 """
630
630
631 def check_permissions(self):
631 def check_permissions(self):
632 if self.required_perms.intersection(self.user_perms.get('global')):
632 if self.required_perms.intersection(self.user_perms.get('global')):
633 return True
633 return True
634 return False
634 return False
635
635
636
636
637 class HasRepoPermissionAllDecorator(PermsDecorator):
637 class HasRepoPermissionAllDecorator(PermsDecorator):
638 """
638 """
639 Checks for access permission for all given predicates for specific
639 Checks for access permission for all given predicates for specific
640 repository. All of them have to be meet in order to fulfill the request
640 repository. All of them have to be meet in order to fulfill the request
641 """
641 """
642
642
643 def check_permissions(self):
643 def check_permissions(self):
644 repo_name = get_repo_slug(request)
644 repo_name = get_repo_slug(request)
645 try:
645 try:
646 user_perms = set([self.user_perms['repositories'][repo_name]])
646 user_perms = set([self.user_perms['repositories'][repo_name]])
647 except KeyError:
647 except KeyError:
648 return False
648 return False
649 if self.required_perms.issubset(user_perms):
649 if self.required_perms.issubset(user_perms):
650 return True
650 return True
651 return False
651 return False
652
652
653
653
654 class HasRepoPermissionAnyDecorator(PermsDecorator):
654 class HasRepoPermissionAnyDecorator(PermsDecorator):
655 """
655 """
656 Checks for access permission for any of given predicates for specific
656 Checks for access permission for any of given predicates for specific
657 repository. In order to fulfill the request any of predicates must be meet
657 repository. In order to fulfill the request any of predicates must be meet
658 """
658 """
659
659
660 def check_permissions(self):
660 def check_permissions(self):
661 repo_name = get_repo_slug(request)
661 repo_name = get_repo_slug(request)
662
663 try:
662 try:
664 user_perms = set([self.user_perms['repositories'][repo_name]])
663 user_perms = set([self.user_perms['repositories'][repo_name]])
665 except KeyError:
664 except KeyError:
666 return False
665 return False
667
666
668 if self.required_perms.intersection(user_perms):
667 if self.required_perms.intersection(user_perms):
669 return True
668 return True
670 return False
669 return False
671
670
672
671
673 class HasReposGroupPermissionAllDecorator(PermsDecorator):
672 class HasReposGroupPermissionAllDecorator(PermsDecorator):
674 """
673 """
675 Checks for access permission for all given predicates for specific
674 Checks for access permission for all given predicates for specific
676 repository. All of them have to be meet in order to fulfill the request
675 repository. All of them have to be meet in order to fulfill the request
677 """
676 """
678
677
679 def check_permissions(self):
678 def check_permissions(self):
680 group_name = get_repos_group_slug(request)
679 group_name = get_repos_group_slug(request)
681 try:
680 try:
682 user_perms = set([self.user_perms['repositories_groups'][group_name]])
681 user_perms = set([self.user_perms['repositories_groups'][group_name]])
683 except KeyError:
682 except KeyError:
684 return False
683 return False
684
685 if self.required_perms.issubset(user_perms):
685 if self.required_perms.issubset(user_perms):
686 return True
686 return True
687 return False
687 return False
688
688
689
689
690 class HasReposGroupPermissionAnyDecorator(PermsDecorator):
690 class HasReposGroupPermissionAnyDecorator(PermsDecorator):
691 """
691 """
692 Checks for access permission for any of given predicates for specific
692 Checks for access permission for any of given predicates for specific
693 repository. In order to fulfill the request any of predicates must be meet
693 repository. In order to fulfill the request any of predicates must be meet
694 """
694 """
695
695
696 def check_permissions(self):
696 def check_permissions(self):
697 group_name = get_repos_group_slug(request)
697 group_name = get_repos_group_slug(request)
698
699 try:
698 try:
700 user_perms = set([self.user_perms['repositories_groups'][group_name]])
699 user_perms = set([self.user_perms['repositories_groups'][group_name]])
701 except KeyError:
700 except KeyError:
702 return False
701 return False
702
703 if self.required_perms.intersection(user_perms):
703 if self.required_perms.intersection(user_perms):
704 return True
704 return True
705 return False
705 return False
706
706
707
707
708 #==============================================================================
708 #==============================================================================
709 # CHECK FUNCTIONS
709 # CHECK FUNCTIONS
710 #==============================================================================
710 #==============================================================================
711 class PermsFunction(object):
711 class PermsFunction(object):
712 """Base function for other check functions"""
712 """Base function for other check functions"""
713
713
714 def __init__(self, *perms):
714 def __init__(self, *perms):
715 available_perms = config['available_permissions']
715 available_perms = config['available_permissions']
716
716
717 for perm in perms:
717 for perm in perms:
718 if perm not in available_perms:
718 if perm not in available_perms:
719 raise Exception("'%s' permission is not defined" % perm)
719 raise Exception("'%s' permission is not defined" % perm)
720 self.required_perms = set(perms)
720 self.required_perms = set(perms)
721 self.user_perms = None
721 self.user_perms = None
722 self.repo_name = None
722 self.repo_name = None
723 self.group_name = None
723 self.group_name = None
724
724
725 def __call__(self, check_Location=''):
725 def __call__(self, check_Location=''):
726 user = request.user
726 user = request.user
727 cls_name = self.__class__.__name__
727 cls_name = self.__class__.__name__
728 check_scope = {
728 check_scope = {
729 'HasPermissionAll': '',
729 'HasPermissionAll': '',
730 'HasPermissionAny': '',
730 'HasPermissionAny': '',
731 'HasRepoPermissionAll': 'repo:%s' % self.repo_name,
731 'HasRepoPermissionAll': 'repo:%s' % self.repo_name,
732 'HasRepoPermissionAny': 'repo:%s' % self.repo_name,
732 'HasRepoPermissionAny': 'repo:%s' % self.repo_name,
733 'HasReposGroupPermissionAll': 'group:%s' % self.group_name,
733 'HasReposGroupPermissionAll': 'group:%s' % self.group_name,
734 'HasReposGroupPermissionAny': 'group:%s' % self.group_name,
734 'HasReposGroupPermissionAny': 'group:%s' % self.group_name,
735 }.get(cls_name, '?')
735 }.get(cls_name, '?')
736 log.debug('checking cls:%s %s usr:%s %s @ %s', cls_name,
736 log.debug('checking cls:%s %s usr:%s %s @ %s', cls_name,
737 self.required_perms, user, check_scope,
737 self.required_perms, user, check_scope,
738 check_Location or 'unspecified location')
738 check_Location or 'unspecified location')
739 if not user:
739 if not user:
740 log.debug('Empty request user')
740 log.debug('Empty request user')
741 return False
741 return False
742 self.user_perms = user.permissions
742 self.user_perms = user.permissions
743 if self.check_permissions():
743 if self.check_permissions():
744 log.debug('Permission to %s granted for user: %s @ %s', self.repo_name, user,
744 log.debug('Permission to %s granted for user: %s @ %s', self.repo_name, user,
745 check_Location or 'unspecified location')
745 check_Location or 'unspecified location')
746 return True
746 return True
747
747
748 else:
748 else:
749 log.debug('Permission to %s denied for user: %s @ %s', self.repo_name, user,
749 log.debug('Permission to %s denied for user: %s @ %s', self.repo_name, user,
750 check_Location or 'unspecified location')
750 check_Location or 'unspecified location')
751 return False
751 return False
752
752
753 def check_permissions(self):
753 def check_permissions(self):
754 """Dummy function for overriding"""
754 """Dummy function for overriding"""
755 raise Exception('You have to write this function in child class')
755 raise Exception('You have to write this function in child class')
756
756
757
757
758 class HasPermissionAll(PermsFunction):
758 class HasPermissionAll(PermsFunction):
759 def check_permissions(self):
759 def check_permissions(self):
760 if self.required_perms.issubset(self.user_perms.get('global')):
760 if self.required_perms.issubset(self.user_perms.get('global')):
761 return True
761 return True
762 return False
762 return False
763
763
764
764
765 class HasPermissionAny(PermsFunction):
765 class HasPermissionAny(PermsFunction):
766 def check_permissions(self):
766 def check_permissions(self):
767 if self.required_perms.intersection(self.user_perms.get('global')):
767 if self.required_perms.intersection(self.user_perms.get('global')):
768 return True
768 return True
769 return False
769 return False
770
770
771
771
772 class HasRepoPermissionAll(PermsFunction):
772 class HasRepoPermissionAll(PermsFunction):
773 def __call__(self, repo_name=None, check_Location=''):
773 def __call__(self, repo_name=None, check_Location=''):
774 self.repo_name = repo_name
774 self.repo_name = repo_name
775 return super(HasRepoPermissionAll, self).__call__(check_Location)
775 return super(HasRepoPermissionAll, self).__call__(check_Location)
776
776
777 def check_permissions(self):
777 def check_permissions(self):
778 if not self.repo_name:
778 if not self.repo_name:
779 self.repo_name = get_repo_slug(request)
779 self.repo_name = get_repo_slug(request)
780
780
781 try:
781 try:
782 self._user_perms = set(
782 self._user_perms = set(
783 [self.user_perms['repositories'][self.repo_name]]
783 [self.user_perms['repositories'][self.repo_name]]
784 )
784 )
785 except KeyError:
785 except KeyError:
786 return False
786 return False
787 if self.required_perms.issubset(self._user_perms):
787 if self.required_perms.issubset(self._user_perms):
788 return True
788 return True
789 return False
789 return False
790
790
791
791
792 class HasRepoPermissionAny(PermsFunction):
792 class HasRepoPermissionAny(PermsFunction):
793 def __call__(self, repo_name=None, check_Location=''):
793 def __call__(self, repo_name=None, check_Location=''):
794 self.repo_name = repo_name
794 self.repo_name = repo_name
795 return super(HasRepoPermissionAny, self).__call__(check_Location)
795 return super(HasRepoPermissionAny, self).__call__(check_Location)
796
796
797 def check_permissions(self):
797 def check_permissions(self):
798 if not self.repo_name:
798 if not self.repo_name:
799 self.repo_name = get_repo_slug(request)
799 self.repo_name = get_repo_slug(request)
800
800
801 try:
801 try:
802 self._user_perms = set(
802 self._user_perms = set(
803 [self.user_perms['repositories'][self.repo_name]]
803 [self.user_perms['repositories'][self.repo_name]]
804 )
804 )
805 except KeyError:
805 except KeyError:
806 return False
806 return False
807 if self.required_perms.intersection(self._user_perms):
807 if self.required_perms.intersection(self._user_perms):
808 return True
808 return True
809 return False
809 return False
810
810
811
811
812 class HasReposGroupPermissionAny(PermsFunction):
812 class HasReposGroupPermissionAny(PermsFunction):
813 def __call__(self, group_name=None, check_Location=''):
813 def __call__(self, group_name=None, check_Location=''):
814 self.group_name = group_name
814 self.group_name = group_name
815 return super(HasReposGroupPermissionAny, self).__call__(check_Location)
815 return super(HasReposGroupPermissionAny, self).__call__(check_Location)
816
816
817 def check_permissions(self):
817 def check_permissions(self):
818 try:
818 try:
819 self._user_perms = set(
819 self._user_perms = set(
820 [self.user_perms['repositories_groups'][self.group_name]]
820 [self.user_perms['repositories_groups'][self.group_name]]
821 )
821 )
822 except KeyError:
822 except KeyError:
823 return False
823 return False
824 if self.required_perms.intersection(self._user_perms):
824 if self.required_perms.intersection(self._user_perms):
825 return True
825 return True
826 return False
826 return False
827
827
828
828
829 class HasReposGroupPermissionAll(PermsFunction):
829 class HasReposGroupPermissionAll(PermsFunction):
830 def __call__(self, group_name=None, check_Location=''):
830 def __call__(self, group_name=None, check_Location=''):
831 self.group_name = group_name
831 self.group_name = group_name
832 return super(HasReposGroupPermissionAll, self).__call__(check_Location)
832 return super(HasReposGroupPermissionAll, self).__call__(check_Location)
833
833
834 def check_permissions(self):
834 def check_permissions(self):
835 try:
835 try:
836 self._user_perms = set(
836 self._user_perms = set(
837 [self.user_perms['repositories_groups'][self.group_name]]
837 [self.user_perms['repositories_groups'][self.group_name]]
838 )
838 )
839 except KeyError:
839 except KeyError:
840 return False
840 return False
841 if self.required_perms.issubset(self._user_perms):
841 if self.required_perms.issubset(self._user_perms):
842 return True
842 return True
843 return False
843 return False
844
844
845
845
846 #==============================================================================
846 #==============================================================================
847 # SPECIAL VERSION TO HANDLE MIDDLEWARE AUTH
847 # SPECIAL VERSION TO HANDLE MIDDLEWARE AUTH
848 #==============================================================================
848 #==============================================================================
849 class HasPermissionAnyMiddleware(object):
849 class HasPermissionAnyMiddleware(object):
850 def __init__(self, *perms):
850 def __init__(self, *perms):
851 self.required_perms = set(perms)
851 self.required_perms = set(perms)
852
852
853 def __call__(self, user, repo_name):
853 def __call__(self, user, repo_name):
854 # repo_name MUST be unicode, since we handle keys in permission
854 # repo_name MUST be unicode, since we handle keys in permission
855 # dict by unicode
855 # dict by unicode
856 repo_name = safe_unicode(repo_name)
856 repo_name = safe_unicode(repo_name)
857 usr = AuthUser(user.user_id)
857 usr = AuthUser(user.user_id)
858 try:
858 try:
859 self.user_perms = set([usr.permissions['repositories'][repo_name]])
859 self.user_perms = set([usr.permissions['repositories'][repo_name]])
860 except Exception:
860 except Exception:
861 log.error('Exception while accessing permissions %s' %
861 log.error('Exception while accessing permissions %s' %
862 traceback.format_exc())
862 traceback.format_exc())
863 self.user_perms = set()
863 self.user_perms = set()
864 self.username = user.username
864 self.username = user.username
865 self.repo_name = repo_name
865 self.repo_name = repo_name
866 return self.check_permissions()
866 return self.check_permissions()
867
867
868 def check_permissions(self):
868 def check_permissions(self):
869 log.debug('checking VCS protocol '
869 log.debug('checking VCS protocol '
870 'permissions %s for user:%s repository:%s', self.user_perms,
870 'permissions %s for user:%s repository:%s', self.user_perms,
871 self.username, self.repo_name)
871 self.username, self.repo_name)
872 if self.required_perms.intersection(self.user_perms):
872 if self.required_perms.intersection(self.user_perms):
873 log.debug('permission granted for user:%s on repo:%s' % (
873 log.debug('permission granted for user:%s on repo:%s' % (
874 self.username, self.repo_name
874 self.username, self.repo_name
875 )
875 )
876 )
876 )
877 return True
877 return True
878 log.debug('permission denied for user:%s on repo:%s' % (
878 log.debug('permission denied for user:%s on repo:%s' % (
879 self.username, self.repo_name
879 self.username, self.repo_name
880 )
880 )
881 )
881 )
882 return False
882 return False
883
883
884
884
885 #==============================================================================
885 #==============================================================================
886 # SPECIAL VERSION TO HANDLE API AUTH
886 # SPECIAL VERSION TO HANDLE API AUTH
887 #==============================================================================
887 #==============================================================================
888 class _BaseApiPerm(object):
888 class _BaseApiPerm(object):
889 def __init__(self, *perms):
889 def __init__(self, *perms):
890 self.required_perms = set(perms)
890 self.required_perms = set(perms)
891
891
892 def __call__(self, check_location='unspecified', user=None, repo_name=None):
892 def __call__(self, check_location='unspecified', user=None, repo_name=None):
893 cls_name = self.__class__.__name__
893 cls_name = self.__class__.__name__
894 check_scope = 'user:%s, repo:%s' % (user, repo_name)
894 check_scope = 'user:%s, repo:%s' % (user, repo_name)
895 log.debug('checking cls:%s %s %s @ %s', cls_name,
895 log.debug('checking cls:%s %s %s @ %s', cls_name,
896 self.required_perms, check_scope, check_location)
896 self.required_perms, check_scope, check_location)
897 if not user:
897 if not user:
898 log.debug('Empty User passed into arguments')
898 log.debug('Empty User passed into arguments')
899 return False
899 return False
900
900
901 ## process user
901 ## process user
902 if not isinstance(user, AuthUser):
902 if not isinstance(user, AuthUser):
903 user = AuthUser(user.user_id)
903 user = AuthUser(user.user_id)
904
904
905 if self.check_permissions(user.permissions, repo_name):
905 if self.check_permissions(user.permissions, repo_name):
906 log.debug('Permission to %s granted for user: %s @ %s', repo_name,
906 log.debug('Permission to %s granted for user: %s @ %s', repo_name,
907 user, check_location)
907 user, check_location)
908 return True
908 return True
909
909
910 else:
910 else:
911 log.debug('Permission to %s denied for user: %s @ %s', repo_name,
911 log.debug('Permission to %s denied for user: %s @ %s', repo_name,
912 user, check_location)
912 user, check_location)
913 return False
913 return False
914
914
915 def check_permissions(self, perm_defs, repo_name):
915 def check_permissions(self, perm_defs, repo_name):
916 """
916 """
917 implement in child class should return True if permissions are ok,
917 implement in child class should return True if permissions are ok,
918 False otherwise
918 False otherwise
919
919
920 :param perm_defs: dict with permission definitions
920 :param perm_defs: dict with permission definitions
921 :param repo_name: repo name
921 :param repo_name: repo name
922 """
922 """
923 raise NotImplementedError()
923 raise NotImplementedError()
924
924
925
925
926 class HasPermissionAllApi(_BaseApiPerm):
926 class HasPermissionAllApi(_BaseApiPerm):
927 def __call__(self, user, check_location=''):
927 def __call__(self, user, check_location=''):
928 return super(HasPermissionAllApi, self)\
928 return super(HasPermissionAllApi, self)\
929 .__call__(check_location=check_location, user=user)
929 .__call__(check_location=check_location, user=user)
930
930
931 def check_permissions(self, perm_defs, repo):
931 def check_permissions(self, perm_defs, repo):
932 if self.required_perms.issubset(perm_defs.get('global')):
932 if self.required_perms.issubset(perm_defs.get('global')):
933 return True
933 return True
934 return False
934 return False
935
935
936
936
937 class HasPermissionAnyApi(_BaseApiPerm):
937 class HasPermissionAnyApi(_BaseApiPerm):
938 def __call__(self, user, check_location=''):
938 def __call__(self, user, check_location=''):
939 return super(HasPermissionAnyApi, self)\
939 return super(HasPermissionAnyApi, self)\
940 .__call__(check_location=check_location, user=user)
940 .__call__(check_location=check_location, user=user)
941
941
942 def check_permissions(self, perm_defs, repo):
942 def check_permissions(self, perm_defs, repo):
943 if self.required_perms.intersection(perm_defs.get('global')):
943 if self.required_perms.intersection(perm_defs.get('global')):
944 return True
944 return True
945 return False
945 return False
946
946
947
947
948 class HasRepoPermissionAllApi(_BaseApiPerm):
948 class HasRepoPermissionAllApi(_BaseApiPerm):
949 def __call__(self, user, repo_name, check_location=''):
949 def __call__(self, user, repo_name, check_location=''):
950 return super(HasRepoPermissionAllApi, self)\
950 return super(HasRepoPermissionAllApi, self)\
951 .__call__(check_location=check_location, user=user,
951 .__call__(check_location=check_location, user=user,
952 repo_name=repo_name)
952 repo_name=repo_name)
953
953
954 def check_permissions(self, perm_defs, repo_name):
954 def check_permissions(self, perm_defs, repo_name):
955
955
956 try:
956 try:
957 self._user_perms = set(
957 self._user_perms = set(
958 [perm_defs['repositories'][repo_name]]
958 [perm_defs['repositories'][repo_name]]
959 )
959 )
960 except KeyError:
960 except KeyError:
961 log.warning(traceback.format_exc())
961 log.warning(traceback.format_exc())
962 return False
962 return False
963 if self.required_perms.issubset(self._user_perms):
963 if self.required_perms.issubset(self._user_perms):
964 return True
964 return True
965 return False
965 return False
966
966
967
967
968 class HasRepoPermissionAnyApi(_BaseApiPerm):
968 class HasRepoPermissionAnyApi(_BaseApiPerm):
969 def __call__(self, user, repo_name, check_location=''):
969 def __call__(self, user, repo_name, check_location=''):
970 return super(HasRepoPermissionAnyApi, self)\
970 return super(HasRepoPermissionAnyApi, self)\
971 .__call__(check_location=check_location, user=user,
971 .__call__(check_location=check_location, user=user,
972 repo_name=repo_name)
972 repo_name=repo_name)
973
973
974 def check_permissions(self, perm_defs, repo_name):
974 def check_permissions(self, perm_defs, repo_name):
975
975
976 try:
976 try:
977 _user_perms = set(
977 _user_perms = set(
978 [perm_defs['repositories'][repo_name]]
978 [perm_defs['repositories'][repo_name]]
979 )
979 )
980 except KeyError:
980 except KeyError:
981 log.warning(traceback.format_exc())
981 log.warning(traceback.format_exc())
982 return False
982 return False
983 if self.required_perms.intersection(_user_perms):
983 if self.required_perms.intersection(_user_perms):
984 return True
984 return True
985 return False
985 return False
986
986
987
987
988 def check_ip_access(source_ip, allowed_ips=None):
988 def check_ip_access(source_ip, allowed_ips=None):
989 """
989 """
990 Checks if source_ip is a subnet of any of allowed_ips.
990 Checks if source_ip is a subnet of any of allowed_ips.
991
991
992 :param source_ip:
992 :param source_ip:
993 :param allowed_ips: list of allowed ips together with mask
993 :param allowed_ips: list of allowed ips together with mask
994 """
994 """
995 from rhodecode.lib import ipaddr
995 from rhodecode.lib import ipaddr
996 log.debug('checking if ip:%s is subnet of %s' % (source_ip, allowed_ips))
996 log.debug('checking if ip:%s is subnet of %s' % (source_ip, allowed_ips))
997 if isinstance(allowed_ips, (tuple, list, set)):
997 if isinstance(allowed_ips, (tuple, list, set)):
998 for ip in allowed_ips:
998 for ip in allowed_ips:
999 try:
999 try:
1000 if ipaddr.IPAddress(source_ip) in ipaddr.IPNetwork(ip):
1000 if ipaddr.IPAddress(source_ip) in ipaddr.IPNetwork(ip):
1001 return True
1001 return True
1002 # for any case we cannot determine the IP, don't crash just
1002 # for any case we cannot determine the IP, don't crash just
1003 # skip it and log as error, we want to say forbidden still when
1003 # skip it and log as error, we want to say forbidden still when
1004 # sending bad IP
1004 # sending bad IP
1005 except Exception:
1005 except Exception:
1006 log.error(traceback.format_exc())
1006 log.error(traceback.format_exc())
1007 continue
1007 continue
1008 return False
1008 return False
@@ -1,1172 +1,1173 b''
1 """Helper functions
1 """Helper functions
2
2
3 Consists of functions to typically be used within templates, but also
3 Consists of functions to typically be used within templates, but also
4 available to Controllers. This module is available to both as 'h'.
4 available to Controllers. This module is available to both as 'h'.
5 """
5 """
6 import random
6 import random
7 import hashlib
7 import hashlib
8 import StringIO
8 import StringIO
9 import urllib
9 import urllib
10 import math
10 import math
11 import logging
11 import logging
12 import re
12 import re
13 import urlparse
13 import urlparse
14 import textwrap
14 import textwrap
15
15
16 from datetime import datetime
16 from datetime import datetime
17 from pygments.formatters.html import HtmlFormatter
17 from pygments.formatters.html import HtmlFormatter
18 from pygments import highlight as code_highlight
18 from pygments import highlight as code_highlight
19 from pylons import url, request, config
19 from pylons import url, request, config
20 from pylons.i18n.translation import _, ungettext
20 from pylons.i18n.translation import _, ungettext
21 from hashlib import md5
21 from hashlib import md5
22
22
23 from webhelpers.html import literal, HTML, escape
23 from webhelpers.html import literal, HTML, escape
24 from webhelpers.html.tools import *
24 from webhelpers.html.tools import *
25 from webhelpers.html.builder import make_tag
25 from webhelpers.html.builder import make_tag
26 from webhelpers.html.tags import auto_discovery_link, checkbox, css_classes, \
26 from webhelpers.html.tags import auto_discovery_link, checkbox, css_classes, \
27 end_form, file, form, hidden, image, javascript_link, link_to, \
27 end_form, file, form, hidden, image, javascript_link, link_to, \
28 link_to_if, link_to_unless, ol, required_legend, select, stylesheet_link, \
28 link_to_if, link_to_unless, ol, required_legend, select, stylesheet_link, \
29 submit, text, password, textarea, title, ul, xml_declaration, radio
29 submit, text, password, textarea, title, ul, xml_declaration, radio
30 from webhelpers.html.tools import auto_link, button_to, highlight, \
30 from webhelpers.html.tools import auto_link, button_to, highlight, \
31 js_obfuscate, mail_to, strip_links, strip_tags, tag_re
31 js_obfuscate, mail_to, strip_links, strip_tags, tag_re
32 from webhelpers.number import format_byte_size, format_bit_size
32 from webhelpers.number import format_byte_size, format_bit_size
33 from webhelpers.pylonslib import Flash as _Flash
33 from webhelpers.pylonslib import Flash as _Flash
34 from webhelpers.pylonslib.secure_form import secure_form
34 from webhelpers.pylonslib.secure_form import secure_form
35 from webhelpers.text import chop_at, collapse, convert_accented_entities, \
35 from webhelpers.text import chop_at, collapse, convert_accented_entities, \
36 convert_misc_entities, lchop, plural, rchop, remove_formatting, \
36 convert_misc_entities, lchop, plural, rchop, remove_formatting, \
37 replace_whitespace, urlify, truncate, wrap_paragraphs
37 replace_whitespace, urlify, truncate, wrap_paragraphs
38 from webhelpers.date import time_ago_in_words
38 from webhelpers.date import time_ago_in_words
39 from webhelpers.paginate import Page
39 from webhelpers.paginate import Page
40 from webhelpers.html.tags import _set_input_attrs, _set_id_attr, \
40 from webhelpers.html.tags import _set_input_attrs, _set_id_attr, \
41 convert_boolean_attrs, NotGiven, _make_safe_id_component
41 convert_boolean_attrs, NotGiven, _make_safe_id_component
42
42
43 from rhodecode.lib.annotate import annotate_highlight
43 from rhodecode.lib.annotate import annotate_highlight
44 from rhodecode.lib.utils import repo_name_slug
44 from rhodecode.lib.utils import repo_name_slug
45 from rhodecode.lib.utils2 import str2bool, safe_unicode, safe_str, \
45 from rhodecode.lib.utils2 import str2bool, safe_unicode, safe_str, \
46 get_changeset_safe, datetime_to_time, time_to_datetime, AttributeDict
46 get_changeset_safe, datetime_to_time, time_to_datetime, AttributeDict
47 from rhodecode.lib.markup_renderer import MarkupRenderer
47 from rhodecode.lib.markup_renderer import MarkupRenderer
48 from rhodecode.lib.vcs.exceptions import ChangesetDoesNotExistError
48 from rhodecode.lib.vcs.exceptions import ChangesetDoesNotExistError
49 from rhodecode.lib.vcs.backends.base import BaseChangeset, EmptyChangeset
49 from rhodecode.lib.vcs.backends.base import BaseChangeset, EmptyChangeset
50 from rhodecode.config.conf import DATE_FORMAT, DATETIME_FORMAT
50 from rhodecode.config.conf import DATE_FORMAT, DATETIME_FORMAT
51 from rhodecode.model.changeset_status import ChangesetStatusModel
51 from rhodecode.model.changeset_status import ChangesetStatusModel
52 from rhodecode.model.db import URL_SEP, Permission
52 from rhodecode.model.db import URL_SEP, Permission
53
53
54 log = logging.getLogger(__name__)
54 log = logging.getLogger(__name__)
55
55
56
56
57 html_escape_table = {
57 html_escape_table = {
58 "&": "&amp;",
58 "&": "&amp;",
59 '"': "&quot;",
59 '"': "&quot;",
60 "'": "&apos;",
60 "'": "&apos;",
61 ">": "&gt;",
61 ">": "&gt;",
62 "<": "&lt;",
62 "<": "&lt;",
63 }
63 }
64
64
65
65
66 def html_escape(text):
66 def html_escape(text):
67 """Produce entities within text."""
67 """Produce entities within text."""
68 return "".join(html_escape_table.get(c, c) for c in text)
68 return "".join(html_escape_table.get(c, c) for c in text)
69
69
70
70
71 def shorter(text, size=20):
71 def shorter(text, size=20):
72 postfix = '...'
72 postfix = '...'
73 if len(text) > size:
73 if len(text) > size:
74 return text[:size - len(postfix)] + postfix
74 return text[:size - len(postfix)] + postfix
75 return text
75 return text
76
76
77
77
78 def _reset(name, value=None, id=NotGiven, type="reset", **attrs):
78 def _reset(name, value=None, id=NotGiven, type="reset", **attrs):
79 """
79 """
80 Reset button
80 Reset button
81 """
81 """
82 _set_input_attrs(attrs, type, name, value)
82 _set_input_attrs(attrs, type, name, value)
83 _set_id_attr(attrs, id, name)
83 _set_id_attr(attrs, id, name)
84 convert_boolean_attrs(attrs, ["disabled"])
84 convert_boolean_attrs(attrs, ["disabled"])
85 return HTML.input(**attrs)
85 return HTML.input(**attrs)
86
86
87 reset = _reset
87 reset = _reset
88 safeid = _make_safe_id_component
88 safeid = _make_safe_id_component
89
89
90
90
91 def FID(raw_id, path):
91 def FID(raw_id, path):
92 """
92 """
93 Creates a uniqe ID for filenode based on it's hash of path and revision
93 Creates a uniqe ID for filenode based on it's hash of path and revision
94 it's safe to use in urls
94 it's safe to use in urls
95
95
96 :param raw_id:
96 :param raw_id:
97 :param path:
97 :param path:
98 """
98 """
99
99
100 return 'C-%s-%s' % (short_id(raw_id), md5(safe_str(path)).hexdigest()[:12])
100 return 'C-%s-%s' % (short_id(raw_id), md5(safe_str(path)).hexdigest()[:12])
101
101
102
102
103 def get_token():
103 def get_token():
104 """Return the current authentication token, creating one if one doesn't
104 """Return the current authentication token, creating one if one doesn't
105 already exist.
105 already exist.
106 """
106 """
107 token_key = "_authentication_token"
107 token_key = "_authentication_token"
108 from pylons import session
108 from pylons import session
109 if not token_key in session:
109 if not token_key in session:
110 try:
110 try:
111 token = hashlib.sha1(str(random.getrandbits(128))).hexdigest()
111 token = hashlib.sha1(str(random.getrandbits(128))).hexdigest()
112 except AttributeError: # Python < 2.4
112 except AttributeError: # Python < 2.4
113 token = hashlib.sha1(str(random.randrange(2 ** 128))).hexdigest()
113 token = hashlib.sha1(str(random.randrange(2 ** 128))).hexdigest()
114 session[token_key] = token
114 session[token_key] = token
115 if hasattr(session, 'save'):
115 if hasattr(session, 'save'):
116 session.save()
116 session.save()
117 return session[token_key]
117 return session[token_key]
118
118
119
119
120 class _GetError(object):
120 class _GetError(object):
121 """Get error from form_errors, and represent it as span wrapped error
121 """Get error from form_errors, and represent it as span wrapped error
122 message
122 message
123
123
124 :param field_name: field to fetch errors for
124 :param field_name: field to fetch errors for
125 :param form_errors: form errors dict
125 :param form_errors: form errors dict
126 """
126 """
127
127
128 def __call__(self, field_name, form_errors):
128 def __call__(self, field_name, form_errors):
129 tmpl = """<span class="error_msg">%s</span>"""
129 tmpl = """<span class="error_msg">%s</span>"""
130 if form_errors and field_name in form_errors:
130 if form_errors and field_name in form_errors:
131 return literal(tmpl % form_errors.get(field_name))
131 return literal(tmpl % form_errors.get(field_name))
132
132
133 get_error = _GetError()
133 get_error = _GetError()
134
134
135
135
136 class _ToolTip(object):
136 class _ToolTip(object):
137
137
138 def __call__(self, tooltip_title, trim_at=50):
138 def __call__(self, tooltip_title, trim_at=50):
139 """
139 """
140 Special function just to wrap our text into nice formatted
140 Special function just to wrap our text into nice formatted
141 autowrapped text
141 autowrapped text
142
142
143 :param tooltip_title:
143 :param tooltip_title:
144 """
144 """
145 tooltip_title = escape(tooltip_title)
145 tooltip_title = escape(tooltip_title)
146 tooltip_title = tooltip_title.replace('<', '&lt;').replace('>', '&gt;')
146 tooltip_title = tooltip_title.replace('<', '&lt;').replace('>', '&gt;')
147 return tooltip_title
147 return tooltip_title
148 tooltip = _ToolTip()
148 tooltip = _ToolTip()
149
149
150
150
151 class _FilesBreadCrumbs(object):
151 class _FilesBreadCrumbs(object):
152
152
153 def __call__(self, repo_name, rev, paths):
153 def __call__(self, repo_name, rev, paths):
154 if isinstance(paths, str):
154 if isinstance(paths, str):
155 paths = safe_unicode(paths)
155 paths = safe_unicode(paths)
156 url_l = [link_to(repo_name, url('files_home',
156 url_l = [link_to(repo_name, url('files_home',
157 repo_name=repo_name,
157 repo_name=repo_name,
158 revision=rev, f_path=''),
158 revision=rev, f_path=''),
159 class_='ypjax-link')]
159 class_='ypjax-link')]
160 paths_l = paths.split('/')
160 paths_l = paths.split('/')
161 for cnt, p in enumerate(paths_l):
161 for cnt, p in enumerate(paths_l):
162 if p != '':
162 if p != '':
163 url_l.append(link_to(p,
163 url_l.append(link_to(p,
164 url('files_home',
164 url('files_home',
165 repo_name=repo_name,
165 repo_name=repo_name,
166 revision=rev,
166 revision=rev,
167 f_path='/'.join(paths_l[:cnt + 1])
167 f_path='/'.join(paths_l[:cnt + 1])
168 ),
168 ),
169 class_='ypjax-link'
169 class_='ypjax-link'
170 )
170 )
171 )
171 )
172
172
173 return literal('/'.join(url_l))
173 return literal('/'.join(url_l))
174
174
175 files_breadcrumbs = _FilesBreadCrumbs()
175 files_breadcrumbs = _FilesBreadCrumbs()
176
176
177
177
178 class CodeHtmlFormatter(HtmlFormatter):
178 class CodeHtmlFormatter(HtmlFormatter):
179 """
179 """
180 My code Html Formatter for source codes
180 My code Html Formatter for source codes
181 """
181 """
182
182
183 def wrap(self, source, outfile):
183 def wrap(self, source, outfile):
184 return self._wrap_div(self._wrap_pre(self._wrap_code(source)))
184 return self._wrap_div(self._wrap_pre(self._wrap_code(source)))
185
185
186 def _wrap_code(self, source):
186 def _wrap_code(self, source):
187 for cnt, it in enumerate(source):
187 for cnt, it in enumerate(source):
188 i, t = it
188 i, t = it
189 t = '<div id="L%s">%s</div>' % (cnt + 1, t)
189 t = '<div id="L%s">%s</div>' % (cnt + 1, t)
190 yield i, t
190 yield i, t
191
191
192 def _wrap_tablelinenos(self, inner):
192 def _wrap_tablelinenos(self, inner):
193 dummyoutfile = StringIO.StringIO()
193 dummyoutfile = StringIO.StringIO()
194 lncount = 0
194 lncount = 0
195 for t, line in inner:
195 for t, line in inner:
196 if t:
196 if t:
197 lncount += 1
197 lncount += 1
198 dummyoutfile.write(line)
198 dummyoutfile.write(line)
199
199
200 fl = self.linenostart
200 fl = self.linenostart
201 mw = len(str(lncount + fl - 1))
201 mw = len(str(lncount + fl - 1))
202 sp = self.linenospecial
202 sp = self.linenospecial
203 st = self.linenostep
203 st = self.linenostep
204 la = self.lineanchors
204 la = self.lineanchors
205 aln = self.anchorlinenos
205 aln = self.anchorlinenos
206 nocls = self.noclasses
206 nocls = self.noclasses
207 if sp:
207 if sp:
208 lines = []
208 lines = []
209
209
210 for i in range(fl, fl + lncount):
210 for i in range(fl, fl + lncount):
211 if i % st == 0:
211 if i % st == 0:
212 if i % sp == 0:
212 if i % sp == 0:
213 if aln:
213 if aln:
214 lines.append('<a href="#%s%d" class="special">%*d</a>' %
214 lines.append('<a href="#%s%d" class="special">%*d</a>' %
215 (la, i, mw, i))
215 (la, i, mw, i))
216 else:
216 else:
217 lines.append('<span class="special">%*d</span>' % (mw, i))
217 lines.append('<span class="special">%*d</span>' % (mw, i))
218 else:
218 else:
219 if aln:
219 if aln:
220 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
220 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
221 else:
221 else:
222 lines.append('%*d' % (mw, i))
222 lines.append('%*d' % (mw, i))
223 else:
223 else:
224 lines.append('')
224 lines.append('')
225 ls = '\n'.join(lines)
225 ls = '\n'.join(lines)
226 else:
226 else:
227 lines = []
227 lines = []
228 for i in range(fl, fl + lncount):
228 for i in range(fl, fl + lncount):
229 if i % st == 0:
229 if i % st == 0:
230 if aln:
230 if aln:
231 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
231 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
232 else:
232 else:
233 lines.append('%*d' % (mw, i))
233 lines.append('%*d' % (mw, i))
234 else:
234 else:
235 lines.append('')
235 lines.append('')
236 ls = '\n'.join(lines)
236 ls = '\n'.join(lines)
237
237
238 # in case you wonder about the seemingly redundant <div> here: since the
238 # in case you wonder about the seemingly redundant <div> here: since the
239 # content in the other cell also is wrapped in a div, some browsers in
239 # content in the other cell also is wrapped in a div, some browsers in
240 # some configurations seem to mess up the formatting...
240 # some configurations seem to mess up the formatting...
241 if nocls:
241 if nocls:
242 yield 0, ('<table class="%stable">' % self.cssclass +
242 yield 0, ('<table class="%stable">' % self.cssclass +
243 '<tr><td><div class="linenodiv" '
243 '<tr><td><div class="linenodiv" '
244 'style="background-color: #f0f0f0; padding-right: 10px">'
244 'style="background-color: #f0f0f0; padding-right: 10px">'
245 '<pre style="line-height: 125%">' +
245 '<pre style="line-height: 125%">' +
246 ls + '</pre></div></td><td id="hlcode" class="code">')
246 ls + '</pre></div></td><td id="hlcode" class="code">')
247 else:
247 else:
248 yield 0, ('<table class="%stable">' % self.cssclass +
248 yield 0, ('<table class="%stable">' % self.cssclass +
249 '<tr><td class="linenos"><div class="linenodiv"><pre>' +
249 '<tr><td class="linenos"><div class="linenodiv"><pre>' +
250 ls + '</pre></div></td><td id="hlcode" class="code">')
250 ls + '</pre></div></td><td id="hlcode" class="code">')
251 yield 0, dummyoutfile.getvalue()
251 yield 0, dummyoutfile.getvalue()
252 yield 0, '</td></tr></table>'
252 yield 0, '</td></tr></table>'
253
253
254
254
255 def pygmentize(filenode, **kwargs):
255 def pygmentize(filenode, **kwargs):
256 """pygmentize function using pygments
256 """pygmentize function using pygments
257
257
258 :param filenode:
258 :param filenode:
259 """
259 """
260
260
261 return literal(code_highlight(filenode.content,
261 return literal(code_highlight(filenode.content,
262 filenode.lexer, CodeHtmlFormatter(**kwargs)))
262 filenode.lexer, CodeHtmlFormatter(**kwargs)))
263
263
264
264
265 def pygmentize_annotation(repo_name, filenode, **kwargs):
265 def pygmentize_annotation(repo_name, filenode, **kwargs):
266 """
266 """
267 pygmentize function for annotation
267 pygmentize function for annotation
268
268
269 :param filenode:
269 :param filenode:
270 """
270 """
271
271
272 color_dict = {}
272 color_dict = {}
273
273
274 def gen_color(n=10000):
274 def gen_color(n=10000):
275 """generator for getting n of evenly distributed colors using
275 """generator for getting n of evenly distributed colors using
276 hsv color and golden ratio. It always return same order of colors
276 hsv color and golden ratio. It always return same order of colors
277
277
278 :returns: RGB tuple
278 :returns: RGB tuple
279 """
279 """
280
280
281 def hsv_to_rgb(h, s, v):
281 def hsv_to_rgb(h, s, v):
282 if s == 0.0:
282 if s == 0.0:
283 return v, v, v
283 return v, v, v
284 i = int(h * 6.0) # XXX assume int() truncates!
284 i = int(h * 6.0) # XXX assume int() truncates!
285 f = (h * 6.0) - i
285 f = (h * 6.0) - i
286 p = v * (1.0 - s)
286 p = v * (1.0 - s)
287 q = v * (1.0 - s * f)
287 q = v * (1.0 - s * f)
288 t = v * (1.0 - s * (1.0 - f))
288 t = v * (1.0 - s * (1.0 - f))
289 i = i % 6
289 i = i % 6
290 if i == 0:
290 if i == 0:
291 return v, t, p
291 return v, t, p
292 if i == 1:
292 if i == 1:
293 return q, v, p
293 return q, v, p
294 if i == 2:
294 if i == 2:
295 return p, v, t
295 return p, v, t
296 if i == 3:
296 if i == 3:
297 return p, q, v
297 return p, q, v
298 if i == 4:
298 if i == 4:
299 return t, p, v
299 return t, p, v
300 if i == 5:
300 if i == 5:
301 return v, p, q
301 return v, p, q
302
302
303 golden_ratio = 0.618033988749895
303 golden_ratio = 0.618033988749895
304 h = 0.22717784590367374
304 h = 0.22717784590367374
305
305
306 for _ in xrange(n):
306 for _ in xrange(n):
307 h += golden_ratio
307 h += golden_ratio
308 h %= 1
308 h %= 1
309 HSV_tuple = [h, 0.95, 0.95]
309 HSV_tuple = [h, 0.95, 0.95]
310 RGB_tuple = hsv_to_rgb(*HSV_tuple)
310 RGB_tuple = hsv_to_rgb(*HSV_tuple)
311 yield map(lambda x: str(int(x * 256)), RGB_tuple)
311 yield map(lambda x: str(int(x * 256)), RGB_tuple)
312
312
313 cgenerator = gen_color()
313 cgenerator = gen_color()
314
314
315 def get_color_string(cs):
315 def get_color_string(cs):
316 if cs in color_dict:
316 if cs in color_dict:
317 col = color_dict[cs]
317 col = color_dict[cs]
318 else:
318 else:
319 col = color_dict[cs] = cgenerator.next()
319 col = color_dict[cs] = cgenerator.next()
320 return "color: rgb(%s)! important;" % (', '.join(col))
320 return "color: rgb(%s)! important;" % (', '.join(col))
321
321
322 def url_func(repo_name):
322 def url_func(repo_name):
323
323
324 def _url_func(changeset):
324 def _url_func(changeset):
325 author = changeset.author
325 author = changeset.author
326 date = changeset.date
326 date = changeset.date
327 message = tooltip(changeset.message)
327 message = tooltip(changeset.message)
328
328
329 tooltip_html = ("<div style='font-size:0.8em'><b>Author:</b>"
329 tooltip_html = ("<div style='font-size:0.8em'><b>Author:</b>"
330 " %s<br/><b>Date:</b> %s</b><br/><b>Message:"
330 " %s<br/><b>Date:</b> %s</b><br/><b>Message:"
331 "</b> %s<br/></div>")
331 "</b> %s<br/></div>")
332
332
333 tooltip_html = tooltip_html % (author, date, message)
333 tooltip_html = tooltip_html % (author, date, message)
334 lnk_format = '%5s:%s' % ('r%s' % changeset.revision,
334 lnk_format = '%5s:%s' % ('r%s' % changeset.revision,
335 short_id(changeset.raw_id))
335 short_id(changeset.raw_id))
336 uri = link_to(
336 uri = link_to(
337 lnk_format,
337 lnk_format,
338 url('changeset_home', repo_name=repo_name,
338 url('changeset_home', repo_name=repo_name,
339 revision=changeset.raw_id),
339 revision=changeset.raw_id),
340 style=get_color_string(changeset.raw_id),
340 style=get_color_string(changeset.raw_id),
341 class_='tooltip',
341 class_='tooltip',
342 title=tooltip_html
342 title=tooltip_html
343 )
343 )
344
344
345 uri += '\n'
345 uri += '\n'
346 return uri
346 return uri
347 return _url_func
347 return _url_func
348
348
349 return literal(annotate_highlight(filenode, url_func(repo_name), **kwargs))
349 return literal(annotate_highlight(filenode, url_func(repo_name), **kwargs))
350
350
351
351
352 def is_following_repo(repo_name, user_id):
352 def is_following_repo(repo_name, user_id):
353 from rhodecode.model.scm import ScmModel
353 from rhodecode.model.scm import ScmModel
354 return ScmModel().is_following_repo(repo_name, user_id)
354 return ScmModel().is_following_repo(repo_name, user_id)
355
355
356 flash = _Flash()
356 flash = _Flash()
357
357
358 #==============================================================================
358 #==============================================================================
359 # SCM FILTERS available via h.
359 # SCM FILTERS available via h.
360 #==============================================================================
360 #==============================================================================
361 from rhodecode.lib.vcs.utils import author_name, author_email
361 from rhodecode.lib.vcs.utils import author_name, author_email
362 from rhodecode.lib.utils2 import credentials_filter, age as _age
362 from rhodecode.lib.utils2 import credentials_filter, age as _age
363 from rhodecode.model.db import User, ChangesetStatus
363 from rhodecode.model.db import User, ChangesetStatus
364
364
365 age = lambda x: _age(x)
365 age = lambda x: _age(x)
366 capitalize = lambda x: x.capitalize()
366 capitalize = lambda x: x.capitalize()
367 email = author_email
367 email = author_email
368 short_id = lambda x: x[:12]
368 short_id = lambda x: x[:12]
369 hide_credentials = lambda x: ''.join(credentials_filter(x))
369 hide_credentials = lambda x: ''.join(credentials_filter(x))
370
370
371
371
372 def fmt_date(date):
372 def fmt_date(date):
373 if date:
373 if date:
374 _fmt = _(u"%a, %d %b %Y %H:%M:%S").encode('utf8')
374 _fmt = _(u"%a, %d %b %Y %H:%M:%S").encode('utf8')
375 return date.strftime(_fmt).decode('utf8')
375 return date.strftime(_fmt).decode('utf8')
376
376
377 return ""
377 return ""
378
378
379
379
380 def is_git(repository):
380 def is_git(repository):
381 if hasattr(repository, 'alias'):
381 if hasattr(repository, 'alias'):
382 _type = repository.alias
382 _type = repository.alias
383 elif hasattr(repository, 'repo_type'):
383 elif hasattr(repository, 'repo_type'):
384 _type = repository.repo_type
384 _type = repository.repo_type
385 else:
385 else:
386 _type = repository
386 _type = repository
387 return _type == 'git'
387 return _type == 'git'
388
388
389
389
390 def is_hg(repository):
390 def is_hg(repository):
391 if hasattr(repository, 'alias'):
391 if hasattr(repository, 'alias'):
392 _type = repository.alias
392 _type = repository.alias
393 elif hasattr(repository, 'repo_type'):
393 elif hasattr(repository, 'repo_type'):
394 _type = repository.repo_type
394 _type = repository.repo_type
395 else:
395 else:
396 _type = repository
396 _type = repository
397 return _type == 'hg'
397 return _type == 'hg'
398
398
399
399
400 def email_or_none(author):
400 def email_or_none(author):
401 # extract email from the commit string
401 # extract email from the commit string
402 _email = email(author)
402 _email = email(author)
403 if _email != '':
403 if _email != '':
404 # check it against RhodeCode database, and use the MAIN email for this
404 # check it against RhodeCode database, and use the MAIN email for this
405 # user
405 # user
406 user = User.get_by_email(_email, case_insensitive=True, cache=True)
406 user = User.get_by_email(_email, case_insensitive=True, cache=True)
407 if user is not None:
407 if user is not None:
408 return user.email
408 return user.email
409 return _email
409 return _email
410
410
411 # See if it contains a username we can get an email from
411 # See if it contains a username we can get an email from
412 user = User.get_by_username(author_name(author), case_insensitive=True,
412 user = User.get_by_username(author_name(author), case_insensitive=True,
413 cache=True)
413 cache=True)
414 if user is not None:
414 if user is not None:
415 return user.email
415 return user.email
416
416
417 # No valid email, not a valid user in the system, none!
417 # No valid email, not a valid user in the system, none!
418 return None
418 return None
419
419
420
420
421 def person(author, show_attr="username_and_name"):
421 def person(author, show_attr="username_and_name"):
422 # attr to return from fetched user
422 # attr to return from fetched user
423 person_getter = lambda usr: getattr(usr, show_attr)
423 person_getter = lambda usr: getattr(usr, show_attr)
424
424
425 # Valid email in the attribute passed, see if they're in the system
425 # Valid email in the attribute passed, see if they're in the system
426 _email = email(author)
426 _email = email(author)
427 if _email != '':
427 if _email != '':
428 user = User.get_by_email(_email, case_insensitive=True, cache=True)
428 user = User.get_by_email(_email, case_insensitive=True, cache=True)
429 if user is not None:
429 if user is not None:
430 return person_getter(user)
430 return person_getter(user)
431 return _email
431 return _email
432
432
433 # Maybe it's a username?
433 # Maybe it's a username?
434 _author = author_name(author)
434 _author = author_name(author)
435 user = User.get_by_username(_author, case_insensitive=True,
435 user = User.get_by_username(_author, case_insensitive=True,
436 cache=True)
436 cache=True)
437 if user is not None:
437 if user is not None:
438 return person_getter(user)
438 return person_getter(user)
439
439
440 # Still nothing? Just pass back the author name then
440 # Still nothing? Just pass back the author name then
441 return _author
441 return _author
442
442
443
443
444 def person_by_id(id_, show_attr="username_and_name"):
444 def person_by_id(id_, show_attr="username_and_name"):
445 # attr to return from fetched user
445 # attr to return from fetched user
446 person_getter = lambda usr: getattr(usr, show_attr)
446 person_getter = lambda usr: getattr(usr, show_attr)
447
447
448 #maybe it's an ID ?
448 #maybe it's an ID ?
449 if str(id_).isdigit() or isinstance(id_, int):
449 if str(id_).isdigit() or isinstance(id_, int):
450 id_ = int(id_)
450 id_ = int(id_)
451 user = User.get(id_)
451 user = User.get(id_)
452 if user is not None:
452 if user is not None:
453 return person_getter(user)
453 return person_getter(user)
454 return id_
454 return id_
455
455
456
456
457 def desc_stylize(value):
457 def desc_stylize(value):
458 """
458 """
459 converts tags from value into html equivalent
459 converts tags from value into html equivalent
460
460
461 :param value:
461 :param value:
462 """
462 """
463 value = re.sub(r'\[see\ \=\>\ *([a-zA-Z0-9\/\=\?\&\ \:\/\.\-]*)\]',
463 value = re.sub(r'\[see\ \=\>\ *([a-zA-Z0-9\/\=\?\&\ \:\/\.\-]*)\]',
464 '<div class="metatag" tag="see">see =&gt; \\1 </div>', value)
464 '<div class="metatag" tag="see">see =&gt; \\1 </div>', value)
465 value = re.sub(r'\[license\ \=\>\ *([a-zA-Z0-9\/\=\?\&\ \:\/\.\-]*)\]',
465 value = re.sub(r'\[license\ \=\>\ *([a-zA-Z0-9\/\=\?\&\ \:\/\.\-]*)\]',
466 '<div class="metatag" tag="license"><a href="http:\/\/www.opensource.org/licenses/\\1">\\1</a></div>', value)
466 '<div class="metatag" tag="license"><a href="http:\/\/www.opensource.org/licenses/\\1">\\1</a></div>', value)
467 value = re.sub(r'\[(requires|recommends|conflicts|base)\ \=\>\ *([a-zA-Z0-9\-\/]*)\]',
467 value = re.sub(r'\[(requires|recommends|conflicts|base)\ \=\>\ *([a-zA-Z0-9\-\/]*)\]',
468 '<div class="metatag" tag="\\1">\\1 =&gt; <a href="/\\2">\\2</a></div>', value)
468 '<div class="metatag" tag="\\1">\\1 =&gt; <a href="/\\2">\\2</a></div>', value)
469 value = re.sub(r'\[(lang|language)\ \=\>\ *([a-zA-Z\-\/\#\+]*)\]',
469 value = re.sub(r'\[(lang|language)\ \=\>\ *([a-zA-Z\-\/\#\+]*)\]',
470 '<div class="metatag" tag="lang">\\2</div>', value)
470 '<div class="metatag" tag="lang">\\2</div>', value)
471 value = re.sub(r'\[([a-z]+)\]',
471 value = re.sub(r'\[([a-z]+)\]',
472 '<div class="metatag" tag="\\1">\\1</div>', value)
472 '<div class="metatag" tag="\\1">\\1</div>', value)
473
473
474 return value
474 return value
475
475
476
476
477 def bool2icon(value):
477 def bool2icon(value):
478 """Returns True/False values represented as small html image of true/false
478 """Returns True/False values represented as small html image of true/false
479 icons
479 icons
480
480
481 :param value: bool value
481 :param value: bool value
482 """
482 """
483
483
484 if value is True:
484 if value is True:
485 return HTML.tag('img', src=url("/images/icons/accept.png"),
485 return HTML.tag('img', src=url("/images/icons/accept.png"),
486 alt=_('True'))
486 alt=_('True'))
487
487
488 if value is False:
488 if value is False:
489 return HTML.tag('img', src=url("/images/icons/cancel.png"),
489 return HTML.tag('img', src=url("/images/icons/cancel.png"),
490 alt=_('False'))
490 alt=_('False'))
491
491
492 return value
492 return value
493
493
494
494
495 def action_parser(user_log, feed=False, parse_cs=False):
495 def action_parser(user_log, feed=False, parse_cs=False):
496 """
496 """
497 This helper will action_map the specified string action into translated
497 This helper will action_map the specified string action into translated
498 fancy names with icons and links
498 fancy names with icons and links
499
499
500 :param user_log: user log instance
500 :param user_log: user log instance
501 :param feed: use output for feeds (no html and fancy icons)
501 :param feed: use output for feeds (no html and fancy icons)
502 :param parse_cs: parse Changesets into VCS instances
502 :param parse_cs: parse Changesets into VCS instances
503 """
503 """
504
504
505 action = user_log.action
505 action = user_log.action
506 action_params = ' '
506 action_params = ' '
507
507
508 x = action.split(':')
508 x = action.split(':')
509
509
510 if len(x) > 1:
510 if len(x) > 1:
511 action, action_params = x
511 action, action_params = x
512
512
513 def get_cs_links():
513 def get_cs_links():
514 revs_limit = 3 # display this amount always
514 revs_limit = 3 # display this amount always
515 revs_top_limit = 50 # show upto this amount of changesets hidden
515 revs_top_limit = 50 # show upto this amount of changesets hidden
516 revs_ids = action_params.split(',')
516 revs_ids = action_params.split(',')
517 deleted = user_log.repository is None
517 deleted = user_log.repository is None
518 if deleted:
518 if deleted:
519 return ','.join(revs_ids)
519 return ','.join(revs_ids)
520
520
521 repo_name = user_log.repository.repo_name
521 repo_name = user_log.repository.repo_name
522
522
523 def lnk(rev, repo_name):
523 def lnk(rev, repo_name):
524 if isinstance(rev, BaseChangeset) or isinstance(rev, AttributeDict):
524 if isinstance(rev, BaseChangeset) or isinstance(rev, AttributeDict):
525 lazy_cs = True
525 lazy_cs = True
526 if getattr(rev, 'op', None) and getattr(rev, 'ref_name', None):
526 if getattr(rev, 'op', None) and getattr(rev, 'ref_name', None):
527 lazy_cs = False
527 lazy_cs = False
528 lbl = '?'
528 lbl = '?'
529 if rev.op == 'delete_branch':
529 if rev.op == 'delete_branch':
530 lbl = '%s' % _('Deleted branch: %s') % rev.ref_name
530 lbl = '%s' % _('Deleted branch: %s') % rev.ref_name
531 title = ''
531 title = ''
532 elif rev.op == 'tag':
532 elif rev.op == 'tag':
533 lbl = '%s' % _('Created tag: %s') % rev.ref_name
533 lbl = '%s' % _('Created tag: %s') % rev.ref_name
534 title = ''
534 title = ''
535 _url = '#'
535 _url = '#'
536
536
537 else:
537 else:
538 lbl = '%s' % (rev.short_id[:8])
538 lbl = '%s' % (rev.short_id[:8])
539 _url = url('changeset_home', repo_name=repo_name,
539 _url = url('changeset_home', repo_name=repo_name,
540 revision=rev.raw_id)
540 revision=rev.raw_id)
541 title = tooltip(rev.message)
541 title = tooltip(rev.message)
542 else:
542 else:
543 ## changeset cannot be found/striped/removed etc.
543 ## changeset cannot be found/striped/removed etc.
544 lbl = ('%s' % rev)[:12]
544 lbl = ('%s' % rev)[:12]
545 _url = '#'
545 _url = '#'
546 title = _('Changeset not found')
546 title = _('Changeset not found')
547 if parse_cs:
547 if parse_cs:
548 return link_to(lbl, _url, title=title, class_='tooltip')
548 return link_to(lbl, _url, title=title, class_='tooltip')
549 return link_to(lbl, _url, raw_id=rev.raw_id, repo_name=repo_name,
549 return link_to(lbl, _url, raw_id=rev.raw_id, repo_name=repo_name,
550 class_='lazy-cs' if lazy_cs else '')
550 class_='lazy-cs' if lazy_cs else '')
551
551
552 revs = []
552 revs = []
553 if len(filter(lambda v: v != '', revs_ids)) > 0:
553 if len(filter(lambda v: v != '', revs_ids)) > 0:
554 repo = None
554 repo = None
555 for rev in revs_ids[:revs_top_limit]:
555 for rev in revs_ids[:revs_top_limit]:
556 _op = _name = None
556 _op = _name = None
557 if len(rev.split('=>')) == 2:
557 if len(rev.split('=>')) == 2:
558 _op, _name = rev.split('=>')
558 _op, _name = rev.split('=>')
559
559
560 # we want parsed changesets, or new log store format is bad
560 # we want parsed changesets, or new log store format is bad
561 if parse_cs:
561 if parse_cs:
562 try:
562 try:
563 if repo is None:
563 if repo is None:
564 repo = user_log.repository.scm_instance
564 repo = user_log.repository.scm_instance
565 _rev = repo.get_changeset(rev)
565 _rev = repo.get_changeset(rev)
566 revs.append(_rev)
566 revs.append(_rev)
567 except ChangesetDoesNotExistError:
567 except ChangesetDoesNotExistError:
568 log.error('cannot find revision %s in this repo' % rev)
568 log.error('cannot find revision %s in this repo' % rev)
569 revs.append(rev)
569 revs.append(rev)
570 continue
570 continue
571 else:
571 else:
572 _rev = AttributeDict({
572 _rev = AttributeDict({
573 'short_id': rev[:12],
573 'short_id': rev[:12],
574 'raw_id': rev,
574 'raw_id': rev,
575 'message': '',
575 'message': '',
576 'op': _op,
576 'op': _op,
577 'ref_name': _name
577 'ref_name': _name
578 })
578 })
579 revs.append(_rev)
579 revs.append(_rev)
580 cs_links = []
580 cs_links = []
581 cs_links.append(" " + ', '.join(
581 cs_links.append(" " + ', '.join(
582 [lnk(rev, repo_name) for rev in revs[:revs_limit]]
582 [lnk(rev, repo_name) for rev in revs[:revs_limit]]
583 )
583 )
584 )
584 )
585
585
586 compare_view = (
586 compare_view = (
587 ' <div class="compare_view tooltip" title="%s">'
587 ' <div class="compare_view tooltip" title="%s">'
588 '<a href="%s">%s</a> </div>' % (
588 '<a href="%s">%s</a> </div>' % (
589 _('Show all combined changesets %s->%s') % (
589 _('Show all combined changesets %s->%s') % (
590 revs_ids[0][:12], revs_ids[-1][:12]
590 revs_ids[0][:12], revs_ids[-1][:12]
591 ),
591 ),
592 url('changeset_home', repo_name=repo_name,
592 url('changeset_home', repo_name=repo_name,
593 revision='%s...%s' % (revs_ids[0], revs_ids[-1])
593 revision='%s...%s' % (revs_ids[0], revs_ids[-1])
594 ),
594 ),
595 _('compare view')
595 _('compare view')
596 )
596 )
597 )
597 )
598
598
599 # if we have exactly one more than normally displayed
599 # if we have exactly one more than normally displayed
600 # just display it, takes less space than displaying
600 # just display it, takes less space than displaying
601 # "and 1 more revisions"
601 # "and 1 more revisions"
602 if len(revs_ids) == revs_limit + 1:
602 if len(revs_ids) == revs_limit + 1:
603 rev = revs[revs_limit]
603 rev = revs[revs_limit]
604 cs_links.append(", " + lnk(rev, repo_name))
604 cs_links.append(", " + lnk(rev, repo_name))
605
605
606 # hidden-by-default ones
606 # hidden-by-default ones
607 if len(revs_ids) > revs_limit + 1:
607 if len(revs_ids) > revs_limit + 1:
608 uniq_id = revs_ids[0]
608 uniq_id = revs_ids[0]
609 html_tmpl = (
609 html_tmpl = (
610 '<span> %s <a class="show_more" id="_%s" '
610 '<span> %s <a class="show_more" id="_%s" '
611 'href="#more">%s</a> %s</span>'
611 'href="#more">%s</a> %s</span>'
612 )
612 )
613 if not feed:
613 if not feed:
614 cs_links.append(html_tmpl % (
614 cs_links.append(html_tmpl % (
615 _('and'),
615 _('and'),
616 uniq_id, _('%s more') % (len(revs_ids) - revs_limit),
616 uniq_id, _('%s more') % (len(revs_ids) - revs_limit),
617 _('revisions')
617 _('revisions')
618 )
618 )
619 )
619 )
620
620
621 if not feed:
621 if not feed:
622 html_tmpl = '<span id="%s" style="display:none">, %s </span>'
622 html_tmpl = '<span id="%s" style="display:none">, %s </span>'
623 else:
623 else:
624 html_tmpl = '<span id="%s"> %s </span>'
624 html_tmpl = '<span id="%s"> %s </span>'
625
625
626 morelinks = ', '.join(
626 morelinks = ', '.join(
627 [lnk(rev, repo_name) for rev in revs[revs_limit:]]
627 [lnk(rev, repo_name) for rev in revs[revs_limit:]]
628 )
628 )
629
629
630 if len(revs_ids) > revs_top_limit:
630 if len(revs_ids) > revs_top_limit:
631 morelinks += ', ...'
631 morelinks += ', ...'
632
632
633 cs_links.append(html_tmpl % (uniq_id, morelinks))
633 cs_links.append(html_tmpl % (uniq_id, morelinks))
634 if len(revs) > 1:
634 if len(revs) > 1:
635 cs_links.append(compare_view)
635 cs_links.append(compare_view)
636 return ''.join(cs_links)
636 return ''.join(cs_links)
637
637
638 def get_fork_name():
638 def get_fork_name():
639 repo_name = action_params
639 repo_name = action_params
640 _url = url('summary_home', repo_name=repo_name)
640 _url = url('summary_home', repo_name=repo_name)
641 return _('fork name %s') % link_to(action_params, _url)
641 return _('fork name %s') % link_to(action_params, _url)
642
642
643 def get_user_name():
643 def get_user_name():
644 user_name = action_params
644 user_name = action_params
645 return user_name
645 return user_name
646
646
647 def get_users_group():
647 def get_users_group():
648 group_name = action_params
648 group_name = action_params
649 return group_name
649 return group_name
650
650
651 def get_pull_request():
651 def get_pull_request():
652 pull_request_id = action_params
652 pull_request_id = action_params
653 deleted = user_log.repository is None
653 deleted = user_log.repository is None
654 if deleted:
654 if deleted:
655 repo_name = user_log.repository_name
655 repo_name = user_log.repository_name
656 else:
656 else:
657 repo_name = user_log.repository.repo_name
657 repo_name = user_log.repository.repo_name
658 return link_to(_('Pull request #%s') % pull_request_id,
658 return link_to(_('Pull request #%s') % pull_request_id,
659 url('pullrequest_show', repo_name=repo_name,
659 url('pullrequest_show', repo_name=repo_name,
660 pull_request_id=pull_request_id))
660 pull_request_id=pull_request_id))
661
661
662 # action : translated str, callback(extractor), icon
662 # action : translated str, callback(extractor), icon
663 action_map = {
663 action_map = {
664 'user_deleted_repo': (_('[deleted] repository'),
664 'user_deleted_repo': (_('[deleted] repository'),
665 None, 'database_delete.png'),
665 None, 'database_delete.png'),
666 'user_created_repo': (_('[created] repository'),
666 'user_created_repo': (_('[created] repository'),
667 None, 'database_add.png'),
667 None, 'database_add.png'),
668 'user_created_fork': (_('[created] repository as fork'),
668 'user_created_fork': (_('[created] repository as fork'),
669 None, 'arrow_divide.png'),
669 None, 'arrow_divide.png'),
670 'user_forked_repo': (_('[forked] repository'),
670 'user_forked_repo': (_('[forked] repository'),
671 get_fork_name, 'arrow_divide.png'),
671 get_fork_name, 'arrow_divide.png'),
672 'user_updated_repo': (_('[updated] repository'),
672 'user_updated_repo': (_('[updated] repository'),
673 None, 'database_edit.png'),
673 None, 'database_edit.png'),
674 'admin_deleted_repo': (_('[delete] repository'),
674 'admin_deleted_repo': (_('[delete] repository'),
675 None, 'database_delete.png'),
675 None, 'database_delete.png'),
676 'admin_created_repo': (_('[created] repository'),
676 'admin_created_repo': (_('[created] repository'),
677 None, 'database_add.png'),
677 None, 'database_add.png'),
678 'admin_forked_repo': (_('[forked] repository'),
678 'admin_forked_repo': (_('[forked] repository'),
679 None, 'arrow_divide.png'),
679 None, 'arrow_divide.png'),
680 'admin_updated_repo': (_('[updated] repository'),
680 'admin_updated_repo': (_('[updated] repository'),
681 None, 'database_edit.png'),
681 None, 'database_edit.png'),
682 'admin_created_user': (_('[created] user'),
682 'admin_created_user': (_('[created] user'),
683 get_user_name, 'user_add.png'),
683 get_user_name, 'user_add.png'),
684 'admin_updated_user': (_('[updated] user'),
684 'admin_updated_user': (_('[updated] user'),
685 get_user_name, 'user_edit.png'),
685 get_user_name, 'user_edit.png'),
686 'admin_created_users_group': (_('[created] users group'),
686 'admin_created_users_group': (_('[created] users group'),
687 get_users_group, 'group_add.png'),
687 get_users_group, 'group_add.png'),
688 'admin_updated_users_group': (_('[updated] users group'),
688 'admin_updated_users_group': (_('[updated] users group'),
689 get_users_group, 'group_edit.png'),
689 get_users_group, 'group_edit.png'),
690 'user_commented_revision': (_('[commented] on revision in repository'),
690 'user_commented_revision': (_('[commented] on revision in repository'),
691 get_cs_links, 'comment_add.png'),
691 get_cs_links, 'comment_add.png'),
692 'user_commented_pull_request': (_('[commented] on pull request for'),
692 'user_commented_pull_request': (_('[commented] on pull request for'),
693 get_pull_request, 'comment_add.png'),
693 get_pull_request, 'comment_add.png'),
694 'user_closed_pull_request': (_('[closed] pull request for'),
694 'user_closed_pull_request': (_('[closed] pull request for'),
695 get_pull_request, 'tick.png'),
695 get_pull_request, 'tick.png'),
696 'push': (_('[pushed] into'),
696 'push': (_('[pushed] into'),
697 get_cs_links, 'script_add.png'),
697 get_cs_links, 'script_add.png'),
698 'push_local': (_('[committed via RhodeCode] into repository'),
698 'push_local': (_('[committed via RhodeCode] into repository'),
699 get_cs_links, 'script_edit.png'),
699 get_cs_links, 'script_edit.png'),
700 'push_remote': (_('[pulled from remote] into repository'),
700 'push_remote': (_('[pulled from remote] into repository'),
701 get_cs_links, 'connect.png'),
701 get_cs_links, 'connect.png'),
702 'pull': (_('[pulled] from'),
702 'pull': (_('[pulled] from'),
703 None, 'down_16.png'),
703 None, 'down_16.png'),
704 'started_following_repo': (_('[started following] repository'),
704 'started_following_repo': (_('[started following] repository'),
705 None, 'heart_add.png'),
705 None, 'heart_add.png'),
706 'stopped_following_repo': (_('[stopped following] repository'),
706 'stopped_following_repo': (_('[stopped following] repository'),
707 None, 'heart_delete.png'),
707 None, 'heart_delete.png'),
708 }
708 }
709
709
710 action_str = action_map.get(action, action)
710 action_str = action_map.get(action, action)
711 if feed:
711 if feed:
712 action = action_str[0].replace('[', '').replace(']', '')
712 action = action_str[0].replace('[', '').replace(']', '')
713 else:
713 else:
714 action = action_str[0]\
714 action = action_str[0]\
715 .replace('[', '<span class="journal_highlight">')\
715 .replace('[', '<span class="journal_highlight">')\
716 .replace(']', '</span>')
716 .replace(']', '</span>')
717
717
718 action_params_func = lambda: ""
718 action_params_func = lambda: ""
719
719
720 if callable(action_str[1]):
720 if callable(action_str[1]):
721 action_params_func = action_str[1]
721 action_params_func = action_str[1]
722
722
723 def action_parser_icon():
723 def action_parser_icon():
724 action = user_log.action
724 action = user_log.action
725 action_params = None
725 action_params = None
726 x = action.split(':')
726 x = action.split(':')
727
727
728 if len(x) > 1:
728 if len(x) > 1:
729 action, action_params = x
729 action, action_params = x
730
730
731 tmpl = """<img src="%s%s" alt="%s"/>"""
731 tmpl = """<img src="%s%s" alt="%s"/>"""
732 ico = action_map.get(action, ['', '', ''])[2]
732 ico = action_map.get(action, ['', '', ''])[2]
733 return literal(tmpl % ((url('/images/icons/')), ico, action))
733 return literal(tmpl % ((url('/images/icons/')), ico, action))
734
734
735 # returned callbacks we need to call to get
735 # returned callbacks we need to call to get
736 return [lambda: literal(action), action_params_func, action_parser_icon]
736 return [lambda: literal(action), action_params_func, action_parser_icon]
737
737
738
738
739
739
740 #==============================================================================
740 #==============================================================================
741 # PERMS
741 # PERMS
742 #==============================================================================
742 #==============================================================================
743 from rhodecode.lib.auth import HasPermissionAny, HasPermissionAll, \
743 from rhodecode.lib.auth import HasPermissionAny, HasPermissionAll, \
744 HasRepoPermissionAny, HasRepoPermissionAll
744 HasRepoPermissionAny, HasRepoPermissionAll, HasReposGroupPermissionAll, \
745 HasReposGroupPermissionAny
745
746
746
747
747 #==============================================================================
748 #==============================================================================
748 # GRAVATAR URL
749 # GRAVATAR URL
749 #==============================================================================
750 #==============================================================================
750
751
751 def gravatar_url(email_address, size=30):
752 def gravatar_url(email_address, size=30):
752 from pylons import url # doh, we need to re-import url to mock it later
753 from pylons import url # doh, we need to re-import url to mock it later
753
754
754 if (not str2bool(config['app_conf'].get('use_gravatar')) or
755 if (not str2bool(config['app_conf'].get('use_gravatar')) or
755 not email_address or email_address == 'anonymous@rhodecode.org'):
756 not email_address or email_address == 'anonymous@rhodecode.org'):
756 f = lambda a, l: min(l, key=lambda x: abs(x - a))
757 f = lambda a, l: min(l, key=lambda x: abs(x - a))
757 return url("/images/user%s.png" % f(size, [14, 16, 20, 24, 30]))
758 return url("/images/user%s.png" % f(size, [14, 16, 20, 24, 30]))
758
759
759 if(str2bool(config['app_conf'].get('use_gravatar')) and
760 if(str2bool(config['app_conf'].get('use_gravatar')) and
760 config['app_conf'].get('alternative_gravatar_url')):
761 config['app_conf'].get('alternative_gravatar_url')):
761 tmpl = config['app_conf'].get('alternative_gravatar_url', '')
762 tmpl = config['app_conf'].get('alternative_gravatar_url', '')
762 parsed_url = urlparse.urlparse(url.current(qualified=True))
763 parsed_url = urlparse.urlparse(url.current(qualified=True))
763 tmpl = tmpl.replace('{email}', email_address)\
764 tmpl = tmpl.replace('{email}', email_address)\
764 .replace('{md5email}', hashlib.md5(email_address.lower()).hexdigest()) \
765 .replace('{md5email}', hashlib.md5(email_address.lower()).hexdigest()) \
765 .replace('{netloc}', parsed_url.netloc)\
766 .replace('{netloc}', parsed_url.netloc)\
766 .replace('{scheme}', parsed_url.scheme)\
767 .replace('{scheme}', parsed_url.scheme)\
767 .replace('{size}', str(size))
768 .replace('{size}', str(size))
768 return tmpl
769 return tmpl
769
770
770 ssl_enabled = 'https' == request.environ.get('wsgi.url_scheme')
771 ssl_enabled = 'https' == request.environ.get('wsgi.url_scheme')
771 default = 'identicon'
772 default = 'identicon'
772 baseurl_nossl = "http://www.gravatar.com/avatar/"
773 baseurl_nossl = "http://www.gravatar.com/avatar/"
773 baseurl_ssl = "https://secure.gravatar.com/avatar/"
774 baseurl_ssl = "https://secure.gravatar.com/avatar/"
774 baseurl = baseurl_ssl if ssl_enabled else baseurl_nossl
775 baseurl = baseurl_ssl if ssl_enabled else baseurl_nossl
775
776
776 if isinstance(email_address, unicode):
777 if isinstance(email_address, unicode):
777 #hashlib crashes on unicode items
778 #hashlib crashes on unicode items
778 email_address = safe_str(email_address)
779 email_address = safe_str(email_address)
779 # construct the url
780 # construct the url
780 gravatar_url = baseurl + hashlib.md5(email_address.lower()).hexdigest() + "?"
781 gravatar_url = baseurl + hashlib.md5(email_address.lower()).hexdigest() + "?"
781 gravatar_url += urllib.urlencode({'d': default, 's': str(size)})
782 gravatar_url += urllib.urlencode({'d': default, 's': str(size)})
782
783
783 return gravatar_url
784 return gravatar_url
784
785
785
786
786 #==============================================================================
787 #==============================================================================
787 # REPO PAGER, PAGER FOR REPOSITORY
788 # REPO PAGER, PAGER FOR REPOSITORY
788 #==============================================================================
789 #==============================================================================
789 class RepoPage(Page):
790 class RepoPage(Page):
790
791
791 def __init__(self, collection, page=1, items_per_page=20,
792 def __init__(self, collection, page=1, items_per_page=20,
792 item_count=None, url=None, **kwargs):
793 item_count=None, url=None, **kwargs):
793
794
794 """Create a "RepoPage" instance. special pager for paging
795 """Create a "RepoPage" instance. special pager for paging
795 repository
796 repository
796 """
797 """
797 self._url_generator = url
798 self._url_generator = url
798
799
799 # Safe the kwargs class-wide so they can be used in the pager() method
800 # Safe the kwargs class-wide so they can be used in the pager() method
800 self.kwargs = kwargs
801 self.kwargs = kwargs
801
802
802 # Save a reference to the collection
803 # Save a reference to the collection
803 self.original_collection = collection
804 self.original_collection = collection
804
805
805 self.collection = collection
806 self.collection = collection
806
807
807 # The self.page is the number of the current page.
808 # The self.page is the number of the current page.
808 # The first page has the number 1!
809 # The first page has the number 1!
809 try:
810 try:
810 self.page = int(page) # make it int() if we get it as a string
811 self.page = int(page) # make it int() if we get it as a string
811 except (ValueError, TypeError):
812 except (ValueError, TypeError):
812 self.page = 1
813 self.page = 1
813
814
814 self.items_per_page = items_per_page
815 self.items_per_page = items_per_page
815
816
816 # Unless the user tells us how many items the collections has
817 # Unless the user tells us how many items the collections has
817 # we calculate that ourselves.
818 # we calculate that ourselves.
818 if item_count is not None:
819 if item_count is not None:
819 self.item_count = item_count
820 self.item_count = item_count
820 else:
821 else:
821 self.item_count = len(self.collection)
822 self.item_count = len(self.collection)
822
823
823 # Compute the number of the first and last available page
824 # Compute the number of the first and last available page
824 if self.item_count > 0:
825 if self.item_count > 0:
825 self.first_page = 1
826 self.first_page = 1
826 self.page_count = int(math.ceil(float(self.item_count) /
827 self.page_count = int(math.ceil(float(self.item_count) /
827 self.items_per_page))
828 self.items_per_page))
828 self.last_page = self.first_page + self.page_count - 1
829 self.last_page = self.first_page + self.page_count - 1
829
830
830 # Make sure that the requested page number is the range of
831 # Make sure that the requested page number is the range of
831 # valid pages
832 # valid pages
832 if self.page > self.last_page:
833 if self.page > self.last_page:
833 self.page = self.last_page
834 self.page = self.last_page
834 elif self.page < self.first_page:
835 elif self.page < self.first_page:
835 self.page = self.first_page
836 self.page = self.first_page
836
837
837 # Note: the number of items on this page can be less than
838 # Note: the number of items on this page can be less than
838 # items_per_page if the last page is not full
839 # items_per_page if the last page is not full
839 self.first_item = max(0, (self.item_count) - (self.page *
840 self.first_item = max(0, (self.item_count) - (self.page *
840 items_per_page))
841 items_per_page))
841 self.last_item = ((self.item_count - 1) - items_per_page *
842 self.last_item = ((self.item_count - 1) - items_per_page *
842 (self.page - 1))
843 (self.page - 1))
843
844
844 self.items = list(self.collection[self.first_item:self.last_item + 1])
845 self.items = list(self.collection[self.first_item:self.last_item + 1])
845
846
846 # Links to previous and next page
847 # Links to previous and next page
847 if self.page > self.first_page:
848 if self.page > self.first_page:
848 self.previous_page = self.page - 1
849 self.previous_page = self.page - 1
849 else:
850 else:
850 self.previous_page = None
851 self.previous_page = None
851
852
852 if self.page < self.last_page:
853 if self.page < self.last_page:
853 self.next_page = self.page + 1
854 self.next_page = self.page + 1
854 else:
855 else:
855 self.next_page = None
856 self.next_page = None
856
857
857 # No items available
858 # No items available
858 else:
859 else:
859 self.first_page = None
860 self.first_page = None
860 self.page_count = 0
861 self.page_count = 0
861 self.last_page = None
862 self.last_page = None
862 self.first_item = None
863 self.first_item = None
863 self.last_item = None
864 self.last_item = None
864 self.previous_page = None
865 self.previous_page = None
865 self.next_page = None
866 self.next_page = None
866 self.items = []
867 self.items = []
867
868
868 # This is a subclass of the 'list' type. Initialise the list now.
869 # This is a subclass of the 'list' type. Initialise the list now.
869 list.__init__(self, reversed(self.items))
870 list.__init__(self, reversed(self.items))
870
871
871
872
872 def changed_tooltip(nodes):
873 def changed_tooltip(nodes):
873 """
874 """
874 Generates a html string for changed nodes in changeset page.
875 Generates a html string for changed nodes in changeset page.
875 It limits the output to 30 entries
876 It limits the output to 30 entries
876
877
877 :param nodes: LazyNodesGenerator
878 :param nodes: LazyNodesGenerator
878 """
879 """
879 if nodes:
880 if nodes:
880 pref = ': <br/> '
881 pref = ': <br/> '
881 suf = ''
882 suf = ''
882 if len(nodes) > 30:
883 if len(nodes) > 30:
883 suf = '<br/>' + _(' and %s more') % (len(nodes) - 30)
884 suf = '<br/>' + _(' and %s more') % (len(nodes) - 30)
884 return literal(pref + '<br/> '.join([safe_unicode(x.path)
885 return literal(pref + '<br/> '.join([safe_unicode(x.path)
885 for x in nodes[:30]]) + suf)
886 for x in nodes[:30]]) + suf)
886 else:
887 else:
887 return ': ' + _('No Files')
888 return ': ' + _('No Files')
888
889
889
890
890 def repo_link(groups_and_repos, last_url=None):
891 def repo_link(groups_and_repos, last_url=None):
891 """
892 """
892 Makes a breadcrumbs link to repo within a group
893 Makes a breadcrumbs link to repo within a group
893 joins &raquo; on each group to create a fancy link
894 joins &raquo; on each group to create a fancy link
894
895
895 ex::
896 ex::
896 group >> subgroup >> repo
897 group >> subgroup >> repo
897
898
898 :param groups_and_repos:
899 :param groups_and_repos:
899 :param last_url:
900 :param last_url:
900 """
901 """
901 groups, repo_name = groups_and_repos
902 groups, repo_name = groups_and_repos
902 last_link = link_to(repo_name, last_url) if last_url else repo_name
903 last_link = link_to(repo_name, last_url) if last_url else repo_name
903
904
904 if not groups:
905 if not groups:
905 if last_url:
906 if last_url:
906 return last_link
907 return last_link
907 return repo_name
908 return repo_name
908 else:
909 else:
909 def make_link(group):
910 def make_link(group):
910 return link_to(group.name,
911 return link_to(group.name,
911 url('repos_group_home', group_name=group.group_name))
912 url('repos_group_home', group_name=group.group_name))
912 return literal(' &raquo; '.join(map(make_link, groups) + [last_link]))
913 return literal(' &raquo; '.join(map(make_link, groups) + [last_link]))
913
914
914
915
915 def fancy_file_stats(stats):
916 def fancy_file_stats(stats):
916 """
917 """
917 Displays a fancy two colored bar for number of added/deleted
918 Displays a fancy two colored bar for number of added/deleted
918 lines of code on file
919 lines of code on file
919
920
920 :param stats: two element list of added/deleted lines of code
921 :param stats: two element list of added/deleted lines of code
921 """
922 """
922 def cgen(l_type, a_v, d_v):
923 def cgen(l_type, a_v, d_v):
923 mapping = {'tr': 'top-right-rounded-corner-mid',
924 mapping = {'tr': 'top-right-rounded-corner-mid',
924 'tl': 'top-left-rounded-corner-mid',
925 'tl': 'top-left-rounded-corner-mid',
925 'br': 'bottom-right-rounded-corner-mid',
926 'br': 'bottom-right-rounded-corner-mid',
926 'bl': 'bottom-left-rounded-corner-mid'}
927 'bl': 'bottom-left-rounded-corner-mid'}
927 map_getter = lambda x: mapping[x]
928 map_getter = lambda x: mapping[x]
928
929
929 if l_type == 'a' and d_v:
930 if l_type == 'a' and d_v:
930 #case when added and deleted are present
931 #case when added and deleted are present
931 return ' '.join(map(map_getter, ['tl', 'bl']))
932 return ' '.join(map(map_getter, ['tl', 'bl']))
932
933
933 if l_type == 'a' and not d_v:
934 if l_type == 'a' and not d_v:
934 return ' '.join(map(map_getter, ['tr', 'br', 'tl', 'bl']))
935 return ' '.join(map(map_getter, ['tr', 'br', 'tl', 'bl']))
935
936
936 if l_type == 'd' and a_v:
937 if l_type == 'd' and a_v:
937 return ' '.join(map(map_getter, ['tr', 'br']))
938 return ' '.join(map(map_getter, ['tr', 'br']))
938
939
939 if l_type == 'd' and not a_v:
940 if l_type == 'd' and not a_v:
940 return ' '.join(map(map_getter, ['tr', 'br', 'tl', 'bl']))
941 return ' '.join(map(map_getter, ['tr', 'br', 'tl', 'bl']))
941
942
942 a, d = stats[0], stats[1]
943 a, d = stats[0], stats[1]
943 width = 100
944 width = 100
944
945
945 if a == 'b':
946 if a == 'b':
946 #binary mode
947 #binary mode
947 b_d = '<div class="bin%s %s" style="width:100%%">%s</div>' % (d, cgen('a', a_v='', d_v=0), 'bin')
948 b_d = '<div class="bin%s %s" style="width:100%%">%s</div>' % (d, cgen('a', a_v='', d_v=0), 'bin')
948 b_a = '<div class="bin1" style="width:0%%">%s</div>' % ('bin')
949 b_a = '<div class="bin1" style="width:0%%">%s</div>' % ('bin')
949 return literal('<div style="width:%spx">%s%s</div>' % (width, b_a, b_d))
950 return literal('<div style="width:%spx">%s%s</div>' % (width, b_a, b_d))
950
951
951 t = stats[0] + stats[1]
952 t = stats[0] + stats[1]
952 unit = float(width) / (t or 1)
953 unit = float(width) / (t or 1)
953
954
954 # needs > 9% of width to be visible or 0 to be hidden
955 # needs > 9% of width to be visible or 0 to be hidden
955 a_p = max(9, unit * a) if a > 0 else 0
956 a_p = max(9, unit * a) if a > 0 else 0
956 d_p = max(9, unit * d) if d > 0 else 0
957 d_p = max(9, unit * d) if d > 0 else 0
957 p_sum = a_p + d_p
958 p_sum = a_p + d_p
958
959
959 if p_sum > width:
960 if p_sum > width:
960 #adjust the percentage to be == 100% since we adjusted to 9
961 #adjust the percentage to be == 100% since we adjusted to 9
961 if a_p > d_p:
962 if a_p > d_p:
962 a_p = a_p - (p_sum - width)
963 a_p = a_p - (p_sum - width)
963 else:
964 else:
964 d_p = d_p - (p_sum - width)
965 d_p = d_p - (p_sum - width)
965
966
966 a_v = a if a > 0 else ''
967 a_v = a if a > 0 else ''
967 d_v = d if d > 0 else ''
968 d_v = d if d > 0 else ''
968
969
969 d_a = '<div class="added %s" style="width:%s%%">%s</div>' % (
970 d_a = '<div class="added %s" style="width:%s%%">%s</div>' % (
970 cgen('a', a_v, d_v), a_p, a_v
971 cgen('a', a_v, d_v), a_p, a_v
971 )
972 )
972 d_d = '<div class="deleted %s" style="width:%s%%">%s</div>' % (
973 d_d = '<div class="deleted %s" style="width:%s%%">%s</div>' % (
973 cgen('d', a_v, d_v), d_p, d_v
974 cgen('d', a_v, d_v), d_p, d_v
974 )
975 )
975 return literal('<div style="width:%spx">%s%s</div>' % (width, d_a, d_d))
976 return literal('<div style="width:%spx">%s%s</div>' % (width, d_a, d_d))
976
977
977
978
978 def urlify_text(text_):
979 def urlify_text(text_):
979
980
980 url_pat = re.compile(r'''(http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]'''
981 url_pat = re.compile(r'''(http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]'''
981 '''|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+)''')
982 '''|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+)''')
982
983
983 def url_func(match_obj):
984 def url_func(match_obj):
984 url_full = match_obj.groups()[0]
985 url_full = match_obj.groups()[0]
985 return '<a href="%(url)s">%(url)s</a>' % ({'url': url_full})
986 return '<a href="%(url)s">%(url)s</a>' % ({'url': url_full})
986
987
987 return literal(url_pat.sub(url_func, text_))
988 return literal(url_pat.sub(url_func, text_))
988
989
989
990
990 def urlify_changesets(text_, repository):
991 def urlify_changesets(text_, repository):
991 """
992 """
992 Extract revision ids from changeset and make link from them
993 Extract revision ids from changeset and make link from them
993
994
994 :param text_:
995 :param text_:
995 :param repository:
996 :param repository:
996 """
997 """
997
998
998 URL_PAT = re.compile(r'([0-9a-fA-F]{12,})')
999 URL_PAT = re.compile(r'([0-9a-fA-F]{12,})')
999
1000
1000 def url_func(match_obj):
1001 def url_func(match_obj):
1001 rev = match_obj.groups()[0]
1002 rev = match_obj.groups()[0]
1002 pref = ''
1003 pref = ''
1003 if match_obj.group().startswith(' '):
1004 if match_obj.group().startswith(' '):
1004 pref = ' '
1005 pref = ' '
1005 tmpl = (
1006 tmpl = (
1006 '%(pref)s<a class="%(cls)s" href="%(url)s">'
1007 '%(pref)s<a class="%(cls)s" href="%(url)s">'
1007 '%(rev)s'
1008 '%(rev)s'
1008 '</a>'
1009 '</a>'
1009 )
1010 )
1010 return tmpl % {
1011 return tmpl % {
1011 'pref': pref,
1012 'pref': pref,
1012 'cls': 'revision-link',
1013 'cls': 'revision-link',
1013 'url': url('changeset_home', repo_name=repository, revision=rev),
1014 'url': url('changeset_home', repo_name=repository, revision=rev),
1014 'rev': rev,
1015 'rev': rev,
1015 }
1016 }
1016
1017
1017 newtext = URL_PAT.sub(url_func, text_)
1018 newtext = URL_PAT.sub(url_func, text_)
1018
1019
1019 return newtext
1020 return newtext
1020
1021
1021
1022
1022 def urlify_commit(text_, repository=None, link_=None):
1023 def urlify_commit(text_, repository=None, link_=None):
1023 """
1024 """
1024 Parses given text message and makes proper links.
1025 Parses given text message and makes proper links.
1025 issues are linked to given issue-server, and rest is a changeset link
1026 issues are linked to given issue-server, and rest is a changeset link
1026 if link_ is given, in other case it's a plain text
1027 if link_ is given, in other case it's a plain text
1027
1028
1028 :param text_:
1029 :param text_:
1029 :param repository:
1030 :param repository:
1030 :param link_: changeset link
1031 :param link_: changeset link
1031 """
1032 """
1032 import traceback
1033 import traceback
1033
1034
1034 def escaper(string):
1035 def escaper(string):
1035 return string.replace('<', '&lt;').replace('>', '&gt;')
1036 return string.replace('<', '&lt;').replace('>', '&gt;')
1036
1037
1037 def linkify_others(t, l):
1038 def linkify_others(t, l):
1038 urls = re.compile(r'(\<a.*?\<\/a\>)',)
1039 urls = re.compile(r'(\<a.*?\<\/a\>)',)
1039 links = []
1040 links = []
1040 for e in urls.split(t):
1041 for e in urls.split(t):
1041 if not urls.match(e):
1042 if not urls.match(e):
1042 links.append('<a class="message-link" href="%s">%s</a>' % (l, e))
1043 links.append('<a class="message-link" href="%s">%s</a>' % (l, e))
1043 else:
1044 else:
1044 links.append(e)
1045 links.append(e)
1045
1046
1046 return ''.join(links)
1047 return ''.join(links)
1047
1048
1048 # urlify changesets - extrac revisions and make link out of them
1049 # urlify changesets - extrac revisions and make link out of them
1049 newtext = urlify_changesets(escaper(text_), repository)
1050 newtext = urlify_changesets(escaper(text_), repository)
1050
1051
1051 try:
1052 try:
1052 conf = config['app_conf']
1053 conf = config['app_conf']
1053
1054
1054 # allow multiple issue servers to be used
1055 # allow multiple issue servers to be used
1055 valid_indices = [
1056 valid_indices = [
1056 x.group(1)
1057 x.group(1)
1057 for x in map(lambda x: re.match(r'issue_pat(.*)', x), conf.keys())
1058 for x in map(lambda x: re.match(r'issue_pat(.*)', x), conf.keys())
1058 if x and 'issue_server_link%s' % x.group(1) in conf
1059 if x and 'issue_server_link%s' % x.group(1) in conf
1059 and 'issue_prefix%s' % x.group(1) in conf
1060 and 'issue_prefix%s' % x.group(1) in conf
1060 ]
1061 ]
1061
1062
1062 log.debug('found issue server suffixes `%s` during valuation of: %s'
1063 log.debug('found issue server suffixes `%s` during valuation of: %s'
1063 % (','.join(valid_indices), newtext))
1064 % (','.join(valid_indices), newtext))
1064
1065
1065 for pattern_index in valid_indices:
1066 for pattern_index in valid_indices:
1066 ISSUE_PATTERN = conf.get('issue_pat%s' % pattern_index)
1067 ISSUE_PATTERN = conf.get('issue_pat%s' % pattern_index)
1067 ISSUE_SERVER_LNK = conf.get('issue_server_link%s' % pattern_index)
1068 ISSUE_SERVER_LNK = conf.get('issue_server_link%s' % pattern_index)
1068 ISSUE_PREFIX = conf.get('issue_prefix%s' % pattern_index)
1069 ISSUE_PREFIX = conf.get('issue_prefix%s' % pattern_index)
1069
1070
1070 log.debug('pattern suffix `%s` PAT:%s SERVER_LINK:%s PREFIX:%s'
1071 log.debug('pattern suffix `%s` PAT:%s SERVER_LINK:%s PREFIX:%s'
1071 % (pattern_index, ISSUE_PATTERN, ISSUE_SERVER_LNK,
1072 % (pattern_index, ISSUE_PATTERN, ISSUE_SERVER_LNK,
1072 ISSUE_PREFIX))
1073 ISSUE_PREFIX))
1073
1074
1074 URL_PAT = re.compile(r'%s' % ISSUE_PATTERN)
1075 URL_PAT = re.compile(r'%s' % ISSUE_PATTERN)
1075
1076
1076 def url_func(match_obj):
1077 def url_func(match_obj):
1077 pref = ''
1078 pref = ''
1078 if match_obj.group().startswith(' '):
1079 if match_obj.group().startswith(' '):
1079 pref = ' '
1080 pref = ' '
1080
1081
1081 issue_id = ''.join(match_obj.groups())
1082 issue_id = ''.join(match_obj.groups())
1082 tmpl = (
1083 tmpl = (
1083 '%(pref)s<a class="%(cls)s" href="%(url)s">'
1084 '%(pref)s<a class="%(cls)s" href="%(url)s">'
1084 '%(issue-prefix)s%(id-repr)s'
1085 '%(issue-prefix)s%(id-repr)s'
1085 '</a>'
1086 '</a>'
1086 )
1087 )
1087 url = ISSUE_SERVER_LNK.replace('{id}', issue_id)
1088 url = ISSUE_SERVER_LNK.replace('{id}', issue_id)
1088 if repository:
1089 if repository:
1089 url = url.replace('{repo}', repository)
1090 url = url.replace('{repo}', repository)
1090 repo_name = repository.split(URL_SEP)[-1]
1091 repo_name = repository.split(URL_SEP)[-1]
1091 url = url.replace('{repo_name}', repo_name)
1092 url = url.replace('{repo_name}', repo_name)
1092
1093
1093 return tmpl % {
1094 return tmpl % {
1094 'pref': pref,
1095 'pref': pref,
1095 'cls': 'issue-tracker-link',
1096 'cls': 'issue-tracker-link',
1096 'url': url,
1097 'url': url,
1097 'id-repr': issue_id,
1098 'id-repr': issue_id,
1098 'issue-prefix': ISSUE_PREFIX,
1099 'issue-prefix': ISSUE_PREFIX,
1099 'serv': ISSUE_SERVER_LNK,
1100 'serv': ISSUE_SERVER_LNK,
1100 }
1101 }
1101 newtext = URL_PAT.sub(url_func, newtext)
1102 newtext = URL_PAT.sub(url_func, newtext)
1102 log.debug('processed prefix:`%s` => %s' % (pattern_index, newtext))
1103 log.debug('processed prefix:`%s` => %s' % (pattern_index, newtext))
1103
1104
1104 # if we actually did something above
1105 # if we actually did something above
1105 if link_:
1106 if link_:
1106 # wrap not links into final link => link_
1107 # wrap not links into final link => link_
1107 newtext = linkify_others(newtext, link_)
1108 newtext = linkify_others(newtext, link_)
1108 except:
1109 except:
1109 log.error(traceback.format_exc())
1110 log.error(traceback.format_exc())
1110 pass
1111 pass
1111
1112
1112 return literal(newtext)
1113 return literal(newtext)
1113
1114
1114
1115
1115 def rst(source):
1116 def rst(source):
1116 return literal('<div class="rst-block">%s</div>' %
1117 return literal('<div class="rst-block">%s</div>' %
1117 MarkupRenderer.rst(source))
1118 MarkupRenderer.rst(source))
1118
1119
1119
1120
1120 def rst_w_mentions(source):
1121 def rst_w_mentions(source):
1121 """
1122 """
1122 Wrapped rst renderer with @mention highlighting
1123 Wrapped rst renderer with @mention highlighting
1123
1124
1124 :param source:
1125 :param source:
1125 """
1126 """
1126 return literal('<div class="rst-block">%s</div>' %
1127 return literal('<div class="rst-block">%s</div>' %
1127 MarkupRenderer.rst_with_mentions(source))
1128 MarkupRenderer.rst_with_mentions(source))
1128
1129
1129
1130
1130 def changeset_status(repo, revision):
1131 def changeset_status(repo, revision):
1131 return ChangesetStatusModel().get_status(repo, revision)
1132 return ChangesetStatusModel().get_status(repo, revision)
1132
1133
1133
1134
1134 def changeset_status_lbl(changeset_status):
1135 def changeset_status_lbl(changeset_status):
1135 return dict(ChangesetStatus.STATUSES).get(changeset_status)
1136 return dict(ChangesetStatus.STATUSES).get(changeset_status)
1136
1137
1137
1138
1138 def get_permission_name(key):
1139 def get_permission_name(key):
1139 return dict(Permission.PERMS).get(key)
1140 return dict(Permission.PERMS).get(key)
1140
1141
1141
1142
1142 def journal_filter_help():
1143 def journal_filter_help():
1143 return _(textwrap.dedent('''
1144 return _(textwrap.dedent('''
1144 Example filter terms:
1145 Example filter terms:
1145 repository:vcs
1146 repository:vcs
1146 username:marcin
1147 username:marcin
1147 action:*push*
1148 action:*push*
1148 ip:127.0.0.1
1149 ip:127.0.0.1
1149 date:20120101
1150 date:20120101
1150 date:[20120101100000 TO 20120102]
1151 date:[20120101100000 TO 20120102]
1151
1152
1152 Generate wildcards using '*' character:
1153 Generate wildcards using '*' character:
1153 "repositroy:vcs*" - search everything starting with 'vcs'
1154 "repositroy:vcs*" - search everything starting with 'vcs'
1154 "repository:*vcs*" - search for repository containing 'vcs'
1155 "repository:*vcs*" - search for repository containing 'vcs'
1155
1156
1156 Optional AND / OR operators in queries
1157 Optional AND / OR operators in queries
1157 "repository:vcs OR repository:test"
1158 "repository:vcs OR repository:test"
1158 "username:test AND repository:test*"
1159 "username:test AND repository:test*"
1159 '''))
1160 '''))
1160
1161
1161
1162
1162 def not_mapped_error(repo_name):
1163 def not_mapped_error(repo_name):
1163 flash(_('%s repository is not mapped to db perhaps'
1164 flash(_('%s repository is not mapped to db perhaps'
1164 ' it was created or renamed from the filesystem'
1165 ' it was created or renamed from the filesystem'
1165 ' please run the application again'
1166 ' please run the application again'
1166 ' in order to rescan repositories') % repo_name, category='error')
1167 ' in order to rescan repositories') % repo_name, category='error')
1167
1168
1168
1169
1169 def ip_range(ip_addr):
1170 def ip_range(ip_addr):
1170 from rhodecode.model.db import UserIpMap
1171 from rhodecode.model.db import UserIpMap
1171 s, e = UserIpMap._get_ip_range(ip_addr)
1172 s, e = UserIpMap._get_ip_range(ip_addr)
1172 return '%s - %s' % (s, e)
1173 return '%s - %s' % (s, e)
@@ -1,1968 +1,1971 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.model.db
3 rhodecode.model.db
4 ~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~
5
5
6 Database Models for RhodeCode
6 Database Models for RhodeCode
7
7
8 :created_on: Apr 08, 2010
8 :created_on: Apr 08, 2010
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software: you can redistribute it and/or modify
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
16 # (at your option) any later version.
17 #
17 #
18 # This program is distributed in the hope that it will be useful,
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # GNU General Public License for more details.
21 # GNU General Public License for more details.
22 #
22 #
23 # You should have received a copy of the GNU General Public License
23 # You should have received a copy of the GNU General Public License
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25
25
26 import os
26 import os
27 import logging
27 import logging
28 import datetime
28 import datetime
29 import traceback
29 import traceback
30 import hashlib
30 import hashlib
31 import time
31 import time
32 from collections import defaultdict
32 from collections import defaultdict
33
33
34 from sqlalchemy import *
34 from sqlalchemy import *
35 from sqlalchemy.ext.hybrid import hybrid_property
35 from sqlalchemy.ext.hybrid import hybrid_property
36 from sqlalchemy.orm import relationship, joinedload, class_mapper, validates
36 from sqlalchemy.orm import relationship, joinedload, class_mapper, validates
37 from sqlalchemy.exc import DatabaseError
37 from sqlalchemy.exc import DatabaseError
38 from beaker.cache import cache_region, region_invalidate
38 from beaker.cache import cache_region, region_invalidate
39 from webob.exc import HTTPNotFound
39 from webob.exc import HTTPNotFound
40
40
41 from pylons.i18n.translation import lazy_ugettext as _
41 from pylons.i18n.translation import lazy_ugettext as _
42
42
43 from rhodecode.lib.vcs import get_backend
43 from rhodecode.lib.vcs import get_backend
44 from rhodecode.lib.vcs.utils.helpers import get_scm
44 from rhodecode.lib.vcs.utils.helpers import get_scm
45 from rhodecode.lib.vcs.exceptions import VCSError
45 from rhodecode.lib.vcs.exceptions import VCSError
46 from rhodecode.lib.vcs.utils.lazy import LazyProperty
46 from rhodecode.lib.vcs.utils.lazy import LazyProperty
47
47
48 from rhodecode.lib.utils2 import str2bool, safe_str, get_changeset_safe, \
48 from rhodecode.lib.utils2 import str2bool, safe_str, get_changeset_safe, \
49 safe_unicode, remove_suffix, remove_prefix
49 safe_unicode, remove_suffix, remove_prefix
50 from rhodecode.lib.compat import json
50 from rhodecode.lib.compat import json
51 from rhodecode.lib.caching_query import FromCache
51 from rhodecode.lib.caching_query import FromCache
52
52
53 from rhodecode.model.meta import Base, Session
53 from rhodecode.model.meta import Base, Session
54
54
55 URL_SEP = '/'
55 URL_SEP = '/'
56 log = logging.getLogger(__name__)
56 log = logging.getLogger(__name__)
57
57
58 #==============================================================================
58 #==============================================================================
59 # BASE CLASSES
59 # BASE CLASSES
60 #==============================================================================
60 #==============================================================================
61
61
62 _hash_key = lambda k: hashlib.md5(safe_str(k)).hexdigest()
62 _hash_key = lambda k: hashlib.md5(safe_str(k)).hexdigest()
63
63
64
64
65 class BaseModel(object):
65 class BaseModel(object):
66 """
66 """
67 Base Model for all classess
67 Base Model for all classess
68 """
68 """
69
69
70 @classmethod
70 @classmethod
71 def _get_keys(cls):
71 def _get_keys(cls):
72 """return column names for this model """
72 """return column names for this model """
73 return class_mapper(cls).c.keys()
73 return class_mapper(cls).c.keys()
74
74
75 def get_dict(self):
75 def get_dict(self):
76 """
76 """
77 return dict with keys and values corresponding
77 return dict with keys and values corresponding
78 to this model data """
78 to this model data """
79
79
80 d = {}
80 d = {}
81 for k in self._get_keys():
81 for k in self._get_keys():
82 d[k] = getattr(self, k)
82 d[k] = getattr(self, k)
83
83
84 # also use __json__() if present to get additional fields
84 # also use __json__() if present to get additional fields
85 _json_attr = getattr(self, '__json__', None)
85 _json_attr = getattr(self, '__json__', None)
86 if _json_attr:
86 if _json_attr:
87 # update with attributes from __json__
87 # update with attributes from __json__
88 if callable(_json_attr):
88 if callable(_json_attr):
89 _json_attr = _json_attr()
89 _json_attr = _json_attr()
90 for k, val in _json_attr.iteritems():
90 for k, val in _json_attr.iteritems():
91 d[k] = val
91 d[k] = val
92 return d
92 return d
93
93
94 def get_appstruct(self):
94 def get_appstruct(self):
95 """return list with keys and values tupples corresponding
95 """return list with keys and values tupples corresponding
96 to this model data """
96 to this model data """
97
97
98 l = []
98 l = []
99 for k in self._get_keys():
99 for k in self._get_keys():
100 l.append((k, getattr(self, k),))
100 l.append((k, getattr(self, k),))
101 return l
101 return l
102
102
103 def populate_obj(self, populate_dict):
103 def populate_obj(self, populate_dict):
104 """populate model with data from given populate_dict"""
104 """populate model with data from given populate_dict"""
105
105
106 for k in self._get_keys():
106 for k in self._get_keys():
107 if k in populate_dict:
107 if k in populate_dict:
108 setattr(self, k, populate_dict[k])
108 setattr(self, k, populate_dict[k])
109
109
110 @classmethod
110 @classmethod
111 def query(cls):
111 def query(cls):
112 return Session().query(cls)
112 return Session().query(cls)
113
113
114 @classmethod
114 @classmethod
115 def get(cls, id_):
115 def get(cls, id_):
116 if id_:
116 if id_:
117 return cls.query().get(id_)
117 return cls.query().get(id_)
118
118
119 @classmethod
119 @classmethod
120 def get_or_404(cls, id_):
120 def get_or_404(cls, id_):
121 try:
121 try:
122 id_ = int(id_)
122 id_ = int(id_)
123 except (TypeError, ValueError):
123 except (TypeError, ValueError):
124 raise HTTPNotFound
124 raise HTTPNotFound
125
125
126 res = cls.query().get(id_)
126 res = cls.query().get(id_)
127 if not res:
127 if not res:
128 raise HTTPNotFound
128 raise HTTPNotFound
129 return res
129 return res
130
130
131 @classmethod
131 @classmethod
132 def getAll(cls):
132 def getAll(cls):
133 return cls.query().all()
133 return cls.query().all()
134
134
135 @classmethod
135 @classmethod
136 def delete(cls, id_):
136 def delete(cls, id_):
137 obj = cls.query().get(id_)
137 obj = cls.query().get(id_)
138 Session().delete(obj)
138 Session().delete(obj)
139
139
140 def __repr__(self):
140 def __repr__(self):
141 if hasattr(self, '__unicode__'):
141 if hasattr(self, '__unicode__'):
142 # python repr needs to return str
142 # python repr needs to return str
143 return safe_str(self.__unicode__())
143 return safe_str(self.__unicode__())
144 return '<DB:%s>' % (self.__class__.__name__)
144 return '<DB:%s>' % (self.__class__.__name__)
145
145
146
146
147 class RhodeCodeSetting(Base, BaseModel):
147 class RhodeCodeSetting(Base, BaseModel):
148 __tablename__ = 'rhodecode_settings'
148 __tablename__ = 'rhodecode_settings'
149 __table_args__ = (
149 __table_args__ = (
150 UniqueConstraint('app_settings_name'),
150 UniqueConstraint('app_settings_name'),
151 {'extend_existing': True, 'mysql_engine': 'InnoDB',
151 {'extend_existing': True, 'mysql_engine': 'InnoDB',
152 'mysql_charset': 'utf8'}
152 'mysql_charset': 'utf8'}
153 )
153 )
154 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
154 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
155 app_settings_name = Column("app_settings_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
155 app_settings_name = Column("app_settings_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
156 _app_settings_value = Column("app_settings_value", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
156 _app_settings_value = Column("app_settings_value", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
157
157
158 def __init__(self, k='', v=''):
158 def __init__(self, k='', v=''):
159 self.app_settings_name = k
159 self.app_settings_name = k
160 self.app_settings_value = v
160 self.app_settings_value = v
161
161
162 @validates('_app_settings_value')
162 @validates('_app_settings_value')
163 def validate_settings_value(self, key, val):
163 def validate_settings_value(self, key, val):
164 assert type(val) == unicode
164 assert type(val) == unicode
165 return val
165 return val
166
166
167 @hybrid_property
167 @hybrid_property
168 def app_settings_value(self):
168 def app_settings_value(self):
169 v = self._app_settings_value
169 v = self._app_settings_value
170 if self.app_settings_name in ["ldap_active",
170 if self.app_settings_name in ["ldap_active",
171 "default_repo_enable_statistics",
171 "default_repo_enable_statistics",
172 "default_repo_enable_locking",
172 "default_repo_enable_locking",
173 "default_repo_private",
173 "default_repo_private",
174 "default_repo_enable_downloads"]:
174 "default_repo_enable_downloads"]:
175 v = str2bool(v)
175 v = str2bool(v)
176 return v
176 return v
177
177
178 @app_settings_value.setter
178 @app_settings_value.setter
179 def app_settings_value(self, val):
179 def app_settings_value(self, val):
180 """
180 """
181 Setter that will always make sure we use unicode in app_settings_value
181 Setter that will always make sure we use unicode in app_settings_value
182
182
183 :param val:
183 :param val:
184 """
184 """
185 self._app_settings_value = safe_unicode(val)
185 self._app_settings_value = safe_unicode(val)
186
186
187 def __unicode__(self):
187 def __unicode__(self):
188 return u"<%s('%s:%s')>" % (
188 return u"<%s('%s:%s')>" % (
189 self.__class__.__name__,
189 self.__class__.__name__,
190 self.app_settings_name, self.app_settings_value
190 self.app_settings_name, self.app_settings_value
191 )
191 )
192
192
193 @classmethod
193 @classmethod
194 def get_by_name(cls, key):
194 def get_by_name(cls, key):
195 return cls.query()\
195 return cls.query()\
196 .filter(cls.app_settings_name == key).scalar()
196 .filter(cls.app_settings_name == key).scalar()
197
197
198 @classmethod
198 @classmethod
199 def get_by_name_or_create(cls, key):
199 def get_by_name_or_create(cls, key):
200 res = cls.get_by_name(key)
200 res = cls.get_by_name(key)
201 if not res:
201 if not res:
202 res = cls(key)
202 res = cls(key)
203 return res
203 return res
204
204
205 @classmethod
205 @classmethod
206 def get_app_settings(cls, cache=False):
206 def get_app_settings(cls, cache=False):
207
207
208 ret = cls.query()
208 ret = cls.query()
209
209
210 if cache:
210 if cache:
211 ret = ret.options(FromCache("sql_cache_short", "get_hg_settings"))
211 ret = ret.options(FromCache("sql_cache_short", "get_hg_settings"))
212
212
213 if not ret:
213 if not ret:
214 raise Exception('Could not get application settings !')
214 raise Exception('Could not get application settings !')
215 settings = {}
215 settings = {}
216 for each in ret:
216 for each in ret:
217 settings['rhodecode_' + each.app_settings_name] = \
217 settings['rhodecode_' + each.app_settings_name] = \
218 each.app_settings_value
218 each.app_settings_value
219
219
220 return settings
220 return settings
221
221
222 @classmethod
222 @classmethod
223 def get_ldap_settings(cls, cache=False):
223 def get_ldap_settings(cls, cache=False):
224 ret = cls.query()\
224 ret = cls.query()\
225 .filter(cls.app_settings_name.startswith('ldap_')).all()
225 .filter(cls.app_settings_name.startswith('ldap_')).all()
226 fd = {}
226 fd = {}
227 for row in ret:
227 for row in ret:
228 fd.update({row.app_settings_name: row.app_settings_value})
228 fd.update({row.app_settings_name: row.app_settings_value})
229
229
230 return fd
230 return fd
231
231
232 @classmethod
232 @classmethod
233 def get_default_repo_settings(cls, cache=False, strip_prefix=False):
233 def get_default_repo_settings(cls, cache=False, strip_prefix=False):
234 ret = cls.query()\
234 ret = cls.query()\
235 .filter(cls.app_settings_name.startswith('default_')).all()
235 .filter(cls.app_settings_name.startswith('default_')).all()
236 fd = {}
236 fd = {}
237 for row in ret:
237 for row in ret:
238 key = row.app_settings_name
238 key = row.app_settings_name
239 if strip_prefix:
239 if strip_prefix:
240 key = remove_prefix(key, prefix='default_')
240 key = remove_prefix(key, prefix='default_')
241 fd.update({key: row.app_settings_value})
241 fd.update({key: row.app_settings_value})
242
242
243 return fd
243 return fd
244
244
245
245
246 class RhodeCodeUi(Base, BaseModel):
246 class RhodeCodeUi(Base, BaseModel):
247 __tablename__ = 'rhodecode_ui'
247 __tablename__ = 'rhodecode_ui'
248 __table_args__ = (
248 __table_args__ = (
249 UniqueConstraint('ui_key'),
249 UniqueConstraint('ui_key'),
250 {'extend_existing': True, 'mysql_engine': 'InnoDB',
250 {'extend_existing': True, 'mysql_engine': 'InnoDB',
251 'mysql_charset': 'utf8'}
251 'mysql_charset': 'utf8'}
252 )
252 )
253
253
254 HOOK_UPDATE = 'changegroup.update'
254 HOOK_UPDATE = 'changegroup.update'
255 HOOK_REPO_SIZE = 'changegroup.repo_size'
255 HOOK_REPO_SIZE = 'changegroup.repo_size'
256 HOOK_PUSH = 'changegroup.push_logger'
256 HOOK_PUSH = 'changegroup.push_logger'
257 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
257 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
258 HOOK_PULL = 'outgoing.pull_logger'
258 HOOK_PULL = 'outgoing.pull_logger'
259 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
259 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
260
260
261 ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
261 ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
262 ui_section = Column("ui_section", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
262 ui_section = Column("ui_section", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
263 ui_key = Column("ui_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
263 ui_key = Column("ui_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
264 ui_value = Column("ui_value", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
264 ui_value = Column("ui_value", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
265 ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True)
265 ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True)
266
266
267 @classmethod
267 @classmethod
268 def get_by_key(cls, key):
268 def get_by_key(cls, key):
269 return cls.query().filter(cls.ui_key == key).scalar()
269 return cls.query().filter(cls.ui_key == key).scalar()
270
270
271 @classmethod
271 @classmethod
272 def get_builtin_hooks(cls):
272 def get_builtin_hooks(cls):
273 q = cls.query()
273 q = cls.query()
274 q = q.filter(cls.ui_key.in_([cls.HOOK_UPDATE, cls.HOOK_REPO_SIZE,
274 q = q.filter(cls.ui_key.in_([cls.HOOK_UPDATE, cls.HOOK_REPO_SIZE,
275 cls.HOOK_PUSH, cls.HOOK_PRE_PUSH,
275 cls.HOOK_PUSH, cls.HOOK_PRE_PUSH,
276 cls.HOOK_PULL, cls.HOOK_PRE_PULL]))
276 cls.HOOK_PULL, cls.HOOK_PRE_PULL]))
277 return q.all()
277 return q.all()
278
278
279 @classmethod
279 @classmethod
280 def get_custom_hooks(cls):
280 def get_custom_hooks(cls):
281 q = cls.query()
281 q = cls.query()
282 q = q.filter(~cls.ui_key.in_([cls.HOOK_UPDATE, cls.HOOK_REPO_SIZE,
282 q = q.filter(~cls.ui_key.in_([cls.HOOK_UPDATE, cls.HOOK_REPO_SIZE,
283 cls.HOOK_PUSH, cls.HOOK_PRE_PUSH,
283 cls.HOOK_PUSH, cls.HOOK_PRE_PUSH,
284 cls.HOOK_PULL, cls.HOOK_PRE_PULL]))
284 cls.HOOK_PULL, cls.HOOK_PRE_PULL]))
285 q = q.filter(cls.ui_section == 'hooks')
285 q = q.filter(cls.ui_section == 'hooks')
286 return q.all()
286 return q.all()
287
287
288 @classmethod
288 @classmethod
289 def get_repos_location(cls):
289 def get_repos_location(cls):
290 return cls.get_by_key('/').ui_value
290 return cls.get_by_key('/').ui_value
291
291
292 @classmethod
292 @classmethod
293 def create_or_update_hook(cls, key, val):
293 def create_or_update_hook(cls, key, val):
294 new_ui = cls.get_by_key(key) or cls()
294 new_ui = cls.get_by_key(key) or cls()
295 new_ui.ui_section = 'hooks'
295 new_ui.ui_section = 'hooks'
296 new_ui.ui_active = True
296 new_ui.ui_active = True
297 new_ui.ui_key = key
297 new_ui.ui_key = key
298 new_ui.ui_value = val
298 new_ui.ui_value = val
299
299
300 Session().add(new_ui)
300 Session().add(new_ui)
301
301
302 def __repr__(self):
302 def __repr__(self):
303 return '<DB:%s[%s:%s]>' % (self.__class__.__name__, self.ui_key,
303 return '<DB:%s[%s:%s]>' % (self.__class__.__name__, self.ui_key,
304 self.ui_value)
304 self.ui_value)
305
305
306
306
307 class User(Base, BaseModel):
307 class User(Base, BaseModel):
308 __tablename__ = 'users'
308 __tablename__ = 'users'
309 __table_args__ = (
309 __table_args__ = (
310 UniqueConstraint('username'), UniqueConstraint('email'),
310 UniqueConstraint('username'), UniqueConstraint('email'),
311 Index('u_username_idx', 'username'),
311 Index('u_username_idx', 'username'),
312 Index('u_email_idx', 'email'),
312 Index('u_email_idx', 'email'),
313 {'extend_existing': True, 'mysql_engine': 'InnoDB',
313 {'extend_existing': True, 'mysql_engine': 'InnoDB',
314 'mysql_charset': 'utf8'}
314 'mysql_charset': 'utf8'}
315 )
315 )
316 DEFAULT_USER = 'default'
316 DEFAULT_USER = 'default'
317 DEFAULT_PERMISSIONS = [
317 DEFAULT_PERMISSIONS = [
318 'hg.register.manual_activate', 'hg.create.repository',
318 'hg.register.manual_activate', 'hg.create.repository',
319 'hg.fork.repository', 'repository.read', 'group.read'
319 'hg.fork.repository', 'repository.read', 'group.read'
320 ]
320 ]
321 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
321 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
322 username = Column("username", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
322 username = Column("username", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
323 password = Column("password", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
323 password = Column("password", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
324 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
324 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
325 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
325 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
326 name = Column("firstname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
326 name = Column("firstname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
327 lastname = Column("lastname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
327 lastname = Column("lastname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
328 _email = Column("email", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
328 _email = Column("email", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
329 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
329 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
330 ldap_dn = Column("ldap_dn", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
330 ldap_dn = Column("ldap_dn", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
331 api_key = Column("api_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
331 api_key = Column("api_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
332 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
332 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
333
333
334 user_log = relationship('UserLog')
334 user_log = relationship('UserLog')
335 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
335 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
336
336
337 repositories = relationship('Repository')
337 repositories = relationship('Repository')
338 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
338 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
339 followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
339 followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
340
340
341 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
341 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
342 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
342 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
343
343
344 group_member = relationship('UsersGroupMember', cascade='all')
344 group_member = relationship('UsersGroupMember', cascade='all')
345
345
346 notifications = relationship('UserNotification', cascade='all')
346 notifications = relationship('UserNotification', cascade='all')
347 # notifications assigned to this user
347 # notifications assigned to this user
348 user_created_notifications = relationship('Notification', cascade='all')
348 user_created_notifications = relationship('Notification', cascade='all')
349 # comments created by this user
349 # comments created by this user
350 user_comments = relationship('ChangesetComment', cascade='all')
350 user_comments = relationship('ChangesetComment', cascade='all')
351 #extra emails for this user
351 #extra emails for this user
352 user_emails = relationship('UserEmailMap', cascade='all')
352 user_emails = relationship('UserEmailMap', cascade='all')
353
353
354 @hybrid_property
354 @hybrid_property
355 def email(self):
355 def email(self):
356 return self._email
356 return self._email
357
357
358 @email.setter
358 @email.setter
359 def email(self, val):
359 def email(self, val):
360 self._email = val.lower() if val else None
360 self._email = val.lower() if val else None
361
361
362 @property
362 @property
363 def firstname(self):
363 def firstname(self):
364 # alias for future
364 # alias for future
365 return self.name
365 return self.name
366
366
367 @property
367 @property
368 def emails(self):
368 def emails(self):
369 other = UserEmailMap.query().filter(UserEmailMap.user==self).all()
369 other = UserEmailMap.query().filter(UserEmailMap.user==self).all()
370 return [self.email] + [x.email for x in other]
370 return [self.email] + [x.email for x in other]
371
371
372 @property
372 @property
373 def ip_addresses(self):
373 def ip_addresses(self):
374 ret = UserIpMap.query().filter(UserIpMap.user == self).all()
374 ret = UserIpMap.query().filter(UserIpMap.user == self).all()
375 return [x.ip_addr for x in ret]
375 return [x.ip_addr for x in ret]
376
376
377 @property
377 @property
378 def username_and_name(self):
378 def username_and_name(self):
379 return '%s (%s %s)' % (self.username, self.firstname, self.lastname)
379 return '%s (%s %s)' % (self.username, self.firstname, self.lastname)
380
380
381 @property
381 @property
382 def full_name(self):
382 def full_name(self):
383 return '%s %s' % (self.firstname, self.lastname)
383 return '%s %s' % (self.firstname, self.lastname)
384
384
385 @property
385 @property
386 def full_name_or_username(self):
386 def full_name_or_username(self):
387 return ('%s %s' % (self.firstname, self.lastname)
387 return ('%s %s' % (self.firstname, self.lastname)
388 if (self.firstname and self.lastname) else self.username)
388 if (self.firstname and self.lastname) else self.username)
389
389
390 @property
390 @property
391 def full_contact(self):
391 def full_contact(self):
392 return '%s %s <%s>' % (self.firstname, self.lastname, self.email)
392 return '%s %s <%s>' % (self.firstname, self.lastname, self.email)
393
393
394 @property
394 @property
395 def short_contact(self):
395 def short_contact(self):
396 return '%s %s' % (self.firstname, self.lastname)
396 return '%s %s' % (self.firstname, self.lastname)
397
397
398 @property
398 @property
399 def is_admin(self):
399 def is_admin(self):
400 return self.admin
400 return self.admin
401
401
402 def __unicode__(self):
402 def __unicode__(self):
403 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
403 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
404 self.user_id, self.username)
404 self.user_id, self.username)
405
405
406 @classmethod
406 @classmethod
407 def get_by_username(cls, username, case_insensitive=False, cache=False):
407 def get_by_username(cls, username, case_insensitive=False, cache=False):
408 if case_insensitive:
408 if case_insensitive:
409 q = cls.query().filter(cls.username.ilike(username))
409 q = cls.query().filter(cls.username.ilike(username))
410 else:
410 else:
411 q = cls.query().filter(cls.username == username)
411 q = cls.query().filter(cls.username == username)
412
412
413 if cache:
413 if cache:
414 q = q.options(FromCache(
414 q = q.options(FromCache(
415 "sql_cache_short",
415 "sql_cache_short",
416 "get_user_%s" % _hash_key(username)
416 "get_user_%s" % _hash_key(username)
417 )
417 )
418 )
418 )
419 return q.scalar()
419 return q.scalar()
420
420
421 @classmethod
421 @classmethod
422 def get_by_api_key(cls, api_key, cache=False):
422 def get_by_api_key(cls, api_key, cache=False):
423 q = cls.query().filter(cls.api_key == api_key)
423 q = cls.query().filter(cls.api_key == api_key)
424
424
425 if cache:
425 if cache:
426 q = q.options(FromCache("sql_cache_short",
426 q = q.options(FromCache("sql_cache_short",
427 "get_api_key_%s" % api_key))
427 "get_api_key_%s" % api_key))
428 return q.scalar()
428 return q.scalar()
429
429
430 @classmethod
430 @classmethod
431 def get_by_email(cls, email, case_insensitive=False, cache=False):
431 def get_by_email(cls, email, case_insensitive=False, cache=False):
432 if case_insensitive:
432 if case_insensitive:
433 q = cls.query().filter(cls.email.ilike(email))
433 q = cls.query().filter(cls.email.ilike(email))
434 else:
434 else:
435 q = cls.query().filter(cls.email == email)
435 q = cls.query().filter(cls.email == email)
436
436
437 if cache:
437 if cache:
438 q = q.options(FromCache("sql_cache_short",
438 q = q.options(FromCache("sql_cache_short",
439 "get_email_key_%s" % email))
439 "get_email_key_%s" % email))
440
440
441 ret = q.scalar()
441 ret = q.scalar()
442 if ret is None:
442 if ret is None:
443 q = UserEmailMap.query()
443 q = UserEmailMap.query()
444 # try fetching in alternate email map
444 # try fetching in alternate email map
445 if case_insensitive:
445 if case_insensitive:
446 q = q.filter(UserEmailMap.email.ilike(email))
446 q = q.filter(UserEmailMap.email.ilike(email))
447 else:
447 else:
448 q = q.filter(UserEmailMap.email == email)
448 q = q.filter(UserEmailMap.email == email)
449 q = q.options(joinedload(UserEmailMap.user))
449 q = q.options(joinedload(UserEmailMap.user))
450 if cache:
450 if cache:
451 q = q.options(FromCache("sql_cache_short",
451 q = q.options(FromCache("sql_cache_short",
452 "get_email_map_key_%s" % email))
452 "get_email_map_key_%s" % email))
453 ret = getattr(q.scalar(), 'user', None)
453 ret = getattr(q.scalar(), 'user', None)
454
454
455 return ret
455 return ret
456
456
457 @classmethod
457 @classmethod
458 def get_from_cs_author(cls, author):
458 def get_from_cs_author(cls, author):
459 """
459 """
460 Tries to get User objects out of commit author string
460 Tries to get User objects out of commit author string
461
461
462 :param author:
462 :param author:
463 """
463 """
464 from rhodecode.lib.helpers import email, author_name
464 from rhodecode.lib.helpers import email, author_name
465 # Valid email in the attribute passed, see if they're in the system
465 # Valid email in the attribute passed, see if they're in the system
466 _email = email(author)
466 _email = email(author)
467 if _email:
467 if _email:
468 user = cls.get_by_email(_email, case_insensitive=True)
468 user = cls.get_by_email(_email, case_insensitive=True)
469 if user:
469 if user:
470 return user
470 return user
471 # Maybe we can match by username?
471 # Maybe we can match by username?
472 _author = author_name(author)
472 _author = author_name(author)
473 user = cls.get_by_username(_author, case_insensitive=True)
473 user = cls.get_by_username(_author, case_insensitive=True)
474 if user:
474 if user:
475 return user
475 return user
476
476
477 def update_lastlogin(self):
477 def update_lastlogin(self):
478 """Update user lastlogin"""
478 """Update user lastlogin"""
479 self.last_login = datetime.datetime.now()
479 self.last_login = datetime.datetime.now()
480 Session().add(self)
480 Session().add(self)
481 log.debug('updated user %s lastlogin' % self.username)
481 log.debug('updated user %s lastlogin' % self.username)
482
482
483 def get_api_data(self):
483 def get_api_data(self):
484 """
484 """
485 Common function for generating user related data for API
485 Common function for generating user related data for API
486 """
486 """
487 user = self
487 user = self
488 data = dict(
488 data = dict(
489 user_id=user.user_id,
489 user_id=user.user_id,
490 username=user.username,
490 username=user.username,
491 firstname=user.name,
491 firstname=user.name,
492 lastname=user.lastname,
492 lastname=user.lastname,
493 email=user.email,
493 email=user.email,
494 emails=user.emails,
494 emails=user.emails,
495 api_key=user.api_key,
495 api_key=user.api_key,
496 active=user.active,
496 active=user.active,
497 admin=user.admin,
497 admin=user.admin,
498 ldap_dn=user.ldap_dn,
498 ldap_dn=user.ldap_dn,
499 last_login=user.last_login,
499 last_login=user.last_login,
500 ip_addresses=user.ip_addresses
500 ip_addresses=user.ip_addresses
501 )
501 )
502 return data
502 return data
503
503
504 def __json__(self):
504 def __json__(self):
505 data = dict(
505 data = dict(
506 full_name=self.full_name,
506 full_name=self.full_name,
507 full_name_or_username=self.full_name_or_username,
507 full_name_or_username=self.full_name_or_username,
508 short_contact=self.short_contact,
508 short_contact=self.short_contact,
509 full_contact=self.full_contact
509 full_contact=self.full_contact
510 )
510 )
511 data.update(self.get_api_data())
511 data.update(self.get_api_data())
512 return data
512 return data
513
513
514
514
515 class UserEmailMap(Base, BaseModel):
515 class UserEmailMap(Base, BaseModel):
516 __tablename__ = 'user_email_map'
516 __tablename__ = 'user_email_map'
517 __table_args__ = (
517 __table_args__ = (
518 Index('uem_email_idx', 'email'),
518 Index('uem_email_idx', 'email'),
519 UniqueConstraint('email'),
519 UniqueConstraint('email'),
520 {'extend_existing': True, 'mysql_engine': 'InnoDB',
520 {'extend_existing': True, 'mysql_engine': 'InnoDB',
521 'mysql_charset': 'utf8'}
521 'mysql_charset': 'utf8'}
522 )
522 )
523 __mapper_args__ = {}
523 __mapper_args__ = {}
524
524
525 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
525 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
526 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
526 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
527 _email = Column("email", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
527 _email = Column("email", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
528 user = relationship('User', lazy='joined')
528 user = relationship('User', lazy='joined')
529
529
530 @validates('_email')
530 @validates('_email')
531 def validate_email(self, key, email):
531 def validate_email(self, key, email):
532 # check if this email is not main one
532 # check if this email is not main one
533 main_email = Session().query(User).filter(User.email == email).scalar()
533 main_email = Session().query(User).filter(User.email == email).scalar()
534 if main_email is not None:
534 if main_email is not None:
535 raise AttributeError('email %s is present is user table' % email)
535 raise AttributeError('email %s is present is user table' % email)
536 return email
536 return email
537
537
538 @hybrid_property
538 @hybrid_property
539 def email(self):
539 def email(self):
540 return self._email
540 return self._email
541
541
542 @email.setter
542 @email.setter
543 def email(self, val):
543 def email(self, val):
544 self._email = val.lower() if val else None
544 self._email = val.lower() if val else None
545
545
546
546
547 class UserIpMap(Base, BaseModel):
547 class UserIpMap(Base, BaseModel):
548 __tablename__ = 'user_ip_map'
548 __tablename__ = 'user_ip_map'
549 __table_args__ = (
549 __table_args__ = (
550 UniqueConstraint('user_id', 'ip_addr'),
550 UniqueConstraint('user_id', 'ip_addr'),
551 {'extend_existing': True, 'mysql_engine': 'InnoDB',
551 {'extend_existing': True, 'mysql_engine': 'InnoDB',
552 'mysql_charset': 'utf8'}
552 'mysql_charset': 'utf8'}
553 )
553 )
554 __mapper_args__ = {}
554 __mapper_args__ = {}
555
555
556 ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
556 ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
557 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
557 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
558 ip_addr = Column("ip_addr", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
558 ip_addr = Column("ip_addr", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
559 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
559 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
560 user = relationship('User', lazy='joined')
560 user = relationship('User', lazy='joined')
561
561
562 @classmethod
562 @classmethod
563 def _get_ip_range(cls, ip_addr):
563 def _get_ip_range(cls, ip_addr):
564 from rhodecode.lib import ipaddr
564 from rhodecode.lib import ipaddr
565 net = ipaddr.IPNetwork(address=ip_addr)
565 net = ipaddr.IPNetwork(address=ip_addr)
566 return [str(net.network), str(net.broadcast)]
566 return [str(net.network), str(net.broadcast)]
567
567
568 def __json__(self):
568 def __json__(self):
569 return dict(
569 return dict(
570 ip_addr=self.ip_addr,
570 ip_addr=self.ip_addr,
571 ip_range=self._get_ip_range(self.ip_addr)
571 ip_range=self._get_ip_range(self.ip_addr)
572 )
572 )
573
573
574
574
575 class UserLog(Base, BaseModel):
575 class UserLog(Base, BaseModel):
576 __tablename__ = 'user_logs'
576 __tablename__ = 'user_logs'
577 __table_args__ = (
577 __table_args__ = (
578 {'extend_existing': True, 'mysql_engine': 'InnoDB',
578 {'extend_existing': True, 'mysql_engine': 'InnoDB',
579 'mysql_charset': 'utf8'},
579 'mysql_charset': 'utf8'},
580 )
580 )
581 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
581 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
582 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
582 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
583 username = Column("username", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
583 username = Column("username", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
584 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True)
584 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True)
585 repository_name = Column("repository_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
585 repository_name = Column("repository_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
586 user_ip = Column("user_ip", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
586 user_ip = Column("user_ip", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
587 action = Column("action", UnicodeText(1200000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
587 action = Column("action", UnicodeText(1200000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
588 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
588 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
589
589
590 @property
590 @property
591 def action_as_day(self):
591 def action_as_day(self):
592 return datetime.date(*self.action_date.timetuple()[:3])
592 return datetime.date(*self.action_date.timetuple()[:3])
593
593
594 user = relationship('User')
594 user = relationship('User')
595 repository = relationship('Repository', cascade='')
595 repository = relationship('Repository', cascade='')
596
596
597
597
598 class UsersGroup(Base, BaseModel):
598 class UsersGroup(Base, BaseModel):
599 __tablename__ = 'users_groups'
599 __tablename__ = 'users_groups'
600 __table_args__ = (
600 __table_args__ = (
601 {'extend_existing': True, 'mysql_engine': 'InnoDB',
601 {'extend_existing': True, 'mysql_engine': 'InnoDB',
602 'mysql_charset': 'utf8'},
602 'mysql_charset': 'utf8'},
603 )
603 )
604
604
605 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
605 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
606 users_group_name = Column("users_group_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
606 users_group_name = Column("users_group_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
607 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
607 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
608 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
608 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
609
609
610 members = relationship('UsersGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
610 members = relationship('UsersGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
611 users_group_to_perm = relationship('UsersGroupToPerm', cascade='all')
611 users_group_to_perm = relationship('UsersGroupToPerm', cascade='all')
612 users_group_repo_to_perm = relationship('UsersGroupRepoToPerm', cascade='all')
612 users_group_repo_to_perm = relationship('UsersGroupRepoToPerm', cascade='all')
613
613
614 def __unicode__(self):
614 def __unicode__(self):
615 return u'<userGroup(%s)>' % (self.users_group_name)
615 return u'<userGroup(%s)>' % (self.users_group_name)
616
616
617 @classmethod
617 @classmethod
618 def get_by_group_name(cls, group_name, cache=False,
618 def get_by_group_name(cls, group_name, cache=False,
619 case_insensitive=False):
619 case_insensitive=False):
620 if case_insensitive:
620 if case_insensitive:
621 q = cls.query().filter(cls.users_group_name.ilike(group_name))
621 q = cls.query().filter(cls.users_group_name.ilike(group_name))
622 else:
622 else:
623 q = cls.query().filter(cls.users_group_name == group_name)
623 q = cls.query().filter(cls.users_group_name == group_name)
624 if cache:
624 if cache:
625 q = q.options(FromCache(
625 q = q.options(FromCache(
626 "sql_cache_short",
626 "sql_cache_short",
627 "get_user_%s" % _hash_key(group_name)
627 "get_user_%s" % _hash_key(group_name)
628 )
628 )
629 )
629 )
630 return q.scalar()
630 return q.scalar()
631
631
632 @classmethod
632 @classmethod
633 def get(cls, users_group_id, cache=False):
633 def get(cls, users_group_id, cache=False):
634 users_group = cls.query()
634 users_group = cls.query()
635 if cache:
635 if cache:
636 users_group = users_group.options(FromCache("sql_cache_short",
636 users_group = users_group.options(FromCache("sql_cache_short",
637 "get_users_group_%s" % users_group_id))
637 "get_users_group_%s" % users_group_id))
638 return users_group.get(users_group_id)
638 return users_group.get(users_group_id)
639
639
640 def get_api_data(self):
640 def get_api_data(self):
641 users_group = self
641 users_group = self
642
642
643 data = dict(
643 data = dict(
644 users_group_id=users_group.users_group_id,
644 users_group_id=users_group.users_group_id,
645 group_name=users_group.users_group_name,
645 group_name=users_group.users_group_name,
646 active=users_group.users_group_active,
646 active=users_group.users_group_active,
647 )
647 )
648
648
649 return data
649 return data
650
650
651
651
652 class UsersGroupMember(Base, BaseModel):
652 class UsersGroupMember(Base, BaseModel):
653 __tablename__ = 'users_groups_members'
653 __tablename__ = 'users_groups_members'
654 __table_args__ = (
654 __table_args__ = (
655 {'extend_existing': True, 'mysql_engine': 'InnoDB',
655 {'extend_existing': True, 'mysql_engine': 'InnoDB',
656 'mysql_charset': 'utf8'},
656 'mysql_charset': 'utf8'},
657 )
657 )
658
658
659 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
659 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
660 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
660 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
661 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
661 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
662
662
663 user = relationship('User', lazy='joined')
663 user = relationship('User', lazy='joined')
664 users_group = relationship('UsersGroup')
664 users_group = relationship('UsersGroup')
665
665
666 def __init__(self, gr_id='', u_id=''):
666 def __init__(self, gr_id='', u_id=''):
667 self.users_group_id = gr_id
667 self.users_group_id = gr_id
668 self.user_id = u_id
668 self.user_id = u_id
669
669
670
670
671 class Repository(Base, BaseModel):
671 class Repository(Base, BaseModel):
672 __tablename__ = 'repositories'
672 __tablename__ = 'repositories'
673 __table_args__ = (
673 __table_args__ = (
674 UniqueConstraint('repo_name'),
674 UniqueConstraint('repo_name'),
675 Index('r_repo_name_idx', 'repo_name'),
675 Index('r_repo_name_idx', 'repo_name'),
676 {'extend_existing': True, 'mysql_engine': 'InnoDB',
676 {'extend_existing': True, 'mysql_engine': 'InnoDB',
677 'mysql_charset': 'utf8'},
677 'mysql_charset': 'utf8'},
678 )
678 )
679
679
680 repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
680 repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
681 repo_name = Column("repo_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
681 repo_name = Column("repo_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
682 clone_uri = Column("clone_uri", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
682 clone_uri = Column("clone_uri", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
683 repo_type = Column("repo_type", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default=None)
683 repo_type = Column("repo_type", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default=None)
684 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
684 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
685 private = Column("private", Boolean(), nullable=True, unique=None, default=None)
685 private = Column("private", Boolean(), nullable=True, unique=None, default=None)
686 enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True)
686 enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True)
687 enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True)
687 enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True)
688 description = Column("description", String(10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
688 description = Column("description", String(10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
689 created_on = Column('created_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
689 created_on = Column('created_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
690 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
690 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
691 landing_rev = Column("landing_revision", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default=None)
691 landing_rev = Column("landing_revision", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default=None)
692 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
692 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
693 _locked = Column("locked", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
693 _locked = Column("locked", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
694 _changeset_cache = Column("changeset_cache", LargeBinary(), nullable=True) #JSON data
694 _changeset_cache = Column("changeset_cache", LargeBinary(), nullable=True) #JSON data
695
695
696 fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None)
696 fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None)
697 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None)
697 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None)
698
698
699 user = relationship('User')
699 user = relationship('User')
700 fork = relationship('Repository', remote_side=repo_id)
700 fork = relationship('Repository', remote_side=repo_id)
701 group = relationship('RepoGroup')
701 group = relationship('RepoGroup')
702 repo_to_perm = relationship('UserRepoToPerm', cascade='all', order_by='UserRepoToPerm.repo_to_perm_id')
702 repo_to_perm = relationship('UserRepoToPerm', cascade='all', order_by='UserRepoToPerm.repo_to_perm_id')
703 users_group_to_perm = relationship('UsersGroupRepoToPerm', cascade='all')
703 users_group_to_perm = relationship('UsersGroupRepoToPerm', cascade='all')
704 stats = relationship('Statistics', cascade='all', uselist=False)
704 stats = relationship('Statistics', cascade='all', uselist=False)
705
705
706 followers = relationship('UserFollowing',
706 followers = relationship('UserFollowing',
707 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
707 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
708 cascade='all')
708 cascade='all')
709
709
710 logs = relationship('UserLog')
710 logs = relationship('UserLog')
711 comments = relationship('ChangesetComment', cascade="all, delete, delete-orphan")
711 comments = relationship('ChangesetComment', cascade="all, delete, delete-orphan")
712
712
713 pull_requests_org = relationship('PullRequest',
713 pull_requests_org = relationship('PullRequest',
714 primaryjoin='PullRequest.org_repo_id==Repository.repo_id',
714 primaryjoin='PullRequest.org_repo_id==Repository.repo_id',
715 cascade="all, delete, delete-orphan")
715 cascade="all, delete, delete-orphan")
716
716
717 pull_requests_other = relationship('PullRequest',
717 pull_requests_other = relationship('PullRequest',
718 primaryjoin='PullRequest.other_repo_id==Repository.repo_id',
718 primaryjoin='PullRequest.other_repo_id==Repository.repo_id',
719 cascade="all, delete, delete-orphan")
719 cascade="all, delete, delete-orphan")
720
720
721 def __unicode__(self):
721 def __unicode__(self):
722 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
722 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
723 self.repo_name)
723 self.repo_name)
724
724
725 @hybrid_property
725 @hybrid_property
726 def locked(self):
726 def locked(self):
727 # always should return [user_id, timelocked]
727 # always should return [user_id, timelocked]
728 if self._locked:
728 if self._locked:
729 _lock_info = self._locked.split(':')
729 _lock_info = self._locked.split(':')
730 return int(_lock_info[0]), _lock_info[1]
730 return int(_lock_info[0]), _lock_info[1]
731 return [None, None]
731 return [None, None]
732
732
733 @locked.setter
733 @locked.setter
734 def locked(self, val):
734 def locked(self, val):
735 if val and isinstance(val, (list, tuple)):
735 if val and isinstance(val, (list, tuple)):
736 self._locked = ':'.join(map(str, val))
736 self._locked = ':'.join(map(str, val))
737 else:
737 else:
738 self._locked = None
738 self._locked = None
739
739
740 @hybrid_property
740 @hybrid_property
741 def changeset_cache(self):
741 def changeset_cache(self):
742 from rhodecode.lib.vcs.backends.base import EmptyChangeset
742 from rhodecode.lib.vcs.backends.base import EmptyChangeset
743 dummy = EmptyChangeset().__json__()
743 dummy = EmptyChangeset().__json__()
744 if not self._changeset_cache:
744 if not self._changeset_cache:
745 return dummy
745 return dummy
746 try:
746 try:
747 return json.loads(self._changeset_cache)
747 return json.loads(self._changeset_cache)
748 except TypeError:
748 except TypeError:
749 return dummy
749 return dummy
750
750
751 @changeset_cache.setter
751 @changeset_cache.setter
752 def changeset_cache(self, val):
752 def changeset_cache(self, val):
753 try:
753 try:
754 self._changeset_cache = json.dumps(val)
754 self._changeset_cache = json.dumps(val)
755 except:
755 except:
756 log.error(traceback.format_exc())
756 log.error(traceback.format_exc())
757
757
758 @classmethod
758 @classmethod
759 def url_sep(cls):
759 def url_sep(cls):
760 return URL_SEP
760 return URL_SEP
761
761
762 @classmethod
762 @classmethod
763 def normalize_repo_name(cls, repo_name):
763 def normalize_repo_name(cls, repo_name):
764 """
764 """
765 Normalizes os specific repo_name to the format internally stored inside
765 Normalizes os specific repo_name to the format internally stored inside
766 dabatabase using URL_SEP
766 dabatabase using URL_SEP
767
767
768 :param cls:
768 :param cls:
769 :param repo_name:
769 :param repo_name:
770 """
770 """
771 return cls.url_sep().join(repo_name.split(os.sep))
771 return cls.url_sep().join(repo_name.split(os.sep))
772
772
773 @classmethod
773 @classmethod
774 def get_by_repo_name(cls, repo_name):
774 def get_by_repo_name(cls, repo_name):
775 q = Session().query(cls).filter(cls.repo_name == repo_name)
775 q = Session().query(cls).filter(cls.repo_name == repo_name)
776 q = q.options(joinedload(Repository.fork))\
776 q = q.options(joinedload(Repository.fork))\
777 .options(joinedload(Repository.user))\
777 .options(joinedload(Repository.user))\
778 .options(joinedload(Repository.group))
778 .options(joinedload(Repository.group))
779 return q.scalar()
779 return q.scalar()
780
780
781 @classmethod
781 @classmethod
782 def get_by_full_path(cls, repo_full_path):
782 def get_by_full_path(cls, repo_full_path):
783 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
783 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
784 repo_name = cls.normalize_repo_name(repo_name)
784 repo_name = cls.normalize_repo_name(repo_name)
785 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
785 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
786
786
787 @classmethod
787 @classmethod
788 def get_repo_forks(cls, repo_id):
788 def get_repo_forks(cls, repo_id):
789 return cls.query().filter(Repository.fork_id == repo_id)
789 return cls.query().filter(Repository.fork_id == repo_id)
790
790
791 @classmethod
791 @classmethod
792 def base_path(cls):
792 def base_path(cls):
793 """
793 """
794 Returns base path when all repos are stored
794 Returns base path when all repos are stored
795
795
796 :param cls:
796 :param cls:
797 """
797 """
798 q = Session().query(RhodeCodeUi)\
798 q = Session().query(RhodeCodeUi)\
799 .filter(RhodeCodeUi.ui_key == cls.url_sep())
799 .filter(RhodeCodeUi.ui_key == cls.url_sep())
800 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
800 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
801 return q.one().ui_value
801 return q.one().ui_value
802
802
803 @property
803 @property
804 def forks(self):
804 def forks(self):
805 """
805 """
806 Return forks of this repo
806 Return forks of this repo
807 """
807 """
808 return Repository.get_repo_forks(self.repo_id)
808 return Repository.get_repo_forks(self.repo_id)
809
809
810 @property
810 @property
811 def parent(self):
811 def parent(self):
812 """
812 """
813 Returns fork parent
813 Returns fork parent
814 """
814 """
815 return self.fork
815 return self.fork
816
816
817 @property
817 @property
818 def just_name(self):
818 def just_name(self):
819 return self.repo_name.split(Repository.url_sep())[-1]
819 return self.repo_name.split(Repository.url_sep())[-1]
820
820
821 @property
821 @property
822 def groups_with_parents(self):
822 def groups_with_parents(self):
823 groups = []
823 groups = []
824 if self.group is None:
824 if self.group is None:
825 return groups
825 return groups
826
826
827 cur_gr = self.group
827 cur_gr = self.group
828 groups.insert(0, cur_gr)
828 groups.insert(0, cur_gr)
829 while 1:
829 while 1:
830 gr = getattr(cur_gr, 'parent_group', None)
830 gr = getattr(cur_gr, 'parent_group', None)
831 cur_gr = cur_gr.parent_group
831 cur_gr = cur_gr.parent_group
832 if gr is None:
832 if gr is None:
833 break
833 break
834 groups.insert(0, gr)
834 groups.insert(0, gr)
835
835
836 return groups
836 return groups
837
837
838 @property
838 @property
839 def groups_and_repo(self):
839 def groups_and_repo(self):
840 return self.groups_with_parents, self.just_name
840 return self.groups_with_parents, self.just_name
841
841
842 @LazyProperty
842 @LazyProperty
843 def repo_path(self):
843 def repo_path(self):
844 """
844 """
845 Returns base full path for that repository means where it actually
845 Returns base full path for that repository means where it actually
846 exists on a filesystem
846 exists on a filesystem
847 """
847 """
848 q = Session().query(RhodeCodeUi).filter(RhodeCodeUi.ui_key ==
848 q = Session().query(RhodeCodeUi).filter(RhodeCodeUi.ui_key ==
849 Repository.url_sep())
849 Repository.url_sep())
850 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
850 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
851 return q.one().ui_value
851 return q.one().ui_value
852
852
853 @property
853 @property
854 def repo_full_path(self):
854 def repo_full_path(self):
855 p = [self.repo_path]
855 p = [self.repo_path]
856 # we need to split the name by / since this is how we store the
856 # we need to split the name by / since this is how we store the
857 # names in the database, but that eventually needs to be converted
857 # names in the database, but that eventually needs to be converted
858 # into a valid system path
858 # into a valid system path
859 p += self.repo_name.split(Repository.url_sep())
859 p += self.repo_name.split(Repository.url_sep())
860 return os.path.join(*p)
860 return os.path.join(*p)
861
861
862 @property
862 @property
863 def cache_keys(self):
863 def cache_keys(self):
864 """
864 """
865 Returns associated cache keys for that repo
865 Returns associated cache keys for that repo
866 """
866 """
867 return CacheInvalidation.query()\
867 return CacheInvalidation.query()\
868 .filter(CacheInvalidation.cache_args == self.repo_name)\
868 .filter(CacheInvalidation.cache_args == self.repo_name)\
869 .order_by(CacheInvalidation.cache_key)\
869 .order_by(CacheInvalidation.cache_key)\
870 .all()
870 .all()
871
871
872 def get_new_name(self, repo_name):
872 def get_new_name(self, repo_name):
873 """
873 """
874 returns new full repository name based on assigned group and new new
874 returns new full repository name based on assigned group and new new
875
875
876 :param group_name:
876 :param group_name:
877 """
877 """
878 path_prefix = self.group.full_path_splitted if self.group else []
878 path_prefix = self.group.full_path_splitted if self.group else []
879 return Repository.url_sep().join(path_prefix + [repo_name])
879 return Repository.url_sep().join(path_prefix + [repo_name])
880
880
881 @property
881 @property
882 def _ui(self):
882 def _ui(self):
883 """
883 """
884 Creates an db based ui object for this repository
884 Creates an db based ui object for this repository
885 """
885 """
886 from rhodecode.lib.utils import make_ui
886 from rhodecode.lib.utils import make_ui
887 return make_ui('db', clear_session=False)
887 return make_ui('db', clear_session=False)
888
888
889 @classmethod
889 @classmethod
890 def inject_ui(cls, repo, extras={}):
890 def inject_ui(cls, repo, extras={}):
891 from rhodecode.lib.vcs.backends.hg import MercurialRepository
891 from rhodecode.lib.vcs.backends.hg import MercurialRepository
892 from rhodecode.lib.vcs.backends.git import GitRepository
892 from rhodecode.lib.vcs.backends.git import GitRepository
893 required = (MercurialRepository, GitRepository)
893 required = (MercurialRepository, GitRepository)
894 if not isinstance(repo, required):
894 if not isinstance(repo, required):
895 raise Exception('repo must be instance of %s' % required)
895 raise Exception('repo must be instance of %s' % required)
896
896
897 # inject ui extra param to log this action via push logger
897 # inject ui extra param to log this action via push logger
898 for k, v in extras.items():
898 for k, v in extras.items():
899 repo._repo.ui.setconfig('rhodecode_extras', k, v)
899 repo._repo.ui.setconfig('rhodecode_extras', k, v)
900
900
901 @classmethod
901 @classmethod
902 def is_valid(cls, repo_name):
902 def is_valid(cls, repo_name):
903 """
903 """
904 returns True if given repo name is a valid filesystem repository
904 returns True if given repo name is a valid filesystem repository
905
905
906 :param cls:
906 :param cls:
907 :param repo_name:
907 :param repo_name:
908 """
908 """
909 from rhodecode.lib.utils import is_valid_repo
909 from rhodecode.lib.utils import is_valid_repo
910
910
911 return is_valid_repo(repo_name, cls.base_path())
911 return is_valid_repo(repo_name, cls.base_path())
912
912
913 def get_api_data(self):
913 def get_api_data(self):
914 """
914 """
915 Common function for generating repo api data
915 Common function for generating repo api data
916
916
917 """
917 """
918 repo = self
918 repo = self
919 data = dict(
919 data = dict(
920 repo_id=repo.repo_id,
920 repo_id=repo.repo_id,
921 repo_name=repo.repo_name,
921 repo_name=repo.repo_name,
922 repo_type=repo.repo_type,
922 repo_type=repo.repo_type,
923 clone_uri=repo.clone_uri,
923 clone_uri=repo.clone_uri,
924 private=repo.private,
924 private=repo.private,
925 created_on=repo.created_on,
925 created_on=repo.created_on,
926 description=repo.description,
926 description=repo.description,
927 landing_rev=repo.landing_rev,
927 landing_rev=repo.landing_rev,
928 owner=repo.user.username,
928 owner=repo.user.username,
929 fork_of=repo.fork.repo_name if repo.fork else None,
929 fork_of=repo.fork.repo_name if repo.fork else None,
930 enable_statistics=repo.enable_statistics,
930 enable_statistics=repo.enable_statistics,
931 enable_locking=repo.enable_locking,
931 enable_locking=repo.enable_locking,
932 enable_downloads=repo.enable_downloads,
932 enable_downloads=repo.enable_downloads,
933 last_changeset=repo.changeset_cache
933 last_changeset=repo.changeset_cache
934 )
934 )
935
935
936 return data
936 return data
937
937
938 @classmethod
938 @classmethod
939 def lock(cls, repo, user_id):
939 def lock(cls, repo, user_id):
940 repo.locked = [user_id, time.time()]
940 repo.locked = [user_id, time.time()]
941 Session().add(repo)
941 Session().add(repo)
942 Session().commit()
942 Session().commit()
943
943
944 @classmethod
944 @classmethod
945 def unlock(cls, repo):
945 def unlock(cls, repo):
946 repo.locked = None
946 repo.locked = None
947 Session().add(repo)
947 Session().add(repo)
948 Session().commit()
948 Session().commit()
949
949
950 @property
950 @property
951 def last_db_change(self):
951 def last_db_change(self):
952 return self.updated_on
952 return self.updated_on
953
953
954 def clone_url(self, **override):
954 def clone_url(self, **override):
955 from pylons import url
955 from pylons import url
956 from urlparse import urlparse
956 from urlparse import urlparse
957 import urllib
957 import urllib
958 parsed_url = urlparse(url('home', qualified=True))
958 parsed_url = urlparse(url('home', qualified=True))
959 default_clone_uri = '%(scheme)s://%(user)s%(pass)s%(netloc)s%(prefix)s%(path)s'
959 default_clone_uri = '%(scheme)s://%(user)s%(pass)s%(netloc)s%(prefix)s%(path)s'
960 decoded_path = safe_unicode(urllib.unquote(parsed_url.path))
960 decoded_path = safe_unicode(urllib.unquote(parsed_url.path))
961 args = {
961 args = {
962 'user': '',
962 'user': '',
963 'pass': '',
963 'pass': '',
964 'scheme': parsed_url.scheme,
964 'scheme': parsed_url.scheme,
965 'netloc': parsed_url.netloc,
965 'netloc': parsed_url.netloc,
966 'prefix': decoded_path,
966 'prefix': decoded_path,
967 'path': self.repo_name
967 'path': self.repo_name
968 }
968 }
969
969
970 args.update(override)
970 args.update(override)
971 return default_clone_uri % args
971 return default_clone_uri % args
972
972
973 #==========================================================================
973 #==========================================================================
974 # SCM PROPERTIES
974 # SCM PROPERTIES
975 #==========================================================================
975 #==========================================================================
976
976
977 def get_changeset(self, rev=None):
977 def get_changeset(self, rev=None):
978 return get_changeset_safe(self.scm_instance, rev)
978 return get_changeset_safe(self.scm_instance, rev)
979
979
980 def get_landing_changeset(self):
980 def get_landing_changeset(self):
981 """
981 """
982 Returns landing changeset, or if that doesn't exist returns the tip
982 Returns landing changeset, or if that doesn't exist returns the tip
983 """
983 """
984 cs = self.get_changeset(self.landing_rev) or self.get_changeset()
984 cs = self.get_changeset(self.landing_rev) or self.get_changeset()
985 return cs
985 return cs
986
986
987 def update_changeset_cache(self, cs_cache=None):
987 def update_changeset_cache(self, cs_cache=None):
988 """
988 """
989 Update cache of last changeset for repository, keys should be::
989 Update cache of last changeset for repository, keys should be::
990
990
991 short_id
991 short_id
992 raw_id
992 raw_id
993 revision
993 revision
994 message
994 message
995 date
995 date
996 author
996 author
997
997
998 :param cs_cache:
998 :param cs_cache:
999 """
999 """
1000 from rhodecode.lib.vcs.backends.base import BaseChangeset
1000 from rhodecode.lib.vcs.backends.base import BaseChangeset
1001 if cs_cache is None:
1001 if cs_cache is None:
1002 cs_cache = self.get_changeset()
1002 cs_cache = self.get_changeset()
1003 if isinstance(cs_cache, BaseChangeset):
1003 if isinstance(cs_cache, BaseChangeset):
1004 cs_cache = cs_cache.__json__()
1004 cs_cache = cs_cache.__json__()
1005
1005
1006 if cs_cache != self.changeset_cache:
1006 if cs_cache != self.changeset_cache:
1007 last_change = cs_cache.get('date') or self.last_change
1007 last_change = cs_cache.get('date') or self.last_change
1008 log.debug('updated repo %s with new cs cache %s' % (self, cs_cache))
1008 log.debug('updated repo %s with new cs cache %s' % (self, cs_cache))
1009 self.updated_on = last_change
1009 self.updated_on = last_change
1010 self.changeset_cache = cs_cache
1010 self.changeset_cache = cs_cache
1011 Session().add(self)
1011 Session().add(self)
1012 Session().commit()
1012 Session().commit()
1013
1013
1014 @property
1014 @property
1015 def tip(self):
1015 def tip(self):
1016 return self.get_changeset('tip')
1016 return self.get_changeset('tip')
1017
1017
1018 @property
1018 @property
1019 def author(self):
1019 def author(self):
1020 return self.tip.author
1020 return self.tip.author
1021
1021
1022 @property
1022 @property
1023 def last_change(self):
1023 def last_change(self):
1024 return self.scm_instance.last_change
1024 return self.scm_instance.last_change
1025
1025
1026 def get_comments(self, revisions=None):
1026 def get_comments(self, revisions=None):
1027 """
1027 """
1028 Returns comments for this repository grouped by revisions
1028 Returns comments for this repository grouped by revisions
1029
1029
1030 :param revisions: filter query by revisions only
1030 :param revisions: filter query by revisions only
1031 """
1031 """
1032 cmts = ChangesetComment.query()\
1032 cmts = ChangesetComment.query()\
1033 .filter(ChangesetComment.repo == self)
1033 .filter(ChangesetComment.repo == self)
1034 if revisions:
1034 if revisions:
1035 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
1035 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
1036 grouped = defaultdict(list)
1036 grouped = defaultdict(list)
1037 for cmt in cmts.all():
1037 for cmt in cmts.all():
1038 grouped[cmt.revision].append(cmt)
1038 grouped[cmt.revision].append(cmt)
1039 return grouped
1039 return grouped
1040
1040
1041 def statuses(self, revisions=None):
1041 def statuses(self, revisions=None):
1042 """
1042 """
1043 Returns statuses for this repository
1043 Returns statuses for this repository
1044
1044
1045 :param revisions: list of revisions to get statuses for
1045 :param revisions: list of revisions to get statuses for
1046 :type revisions: list
1046 :type revisions: list
1047 """
1047 """
1048
1048
1049 statuses = ChangesetStatus.query()\
1049 statuses = ChangesetStatus.query()\
1050 .filter(ChangesetStatus.repo == self)\
1050 .filter(ChangesetStatus.repo == self)\
1051 .filter(ChangesetStatus.version == 0)
1051 .filter(ChangesetStatus.version == 0)
1052 if revisions:
1052 if revisions:
1053 statuses = statuses.filter(ChangesetStatus.revision.in_(revisions))
1053 statuses = statuses.filter(ChangesetStatus.revision.in_(revisions))
1054 grouped = {}
1054 grouped = {}
1055
1055
1056 #maybe we have open new pullrequest without a status ?
1056 #maybe we have open new pullrequest without a status ?
1057 stat = ChangesetStatus.STATUS_UNDER_REVIEW
1057 stat = ChangesetStatus.STATUS_UNDER_REVIEW
1058 status_lbl = ChangesetStatus.get_status_lbl(stat)
1058 status_lbl = ChangesetStatus.get_status_lbl(stat)
1059 for pr in PullRequest.query().filter(PullRequest.org_repo == self).all():
1059 for pr in PullRequest.query().filter(PullRequest.org_repo == self).all():
1060 for rev in pr.revisions:
1060 for rev in pr.revisions:
1061 pr_id = pr.pull_request_id
1061 pr_id = pr.pull_request_id
1062 pr_repo = pr.other_repo.repo_name
1062 pr_repo = pr.other_repo.repo_name
1063 grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
1063 grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
1064
1064
1065 for stat in statuses.all():
1065 for stat in statuses.all():
1066 pr_id = pr_repo = None
1066 pr_id = pr_repo = None
1067 if stat.pull_request:
1067 if stat.pull_request:
1068 pr_id = stat.pull_request.pull_request_id
1068 pr_id = stat.pull_request.pull_request_id
1069 pr_repo = stat.pull_request.other_repo.repo_name
1069 pr_repo = stat.pull_request.other_repo.repo_name
1070 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
1070 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
1071 pr_id, pr_repo]
1071 pr_id, pr_repo]
1072 return grouped
1072 return grouped
1073
1073
1074 #==========================================================================
1074 #==========================================================================
1075 # SCM CACHE INSTANCE
1075 # SCM CACHE INSTANCE
1076 #==========================================================================
1076 #==========================================================================
1077
1077
1078 @property
1078 @property
1079 def invalidate(self):
1079 def invalidate(self):
1080 return CacheInvalidation.invalidate(self.repo_name)
1080 return CacheInvalidation.invalidate(self.repo_name)
1081
1081
1082 def set_invalidate(self):
1082 def set_invalidate(self):
1083 """
1083 """
1084 set a cache for invalidation for this instance
1084 set a cache for invalidation for this instance
1085 """
1085 """
1086 CacheInvalidation.set_invalidate(repo_name=self.repo_name)
1086 CacheInvalidation.set_invalidate(repo_name=self.repo_name)
1087
1087
1088 @LazyProperty
1088 @LazyProperty
1089 def scm_instance(self):
1089 def scm_instance(self):
1090 import rhodecode
1090 import rhodecode
1091 full_cache = str2bool(rhodecode.CONFIG.get('vcs_full_cache'))
1091 full_cache = str2bool(rhodecode.CONFIG.get('vcs_full_cache'))
1092 if full_cache:
1092 if full_cache:
1093 return self.scm_instance_cached()
1093 return self.scm_instance_cached()
1094 return self.__get_instance()
1094 return self.__get_instance()
1095
1095
1096 def scm_instance_cached(self, cache_map=None):
1096 def scm_instance_cached(self, cache_map=None):
1097 @cache_region('long_term')
1097 @cache_region('long_term')
1098 def _c(repo_name):
1098 def _c(repo_name):
1099 return self.__get_instance()
1099 return self.__get_instance()
1100 rn = self.repo_name
1100 rn = self.repo_name
1101 log.debug('Getting cached instance of repo')
1101 log.debug('Getting cached instance of repo')
1102
1102
1103 if cache_map:
1103 if cache_map:
1104 # get using prefilled cache_map
1104 # get using prefilled cache_map
1105 invalidate_repo = cache_map[self.repo_name]
1105 invalidate_repo = cache_map[self.repo_name]
1106 if invalidate_repo:
1106 if invalidate_repo:
1107 invalidate_repo = (None if invalidate_repo.cache_active
1107 invalidate_repo = (None if invalidate_repo.cache_active
1108 else invalidate_repo)
1108 else invalidate_repo)
1109 else:
1109 else:
1110 # get from invalidate
1110 # get from invalidate
1111 invalidate_repo = self.invalidate
1111 invalidate_repo = self.invalidate
1112
1112
1113 if invalidate_repo is not None:
1113 if invalidate_repo is not None:
1114 region_invalidate(_c, None, rn)
1114 region_invalidate(_c, None, rn)
1115 # update our cache
1115 # update our cache
1116 CacheInvalidation.set_valid(invalidate_repo.cache_key)
1116 CacheInvalidation.set_valid(invalidate_repo.cache_key)
1117 return _c(rn)
1117 return _c(rn)
1118
1118
1119 def __get_instance(self):
1119 def __get_instance(self):
1120 repo_full_path = self.repo_full_path
1120 repo_full_path = self.repo_full_path
1121 try:
1121 try:
1122 alias = get_scm(repo_full_path)[0]
1122 alias = get_scm(repo_full_path)[0]
1123 log.debug('Creating instance of %s repository' % alias)
1123 log.debug('Creating instance of %s repository' % alias)
1124 backend = get_backend(alias)
1124 backend = get_backend(alias)
1125 except VCSError:
1125 except VCSError:
1126 log.error(traceback.format_exc())
1126 log.error(traceback.format_exc())
1127 log.error('Perhaps this repository is in db and not in '
1127 log.error('Perhaps this repository is in db and not in '
1128 'filesystem run rescan repositories with '
1128 'filesystem run rescan repositories with '
1129 '"destroy old data " option from admin panel')
1129 '"destroy old data " option from admin panel')
1130 return
1130 return
1131
1131
1132 if alias == 'hg':
1132 if alias == 'hg':
1133
1133
1134 repo = backend(safe_str(repo_full_path), create=False,
1134 repo = backend(safe_str(repo_full_path), create=False,
1135 baseui=self._ui)
1135 baseui=self._ui)
1136 # skip hidden web repository
1136 # skip hidden web repository
1137 if repo._get_hidden():
1137 if repo._get_hidden():
1138 return
1138 return
1139 else:
1139 else:
1140 repo = backend(repo_full_path, create=False)
1140 repo = backend(repo_full_path, create=False)
1141
1141
1142 return repo
1142 return repo
1143
1143
1144
1144
1145 class RepoGroup(Base, BaseModel):
1145 class RepoGroup(Base, BaseModel):
1146 __tablename__ = 'groups'
1146 __tablename__ = 'groups'
1147 __table_args__ = (
1147 __table_args__ = (
1148 UniqueConstraint('group_name', 'group_parent_id'),
1148 UniqueConstraint('group_name', 'group_parent_id'),
1149 CheckConstraint('group_id != group_parent_id'),
1149 CheckConstraint('group_id != group_parent_id'),
1150 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1150 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1151 'mysql_charset': 'utf8'},
1151 'mysql_charset': 'utf8'},
1152 )
1152 )
1153 __mapper_args__ = {'order_by': 'group_name'}
1153 __mapper_args__ = {'order_by': 'group_name'}
1154
1154
1155 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1155 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1156 group_name = Column("group_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
1156 group_name = Column("group_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
1157 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
1157 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
1158 group_description = Column("group_description", String(10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1158 group_description = Column("group_description", String(10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1159 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
1159 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
1160
1160
1161 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
1161 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
1162 users_group_to_perm = relationship('UsersGroupRepoGroupToPerm', cascade='all')
1162 users_group_to_perm = relationship('UsersGroupRepoGroupToPerm', cascade='all')
1163
1163
1164 parent_group = relationship('RepoGroup', remote_side=group_id)
1164 parent_group = relationship('RepoGroup', remote_side=group_id)
1165
1165
1166 def __init__(self, group_name='', parent_group=None):
1166 def __init__(self, group_name='', parent_group=None):
1167 self.group_name = group_name
1167 self.group_name = group_name
1168 self.parent_group = parent_group
1168 self.parent_group = parent_group
1169
1169
1170 def __unicode__(self):
1170 def __unicode__(self):
1171 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.group_id,
1171 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.group_id,
1172 self.group_name)
1172 self.group_name)
1173
1173
1174 @classmethod
1174 @classmethod
1175 def groups_choices(cls, check_perms=False):
1175 def groups_choices(cls, groups=None, check_perms=False, show_empty_group=True):
1176 from webhelpers.html import literal as _literal
1176 from webhelpers.html import literal as _literal
1177 from rhodecode.model.scm import ScmModel
1177 from rhodecode.model.scm import ScmModel
1178 groups = cls.query().all()
1178 if not groups:
1179 groups = cls.query().all()
1179 if check_perms:
1180 if check_perms:
1180 #filter group user have access to, it's done
1181 #filter group user have access to, it's done
1181 #magically inside ScmModel based on current user
1182 #magically inside ScmModel based on current user
1182 groups = ScmModel().get_repos_groups(groups)
1183 groups = ScmModel().get_repos_groups(groups)
1183 repo_groups = [('', '')]
1184 repo_groups = []
1185 if show_empty_group:
1186 repo_groups = [('-1', '-- no parent --')]
1184 sep = ' &raquo; '
1187 sep = ' &raquo; '
1185 _name = lambda k: _literal(sep.join(k))
1188 _name = lambda k: _literal(sep.join(k))
1186
1189
1187 repo_groups.extend([(x.group_id, _name(x.full_path_splitted))
1190 repo_groups.extend([(x.group_id, _name(x.full_path_splitted))
1188 for x in groups])
1191 for x in groups])
1189
1192
1190 repo_groups = sorted(repo_groups, key=lambda t: t[1].split(sep)[0])
1193 repo_groups = sorted(repo_groups, key=lambda t: t[1].split(sep)[0])
1191 return repo_groups
1194 return repo_groups
1192
1195
1193 @classmethod
1196 @classmethod
1194 def url_sep(cls):
1197 def url_sep(cls):
1195 return URL_SEP
1198 return URL_SEP
1196
1199
1197 @classmethod
1200 @classmethod
1198 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
1201 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
1199 if case_insensitive:
1202 if case_insensitive:
1200 gr = cls.query()\
1203 gr = cls.query()\
1201 .filter(cls.group_name.ilike(group_name))
1204 .filter(cls.group_name.ilike(group_name))
1202 else:
1205 else:
1203 gr = cls.query()\
1206 gr = cls.query()\
1204 .filter(cls.group_name == group_name)
1207 .filter(cls.group_name == group_name)
1205 if cache:
1208 if cache:
1206 gr = gr.options(FromCache(
1209 gr = gr.options(FromCache(
1207 "sql_cache_short",
1210 "sql_cache_short",
1208 "get_group_%s" % _hash_key(group_name)
1211 "get_group_%s" % _hash_key(group_name)
1209 )
1212 )
1210 )
1213 )
1211 return gr.scalar()
1214 return gr.scalar()
1212
1215
1213 @property
1216 @property
1214 def parents(self):
1217 def parents(self):
1215 parents_recursion_limit = 5
1218 parents_recursion_limit = 5
1216 groups = []
1219 groups = []
1217 if self.parent_group is None:
1220 if self.parent_group is None:
1218 return groups
1221 return groups
1219 cur_gr = self.parent_group
1222 cur_gr = self.parent_group
1220 groups.insert(0, cur_gr)
1223 groups.insert(0, cur_gr)
1221 cnt = 0
1224 cnt = 0
1222 while 1:
1225 while 1:
1223 cnt += 1
1226 cnt += 1
1224 gr = getattr(cur_gr, 'parent_group', None)
1227 gr = getattr(cur_gr, 'parent_group', None)
1225 cur_gr = cur_gr.parent_group
1228 cur_gr = cur_gr.parent_group
1226 if gr is None:
1229 if gr is None:
1227 break
1230 break
1228 if cnt == parents_recursion_limit:
1231 if cnt == parents_recursion_limit:
1229 # this will prevent accidental infinit loops
1232 # this will prevent accidental infinit loops
1230 log.error('group nested more than %s' %
1233 log.error('group nested more than %s' %
1231 parents_recursion_limit)
1234 parents_recursion_limit)
1232 break
1235 break
1233
1236
1234 groups.insert(0, gr)
1237 groups.insert(0, gr)
1235 return groups
1238 return groups
1236
1239
1237 @property
1240 @property
1238 def children(self):
1241 def children(self):
1239 return RepoGroup.query().filter(RepoGroup.parent_group == self)
1242 return RepoGroup.query().filter(RepoGroup.parent_group == self)
1240
1243
1241 @property
1244 @property
1242 def name(self):
1245 def name(self):
1243 return self.group_name.split(RepoGroup.url_sep())[-1]
1246 return self.group_name.split(RepoGroup.url_sep())[-1]
1244
1247
1245 @property
1248 @property
1246 def full_path(self):
1249 def full_path(self):
1247 return self.group_name
1250 return self.group_name
1248
1251
1249 @property
1252 @property
1250 def full_path_splitted(self):
1253 def full_path_splitted(self):
1251 return self.group_name.split(RepoGroup.url_sep())
1254 return self.group_name.split(RepoGroup.url_sep())
1252
1255
1253 @property
1256 @property
1254 def repositories(self):
1257 def repositories(self):
1255 return Repository.query()\
1258 return Repository.query()\
1256 .filter(Repository.group == self)\
1259 .filter(Repository.group == self)\
1257 .order_by(Repository.repo_name)
1260 .order_by(Repository.repo_name)
1258
1261
1259 @property
1262 @property
1260 def repositories_recursive_count(self):
1263 def repositories_recursive_count(self):
1261 cnt = self.repositories.count()
1264 cnt = self.repositories.count()
1262
1265
1263 def children_count(group):
1266 def children_count(group):
1264 cnt = 0
1267 cnt = 0
1265 for child in group.children:
1268 for child in group.children:
1266 cnt += child.repositories.count()
1269 cnt += child.repositories.count()
1267 cnt += children_count(child)
1270 cnt += children_count(child)
1268 return cnt
1271 return cnt
1269
1272
1270 return cnt + children_count(self)
1273 return cnt + children_count(self)
1271
1274
1272 def recursive_groups_and_repos(self):
1275 def recursive_groups_and_repos(self):
1273 """
1276 """
1274 Recursive return all groups, with repositories in those groups
1277 Recursive return all groups, with repositories in those groups
1275 """
1278 """
1276 all_ = []
1279 all_ = []
1277
1280
1278 def _get_members(root_gr):
1281 def _get_members(root_gr):
1279 for r in root_gr.repositories:
1282 for r in root_gr.repositories:
1280 all_.append(r)
1283 all_.append(r)
1281 childs = root_gr.children.all()
1284 childs = root_gr.children.all()
1282 if childs:
1285 if childs:
1283 for gr in childs:
1286 for gr in childs:
1284 all_.append(gr)
1287 all_.append(gr)
1285 _get_members(gr)
1288 _get_members(gr)
1286
1289
1287 _get_members(self)
1290 _get_members(self)
1288 return [self] + all_
1291 return [self] + all_
1289
1292
1290 def get_new_name(self, group_name):
1293 def get_new_name(self, group_name):
1291 """
1294 """
1292 returns new full group name based on parent and new name
1295 returns new full group name based on parent and new name
1293
1296
1294 :param group_name:
1297 :param group_name:
1295 """
1298 """
1296 path_prefix = (self.parent_group.full_path_splitted if
1299 path_prefix = (self.parent_group.full_path_splitted if
1297 self.parent_group else [])
1300 self.parent_group else [])
1298 return RepoGroup.url_sep().join(path_prefix + [group_name])
1301 return RepoGroup.url_sep().join(path_prefix + [group_name])
1299
1302
1300
1303
1301 class Permission(Base, BaseModel):
1304 class Permission(Base, BaseModel):
1302 __tablename__ = 'permissions'
1305 __tablename__ = 'permissions'
1303 __table_args__ = (
1306 __table_args__ = (
1304 Index('p_perm_name_idx', 'permission_name'),
1307 Index('p_perm_name_idx', 'permission_name'),
1305 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1308 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1306 'mysql_charset': 'utf8'},
1309 'mysql_charset': 'utf8'},
1307 )
1310 )
1308 PERMS = [
1311 PERMS = [
1309 ('repository.none', _('Repository no access')),
1312 ('repository.none', _('Repository no access')),
1310 ('repository.read', _('Repository read access')),
1313 ('repository.read', _('Repository read access')),
1311 ('repository.write', _('Repository write access')),
1314 ('repository.write', _('Repository write access')),
1312 ('repository.admin', _('Repository admin access')),
1315 ('repository.admin', _('Repository admin access')),
1313
1316
1314 ('group.none', _('Repositories Group no access')),
1317 ('group.none', _('Repositories Group no access')),
1315 ('group.read', _('Repositories Group read access')),
1318 ('group.read', _('Repositories Group read access')),
1316 ('group.write', _('Repositories Group write access')),
1319 ('group.write', _('Repositories Group write access')),
1317 ('group.admin', _('Repositories Group admin access')),
1320 ('group.admin', _('Repositories Group admin access')),
1318
1321
1319 ('hg.admin', _('RhodeCode Administrator')),
1322 ('hg.admin', _('RhodeCode Administrator')),
1320 ('hg.create.none', _('Repository creation disabled')),
1323 ('hg.create.none', _('Repository creation disabled')),
1321 ('hg.create.repository', _('Repository creation enabled')),
1324 ('hg.create.repository', _('Repository creation enabled')),
1322 ('hg.fork.none', _('Repository forking disabled')),
1325 ('hg.fork.none', _('Repository forking disabled')),
1323 ('hg.fork.repository', _('Repository forking enabled')),
1326 ('hg.fork.repository', _('Repository forking enabled')),
1324 ('hg.register.none', _('Register disabled')),
1327 ('hg.register.none', _('Register disabled')),
1325 ('hg.register.manual_activate', _('Register new user with RhodeCode '
1328 ('hg.register.manual_activate', _('Register new user with RhodeCode '
1326 'with manual activation')),
1329 'with manual activation')),
1327
1330
1328 ('hg.register.auto_activate', _('Register new user with RhodeCode '
1331 ('hg.register.auto_activate', _('Register new user with RhodeCode '
1329 'with auto activation')),
1332 'with auto activation')),
1330 ]
1333 ]
1331
1334
1332 # defines which permissions are more important higher the more important
1335 # defines which permissions are more important higher the more important
1333 PERM_WEIGHTS = {
1336 PERM_WEIGHTS = {
1334 'repository.none': 0,
1337 'repository.none': 0,
1335 'repository.read': 1,
1338 'repository.read': 1,
1336 'repository.write': 3,
1339 'repository.write': 3,
1337 'repository.admin': 4,
1340 'repository.admin': 4,
1338
1341
1339 'group.none': 0,
1342 'group.none': 0,
1340 'group.read': 1,
1343 'group.read': 1,
1341 'group.write': 3,
1344 'group.write': 3,
1342 'group.admin': 4,
1345 'group.admin': 4,
1343
1346
1344 'hg.fork.none': 0,
1347 'hg.fork.none': 0,
1345 'hg.fork.repository': 1,
1348 'hg.fork.repository': 1,
1346 'hg.create.none': 0,
1349 'hg.create.none': 0,
1347 'hg.create.repository':1
1350 'hg.create.repository':1
1348 }
1351 }
1349
1352
1350 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1353 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1351 permission_name = Column("permission_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1354 permission_name = Column("permission_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1352 permission_longname = Column("permission_longname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1355 permission_longname = Column("permission_longname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1353
1356
1354 def __unicode__(self):
1357 def __unicode__(self):
1355 return u"<%s('%s:%s')>" % (
1358 return u"<%s('%s:%s')>" % (
1356 self.__class__.__name__, self.permission_id, self.permission_name
1359 self.__class__.__name__, self.permission_id, self.permission_name
1357 )
1360 )
1358
1361
1359 @classmethod
1362 @classmethod
1360 def get_by_key(cls, key):
1363 def get_by_key(cls, key):
1361 return cls.query().filter(cls.permission_name == key).scalar()
1364 return cls.query().filter(cls.permission_name == key).scalar()
1362
1365
1363 @classmethod
1366 @classmethod
1364 def get_default_perms(cls, default_user_id):
1367 def get_default_perms(cls, default_user_id):
1365 q = Session().query(UserRepoToPerm, Repository, cls)\
1368 q = Session().query(UserRepoToPerm, Repository, cls)\
1366 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
1369 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
1367 .join((cls, UserRepoToPerm.permission_id == cls.permission_id))\
1370 .join((cls, UserRepoToPerm.permission_id == cls.permission_id))\
1368 .filter(UserRepoToPerm.user_id == default_user_id)
1371 .filter(UserRepoToPerm.user_id == default_user_id)
1369
1372
1370 return q.all()
1373 return q.all()
1371
1374
1372 @classmethod
1375 @classmethod
1373 def get_default_group_perms(cls, default_user_id):
1376 def get_default_group_perms(cls, default_user_id):
1374 q = Session().query(UserRepoGroupToPerm, RepoGroup, cls)\
1377 q = Session().query(UserRepoGroupToPerm, RepoGroup, cls)\
1375 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
1378 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
1376 .join((cls, UserRepoGroupToPerm.permission_id == cls.permission_id))\
1379 .join((cls, UserRepoGroupToPerm.permission_id == cls.permission_id))\
1377 .filter(UserRepoGroupToPerm.user_id == default_user_id)
1380 .filter(UserRepoGroupToPerm.user_id == default_user_id)
1378
1381
1379 return q.all()
1382 return q.all()
1380
1383
1381
1384
1382 class UserRepoToPerm(Base, BaseModel):
1385 class UserRepoToPerm(Base, BaseModel):
1383 __tablename__ = 'repo_to_perm'
1386 __tablename__ = 'repo_to_perm'
1384 __table_args__ = (
1387 __table_args__ = (
1385 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
1388 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
1386 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1389 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1387 'mysql_charset': 'utf8'}
1390 'mysql_charset': 'utf8'}
1388 )
1391 )
1389 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1392 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1390 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1393 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1391 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1394 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1392 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1395 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1393
1396
1394 user = relationship('User')
1397 user = relationship('User')
1395 repository = relationship('Repository')
1398 repository = relationship('Repository')
1396 permission = relationship('Permission')
1399 permission = relationship('Permission')
1397
1400
1398 @classmethod
1401 @classmethod
1399 def create(cls, user, repository, permission):
1402 def create(cls, user, repository, permission):
1400 n = cls()
1403 n = cls()
1401 n.user = user
1404 n.user = user
1402 n.repository = repository
1405 n.repository = repository
1403 n.permission = permission
1406 n.permission = permission
1404 Session().add(n)
1407 Session().add(n)
1405 return n
1408 return n
1406
1409
1407 def __unicode__(self):
1410 def __unicode__(self):
1408 return u'<user:%s => %s >' % (self.user, self.repository)
1411 return u'<user:%s => %s >' % (self.user, self.repository)
1409
1412
1410
1413
1411 class UserToPerm(Base, BaseModel):
1414 class UserToPerm(Base, BaseModel):
1412 __tablename__ = 'user_to_perm'
1415 __tablename__ = 'user_to_perm'
1413 __table_args__ = (
1416 __table_args__ = (
1414 UniqueConstraint('user_id', 'permission_id'),
1417 UniqueConstraint('user_id', 'permission_id'),
1415 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1418 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1416 'mysql_charset': 'utf8'}
1419 'mysql_charset': 'utf8'}
1417 )
1420 )
1418 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1421 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1419 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1422 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1420 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1423 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1421
1424
1422 user = relationship('User')
1425 user = relationship('User')
1423 permission = relationship('Permission', lazy='joined')
1426 permission = relationship('Permission', lazy='joined')
1424
1427
1425
1428
1426 class UsersGroupRepoToPerm(Base, BaseModel):
1429 class UsersGroupRepoToPerm(Base, BaseModel):
1427 __tablename__ = 'users_group_repo_to_perm'
1430 __tablename__ = 'users_group_repo_to_perm'
1428 __table_args__ = (
1431 __table_args__ = (
1429 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
1432 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
1430 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1433 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1431 'mysql_charset': 'utf8'}
1434 'mysql_charset': 'utf8'}
1432 )
1435 )
1433 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1436 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1434 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1437 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1435 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1438 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1436 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1439 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1437
1440
1438 users_group = relationship('UsersGroup')
1441 users_group = relationship('UsersGroup')
1439 permission = relationship('Permission')
1442 permission = relationship('Permission')
1440 repository = relationship('Repository')
1443 repository = relationship('Repository')
1441
1444
1442 @classmethod
1445 @classmethod
1443 def create(cls, users_group, repository, permission):
1446 def create(cls, users_group, repository, permission):
1444 n = cls()
1447 n = cls()
1445 n.users_group = users_group
1448 n.users_group = users_group
1446 n.repository = repository
1449 n.repository = repository
1447 n.permission = permission
1450 n.permission = permission
1448 Session().add(n)
1451 Session().add(n)
1449 return n
1452 return n
1450
1453
1451 def __unicode__(self):
1454 def __unicode__(self):
1452 return u'<userGroup:%s => %s >' % (self.users_group, self.repository)
1455 return u'<userGroup:%s => %s >' % (self.users_group, self.repository)
1453
1456
1454
1457
1455 class UsersGroupToPerm(Base, BaseModel):
1458 class UsersGroupToPerm(Base, BaseModel):
1456 __tablename__ = 'users_group_to_perm'
1459 __tablename__ = 'users_group_to_perm'
1457 __table_args__ = (
1460 __table_args__ = (
1458 UniqueConstraint('users_group_id', 'permission_id',),
1461 UniqueConstraint('users_group_id', 'permission_id',),
1459 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1462 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1460 'mysql_charset': 'utf8'}
1463 'mysql_charset': 'utf8'}
1461 )
1464 )
1462 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1465 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1463 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1466 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1464 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1467 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1465
1468
1466 users_group = relationship('UsersGroup')
1469 users_group = relationship('UsersGroup')
1467 permission = relationship('Permission')
1470 permission = relationship('Permission')
1468
1471
1469
1472
1470 class UserRepoGroupToPerm(Base, BaseModel):
1473 class UserRepoGroupToPerm(Base, BaseModel):
1471 __tablename__ = 'user_repo_group_to_perm'
1474 __tablename__ = 'user_repo_group_to_perm'
1472 __table_args__ = (
1475 __table_args__ = (
1473 UniqueConstraint('user_id', 'group_id', 'permission_id'),
1476 UniqueConstraint('user_id', 'group_id', 'permission_id'),
1474 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1477 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1475 'mysql_charset': 'utf8'}
1478 'mysql_charset': 'utf8'}
1476 )
1479 )
1477
1480
1478 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1481 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1479 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1482 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1480 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
1483 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
1481 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1484 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1482
1485
1483 user = relationship('User')
1486 user = relationship('User')
1484 group = relationship('RepoGroup')
1487 group = relationship('RepoGroup')
1485 permission = relationship('Permission')
1488 permission = relationship('Permission')
1486
1489
1487
1490
1488 class UsersGroupRepoGroupToPerm(Base, BaseModel):
1491 class UsersGroupRepoGroupToPerm(Base, BaseModel):
1489 __tablename__ = 'users_group_repo_group_to_perm'
1492 __tablename__ = 'users_group_repo_group_to_perm'
1490 __table_args__ = (
1493 __table_args__ = (
1491 UniqueConstraint('users_group_id', 'group_id'),
1494 UniqueConstraint('users_group_id', 'group_id'),
1492 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1495 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1493 'mysql_charset': 'utf8'}
1496 'mysql_charset': 'utf8'}
1494 )
1497 )
1495
1498
1496 users_group_repo_group_to_perm_id = Column("users_group_repo_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1499 users_group_repo_group_to_perm_id = Column("users_group_repo_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1497 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1500 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1498 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
1501 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
1499 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1502 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1500
1503
1501 users_group = relationship('UsersGroup')
1504 users_group = relationship('UsersGroup')
1502 permission = relationship('Permission')
1505 permission = relationship('Permission')
1503 group = relationship('RepoGroup')
1506 group = relationship('RepoGroup')
1504
1507
1505
1508
1506 class Statistics(Base, BaseModel):
1509 class Statistics(Base, BaseModel):
1507 __tablename__ = 'statistics'
1510 __tablename__ = 'statistics'
1508 __table_args__ = (
1511 __table_args__ = (
1509 UniqueConstraint('repository_id'),
1512 UniqueConstraint('repository_id'),
1510 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1513 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1511 'mysql_charset': 'utf8'}
1514 'mysql_charset': 'utf8'}
1512 )
1515 )
1513 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1516 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1514 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
1517 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
1515 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
1518 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
1516 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
1519 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
1517 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
1520 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
1518 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
1521 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
1519
1522
1520 repository = relationship('Repository', single_parent=True)
1523 repository = relationship('Repository', single_parent=True)
1521
1524
1522
1525
1523 class UserFollowing(Base, BaseModel):
1526 class UserFollowing(Base, BaseModel):
1524 __tablename__ = 'user_followings'
1527 __tablename__ = 'user_followings'
1525 __table_args__ = (
1528 __table_args__ = (
1526 UniqueConstraint('user_id', 'follows_repository_id'),
1529 UniqueConstraint('user_id', 'follows_repository_id'),
1527 UniqueConstraint('user_id', 'follows_user_id'),
1530 UniqueConstraint('user_id', 'follows_user_id'),
1528 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1531 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1529 'mysql_charset': 'utf8'}
1532 'mysql_charset': 'utf8'}
1530 )
1533 )
1531
1534
1532 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1535 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1533 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1536 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1534 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
1537 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
1535 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1538 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1536 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
1539 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
1537
1540
1538 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
1541 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
1539
1542
1540 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
1543 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
1541 follows_repository = relationship('Repository', order_by='Repository.repo_name')
1544 follows_repository = relationship('Repository', order_by='Repository.repo_name')
1542
1545
1543 @classmethod
1546 @classmethod
1544 def get_repo_followers(cls, repo_id):
1547 def get_repo_followers(cls, repo_id):
1545 return cls.query().filter(cls.follows_repo_id == repo_id)
1548 return cls.query().filter(cls.follows_repo_id == repo_id)
1546
1549
1547
1550
1548 class CacheInvalidation(Base, BaseModel):
1551 class CacheInvalidation(Base, BaseModel):
1549 __tablename__ = 'cache_invalidation'
1552 __tablename__ = 'cache_invalidation'
1550 __table_args__ = (
1553 __table_args__ = (
1551 UniqueConstraint('cache_key'),
1554 UniqueConstraint('cache_key'),
1552 Index('key_idx', 'cache_key'),
1555 Index('key_idx', 'cache_key'),
1553 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1556 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1554 'mysql_charset': 'utf8'},
1557 'mysql_charset': 'utf8'},
1555 )
1558 )
1556 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1559 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1557 cache_key = Column("cache_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1560 cache_key = Column("cache_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1558 cache_args = Column("cache_args", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1561 cache_args = Column("cache_args", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1559 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
1562 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
1560
1563
1561 def __init__(self, cache_key, cache_args=''):
1564 def __init__(self, cache_key, cache_args=''):
1562 self.cache_key = cache_key
1565 self.cache_key = cache_key
1563 self.cache_args = cache_args
1566 self.cache_args = cache_args
1564 self.cache_active = False
1567 self.cache_active = False
1565
1568
1566 def __unicode__(self):
1569 def __unicode__(self):
1567 return u"<%s('%s:%s')>" % (self.__class__.__name__,
1570 return u"<%s('%s:%s')>" % (self.__class__.__name__,
1568 self.cache_id, self.cache_key)
1571 self.cache_id, self.cache_key)
1569
1572
1570 @property
1573 @property
1571 def prefix(self):
1574 def prefix(self):
1572 _split = self.cache_key.split(self.cache_args, 1)
1575 _split = self.cache_key.split(self.cache_args, 1)
1573 if _split and len(_split) == 2:
1576 if _split and len(_split) == 2:
1574 return _split[0]
1577 return _split[0]
1575 return ''
1578 return ''
1576
1579
1577 @classmethod
1580 @classmethod
1578 def clear_cache(cls):
1581 def clear_cache(cls):
1579 cls.query().delete()
1582 cls.query().delete()
1580
1583
1581 @classmethod
1584 @classmethod
1582 def _get_key(cls, key):
1585 def _get_key(cls, key):
1583 """
1586 """
1584 Wrapper for generating a key, together with a prefix
1587 Wrapper for generating a key, together with a prefix
1585
1588
1586 :param key:
1589 :param key:
1587 """
1590 """
1588 import rhodecode
1591 import rhodecode
1589 prefix = ''
1592 prefix = ''
1590 org_key = key
1593 org_key = key
1591 iid = rhodecode.CONFIG.get('instance_id')
1594 iid = rhodecode.CONFIG.get('instance_id')
1592 if iid:
1595 if iid:
1593 prefix = iid
1596 prefix = iid
1594
1597
1595 return "%s%s" % (prefix, key), prefix, org_key
1598 return "%s%s" % (prefix, key), prefix, org_key
1596
1599
1597 @classmethod
1600 @classmethod
1598 def get_by_key(cls, key):
1601 def get_by_key(cls, key):
1599 return cls.query().filter(cls.cache_key == key).scalar()
1602 return cls.query().filter(cls.cache_key == key).scalar()
1600
1603
1601 @classmethod
1604 @classmethod
1602 def get_by_repo_name(cls, repo_name):
1605 def get_by_repo_name(cls, repo_name):
1603 return cls.query().filter(cls.cache_args == repo_name).all()
1606 return cls.query().filter(cls.cache_args == repo_name).all()
1604
1607
1605 @classmethod
1608 @classmethod
1606 def _get_or_create_key(cls, key, repo_name, commit=True):
1609 def _get_or_create_key(cls, key, repo_name, commit=True):
1607 inv_obj = Session().query(cls).filter(cls.cache_key == key).scalar()
1610 inv_obj = Session().query(cls).filter(cls.cache_key == key).scalar()
1608 if not inv_obj:
1611 if not inv_obj:
1609 try:
1612 try:
1610 inv_obj = CacheInvalidation(key, repo_name)
1613 inv_obj = CacheInvalidation(key, repo_name)
1611 Session().add(inv_obj)
1614 Session().add(inv_obj)
1612 if commit:
1615 if commit:
1613 Session().commit()
1616 Session().commit()
1614 except Exception:
1617 except Exception:
1615 log.error(traceback.format_exc())
1618 log.error(traceback.format_exc())
1616 Session().rollback()
1619 Session().rollback()
1617 return inv_obj
1620 return inv_obj
1618
1621
1619 @classmethod
1622 @classmethod
1620 def invalidate(cls, key):
1623 def invalidate(cls, key):
1621 """
1624 """
1622 Returns Invalidation object if this given key should be invalidated
1625 Returns Invalidation object if this given key should be invalidated
1623 None otherwise. `cache_active = False` means that this cache
1626 None otherwise. `cache_active = False` means that this cache
1624 state is not valid and needs to be invalidated
1627 state is not valid and needs to be invalidated
1625
1628
1626 :param key:
1629 :param key:
1627 """
1630 """
1628 repo_name = key
1631 repo_name = key
1629 repo_name = remove_suffix(repo_name, '_README')
1632 repo_name = remove_suffix(repo_name, '_README')
1630 repo_name = remove_suffix(repo_name, '_RSS')
1633 repo_name = remove_suffix(repo_name, '_RSS')
1631 repo_name = remove_suffix(repo_name, '_ATOM')
1634 repo_name = remove_suffix(repo_name, '_ATOM')
1632
1635
1633 # adds instance prefix
1636 # adds instance prefix
1634 key, _prefix, _org_key = cls._get_key(key)
1637 key, _prefix, _org_key = cls._get_key(key)
1635 inv = cls._get_or_create_key(key, repo_name)
1638 inv = cls._get_or_create_key(key, repo_name)
1636
1639
1637 if inv and inv.cache_active is False:
1640 if inv and inv.cache_active is False:
1638 return inv
1641 return inv
1639
1642
1640 @classmethod
1643 @classmethod
1641 def set_invalidate(cls, key=None, repo_name=None):
1644 def set_invalidate(cls, key=None, repo_name=None):
1642 """
1645 """
1643 Mark this Cache key for invalidation, either by key or whole
1646 Mark this Cache key for invalidation, either by key or whole
1644 cache sets based on repo_name
1647 cache sets based on repo_name
1645
1648
1646 :param key:
1649 :param key:
1647 """
1650 """
1648 if key:
1651 if key:
1649 key, _prefix, _org_key = cls._get_key(key)
1652 key, _prefix, _org_key = cls._get_key(key)
1650 inv_objs = Session().query(cls).filter(cls.cache_key == key).all()
1653 inv_objs = Session().query(cls).filter(cls.cache_key == key).all()
1651 elif repo_name:
1654 elif repo_name:
1652 inv_objs = Session().query(cls).filter(cls.cache_args == repo_name).all()
1655 inv_objs = Session().query(cls).filter(cls.cache_args == repo_name).all()
1653
1656
1654 log.debug('marking %s key[s] for invalidation based on key=%s,repo_name=%s'
1657 log.debug('marking %s key[s] for invalidation based on key=%s,repo_name=%s'
1655 % (len(inv_objs), key, repo_name))
1658 % (len(inv_objs), key, repo_name))
1656 try:
1659 try:
1657 for inv_obj in inv_objs:
1660 for inv_obj in inv_objs:
1658 inv_obj.cache_active = False
1661 inv_obj.cache_active = False
1659 Session().add(inv_obj)
1662 Session().add(inv_obj)
1660 Session().commit()
1663 Session().commit()
1661 except Exception:
1664 except Exception:
1662 log.error(traceback.format_exc())
1665 log.error(traceback.format_exc())
1663 Session().rollback()
1666 Session().rollback()
1664
1667
1665 @classmethod
1668 @classmethod
1666 def set_valid(cls, key):
1669 def set_valid(cls, key):
1667 """
1670 """
1668 Mark this cache key as active and currently cached
1671 Mark this cache key as active and currently cached
1669
1672
1670 :param key:
1673 :param key:
1671 """
1674 """
1672 inv_obj = cls.get_by_key(key)
1675 inv_obj = cls.get_by_key(key)
1673 inv_obj.cache_active = True
1676 inv_obj.cache_active = True
1674 Session().add(inv_obj)
1677 Session().add(inv_obj)
1675 Session().commit()
1678 Session().commit()
1676
1679
1677 @classmethod
1680 @classmethod
1678 def get_cache_map(cls):
1681 def get_cache_map(cls):
1679
1682
1680 class cachemapdict(dict):
1683 class cachemapdict(dict):
1681
1684
1682 def __init__(self, *args, **kwargs):
1685 def __init__(self, *args, **kwargs):
1683 fixkey = kwargs.get('fixkey')
1686 fixkey = kwargs.get('fixkey')
1684 if fixkey:
1687 if fixkey:
1685 del kwargs['fixkey']
1688 del kwargs['fixkey']
1686 self.fixkey = fixkey
1689 self.fixkey = fixkey
1687 super(cachemapdict, self).__init__(*args, **kwargs)
1690 super(cachemapdict, self).__init__(*args, **kwargs)
1688
1691
1689 def __getattr__(self, name):
1692 def __getattr__(self, name):
1690 key = name
1693 key = name
1691 if self.fixkey:
1694 if self.fixkey:
1692 key, _prefix, _org_key = cls._get_key(key)
1695 key, _prefix, _org_key = cls._get_key(key)
1693 if key in self.__dict__:
1696 if key in self.__dict__:
1694 return self.__dict__[key]
1697 return self.__dict__[key]
1695 else:
1698 else:
1696 return self[key]
1699 return self[key]
1697
1700
1698 def __getitem__(self, key):
1701 def __getitem__(self, key):
1699 if self.fixkey:
1702 if self.fixkey:
1700 key, _prefix, _org_key = cls._get_key(key)
1703 key, _prefix, _org_key = cls._get_key(key)
1701 try:
1704 try:
1702 return super(cachemapdict, self).__getitem__(key)
1705 return super(cachemapdict, self).__getitem__(key)
1703 except KeyError:
1706 except KeyError:
1704 return
1707 return
1705
1708
1706 cache_map = cachemapdict(fixkey=True)
1709 cache_map = cachemapdict(fixkey=True)
1707 for obj in cls.query().all():
1710 for obj in cls.query().all():
1708 cache_map[obj.cache_key] = cachemapdict(obj.get_dict())
1711 cache_map[obj.cache_key] = cachemapdict(obj.get_dict())
1709 return cache_map
1712 return cache_map
1710
1713
1711
1714
1712 class ChangesetComment(Base, BaseModel):
1715 class ChangesetComment(Base, BaseModel):
1713 __tablename__ = 'changeset_comments'
1716 __tablename__ = 'changeset_comments'
1714 __table_args__ = (
1717 __table_args__ = (
1715 Index('cc_revision_idx', 'revision'),
1718 Index('cc_revision_idx', 'revision'),
1716 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1719 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1717 'mysql_charset': 'utf8'},
1720 'mysql_charset': 'utf8'},
1718 )
1721 )
1719 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
1722 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
1720 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1723 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1721 revision = Column('revision', String(40), nullable=True)
1724 revision = Column('revision', String(40), nullable=True)
1722 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
1725 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
1723 line_no = Column('line_no', Unicode(10), nullable=True)
1726 line_no = Column('line_no', Unicode(10), nullable=True)
1724 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
1727 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
1725 f_path = Column('f_path', Unicode(1000), nullable=True)
1728 f_path = Column('f_path', Unicode(1000), nullable=True)
1726 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
1729 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
1727 text = Column('text', UnicodeText(25000), nullable=False)
1730 text = Column('text', UnicodeText(25000), nullable=False)
1728 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1731 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1729 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1732 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1730
1733
1731 author = relationship('User', lazy='joined')
1734 author = relationship('User', lazy='joined')
1732 repo = relationship('Repository')
1735 repo = relationship('Repository')
1733 status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan")
1736 status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan")
1734 pull_request = relationship('PullRequest', lazy='joined')
1737 pull_request = relationship('PullRequest', lazy='joined')
1735
1738
1736 @classmethod
1739 @classmethod
1737 def get_users(cls, revision=None, pull_request_id=None):
1740 def get_users(cls, revision=None, pull_request_id=None):
1738 """
1741 """
1739 Returns user associated with this ChangesetComment. ie those
1742 Returns user associated with this ChangesetComment. ie those
1740 who actually commented
1743 who actually commented
1741
1744
1742 :param cls:
1745 :param cls:
1743 :param revision:
1746 :param revision:
1744 """
1747 """
1745 q = Session().query(User)\
1748 q = Session().query(User)\
1746 .join(ChangesetComment.author)
1749 .join(ChangesetComment.author)
1747 if revision:
1750 if revision:
1748 q = q.filter(cls.revision == revision)
1751 q = q.filter(cls.revision == revision)
1749 elif pull_request_id:
1752 elif pull_request_id:
1750 q = q.filter(cls.pull_request_id == pull_request_id)
1753 q = q.filter(cls.pull_request_id == pull_request_id)
1751 return q.all()
1754 return q.all()
1752
1755
1753
1756
1754 class ChangesetStatus(Base, BaseModel):
1757 class ChangesetStatus(Base, BaseModel):
1755 __tablename__ = 'changeset_statuses'
1758 __tablename__ = 'changeset_statuses'
1756 __table_args__ = (
1759 __table_args__ = (
1757 Index('cs_revision_idx', 'revision'),
1760 Index('cs_revision_idx', 'revision'),
1758 Index('cs_version_idx', 'version'),
1761 Index('cs_version_idx', 'version'),
1759 UniqueConstraint('repo_id', 'revision', 'version'),
1762 UniqueConstraint('repo_id', 'revision', 'version'),
1760 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1763 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1761 'mysql_charset': 'utf8'}
1764 'mysql_charset': 'utf8'}
1762 )
1765 )
1763 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
1766 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
1764 STATUS_APPROVED = 'approved'
1767 STATUS_APPROVED = 'approved'
1765 STATUS_REJECTED = 'rejected'
1768 STATUS_REJECTED = 'rejected'
1766 STATUS_UNDER_REVIEW = 'under_review'
1769 STATUS_UNDER_REVIEW = 'under_review'
1767
1770
1768 STATUSES = [
1771 STATUSES = [
1769 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
1772 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
1770 (STATUS_APPROVED, _("Approved")),
1773 (STATUS_APPROVED, _("Approved")),
1771 (STATUS_REJECTED, _("Rejected")),
1774 (STATUS_REJECTED, _("Rejected")),
1772 (STATUS_UNDER_REVIEW, _("Under Review")),
1775 (STATUS_UNDER_REVIEW, _("Under Review")),
1773 ]
1776 ]
1774
1777
1775 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
1778 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
1776 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1779 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1777 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
1780 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
1778 revision = Column('revision', String(40), nullable=False)
1781 revision = Column('revision', String(40), nullable=False)
1779 status = Column('status', String(128), nullable=False, default=DEFAULT)
1782 status = Column('status', String(128), nullable=False, default=DEFAULT)
1780 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
1783 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
1781 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
1784 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
1782 version = Column('version', Integer(), nullable=False, default=0)
1785 version = Column('version', Integer(), nullable=False, default=0)
1783 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
1786 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
1784
1787
1785 author = relationship('User', lazy='joined')
1788 author = relationship('User', lazy='joined')
1786 repo = relationship('Repository')
1789 repo = relationship('Repository')
1787 comment = relationship('ChangesetComment', lazy='joined')
1790 comment = relationship('ChangesetComment', lazy='joined')
1788 pull_request = relationship('PullRequest', lazy='joined')
1791 pull_request = relationship('PullRequest', lazy='joined')
1789
1792
1790 def __unicode__(self):
1793 def __unicode__(self):
1791 return u"<%s('%s:%s')>" % (
1794 return u"<%s('%s:%s')>" % (
1792 self.__class__.__name__,
1795 self.__class__.__name__,
1793 self.status, self.author
1796 self.status, self.author
1794 )
1797 )
1795
1798
1796 @classmethod
1799 @classmethod
1797 def get_status_lbl(cls, value):
1800 def get_status_lbl(cls, value):
1798 return dict(cls.STATUSES).get(value)
1801 return dict(cls.STATUSES).get(value)
1799
1802
1800 @property
1803 @property
1801 def status_lbl(self):
1804 def status_lbl(self):
1802 return ChangesetStatus.get_status_lbl(self.status)
1805 return ChangesetStatus.get_status_lbl(self.status)
1803
1806
1804
1807
1805 class PullRequest(Base, BaseModel):
1808 class PullRequest(Base, BaseModel):
1806 __tablename__ = 'pull_requests'
1809 __tablename__ = 'pull_requests'
1807 __table_args__ = (
1810 __table_args__ = (
1808 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1811 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1809 'mysql_charset': 'utf8'},
1812 'mysql_charset': 'utf8'},
1810 )
1813 )
1811
1814
1812 STATUS_NEW = u'new'
1815 STATUS_NEW = u'new'
1813 STATUS_OPEN = u'open'
1816 STATUS_OPEN = u'open'
1814 STATUS_CLOSED = u'closed'
1817 STATUS_CLOSED = u'closed'
1815
1818
1816 pull_request_id = Column('pull_request_id', Integer(), nullable=False, primary_key=True)
1819 pull_request_id = Column('pull_request_id', Integer(), nullable=False, primary_key=True)
1817 title = Column('title', Unicode(256), nullable=True)
1820 title = Column('title', Unicode(256), nullable=True)
1818 description = Column('description', UnicodeText(10240), nullable=True)
1821 description = Column('description', UnicodeText(10240), nullable=True)
1819 status = Column('status', Unicode(256), nullable=False, default=STATUS_NEW)
1822 status = Column('status', Unicode(256), nullable=False, default=STATUS_NEW)
1820 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1823 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1821 updated_on = Column('updated_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1824 updated_on = Column('updated_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1822 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
1825 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
1823 _revisions = Column('revisions', UnicodeText(20500)) # 500 revisions max
1826 _revisions = Column('revisions', UnicodeText(20500)) # 500 revisions max
1824 org_repo_id = Column('org_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1827 org_repo_id = Column('org_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1825 org_ref = Column('org_ref', Unicode(256), nullable=False)
1828 org_ref = Column('org_ref', Unicode(256), nullable=False)
1826 other_repo_id = Column('other_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1829 other_repo_id = Column('other_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1827 other_ref = Column('other_ref', Unicode(256), nullable=False)
1830 other_ref = Column('other_ref', Unicode(256), nullable=False)
1828
1831
1829 @hybrid_property
1832 @hybrid_property
1830 def revisions(self):
1833 def revisions(self):
1831 return self._revisions.split(':')
1834 return self._revisions.split(':')
1832
1835
1833 @revisions.setter
1836 @revisions.setter
1834 def revisions(self, val):
1837 def revisions(self, val):
1835 self._revisions = ':'.join(val)
1838 self._revisions = ':'.join(val)
1836
1839
1837 @property
1840 @property
1838 def org_ref_parts(self):
1841 def org_ref_parts(self):
1839 return self.org_ref.split(':')
1842 return self.org_ref.split(':')
1840
1843
1841 @property
1844 @property
1842 def other_ref_parts(self):
1845 def other_ref_parts(self):
1843 return self.other_ref.split(':')
1846 return self.other_ref.split(':')
1844
1847
1845 author = relationship('User', lazy='joined')
1848 author = relationship('User', lazy='joined')
1846 reviewers = relationship('PullRequestReviewers',
1849 reviewers = relationship('PullRequestReviewers',
1847 cascade="all, delete, delete-orphan")
1850 cascade="all, delete, delete-orphan")
1848 org_repo = relationship('Repository', primaryjoin='PullRequest.org_repo_id==Repository.repo_id')
1851 org_repo = relationship('Repository', primaryjoin='PullRequest.org_repo_id==Repository.repo_id')
1849 other_repo = relationship('Repository', primaryjoin='PullRequest.other_repo_id==Repository.repo_id')
1852 other_repo = relationship('Repository', primaryjoin='PullRequest.other_repo_id==Repository.repo_id')
1850 statuses = relationship('ChangesetStatus')
1853 statuses = relationship('ChangesetStatus')
1851 comments = relationship('ChangesetComment',
1854 comments = relationship('ChangesetComment',
1852 cascade="all, delete, delete-orphan")
1855 cascade="all, delete, delete-orphan")
1853
1856
1854 def is_closed(self):
1857 def is_closed(self):
1855 return self.status == self.STATUS_CLOSED
1858 return self.status == self.STATUS_CLOSED
1856
1859
1857 def __json__(self):
1860 def __json__(self):
1858 return dict(
1861 return dict(
1859 revisions=self.revisions
1862 revisions=self.revisions
1860 )
1863 )
1861
1864
1862
1865
1863 class PullRequestReviewers(Base, BaseModel):
1866 class PullRequestReviewers(Base, BaseModel):
1864 __tablename__ = 'pull_request_reviewers'
1867 __tablename__ = 'pull_request_reviewers'
1865 __table_args__ = (
1868 __table_args__ = (
1866 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1869 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1867 'mysql_charset': 'utf8'},
1870 'mysql_charset': 'utf8'},
1868 )
1871 )
1869
1872
1870 def __init__(self, user=None, pull_request=None):
1873 def __init__(self, user=None, pull_request=None):
1871 self.user = user
1874 self.user = user
1872 self.pull_request = pull_request
1875 self.pull_request = pull_request
1873
1876
1874 pull_requests_reviewers_id = Column('pull_requests_reviewers_id', Integer(), nullable=False, primary_key=True)
1877 pull_requests_reviewers_id = Column('pull_requests_reviewers_id', Integer(), nullable=False, primary_key=True)
1875 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=False)
1878 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=False)
1876 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
1879 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
1877
1880
1878 user = relationship('User')
1881 user = relationship('User')
1879 pull_request = relationship('PullRequest')
1882 pull_request = relationship('PullRequest')
1880
1883
1881
1884
1882 class Notification(Base, BaseModel):
1885 class Notification(Base, BaseModel):
1883 __tablename__ = 'notifications'
1886 __tablename__ = 'notifications'
1884 __table_args__ = (
1887 __table_args__ = (
1885 Index('notification_type_idx', 'type'),
1888 Index('notification_type_idx', 'type'),
1886 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1889 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1887 'mysql_charset': 'utf8'},
1890 'mysql_charset': 'utf8'},
1888 )
1891 )
1889
1892
1890 TYPE_CHANGESET_COMMENT = u'cs_comment'
1893 TYPE_CHANGESET_COMMENT = u'cs_comment'
1891 TYPE_MESSAGE = u'message'
1894 TYPE_MESSAGE = u'message'
1892 TYPE_MENTION = u'mention'
1895 TYPE_MENTION = u'mention'
1893 TYPE_REGISTRATION = u'registration'
1896 TYPE_REGISTRATION = u'registration'
1894 TYPE_PULL_REQUEST = u'pull_request'
1897 TYPE_PULL_REQUEST = u'pull_request'
1895 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
1898 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
1896
1899
1897 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
1900 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
1898 subject = Column('subject', Unicode(512), nullable=True)
1901 subject = Column('subject', Unicode(512), nullable=True)
1899 body = Column('body', UnicodeText(50000), nullable=True)
1902 body = Column('body', UnicodeText(50000), nullable=True)
1900 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
1903 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
1901 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1904 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1902 type_ = Column('type', Unicode(256))
1905 type_ = Column('type', Unicode(256))
1903
1906
1904 created_by_user = relationship('User')
1907 created_by_user = relationship('User')
1905 notifications_to_users = relationship('UserNotification', lazy='joined',
1908 notifications_to_users = relationship('UserNotification', lazy='joined',
1906 cascade="all, delete, delete-orphan")
1909 cascade="all, delete, delete-orphan")
1907
1910
1908 @property
1911 @property
1909 def recipients(self):
1912 def recipients(self):
1910 return [x.user for x in UserNotification.query()\
1913 return [x.user for x in UserNotification.query()\
1911 .filter(UserNotification.notification == self)\
1914 .filter(UserNotification.notification == self)\
1912 .order_by(UserNotification.user_id.asc()).all()]
1915 .order_by(UserNotification.user_id.asc()).all()]
1913
1916
1914 @classmethod
1917 @classmethod
1915 def create(cls, created_by, subject, body, recipients, type_=None):
1918 def create(cls, created_by, subject, body, recipients, type_=None):
1916 if type_ is None:
1919 if type_ is None:
1917 type_ = Notification.TYPE_MESSAGE
1920 type_ = Notification.TYPE_MESSAGE
1918
1921
1919 notification = cls()
1922 notification = cls()
1920 notification.created_by_user = created_by
1923 notification.created_by_user = created_by
1921 notification.subject = subject
1924 notification.subject = subject
1922 notification.body = body
1925 notification.body = body
1923 notification.type_ = type_
1926 notification.type_ = type_
1924 notification.created_on = datetime.datetime.now()
1927 notification.created_on = datetime.datetime.now()
1925
1928
1926 for u in recipients:
1929 for u in recipients:
1927 assoc = UserNotification()
1930 assoc = UserNotification()
1928 assoc.notification = notification
1931 assoc.notification = notification
1929 u.notifications.append(assoc)
1932 u.notifications.append(assoc)
1930 Session().add(notification)
1933 Session().add(notification)
1931 return notification
1934 return notification
1932
1935
1933 @property
1936 @property
1934 def description(self):
1937 def description(self):
1935 from rhodecode.model.notification import NotificationModel
1938 from rhodecode.model.notification import NotificationModel
1936 return NotificationModel().make_description(self)
1939 return NotificationModel().make_description(self)
1937
1940
1938
1941
1939 class UserNotification(Base, BaseModel):
1942 class UserNotification(Base, BaseModel):
1940 __tablename__ = 'user_to_notification'
1943 __tablename__ = 'user_to_notification'
1941 __table_args__ = (
1944 __table_args__ = (
1942 UniqueConstraint('user_id', 'notification_id'),
1945 UniqueConstraint('user_id', 'notification_id'),
1943 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1946 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1944 'mysql_charset': 'utf8'}
1947 'mysql_charset': 'utf8'}
1945 )
1948 )
1946 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
1949 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
1947 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
1950 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
1948 read = Column('read', Boolean, default=False)
1951 read = Column('read', Boolean, default=False)
1949 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
1952 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
1950
1953
1951 user = relationship('User', lazy="joined")
1954 user = relationship('User', lazy="joined")
1952 notification = relationship('Notification', lazy="joined",
1955 notification = relationship('Notification', lazy="joined",
1953 order_by=lambda: Notification.created_on.desc(),)
1956 order_by=lambda: Notification.created_on.desc(),)
1954
1957
1955 def mark_as_read(self):
1958 def mark_as_read(self):
1956 self.read = True
1959 self.read = True
1957 Session().add(self)
1960 Session().add(self)
1958
1961
1959
1962
1960 class DbMigrateVersion(Base, BaseModel):
1963 class DbMigrateVersion(Base, BaseModel):
1961 __tablename__ = 'db_migrate_version'
1964 __tablename__ = 'db_migrate_version'
1962 __table_args__ = (
1965 __table_args__ = (
1963 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1966 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1964 'mysql_charset': 'utf8'},
1967 'mysql_charset': 'utf8'},
1965 )
1968 )
1966 repository_id = Column('repository_id', String(250), primary_key=True)
1969 repository_id = Column('repository_id', String(250), primary_key=True)
1967 repository_path = Column('repository_path', Text)
1970 repository_path = Column('repository_path', Text)
1968 version = Column('version', Integer)
1971 version = Column('version', Integer)
@@ -1,375 +1,381 b''
1 """ this is forms validation classes
1 """ this is forms validation classes
2 http://formencode.org/module-formencode.validators.html
2 http://formencode.org/module-formencode.validators.html
3 for list off all availible validators
3 for list off all availible validators
4
4
5 we can create our own validators
5 we can create our own validators
6
6
7 The table below outlines the options which can be used in a schema in addition to the validators themselves
7 The table below outlines the options which can be used in a schema in addition to the validators themselves
8 pre_validators [] These validators will be applied before the schema
8 pre_validators [] These validators will be applied before the schema
9 chained_validators [] These validators will be applied after the schema
9 chained_validators [] These validators will be applied after the schema
10 allow_extra_fields False If True, then it is not an error when keys that aren't associated with a validator are present
10 allow_extra_fields False If True, then it is not an error when keys that aren't associated with a validator are present
11 filter_extra_fields False If True, then keys that aren't associated with a validator are removed
11 filter_extra_fields False If True, then keys that aren't associated with a validator are removed
12 if_key_missing NoDefault If this is given, then any keys that aren't available but are expected will be replaced with this value (and then validated). This does not override a present .if_missing attribute on validators. NoDefault is a special FormEncode class to mean that no default values has been specified and therefore missing keys shouldn't take a default value.
12 if_key_missing NoDefault If this is given, then any keys that aren't available but are expected will be replaced with this value (and then validated). This does not override a present .if_missing attribute on validators. NoDefault is a special FormEncode class to mean that no default values has been specified and therefore missing keys shouldn't take a default value.
13 ignore_key_missing False If True, then missing keys will be missing in the result, if the validator doesn't have .if_missing on it already
13 ignore_key_missing False If True, then missing keys will be missing in the result, if the validator doesn't have .if_missing on it already
14
14
15
15
16 <name> = formencode.validators.<name of validator>
16 <name> = formencode.validators.<name of validator>
17 <name> must equal form name
17 <name> must equal form name
18 list=[1,2,3,4,5]
18 list=[1,2,3,4,5]
19 for SELECT use formencode.All(OneOf(list), Int())
19 for SELECT use formencode.All(OneOf(list), Int())
20
20
21 """
21 """
22 import logging
22 import logging
23
23
24 import formencode
24 import formencode
25 from formencode import All
25 from formencode import All
26
26
27 from pylons.i18n.translation import _
27 from pylons.i18n.translation import _
28
28
29 from rhodecode.model import validators as v
29 from rhodecode.model import validators as v
30 from rhodecode import BACKENDS
30 from rhodecode import BACKENDS
31
31
32 log = logging.getLogger(__name__)
32 log = logging.getLogger(__name__)
33
33
34
34
35 class LoginForm(formencode.Schema):
35 class LoginForm(formencode.Schema):
36 allow_extra_fields = True
36 allow_extra_fields = True
37 filter_extra_fields = True
37 filter_extra_fields = True
38 username = v.UnicodeString(
38 username = v.UnicodeString(
39 strip=True,
39 strip=True,
40 min=1,
40 min=1,
41 not_empty=True,
41 not_empty=True,
42 messages={
42 messages={
43 'empty': _(u'Please enter a login'),
43 'empty': _(u'Please enter a login'),
44 'tooShort': _(u'Enter a value %(min)i characters long or more')}
44 'tooShort': _(u'Enter a value %(min)i characters long or more')}
45 )
45 )
46
46
47 password = v.UnicodeString(
47 password = v.UnicodeString(
48 strip=False,
48 strip=False,
49 min=3,
49 min=3,
50 not_empty=True,
50 not_empty=True,
51 messages={
51 messages={
52 'empty': _(u'Please enter a password'),
52 'empty': _(u'Please enter a password'),
53 'tooShort': _(u'Enter %(min)i characters or more')}
53 'tooShort': _(u'Enter %(min)i characters or more')}
54 )
54 )
55
55
56 remember = v.StringBoolean(if_missing=False)
56 remember = v.StringBoolean(if_missing=False)
57
57
58 chained_validators = [v.ValidAuth()]
58 chained_validators = [v.ValidAuth()]
59
59
60
60
61 def UserForm(edit=False, old_data={}):
61 def UserForm(edit=False, old_data={}):
62 class _UserForm(formencode.Schema):
62 class _UserForm(formencode.Schema):
63 allow_extra_fields = True
63 allow_extra_fields = True
64 filter_extra_fields = True
64 filter_extra_fields = True
65 username = All(v.UnicodeString(strip=True, min=1, not_empty=True),
65 username = All(v.UnicodeString(strip=True, min=1, not_empty=True),
66 v.ValidUsername(edit, old_data))
66 v.ValidUsername(edit, old_data))
67 if edit:
67 if edit:
68 new_password = All(
68 new_password = All(
69 v.ValidPassword(),
69 v.ValidPassword(),
70 v.UnicodeString(strip=False, min=6, not_empty=False)
70 v.UnicodeString(strip=False, min=6, not_empty=False)
71 )
71 )
72 password_confirmation = All(
72 password_confirmation = All(
73 v.ValidPassword(),
73 v.ValidPassword(),
74 v.UnicodeString(strip=False, min=6, not_empty=False),
74 v.UnicodeString(strip=False, min=6, not_empty=False),
75 )
75 )
76 admin = v.StringBoolean(if_missing=False)
76 admin = v.StringBoolean(if_missing=False)
77 else:
77 else:
78 password = All(
78 password = All(
79 v.ValidPassword(),
79 v.ValidPassword(),
80 v.UnicodeString(strip=False, min=6, not_empty=True)
80 v.UnicodeString(strip=False, min=6, not_empty=True)
81 )
81 )
82 password_confirmation = All(
82 password_confirmation = All(
83 v.ValidPassword(),
83 v.ValidPassword(),
84 v.UnicodeString(strip=False, min=6, not_empty=False)
84 v.UnicodeString(strip=False, min=6, not_empty=False)
85 )
85 )
86
86
87 active = v.StringBoolean(if_missing=False)
87 active = v.StringBoolean(if_missing=False)
88 firstname = v.UnicodeString(strip=True, min=1, not_empty=False)
88 firstname = v.UnicodeString(strip=True, min=1, not_empty=False)
89 lastname = v.UnicodeString(strip=True, min=1, not_empty=False)
89 lastname = v.UnicodeString(strip=True, min=1, not_empty=False)
90 email = All(v.Email(not_empty=True), v.UniqSystemEmail(old_data))
90 email = All(v.Email(not_empty=True), v.UniqSystemEmail(old_data))
91
91
92 chained_validators = [v.ValidPasswordsMatch()]
92 chained_validators = [v.ValidPasswordsMatch()]
93
93
94 return _UserForm
94 return _UserForm
95
95
96
96
97 def UsersGroupForm(edit=False, old_data={}, available_members=[]):
97 def UsersGroupForm(edit=False, old_data={}, available_members=[]):
98 class _UsersGroupForm(formencode.Schema):
98 class _UsersGroupForm(formencode.Schema):
99 allow_extra_fields = True
99 allow_extra_fields = True
100 filter_extra_fields = True
100 filter_extra_fields = True
101
101
102 users_group_name = All(
102 users_group_name = All(
103 v.UnicodeString(strip=True, min=1, not_empty=True),
103 v.UnicodeString(strip=True, min=1, not_empty=True),
104 v.ValidUsersGroup(edit, old_data)
104 v.ValidUsersGroup(edit, old_data)
105 )
105 )
106
106
107 users_group_active = v.StringBoolean(if_missing=False)
107 users_group_active = v.StringBoolean(if_missing=False)
108
108
109 if edit:
109 if edit:
110 users_group_members = v.OneOf(
110 users_group_members = v.OneOf(
111 available_members, hideList=False, testValueList=True,
111 available_members, hideList=False, testValueList=True,
112 if_missing=None, not_empty=False
112 if_missing=None, not_empty=False
113 )
113 )
114
114
115 return _UsersGroupForm
115 return _UsersGroupForm
116
116
117
117
118 def ReposGroupForm(edit=False, old_data={}, available_groups=[]):
118 def ReposGroupForm(edit=False, old_data={}, available_groups=[],
119 can_create_in_root=False):
119 class _ReposGroupForm(formencode.Schema):
120 class _ReposGroupForm(formencode.Schema):
120 allow_extra_fields = True
121 allow_extra_fields = True
121 filter_extra_fields = False
122 filter_extra_fields = False
122
123
123 group_name = All(v.UnicodeString(strip=True, min=1, not_empty=True),
124 group_name = All(v.UnicodeString(strip=True, min=1, not_empty=True),
124 v.SlugifyName())
125 v.SlugifyName())
125 group_description = v.UnicodeString(strip=True, min=1,
126 group_description = v.UnicodeString(strip=True, min=1,
126 not_empty=True)
127 not_empty=False)
127 group_parent_id = v.OneOf(available_groups, hideList=False,
128 if edit:
128 testValueList=True,
129 #FIXME: do a special check that we cannot move a group to one of
129 if_missing=None, not_empty=False)
130 #it's children
131 pass
132 group_parent_id = All(v.CanCreateGroup(can_create_in_root),
133 v.OneOf(available_groups, hideList=False,
134 testValueList=True,
135 if_missing=None, not_empty=True))
130 enable_locking = v.StringBoolean(if_missing=False)
136 enable_locking = v.StringBoolean(if_missing=False)
131 recursive = v.StringBoolean(if_missing=False)
137 recursive = v.StringBoolean(if_missing=False)
132 chained_validators = [v.ValidReposGroup(edit, old_data),
138 chained_validators = [v.ValidReposGroup(edit, old_data),
133 v.ValidPerms('group')]
139 v.ValidPerms('group')]
134
140
135 return _ReposGroupForm
141 return _ReposGroupForm
136
142
137
143
138 def RegisterForm(edit=False, old_data={}):
144 def RegisterForm(edit=False, old_data={}):
139 class _RegisterForm(formencode.Schema):
145 class _RegisterForm(formencode.Schema):
140 allow_extra_fields = True
146 allow_extra_fields = True
141 filter_extra_fields = True
147 filter_extra_fields = True
142 username = All(
148 username = All(
143 v.ValidUsername(edit, old_data),
149 v.ValidUsername(edit, old_data),
144 v.UnicodeString(strip=True, min=1, not_empty=True)
150 v.UnicodeString(strip=True, min=1, not_empty=True)
145 )
151 )
146 password = All(
152 password = All(
147 v.ValidPassword(),
153 v.ValidPassword(),
148 v.UnicodeString(strip=False, min=6, not_empty=True)
154 v.UnicodeString(strip=False, min=6, not_empty=True)
149 )
155 )
150 password_confirmation = All(
156 password_confirmation = All(
151 v.ValidPassword(),
157 v.ValidPassword(),
152 v.UnicodeString(strip=False, min=6, not_empty=True)
158 v.UnicodeString(strip=False, min=6, not_empty=True)
153 )
159 )
154 active = v.StringBoolean(if_missing=False)
160 active = v.StringBoolean(if_missing=False)
155 firstname = v.UnicodeString(strip=True, min=1, not_empty=False)
161 firstname = v.UnicodeString(strip=True, min=1, not_empty=False)
156 lastname = v.UnicodeString(strip=True, min=1, not_empty=False)
162 lastname = v.UnicodeString(strip=True, min=1, not_empty=False)
157 email = All(v.Email(not_empty=True), v.UniqSystemEmail(old_data))
163 email = All(v.Email(not_empty=True), v.UniqSystemEmail(old_data))
158
164
159 chained_validators = [v.ValidPasswordsMatch()]
165 chained_validators = [v.ValidPasswordsMatch()]
160
166
161 return _RegisterForm
167 return _RegisterForm
162
168
163
169
164 def PasswordResetForm():
170 def PasswordResetForm():
165 class _PasswordResetForm(formencode.Schema):
171 class _PasswordResetForm(formencode.Schema):
166 allow_extra_fields = True
172 allow_extra_fields = True
167 filter_extra_fields = True
173 filter_extra_fields = True
168 email = All(v.ValidSystemEmail(), v.Email(not_empty=True))
174 email = All(v.ValidSystemEmail(), v.Email(not_empty=True))
169 return _PasswordResetForm
175 return _PasswordResetForm
170
176
171
177
172 def RepoForm(edit=False, old_data={}, supported_backends=BACKENDS.keys(),
178 def RepoForm(edit=False, old_data={}, supported_backends=BACKENDS.keys(),
173 repo_groups=[], landing_revs=[]):
179 repo_groups=[], landing_revs=[]):
174 class _RepoForm(formencode.Schema):
180 class _RepoForm(formencode.Schema):
175 allow_extra_fields = True
181 allow_extra_fields = True
176 filter_extra_fields = False
182 filter_extra_fields = False
177 repo_name = All(v.UnicodeString(strip=True, min=1, not_empty=True),
183 repo_name = All(v.UnicodeString(strip=True, min=1, not_empty=True),
178 v.SlugifyName())
184 v.SlugifyName())
179 repo_group = All(v.CanWriteGroup(),
185 repo_group = All(v.CanWriteGroup(),
180 v.OneOf(repo_groups, hideList=True))
186 v.OneOf(repo_groups, hideList=True))
181 repo_type = v.OneOf(supported_backends)
187 repo_type = v.OneOf(supported_backends)
182 repo_description = v.UnicodeString(strip=True, min=1, not_empty=False)
188 repo_description = v.UnicodeString(strip=True, min=1, not_empty=False)
183 repo_private = v.StringBoolean(if_missing=False)
189 repo_private = v.StringBoolean(if_missing=False)
184 repo_landing_rev = v.OneOf(landing_revs, hideList=True)
190 repo_landing_rev = v.OneOf(landing_revs, hideList=True)
185 clone_uri = All(v.UnicodeString(strip=True, min=1, not_empty=False))
191 clone_uri = All(v.UnicodeString(strip=True, min=1, not_empty=False))
186
192
187 repo_enable_statistics = v.StringBoolean(if_missing=False)
193 repo_enable_statistics = v.StringBoolean(if_missing=False)
188 repo_enable_downloads = v.StringBoolean(if_missing=False)
194 repo_enable_downloads = v.StringBoolean(if_missing=False)
189 repo_enable_locking = v.StringBoolean(if_missing=False)
195 repo_enable_locking = v.StringBoolean(if_missing=False)
190
196
191 if edit:
197 if edit:
192 #this is repo owner
198 #this is repo owner
193 user = All(v.UnicodeString(not_empty=True), v.ValidRepoUser())
199 user = All(v.UnicodeString(not_empty=True), v.ValidRepoUser())
194
200
195 chained_validators = [v.ValidCloneUri(),
201 chained_validators = [v.ValidCloneUri(),
196 v.ValidRepoName(edit, old_data),
202 v.ValidRepoName(edit, old_data),
197 v.ValidPerms()]
203 v.ValidPerms()]
198 return _RepoForm
204 return _RepoForm
199
205
200
206
201 def RepoSettingsForm(edit=False, old_data={}, supported_backends=BACKENDS.keys(),
207 def RepoSettingsForm(edit=False, old_data={}, supported_backends=BACKENDS.keys(),
202 repo_groups=[], landing_revs=[]):
208 repo_groups=[], landing_revs=[]):
203 class _RepoForm(formencode.Schema):
209 class _RepoForm(formencode.Schema):
204 allow_extra_fields = True
210 allow_extra_fields = True
205 filter_extra_fields = False
211 filter_extra_fields = False
206 repo_name = All(v.UnicodeString(strip=True, min=1, not_empty=True),
212 repo_name = All(v.UnicodeString(strip=True, min=1, not_empty=True),
207 v.SlugifyName())
213 v.SlugifyName())
208 repo_group = All(v.CanWriteGroup(),
214 repo_group = All(v.CanWriteGroup(),
209 v.OneOf(repo_groups, hideList=True))
215 v.OneOf(repo_groups, hideList=True))
210 repo_description = v.UnicodeString(strip=True, min=1, not_empty=False)
216 repo_description = v.UnicodeString(strip=True, min=1, not_empty=False)
211 repo_private = v.StringBoolean(if_missing=False)
217 repo_private = v.StringBoolean(if_missing=False)
212 repo_landing_rev = v.OneOf(landing_revs, hideList=True)
218 repo_landing_rev = v.OneOf(landing_revs, hideList=True)
213 clone_uri = All(v.UnicodeString(strip=True, min=1, not_empty=False))
219 clone_uri = All(v.UnicodeString(strip=True, min=1, not_empty=False))
214
220
215 chained_validators = [v.ValidCloneUri(),
221 chained_validators = [v.ValidCloneUri(),
216 v.ValidRepoName(edit, old_data),
222 v.ValidRepoName(edit, old_data),
217 v.ValidPerms(),
223 v.ValidPerms(),
218 v.ValidSettings()]
224 v.ValidSettings()]
219 return _RepoForm
225 return _RepoForm
220
226
221
227
222 def RepoForkForm(edit=False, old_data={}, supported_backends=BACKENDS.keys(),
228 def RepoForkForm(edit=False, old_data={}, supported_backends=BACKENDS.keys(),
223 repo_groups=[], landing_revs=[]):
229 repo_groups=[], landing_revs=[]):
224 class _RepoForkForm(formencode.Schema):
230 class _RepoForkForm(formencode.Schema):
225 allow_extra_fields = True
231 allow_extra_fields = True
226 filter_extra_fields = False
232 filter_extra_fields = False
227 repo_name = All(v.UnicodeString(strip=True, min=1, not_empty=True),
233 repo_name = All(v.UnicodeString(strip=True, min=1, not_empty=True),
228 v.SlugifyName())
234 v.SlugifyName())
229 repo_group = All(v.CanWriteGroup(),
235 repo_group = All(v.CanWriteGroup(),
230 v.OneOf(repo_groups, hideList=True))
236 v.OneOf(repo_groups, hideList=True))
231 repo_type = All(v.ValidForkType(old_data), v.OneOf(supported_backends))
237 repo_type = All(v.ValidForkType(old_data), v.OneOf(supported_backends))
232 description = v.UnicodeString(strip=True, min=1, not_empty=True)
238 description = v.UnicodeString(strip=True, min=1, not_empty=True)
233 private = v.StringBoolean(if_missing=False)
239 private = v.StringBoolean(if_missing=False)
234 copy_permissions = v.StringBoolean(if_missing=False)
240 copy_permissions = v.StringBoolean(if_missing=False)
235 update_after_clone = v.StringBoolean(if_missing=False)
241 update_after_clone = v.StringBoolean(if_missing=False)
236 fork_parent_id = v.UnicodeString()
242 fork_parent_id = v.UnicodeString()
237 chained_validators = [v.ValidForkName(edit, old_data)]
243 chained_validators = [v.ValidForkName(edit, old_data)]
238 landing_rev = v.OneOf(landing_revs, hideList=True)
244 landing_rev = v.OneOf(landing_revs, hideList=True)
239
245
240 return _RepoForkForm
246 return _RepoForkForm
241
247
242
248
243 def ApplicationSettingsForm():
249 def ApplicationSettingsForm():
244 class _ApplicationSettingsForm(formencode.Schema):
250 class _ApplicationSettingsForm(formencode.Schema):
245 allow_extra_fields = True
251 allow_extra_fields = True
246 filter_extra_fields = False
252 filter_extra_fields = False
247 rhodecode_title = v.UnicodeString(strip=True, min=1, not_empty=True)
253 rhodecode_title = v.UnicodeString(strip=True, min=1, not_empty=True)
248 rhodecode_realm = v.UnicodeString(strip=True, min=1, not_empty=True)
254 rhodecode_realm = v.UnicodeString(strip=True, min=1, not_empty=True)
249 rhodecode_ga_code = v.UnicodeString(strip=True, min=1, not_empty=False)
255 rhodecode_ga_code = v.UnicodeString(strip=True, min=1, not_empty=False)
250
256
251 return _ApplicationSettingsForm
257 return _ApplicationSettingsForm
252
258
253
259
254 def ApplicationVisualisationForm():
260 def ApplicationVisualisationForm():
255 class _ApplicationVisualisationForm(formencode.Schema):
261 class _ApplicationVisualisationForm(formencode.Schema):
256 allow_extra_fields = True
262 allow_extra_fields = True
257 filter_extra_fields = False
263 filter_extra_fields = False
258 rhodecode_show_public_icon = v.StringBoolean(if_missing=False)
264 rhodecode_show_public_icon = v.StringBoolean(if_missing=False)
259 rhodecode_show_private_icon = v.StringBoolean(if_missing=False)
265 rhodecode_show_private_icon = v.StringBoolean(if_missing=False)
260 rhodecode_stylify_metatags = v.StringBoolean(if_missing=False)
266 rhodecode_stylify_metatags = v.StringBoolean(if_missing=False)
261
267
262 rhodecode_lightweight_dashboard = v.StringBoolean(if_missing=False)
268 rhodecode_lightweight_dashboard = v.StringBoolean(if_missing=False)
263 rhodecode_lightweight_journal = v.StringBoolean(if_missing=False)
269 rhodecode_lightweight_journal = v.StringBoolean(if_missing=False)
264
270
265 return _ApplicationVisualisationForm
271 return _ApplicationVisualisationForm
266
272
267
273
268 def ApplicationUiSettingsForm():
274 def ApplicationUiSettingsForm():
269 class _ApplicationUiSettingsForm(formencode.Schema):
275 class _ApplicationUiSettingsForm(formencode.Schema):
270 allow_extra_fields = True
276 allow_extra_fields = True
271 filter_extra_fields = False
277 filter_extra_fields = False
272 web_push_ssl = v.StringBoolean(if_missing=False)
278 web_push_ssl = v.StringBoolean(if_missing=False)
273 paths_root_path = All(
279 paths_root_path = All(
274 v.ValidPath(),
280 v.ValidPath(),
275 v.UnicodeString(strip=True, min=1, not_empty=True)
281 v.UnicodeString(strip=True, min=1, not_empty=True)
276 )
282 )
277 hooks_changegroup_update = v.StringBoolean(if_missing=False)
283 hooks_changegroup_update = v.StringBoolean(if_missing=False)
278 hooks_changegroup_repo_size = v.StringBoolean(if_missing=False)
284 hooks_changegroup_repo_size = v.StringBoolean(if_missing=False)
279 hooks_changegroup_push_logger = v.StringBoolean(if_missing=False)
285 hooks_changegroup_push_logger = v.StringBoolean(if_missing=False)
280 hooks_outgoing_pull_logger = v.StringBoolean(if_missing=False)
286 hooks_outgoing_pull_logger = v.StringBoolean(if_missing=False)
281
287
282 extensions_largefiles = v.StringBoolean(if_missing=False)
288 extensions_largefiles = v.StringBoolean(if_missing=False)
283 extensions_hgsubversion = v.StringBoolean(if_missing=False)
289 extensions_hgsubversion = v.StringBoolean(if_missing=False)
284 extensions_hggit = v.StringBoolean(if_missing=False)
290 extensions_hggit = v.StringBoolean(if_missing=False)
285
291
286 return _ApplicationUiSettingsForm
292 return _ApplicationUiSettingsForm
287
293
288
294
289 def DefaultPermissionsForm(repo_perms_choices, group_perms_choices,
295 def DefaultPermissionsForm(repo_perms_choices, group_perms_choices,
290 register_choices, create_choices, fork_choices):
296 register_choices, create_choices, fork_choices):
291 class _DefaultPermissionsForm(formencode.Schema):
297 class _DefaultPermissionsForm(formencode.Schema):
292 allow_extra_fields = True
298 allow_extra_fields = True
293 filter_extra_fields = True
299 filter_extra_fields = True
294 overwrite_default_repo = v.StringBoolean(if_missing=False)
300 overwrite_default_repo = v.StringBoolean(if_missing=False)
295 overwrite_default_group = v.StringBoolean(if_missing=False)
301 overwrite_default_group = v.StringBoolean(if_missing=False)
296 anonymous = v.StringBoolean(if_missing=False)
302 anonymous = v.StringBoolean(if_missing=False)
297 default_repo_perm = v.OneOf(repo_perms_choices)
303 default_repo_perm = v.OneOf(repo_perms_choices)
298 default_group_perm = v.OneOf(group_perms_choices)
304 default_group_perm = v.OneOf(group_perms_choices)
299 default_register = v.OneOf(register_choices)
305 default_register = v.OneOf(register_choices)
300 default_create = v.OneOf(create_choices)
306 default_create = v.OneOf(create_choices)
301 default_fork = v.OneOf(fork_choices)
307 default_fork = v.OneOf(fork_choices)
302
308
303 return _DefaultPermissionsForm
309 return _DefaultPermissionsForm
304
310
305
311
306 def DefaultsForm(edit=False, old_data={}, supported_backends=BACKENDS.keys()):
312 def DefaultsForm(edit=False, old_data={}, supported_backends=BACKENDS.keys()):
307 class _DefaultsForm(formencode.Schema):
313 class _DefaultsForm(formencode.Schema):
308 allow_extra_fields = True
314 allow_extra_fields = True
309 filter_extra_fields = True
315 filter_extra_fields = True
310 default_repo_type = v.OneOf(supported_backends)
316 default_repo_type = v.OneOf(supported_backends)
311 default_repo_private = v.StringBoolean(if_missing=False)
317 default_repo_private = v.StringBoolean(if_missing=False)
312 default_repo_enable_statistics = v.StringBoolean(if_missing=False)
318 default_repo_enable_statistics = v.StringBoolean(if_missing=False)
313 default_repo_enable_downloads = v.StringBoolean(if_missing=False)
319 default_repo_enable_downloads = v.StringBoolean(if_missing=False)
314 default_repo_enable_locking = v.StringBoolean(if_missing=False)
320 default_repo_enable_locking = v.StringBoolean(if_missing=False)
315
321
316 return _DefaultsForm
322 return _DefaultsForm
317
323
318
324
319 def LdapSettingsForm(tls_reqcert_choices, search_scope_choices,
325 def LdapSettingsForm(tls_reqcert_choices, search_scope_choices,
320 tls_kind_choices):
326 tls_kind_choices):
321 class _LdapSettingsForm(formencode.Schema):
327 class _LdapSettingsForm(formencode.Schema):
322 allow_extra_fields = True
328 allow_extra_fields = True
323 filter_extra_fields = True
329 filter_extra_fields = True
324 #pre_validators = [LdapLibValidator]
330 #pre_validators = [LdapLibValidator]
325 ldap_active = v.StringBoolean(if_missing=False)
331 ldap_active = v.StringBoolean(if_missing=False)
326 ldap_host = v.UnicodeString(strip=True,)
332 ldap_host = v.UnicodeString(strip=True,)
327 ldap_port = v.Number(strip=True,)
333 ldap_port = v.Number(strip=True,)
328 ldap_tls_kind = v.OneOf(tls_kind_choices)
334 ldap_tls_kind = v.OneOf(tls_kind_choices)
329 ldap_tls_reqcert = v.OneOf(tls_reqcert_choices)
335 ldap_tls_reqcert = v.OneOf(tls_reqcert_choices)
330 ldap_dn_user = v.UnicodeString(strip=True,)
336 ldap_dn_user = v.UnicodeString(strip=True,)
331 ldap_dn_pass = v.UnicodeString(strip=True,)
337 ldap_dn_pass = v.UnicodeString(strip=True,)
332 ldap_base_dn = v.UnicodeString(strip=True,)
338 ldap_base_dn = v.UnicodeString(strip=True,)
333 ldap_filter = v.UnicodeString(strip=True,)
339 ldap_filter = v.UnicodeString(strip=True,)
334 ldap_search_scope = v.OneOf(search_scope_choices)
340 ldap_search_scope = v.OneOf(search_scope_choices)
335 ldap_attr_login = All(
341 ldap_attr_login = All(
336 v.AttrLoginValidator(),
342 v.AttrLoginValidator(),
337 v.UnicodeString(strip=True,)
343 v.UnicodeString(strip=True,)
338 )
344 )
339 ldap_attr_firstname = v.UnicodeString(strip=True,)
345 ldap_attr_firstname = v.UnicodeString(strip=True,)
340 ldap_attr_lastname = v.UnicodeString(strip=True,)
346 ldap_attr_lastname = v.UnicodeString(strip=True,)
341 ldap_attr_email = v.UnicodeString(strip=True,)
347 ldap_attr_email = v.UnicodeString(strip=True,)
342
348
343 return _LdapSettingsForm
349 return _LdapSettingsForm
344
350
345
351
346 def UserExtraEmailForm():
352 def UserExtraEmailForm():
347 class _UserExtraEmailForm(formencode.Schema):
353 class _UserExtraEmailForm(formencode.Schema):
348 email = All(v.UniqSystemEmail(), v.Email(not_empty=True))
354 email = All(v.UniqSystemEmail(), v.Email(not_empty=True))
349 return _UserExtraEmailForm
355 return _UserExtraEmailForm
350
356
351
357
352 def UserExtraIpForm():
358 def UserExtraIpForm():
353 class _UserExtraIpForm(formencode.Schema):
359 class _UserExtraIpForm(formencode.Schema):
354 ip = v.ValidIp()(not_empty=True)
360 ip = v.ValidIp()(not_empty=True)
355 return _UserExtraIpForm
361 return _UserExtraIpForm
356
362
357
363
358 def PullRequestForm(repo_id):
364 def PullRequestForm(repo_id):
359 class _PullRequestForm(formencode.Schema):
365 class _PullRequestForm(formencode.Schema):
360 allow_extra_fields = True
366 allow_extra_fields = True
361 filter_extra_fields = True
367 filter_extra_fields = True
362
368
363 user = v.UnicodeString(strip=True, required=True)
369 user = v.UnicodeString(strip=True, required=True)
364 org_repo = v.UnicodeString(strip=True, required=True)
370 org_repo = v.UnicodeString(strip=True, required=True)
365 org_ref = v.UnicodeString(strip=True, required=True)
371 org_ref = v.UnicodeString(strip=True, required=True)
366 other_repo = v.UnicodeString(strip=True, required=True)
372 other_repo = v.UnicodeString(strip=True, required=True)
367 other_ref = v.UnicodeString(strip=True, required=True)
373 other_ref = v.UnicodeString(strip=True, required=True)
368 revisions = All(#v.NotReviewedRevisions(repo_id)(),
374 revisions = All(#v.NotReviewedRevisions(repo_id)(),
369 v.UniqueList(not_empty=True))
375 v.UniqueList(not_empty=True))
370 review_members = v.UniqueList(not_empty=True)
376 review_members = v.UniqueList(not_empty=True)
371
377
372 pullrequest_title = v.UnicodeString(strip=True, required=True, min=3)
378 pullrequest_title = v.UnicodeString(strip=True, required=True, min=3)
373 pullrequest_desc = v.UnicodeString(strip=True, required=False)
379 pullrequest_desc = v.UnicodeString(strip=True, required=False)
374
380
375 return _PullRequestForm
381 return _PullRequestForm
@@ -1,421 +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 users groups 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, 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
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
154 #the owner field of groups
155 self.grant_user_permission(repos_group=new_repos_group,
156 user=owner, perm='group.admin')
157
153 if not just_db:
158 if not just_db:
154 # 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
155 # throw any exceptions, create filesystem dirs at the very end
160 # throw any exceptions, create filesystem dirs at the very end
156 self.sa.flush()
161 self.sa.flush()
157 self.__create_group(new_repos_group.group_name)
162 self.__create_group(new_repos_group.group_name)
158
163
159 return new_repos_group
164 return new_repos_group
160 except:
165 except:
161 log.error(traceback.format_exc())
166 log.error(traceback.format_exc())
162 raise
167 raise
163
168
164 def _update_permissions(self, repos_group, perms_new=None,
169 def _update_permissions(self, repos_group, perms_new=None,
165 perms_updates=None, recursive=False):
170 perms_updates=None, recursive=False):
166 from rhodecode.model.repo import RepoModel
171 from rhodecode.model.repo import RepoModel
167 if not perms_new:
172 if not perms_new:
168 perms_new = []
173 perms_new = []
169 if not perms_updates:
174 if not perms_updates:
170 perms_updates = []
175 perms_updates = []
171
176
172 def _set_perm_user(obj, user, perm):
177 def _set_perm_user(obj, user, perm):
173 if isinstance(obj, RepoGroup):
178 if isinstance(obj, RepoGroup):
174 ReposGroupModel().grant_user_permission(
179 ReposGroupModel().grant_user_permission(
175 repos_group=obj, user=user, perm=perm
180 repos_group=obj, user=user, perm=perm
176 )
181 )
177 elif isinstance(obj, Repository):
182 elif isinstance(obj, Repository):
178 #we do this ONLY IF repository is non-private
183 #we do this ONLY IF repository is non-private
179 if obj.private:
184 if obj.private:
180 return
185 return
181
186
182 # we set group permission but we have to switch to repo
187 # we set group permission but we have to switch to repo
183 # permission
188 # permission
184 perm = perm.replace('group.', 'repository.')
189 perm = perm.replace('group.', 'repository.')
185 RepoModel().grant_user_permission(
190 RepoModel().grant_user_permission(
186 repo=obj, user=user, perm=perm
191 repo=obj, user=user, perm=perm
187 )
192 )
188
193
189 def _set_perm_group(obj, users_group, perm):
194 def _set_perm_group(obj, users_group, perm):
190 if isinstance(obj, RepoGroup):
195 if isinstance(obj, RepoGroup):
191 ReposGroupModel().grant_users_group_permission(
196 ReposGroupModel().grant_users_group_permission(
192 repos_group=obj, group_name=users_group, perm=perm
197 repos_group=obj, group_name=users_group, perm=perm
193 )
198 )
194 elif isinstance(obj, Repository):
199 elif isinstance(obj, Repository):
195 # we set group permission but we have to switch to repo
200 # we set group permission but we have to switch to repo
196 # permission
201 # permission
197 perm = perm.replace('group.', 'repository.')
202 perm = perm.replace('group.', 'repository.')
198 RepoModel().grant_users_group_permission(
203 RepoModel().grant_users_group_permission(
199 repo=obj, group_name=users_group, perm=perm
204 repo=obj, group_name=users_group, perm=perm
200 )
205 )
201 updates = []
206 updates = []
202 log.debug('Now updating permissions for %s in recursive mode:%s'
207 log.debug('Now updating permissions for %s in recursive mode:%s'
203 % (repos_group, recursive))
208 % (repos_group, recursive))
204
209
205 for obj in repos_group.recursive_groups_and_repos():
210 for obj in repos_group.recursive_groups_and_repos():
206 #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
207 if not recursive:
212 if not recursive:
208 obj = repos_group
213 obj = repos_group
209
214
210 # update permissions
215 # update permissions
211 for member, perm, member_type in perms_updates:
216 for member, perm, member_type in perms_updates:
212 ## set for user
217 ## set for user
213 if member_type == 'user':
218 if member_type == 'user':
214 # this updates also current one if found
219 # this updates also current one if found
215 _set_perm_user(obj, user=member, perm=perm)
220 _set_perm_user(obj, user=member, perm=perm)
216 ## set for users group
221 ## set for users group
217 else:
222 else:
218 _set_perm_group(obj, users_group=member, perm=perm)
223 _set_perm_group(obj, users_group=member, perm=perm)
219 # set new permissions
224 # set new permissions
220 for member, perm, member_type in perms_new:
225 for member, perm, member_type in perms_new:
221 if member_type == 'user':
226 if member_type == 'user':
222 _set_perm_user(obj, user=member, perm=perm)
227 _set_perm_user(obj, user=member, perm=perm)
223 else:
228 else:
224 _set_perm_group(obj, users_group=member, perm=perm)
229 _set_perm_group(obj, users_group=member, perm=perm)
225 updates.append(obj)
230 updates.append(obj)
226 #if it's not recursive call
231 #if it's not recursive call
227 # break the loop and don't proceed with other changes
232 # break the loop and don't proceed with other changes
228 if not recursive:
233 if not recursive:
229 break
234 break
230 return updates
235 return updates
231
236
232 def update(self, repos_group_id, form_data):
237 def update(self, repos_group, form_data):
233
238
234 try:
239 try:
235 repos_group = RepoGroup.get(repos_group_id)
240 repos_group = self._get_repos_group(repos_group)
236 recursive = form_data['recursive']
241 recursive = form_data['recursive']
237 # 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
238 # set the permissions !
243 # set the permissions !
239 # this can be potentially heavy operation
244 # this can be potentially heavy operation
240 self._update_permissions(repos_group, form_data['perms_new'],
245 self._update_permissions(repos_group, form_data['perms_new'],
241 form_data['perms_updates'], recursive)
246 form_data['perms_updates'], recursive)
242
247
243 old_path = repos_group.full_path
248 old_path = repos_group.full_path
244
249
245 # change properties
250 # change properties
246 repos_group.group_description = form_data['group_description']
251 repos_group.group_description = form_data['group_description']
247 repos_group.parent_group = RepoGroup.get(form_data['group_parent_id'])
252 repos_group.parent_group = RepoGroup.get(form_data['group_parent_id'])
248 repos_group.group_parent_id = form_data['group_parent_id']
253 repos_group.group_parent_id = form_data['group_parent_id']
249 repos_group.enable_locking = form_data['enable_locking']
254 repos_group.enable_locking = form_data['enable_locking']
250 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'])
251 new_path = repos_group.full_path
256 new_path = repos_group.full_path
252
257
253 self.sa.add(repos_group)
258 self.sa.add(repos_group)
254
259
255 # iterate over all members of this groups and set the locking !
260 # iterate over all members of this groups and set the locking !
256 # this can be potentially heavy operation
261 # this can be potentially heavy operation
257 for obj in repos_group.recursive_groups_and_repos():
262 for obj in repos_group.recursive_groups_and_repos():
258 #set the value from it's parent
263 #set the value from it's parent
259 obj.enable_locking = repos_group.enable_locking
264 obj.enable_locking = repos_group.enable_locking
260 self.sa.add(obj)
265 self.sa.add(obj)
261
266
262 # we need to get all repositories from this new group and
267 # we need to get all repositories from this new group and
263 # rename them accordingly to new group path
268 # rename them accordingly to new group path
264 for r in repos_group.repositories:
269 for r in repos_group.repositories:
265 r.repo_name = r.get_new_name(r.just_name)
270 r.repo_name = r.get_new_name(r.just_name)
266 self.sa.add(r)
271 self.sa.add(r)
267
272
268 self.__rename_group(old_path, new_path)
273 self.__rename_group(old_path, new_path)
269
274
270 return repos_group
275 return repos_group
271 except:
276 except:
272 log.error(traceback.format_exc())
277 log.error(traceback.format_exc())
273 raise
278 raise
274
279
275 def delete(self, repos_group, force_delete=False):
280 def delete(self, repos_group, force_delete=False):
276 repos_group = self._get_repos_group(repos_group)
281 repos_group = self._get_repos_group(repos_group)
277 try:
282 try:
278 self.sa.delete(repos_group)
283 self.sa.delete(repos_group)
279 self.__delete_group(repos_group, force_delete)
284 self.__delete_group(repos_group, force_delete)
280 except:
285 except:
281 log.error('Error removing repos_group %s' % repos_group)
286 log.error('Error removing repos_group %s' % repos_group)
282 raise
287 raise
283
288
284 def delete_permission(self, repos_group, obj, obj_type, recursive):
289 def delete_permission(self, repos_group, obj, obj_type, recursive):
285 """
290 """
286 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),
287 obj_type can be user or users group
292 obj_type can be user or users group
288
293
289 :param repos_group:
294 :param repos_group:
290 :param obj: user or users group id
295 :param obj: user or users group id
291 :param obj_type: user or users group type
296 :param obj_type: user or users group type
292 :param recursive: recurse to all children of group
297 :param recursive: recurse to all children of group
293 """
298 """
294 from rhodecode.model.repo import RepoModel
299 from rhodecode.model.repo import RepoModel
295 repos_group = self._get_repos_group(repos_group)
300 repos_group = self._get_repos_group(repos_group)
296
301
297 for el in repos_group.recursive_groups_and_repos():
302 for el in repos_group.recursive_groups_and_repos():
298 if not recursive:
303 if not recursive:
299 # 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
300 # object
305 # object
301 el = repos_group
306 el = repos_group
302
307
303 if isinstance(el, RepoGroup):
308 if isinstance(el, RepoGroup):
304 if obj_type == 'user':
309 if obj_type == 'user':
305 ReposGroupModel().revoke_user_permission(el, user=obj)
310 ReposGroupModel().revoke_user_permission(el, user=obj)
306 elif obj_type == 'users_group':
311 elif obj_type == 'users_group':
307 ReposGroupModel().revoke_users_group_permission(el, group_name=obj)
312 ReposGroupModel().revoke_users_group_permission(el, group_name=obj)
308 else:
313 else:
309 raise Exception('undefined object type %s' % obj_type)
314 raise Exception('undefined object type %s' % obj_type)
310 elif isinstance(el, Repository):
315 elif isinstance(el, Repository):
311 if obj_type == 'user':
316 if obj_type == 'user':
312 RepoModel().revoke_user_permission(el, user=obj)
317 RepoModel().revoke_user_permission(el, user=obj)
313 elif obj_type == 'users_group':
318 elif obj_type == 'users_group':
314 RepoModel().revoke_users_group_permission(el, group_name=obj)
319 RepoModel().revoke_users_group_permission(el, group_name=obj)
315 else:
320 else:
316 raise Exception('undefined object type %s' % obj_type)
321 raise Exception('undefined object type %s' % obj_type)
317
322
318 #if it's not recursive call
323 #if it's not recursive call
319 # break the loop and don't proceed with other changes
324 # break the loop and don't proceed with other changes
320 if not recursive:
325 if not recursive:
321 break
326 break
322
327
323 def grant_user_permission(self, repos_group, user, perm):
328 def grant_user_permission(self, repos_group, user, perm):
324 """
329 """
325 Grant permission for user on given repositories group, or update
330 Grant permission for user on given repositories group, or update
326 existing one if found
331 existing one if found
327
332
328 :param repos_group: Instance of ReposGroup, repositories_group_id,
333 :param repos_group: Instance of ReposGroup, repositories_group_id,
329 or repositories_group name
334 or repositories_group name
330 :param user: Instance of User, user_id or username
335 :param user: Instance of User, user_id or username
331 :param perm: Instance of Permission, or permission_name
336 :param perm: Instance of Permission, or permission_name
332 """
337 """
333
338
334 repos_group = self._get_repos_group(repos_group)
339 repos_group = self._get_repos_group(repos_group)
335 user = self._get_user(user)
340 user = self._get_user(user)
336 permission = self._get_perm(perm)
341 permission = self._get_perm(perm)
337
342
338 # check if we have that permission already
343 # check if we have that permission already
339 obj = self.sa.query(UserRepoGroupToPerm)\
344 obj = self.sa.query(UserRepoGroupToPerm)\
340 .filter(UserRepoGroupToPerm.user == user)\
345 .filter(UserRepoGroupToPerm.user == user)\
341 .filter(UserRepoGroupToPerm.group == repos_group)\
346 .filter(UserRepoGroupToPerm.group == repos_group)\
342 .scalar()
347 .scalar()
343 if obj is None:
348 if obj is None:
344 # create new !
349 # create new !
345 obj = UserRepoGroupToPerm()
350 obj = UserRepoGroupToPerm()
346 obj.group = repos_group
351 obj.group = repos_group
347 obj.user = user
352 obj.user = user
348 obj.permission = permission
353 obj.permission = permission
349 self.sa.add(obj)
354 self.sa.add(obj)
350 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))
351
356
352 def revoke_user_permission(self, repos_group, user):
357 def revoke_user_permission(self, repos_group, user):
353 """
358 """
354 Revoke permission for user on given repositories group
359 Revoke permission for user on given repositories group
355
360
356 :param repos_group: Instance of ReposGroup, repositories_group_id,
361 :param repos_group: Instance of ReposGroup, repositories_group_id,
357 or repositories_group name
362 or repositories_group name
358 :param user: Instance of User, user_id or username
363 :param user: Instance of User, user_id or username
359 """
364 """
360
365
361 repos_group = self._get_repos_group(repos_group)
366 repos_group = self._get_repos_group(repos_group)
362 user = self._get_user(user)
367 user = self._get_user(user)
363
368
364 obj = self.sa.query(UserRepoGroupToPerm)\
369 obj = self.sa.query(UserRepoGroupToPerm)\
365 .filter(UserRepoGroupToPerm.user == user)\
370 .filter(UserRepoGroupToPerm.user == user)\
366 .filter(UserRepoGroupToPerm.group == repos_group)\
371 .filter(UserRepoGroupToPerm.group == repos_group)\
367 .scalar()
372 .scalar()
368 if obj:
373 if obj:
369 self.sa.delete(obj)
374 self.sa.delete(obj)
370 log.debug('Revoked perm on %s on %s' % (repos_group, user))
375 log.debug('Revoked perm on %s on %s' % (repos_group, user))
371
376
372 def grant_users_group_permission(self, repos_group, group_name, perm):
377 def grant_users_group_permission(self, repos_group, group_name, perm):
373 """
378 """
374 Grant permission for users group on given repositories group, or update
379 Grant permission for users group on given repositories group, or update
375 existing one if found
380 existing one if found
376
381
377 :param repos_group: Instance of ReposGroup, repositories_group_id,
382 :param repos_group: Instance of ReposGroup, repositories_group_id,
378 or repositories_group name
383 or repositories_group name
379 :param group_name: Instance of UserGroup, users_group_id,
384 :param group_name: Instance of UserGroup, users_group_id,
380 or users group name
385 or users group name
381 :param perm: Instance of Permission, or permission_name
386 :param perm: Instance of Permission, or permission_name
382 """
387 """
383 repos_group = self._get_repos_group(repos_group)
388 repos_group = self._get_repos_group(repos_group)
384 group_name = self.__get_users_group(group_name)
389 group_name = self.__get_users_group(group_name)
385 permission = self._get_perm(perm)
390 permission = self._get_perm(perm)
386
391
387 # check if we have that permission already
392 # check if we have that permission already
388 obj = self.sa.query(UsersGroupRepoGroupToPerm)\
393 obj = self.sa.query(UsersGroupRepoGroupToPerm)\
389 .filter(UsersGroupRepoGroupToPerm.group == repos_group)\
394 .filter(UsersGroupRepoGroupToPerm.group == repos_group)\
390 .filter(UsersGroupRepoGroupToPerm.users_group == group_name)\
395 .filter(UsersGroupRepoGroupToPerm.users_group == group_name)\
391 .scalar()
396 .scalar()
392
397
393 if obj is None:
398 if obj is None:
394 # create new
399 # create new
395 obj = UsersGroupRepoGroupToPerm()
400 obj = UsersGroupRepoGroupToPerm()
396
401
397 obj.group = repos_group
402 obj.group = repos_group
398 obj.users_group = group_name
403 obj.users_group = group_name
399 obj.permission = permission
404 obj.permission = permission
400 self.sa.add(obj)
405 self.sa.add(obj)
401 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))
402
407
403 def revoke_users_group_permission(self, repos_group, group_name):
408 def revoke_users_group_permission(self, repos_group, group_name):
404 """
409 """
405 Revoke permission for users group on given repositories group
410 Revoke permission for users group on given repositories group
406
411
407 :param repos_group: Instance of ReposGroup, repositories_group_id,
412 :param repos_group: Instance of ReposGroup, repositories_group_id,
408 or repositories_group name
413 or repositories_group name
409 :param group_name: Instance of UserGroup, users_group_id,
414 :param group_name: Instance of UserGroup, users_group_id,
410 or users group name
415 or users group name
411 """
416 """
412 repos_group = self._get_repos_group(repos_group)
417 repos_group = self._get_repos_group(repos_group)
413 group_name = self.__get_users_group(group_name)
418 group_name = self.__get_users_group(group_name)
414
419
415 obj = self.sa.query(UsersGroupRepoGroupToPerm)\
420 obj = self.sa.query(UsersGroupRepoGroupToPerm)\
416 .filter(UsersGroupRepoGroupToPerm.group == repos_group)\
421 .filter(UsersGroupRepoGroupToPerm.group == repos_group)\
417 .filter(UsersGroupRepoGroupToPerm.users_group == group_name)\
422 .filter(UsersGroupRepoGroupToPerm.users_group == group_name)\
418 .scalar()
423 .scalar()
419 if obj:
424 if obj:
420 self.sa.delete(obj)
425 self.sa.delete(obj)
421 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,621 +1,635 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.model.scm
3 rhodecode.model.scm
4 ~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~
5
5
6 Scm model for RhodeCode
6 Scm model for RhodeCode
7
7
8 :created_on: Apr 9, 2010
8 :created_on: Apr 9, 2010
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 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 from __future__ import with_statement
25 from __future__ import with_statement
26 import os
26 import os
27 import re
27 import re
28 import time
28 import time
29 import traceback
29 import traceback
30 import logging
30 import logging
31 import cStringIO
31 import cStringIO
32 import pkg_resources
32 import pkg_resources
33 from os.path import dirname as dn, join as jn
33 from os.path import dirname as dn, join as jn
34
34
35 from sqlalchemy import func
35 from sqlalchemy import func
36 from pylons.i18n.translation import _
36 from pylons.i18n.translation import _
37
37
38 import rhodecode
38 import rhodecode
39 from rhodecode.lib.vcs import get_backend
39 from rhodecode.lib.vcs import get_backend
40 from rhodecode.lib.vcs.exceptions import RepositoryError
40 from rhodecode.lib.vcs.exceptions import RepositoryError
41 from rhodecode.lib.vcs.utils.lazy import LazyProperty
41 from rhodecode.lib.vcs.utils.lazy import LazyProperty
42 from rhodecode.lib.vcs.nodes import FileNode
42 from rhodecode.lib.vcs.nodes import FileNode
43 from rhodecode.lib.vcs.backends.base import EmptyChangeset
43 from rhodecode.lib.vcs.backends.base import EmptyChangeset
44
44
45 from rhodecode import BACKENDS
45 from rhodecode import BACKENDS
46 from rhodecode.lib import helpers as h
46 from rhodecode.lib import helpers as h
47 from rhodecode.lib.utils2 import safe_str, safe_unicode
47 from rhodecode.lib.utils2 import safe_str, safe_unicode
48 from rhodecode.lib.auth import HasRepoPermissionAny, HasReposGroupPermissionAny
48 from rhodecode.lib.auth import HasRepoPermissionAny, HasReposGroupPermissionAny
49 from rhodecode.lib.utils import get_repos as get_filesystem_repos, make_ui, \
49 from rhodecode.lib.utils import get_repos as get_filesystem_repos, make_ui, \
50 action_logger, REMOVED_REPO_PAT
50 action_logger, REMOVED_REPO_PAT
51 from rhodecode.model import BaseModel
51 from rhodecode.model import BaseModel
52 from rhodecode.model.db import Repository, RhodeCodeUi, CacheInvalidation, \
52 from rhodecode.model.db import Repository, RhodeCodeUi, CacheInvalidation, \
53 UserFollowing, UserLog, User, RepoGroup, PullRequest
53 UserFollowing, UserLog, User, RepoGroup, PullRequest
54
54
55 log = logging.getLogger(__name__)
55 log = logging.getLogger(__name__)
56
56
57
57
58 class UserTemp(object):
58 class UserTemp(object):
59 def __init__(self, user_id):
59 def __init__(self, user_id):
60 self.user_id = user_id
60 self.user_id = user_id
61
61
62 def __repr__(self):
62 def __repr__(self):
63 return "<%s('id:%s')>" % (self.__class__.__name__, self.user_id)
63 return "<%s('id:%s')>" % (self.__class__.__name__, self.user_id)
64
64
65
65
66 class RepoTemp(object):
66 class RepoTemp(object):
67 def __init__(self, repo_id):
67 def __init__(self, repo_id):
68 self.repo_id = repo_id
68 self.repo_id = repo_id
69
69
70 def __repr__(self):
70 def __repr__(self):
71 return "<%s('id:%s')>" % (self.__class__.__name__, self.repo_id)
71 return "<%s('id:%s')>" % (self.__class__.__name__, self.repo_id)
72
72
73
73
74 class CachedRepoList(object):
74 class CachedRepoList(object):
75 """
75 """
76 Cached repo list, uses in-memory cache after initialization, that is
76 Cached repo list, uses in-memory cache after initialization, that is
77 super fast
77 super fast
78 """
78 """
79
79
80 def __init__(self, db_repo_list, repos_path, order_by=None):
80 def __init__(self, db_repo_list, repos_path, order_by=None, perm_set=None):
81 self.db_repo_list = db_repo_list
81 self.db_repo_list = db_repo_list
82 self.repos_path = repos_path
82 self.repos_path = repos_path
83 self.order_by = order_by
83 self.order_by = order_by
84 self.reversed = (order_by or '').startswith('-')
84 self.reversed = (order_by or '').startswith('-')
85 if not perm_set:
86 perm_set = ['repository.read', 'repository.write',
87 'repository.admin']
88 self.perm_set = perm_set
85
89
86 def __len__(self):
90 def __len__(self):
87 return len(self.db_repo_list)
91 return len(self.db_repo_list)
88
92
89 def __repr__(self):
93 def __repr__(self):
90 return '<%s (%s)>' % (self.__class__.__name__, self.__len__())
94 return '<%s (%s)>' % (self.__class__.__name__, self.__len__())
91
95
92 def __iter__(self):
96 def __iter__(self):
93 # pre-propagated cache_map to save executing select statements
97 # pre-propagated cache_map to save executing select statements
94 # for each repo
98 # for each repo
95 cache_map = CacheInvalidation.get_cache_map()
99 cache_map = CacheInvalidation.get_cache_map()
96
100
97 for dbr in self.db_repo_list:
101 for dbr in self.db_repo_list:
98 scmr = dbr.scm_instance_cached(cache_map)
102 scmr = dbr.scm_instance_cached(cache_map)
99 # check permission at this level
103 # check permission at this level
100 if not HasRepoPermissionAny(
104 if not HasRepoPermissionAny(
101 'repository.read', 'repository.write', 'repository.admin'
105 *self.perm_set
102 )(dbr.repo_name, 'get repo check'):
106 )(dbr.repo_name, 'get repo check'):
103 continue
107 continue
104
108
105 if scmr is None:
109 if scmr is None:
106 log.error(
110 log.error(
107 '%s this repository is present in database but it '
111 '%s this repository is present in database but it '
108 'cannot be created as an scm instance' % dbr.repo_name
112 'cannot be created as an scm instance' % dbr.repo_name
109 )
113 )
110 continue
114 continue
111
115
112 last_change = scmr.last_change
116 last_change = scmr.last_change
113 tip = h.get_changeset_safe(scmr, 'tip')
117 tip = h.get_changeset_safe(scmr, 'tip')
114
118
115 tmp_d = {}
119 tmp_d = {}
116 tmp_d['name'] = dbr.repo_name
120 tmp_d['name'] = dbr.repo_name
117 tmp_d['name_sort'] = tmp_d['name'].lower()
121 tmp_d['name_sort'] = tmp_d['name'].lower()
118 tmp_d['raw_name'] = tmp_d['name'].lower()
122 tmp_d['raw_name'] = tmp_d['name'].lower()
119 tmp_d['description'] = dbr.description
123 tmp_d['description'] = dbr.description
120 tmp_d['description_sort'] = tmp_d['description'].lower()
124 tmp_d['description_sort'] = tmp_d['description'].lower()
121 tmp_d['last_change'] = last_change
125 tmp_d['last_change'] = last_change
122 tmp_d['last_change_sort'] = time.mktime(last_change.timetuple())
126 tmp_d['last_change_sort'] = time.mktime(last_change.timetuple())
123 tmp_d['tip'] = tip.raw_id
127 tmp_d['tip'] = tip.raw_id
124 tmp_d['tip_sort'] = tip.revision
128 tmp_d['tip_sort'] = tip.revision
125 tmp_d['rev'] = tip.revision
129 tmp_d['rev'] = tip.revision
126 tmp_d['contact'] = dbr.user.full_contact
130 tmp_d['contact'] = dbr.user.full_contact
127 tmp_d['contact_sort'] = tmp_d['contact']
131 tmp_d['contact_sort'] = tmp_d['contact']
128 tmp_d['owner_sort'] = tmp_d['contact']
132 tmp_d['owner_sort'] = tmp_d['contact']
129 tmp_d['repo_archives'] = list(scmr._get_archives())
133 tmp_d['repo_archives'] = list(scmr._get_archives())
130 tmp_d['last_msg'] = tip.message
134 tmp_d['last_msg'] = tip.message
131 tmp_d['author'] = tip.author
135 tmp_d['author'] = tip.author
132 tmp_d['dbrepo'] = dbr.get_dict()
136 tmp_d['dbrepo'] = dbr.get_dict()
133 tmp_d['dbrepo_fork'] = dbr.fork.get_dict() if dbr.fork else {}
137 tmp_d['dbrepo_fork'] = dbr.fork.get_dict() if dbr.fork else {}
134 yield tmp_d
138 yield tmp_d
135
139
136
140
137 class SimpleCachedRepoList(CachedRepoList):
141 class SimpleCachedRepoList(CachedRepoList):
138 """
142 """
139 Lighter version of CachedRepoList without the scm initialisation
143 Lighter version of CachedRepoList without the scm initialisation
140 """
144 """
141
145
142 def __iter__(self):
146 def __iter__(self):
143 for dbr in self.db_repo_list:
147 for dbr in self.db_repo_list:
144 # check permission at this level
148 # check permission at this level
145 if not HasRepoPermissionAny(
149 if not HasRepoPermissionAny(
146 'repository.read', 'repository.write', 'repository.admin'
150 *self.perm_set
147 )(dbr.repo_name, 'get repo check'):
151 )(dbr.repo_name, 'get repo check'):
148 continue
152 continue
149
153
150 tmp_d = {}
154 tmp_d = {}
151 tmp_d['name'] = dbr.repo_name
155 tmp_d['name'] = dbr.repo_name
152 tmp_d['name_sort'] = tmp_d['name'].lower()
156 tmp_d['name_sort'] = tmp_d['name'].lower()
153 tmp_d['raw_name'] = tmp_d['name'].lower()
157 tmp_d['raw_name'] = tmp_d['name'].lower()
154 tmp_d['description'] = dbr.description
158 tmp_d['description'] = dbr.description
155 tmp_d['description_sort'] = tmp_d['description'].lower()
159 tmp_d['description_sort'] = tmp_d['description'].lower()
156 tmp_d['dbrepo'] = dbr.get_dict()
160 tmp_d['dbrepo'] = dbr.get_dict()
157 tmp_d['dbrepo_fork'] = dbr.fork.get_dict() if dbr.fork else {}
161 tmp_d['dbrepo_fork'] = dbr.fork.get_dict() if dbr.fork else {}
158 yield tmp_d
162 yield tmp_d
159
163
160
164
161 class GroupList(object):
165 class GroupList(object):
162
166
163 def __init__(self, db_repo_group_list):
167 def __init__(self, db_repo_group_list, perm_set=None):
168 """
169 Creates iterator from given list of group objects, additionally
170 checking permission for them from perm_set var
171
172 :param db_repo_group_list:
173 :param perm_set: list of permissons to check
174 """
164 self.db_repo_group_list = db_repo_group_list
175 self.db_repo_group_list = db_repo_group_list
176 if not perm_set:
177 perm_set = ['group.read', 'group.write', 'group.admin']
178 self.perm_set = perm_set
165
179
166 def __len__(self):
180 def __len__(self):
167 return len(self.db_repo_group_list)
181 return len(self.db_repo_group_list)
168
182
169 def __repr__(self):
183 def __repr__(self):
170 return '<%s (%s)>' % (self.__class__.__name__, self.__len__())
184 return '<%s (%s)>' % (self.__class__.__name__, self.__len__())
171
185
172 def __iter__(self):
186 def __iter__(self):
173 for dbgr in self.db_repo_group_list:
187 for dbgr in self.db_repo_group_list:
174 # check permission at this level
188 # check permission at this level
175 if not HasReposGroupPermissionAny(
189 if not HasReposGroupPermissionAny(
176 'group.read', 'group.write', 'group.admin'
190 *self.perm_set
177 )(dbgr.group_name, 'get group repo check'):
191 )(dbgr.group_name, 'get group repo check'):
178 continue
192 continue
179
193
180 yield dbgr
194 yield dbgr
181
195
182
196
183 class ScmModel(BaseModel):
197 class ScmModel(BaseModel):
184 """
198 """
185 Generic Scm Model
199 Generic Scm Model
186 """
200 """
187
201
188 def __get_repo(self, instance):
202 def __get_repo(self, instance):
189 cls = Repository
203 cls = Repository
190 if isinstance(instance, cls):
204 if isinstance(instance, cls):
191 return instance
205 return instance
192 elif isinstance(instance, int) or safe_str(instance).isdigit():
206 elif isinstance(instance, int) or safe_str(instance).isdigit():
193 return cls.get(instance)
207 return cls.get(instance)
194 elif isinstance(instance, basestring):
208 elif isinstance(instance, basestring):
195 return cls.get_by_repo_name(instance)
209 return cls.get_by_repo_name(instance)
196 elif instance:
210 elif instance:
197 raise Exception('given object must be int, basestr or Instance'
211 raise Exception('given object must be int, basestr or Instance'
198 ' of %s got %s' % (type(cls), type(instance)))
212 ' of %s got %s' % (type(cls), type(instance)))
199
213
200 @LazyProperty
214 @LazyProperty
201 def repos_path(self):
215 def repos_path(self):
202 """
216 """
203 Get's the repositories root path from database
217 Get's the repositories root path from database
204 """
218 """
205
219
206 q = self.sa.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key == '/').one()
220 q = self.sa.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key == '/').one()
207
221
208 return q.ui_value
222 return q.ui_value
209
223
210 def repo_scan(self, repos_path=None):
224 def repo_scan(self, repos_path=None):
211 """
225 """
212 Listing of repositories in given path. This path should not be a
226 Listing of repositories in given path. This path should not be a
213 repository itself. Return a dictionary of repository objects
227 repository itself. Return a dictionary of repository objects
214
228
215 :param repos_path: path to directory containing repositories
229 :param repos_path: path to directory containing repositories
216 """
230 """
217
231
218 if repos_path is None:
232 if repos_path is None:
219 repos_path = self.repos_path
233 repos_path = self.repos_path
220
234
221 log.info('scanning for repositories in %s' % repos_path)
235 log.info('scanning for repositories in %s' % repos_path)
222
236
223 baseui = make_ui('db')
237 baseui = make_ui('db')
224 repos = {}
238 repos = {}
225
239
226 for name, path in get_filesystem_repos(repos_path, recursive=True):
240 for name, path in get_filesystem_repos(repos_path, recursive=True):
227 # skip removed repos
241 # skip removed repos
228 if REMOVED_REPO_PAT.match(name) or path[0] is None:
242 if REMOVED_REPO_PAT.match(name) or path[0] is None:
229 continue
243 continue
230
244
231 # name need to be decomposed and put back together using the /
245 # name need to be decomposed and put back together using the /
232 # since this is internal storage separator for rhodecode
246 # since this is internal storage separator for rhodecode
233 name = Repository.normalize_repo_name(name)
247 name = Repository.normalize_repo_name(name)
234
248
235 try:
249 try:
236 if name in repos:
250 if name in repos:
237 raise RepositoryError('Duplicate repository name %s '
251 raise RepositoryError('Duplicate repository name %s '
238 'found in %s' % (name, path))
252 'found in %s' % (name, path))
239 else:
253 else:
240
254
241 klass = get_backend(path[0])
255 klass = get_backend(path[0])
242
256
243 if path[0] == 'hg' and path[0] in BACKENDS.keys():
257 if path[0] == 'hg' and path[0] in BACKENDS.keys():
244 repos[name] = klass(safe_str(path[1]), baseui=baseui)
258 repos[name] = klass(safe_str(path[1]), baseui=baseui)
245
259
246 if path[0] == 'git' and path[0] in BACKENDS.keys():
260 if path[0] == 'git' and path[0] in BACKENDS.keys():
247 repos[name] = klass(path[1])
261 repos[name] = klass(path[1])
248 except OSError:
262 except OSError:
249 continue
263 continue
250
264
251 return repos
265 return repos
252
266
253 def get_repos(self, all_repos=None, sort_key=None, simple=False):
267 def get_repos(self, all_repos=None, sort_key=None, simple=False):
254 """
268 """
255 Get all repos from db and for each repo create it's
269 Get all repos from db and for each repo create it's
256 backend instance and fill that backed with information from database
270 backend instance and fill that backed with information from database
257
271
258 :param all_repos: list of repository names as strings
272 :param all_repos: list of repository names as strings
259 give specific repositories list, good for filtering
273 give specific repositories list, good for filtering
260
274
261 :param sort_key: initial sorting of repos
275 :param sort_key: initial sorting of repos
262 :param simple: use SimpleCachedList - one without the SCM info
276 :param simple: use SimpleCachedList - one without the SCM info
263 """
277 """
264 if all_repos is None:
278 if all_repos is None:
265 all_repos = self.sa.query(Repository)\
279 all_repos = self.sa.query(Repository)\
266 .filter(Repository.group_id == None)\
280 .filter(Repository.group_id == None)\
267 .order_by(func.lower(Repository.repo_name)).all()
281 .order_by(func.lower(Repository.repo_name)).all()
268 if simple:
282 if simple:
269 repo_iter = SimpleCachedRepoList(all_repos,
283 repo_iter = SimpleCachedRepoList(all_repos,
270 repos_path=self.repos_path,
284 repos_path=self.repos_path,
271 order_by=sort_key)
285 order_by=sort_key)
272 else:
286 else:
273 repo_iter = CachedRepoList(all_repos,
287 repo_iter = CachedRepoList(all_repos,
274 repos_path=self.repos_path,
288 repos_path=self.repos_path,
275 order_by=sort_key)
289 order_by=sort_key)
276
290
277 return repo_iter
291 return repo_iter
278
292
279 def get_repos_groups(self, all_groups=None):
293 def get_repos_groups(self, all_groups=None):
280 if all_groups is None:
294 if all_groups is None:
281 all_groups = RepoGroup.query()\
295 all_groups = RepoGroup.query()\
282 .filter(RepoGroup.group_parent_id == None).all()
296 .filter(RepoGroup.group_parent_id == None).all()
283 group_iter = GroupList(all_groups)
297 group_iter = GroupList(all_groups)
284
298
285 return group_iter
299 return group_iter
286
300
287 def mark_for_invalidation(self, repo_name):
301 def mark_for_invalidation(self, repo_name):
288 """
302 """
289 Puts cache invalidation task into db for
303 Puts cache invalidation task into db for
290 further global cache invalidation
304 further global cache invalidation
291
305
292 :param repo_name: this repo that should invalidation take place
306 :param repo_name: this repo that should invalidation take place
293 """
307 """
294 CacheInvalidation.set_invalidate(repo_name=repo_name)
308 CacheInvalidation.set_invalidate(repo_name=repo_name)
295 repo = Repository.get_by_repo_name(repo_name)
309 repo = Repository.get_by_repo_name(repo_name)
296 if repo:
310 if repo:
297 repo.update_changeset_cache()
311 repo.update_changeset_cache()
298
312
299 def toggle_following_repo(self, follow_repo_id, user_id):
313 def toggle_following_repo(self, follow_repo_id, user_id):
300
314
301 f = self.sa.query(UserFollowing)\
315 f = self.sa.query(UserFollowing)\
302 .filter(UserFollowing.follows_repo_id == follow_repo_id)\
316 .filter(UserFollowing.follows_repo_id == follow_repo_id)\
303 .filter(UserFollowing.user_id == user_id).scalar()
317 .filter(UserFollowing.user_id == user_id).scalar()
304
318
305 if f is not None:
319 if f is not None:
306 try:
320 try:
307 self.sa.delete(f)
321 self.sa.delete(f)
308 action_logger(UserTemp(user_id),
322 action_logger(UserTemp(user_id),
309 'stopped_following_repo',
323 'stopped_following_repo',
310 RepoTemp(follow_repo_id))
324 RepoTemp(follow_repo_id))
311 return
325 return
312 except:
326 except:
313 log.error(traceback.format_exc())
327 log.error(traceback.format_exc())
314 raise
328 raise
315
329
316 try:
330 try:
317 f = UserFollowing()
331 f = UserFollowing()
318 f.user_id = user_id
332 f.user_id = user_id
319 f.follows_repo_id = follow_repo_id
333 f.follows_repo_id = follow_repo_id
320 self.sa.add(f)
334 self.sa.add(f)
321
335
322 action_logger(UserTemp(user_id),
336 action_logger(UserTemp(user_id),
323 'started_following_repo',
337 'started_following_repo',
324 RepoTemp(follow_repo_id))
338 RepoTemp(follow_repo_id))
325 except:
339 except:
326 log.error(traceback.format_exc())
340 log.error(traceback.format_exc())
327 raise
341 raise
328
342
329 def toggle_following_user(self, follow_user_id, user_id):
343 def toggle_following_user(self, follow_user_id, user_id):
330 f = self.sa.query(UserFollowing)\
344 f = self.sa.query(UserFollowing)\
331 .filter(UserFollowing.follows_user_id == follow_user_id)\
345 .filter(UserFollowing.follows_user_id == follow_user_id)\
332 .filter(UserFollowing.user_id == user_id).scalar()
346 .filter(UserFollowing.user_id == user_id).scalar()
333
347
334 if f is not None:
348 if f is not None:
335 try:
349 try:
336 self.sa.delete(f)
350 self.sa.delete(f)
337 return
351 return
338 except:
352 except:
339 log.error(traceback.format_exc())
353 log.error(traceback.format_exc())
340 raise
354 raise
341
355
342 try:
356 try:
343 f = UserFollowing()
357 f = UserFollowing()
344 f.user_id = user_id
358 f.user_id = user_id
345 f.follows_user_id = follow_user_id
359 f.follows_user_id = follow_user_id
346 self.sa.add(f)
360 self.sa.add(f)
347 except:
361 except:
348 log.error(traceback.format_exc())
362 log.error(traceback.format_exc())
349 raise
363 raise
350
364
351 def is_following_repo(self, repo_name, user_id, cache=False):
365 def is_following_repo(self, repo_name, user_id, cache=False):
352 r = self.sa.query(Repository)\
366 r = self.sa.query(Repository)\
353 .filter(Repository.repo_name == repo_name).scalar()
367 .filter(Repository.repo_name == repo_name).scalar()
354
368
355 f = self.sa.query(UserFollowing)\
369 f = self.sa.query(UserFollowing)\
356 .filter(UserFollowing.follows_repository == r)\
370 .filter(UserFollowing.follows_repository == r)\
357 .filter(UserFollowing.user_id == user_id).scalar()
371 .filter(UserFollowing.user_id == user_id).scalar()
358
372
359 return f is not None
373 return f is not None
360
374
361 def is_following_user(self, username, user_id, cache=False):
375 def is_following_user(self, username, user_id, cache=False):
362 u = User.get_by_username(username)
376 u = User.get_by_username(username)
363
377
364 f = self.sa.query(UserFollowing)\
378 f = self.sa.query(UserFollowing)\
365 .filter(UserFollowing.follows_user == u)\
379 .filter(UserFollowing.follows_user == u)\
366 .filter(UserFollowing.user_id == user_id).scalar()
380 .filter(UserFollowing.user_id == user_id).scalar()
367
381
368 return f is not None
382 return f is not None
369
383
370 def get_followers(self, repo):
384 def get_followers(self, repo):
371 repo = self._get_repo(repo)
385 repo = self._get_repo(repo)
372
386
373 return self.sa.query(UserFollowing)\
387 return self.sa.query(UserFollowing)\
374 .filter(UserFollowing.follows_repository == repo).count()
388 .filter(UserFollowing.follows_repository == repo).count()
375
389
376 def get_forks(self, repo):
390 def get_forks(self, repo):
377 repo = self._get_repo(repo)
391 repo = self._get_repo(repo)
378 return self.sa.query(Repository)\
392 return self.sa.query(Repository)\
379 .filter(Repository.fork == repo).count()
393 .filter(Repository.fork == repo).count()
380
394
381 def get_pull_requests(self, repo):
395 def get_pull_requests(self, repo):
382 repo = self._get_repo(repo)
396 repo = self._get_repo(repo)
383 return self.sa.query(PullRequest)\
397 return self.sa.query(PullRequest)\
384 .filter(PullRequest.other_repo == repo).count()
398 .filter(PullRequest.other_repo == repo).count()
385
399
386 def mark_as_fork(self, repo, fork, user):
400 def mark_as_fork(self, repo, fork, user):
387 repo = self.__get_repo(repo)
401 repo = self.__get_repo(repo)
388 fork = self.__get_repo(fork)
402 fork = self.__get_repo(fork)
389 if fork and repo.repo_id == fork.repo_id:
403 if fork and repo.repo_id == fork.repo_id:
390 raise Exception("Cannot set repository as fork of itself")
404 raise Exception("Cannot set repository as fork of itself")
391 repo.fork = fork
405 repo.fork = fork
392 self.sa.add(repo)
406 self.sa.add(repo)
393 return repo
407 return repo
394
408
395 def pull_changes(self, repo, username):
409 def pull_changes(self, repo, username):
396 dbrepo = self.__get_repo(repo)
410 dbrepo = self.__get_repo(repo)
397 clone_uri = dbrepo.clone_uri
411 clone_uri = dbrepo.clone_uri
398 if not clone_uri:
412 if not clone_uri:
399 raise Exception("This repository doesn't have a clone uri")
413 raise Exception("This repository doesn't have a clone uri")
400
414
401 repo = dbrepo.scm_instance
415 repo = dbrepo.scm_instance
402 from rhodecode import CONFIG
416 from rhodecode import CONFIG
403 try:
417 try:
404 extras = {
418 extras = {
405 'ip': '',
419 'ip': '',
406 'username': username,
420 'username': username,
407 'action': 'push_remote',
421 'action': 'push_remote',
408 'repository': dbrepo.repo_name,
422 'repository': dbrepo.repo_name,
409 'scm': repo.alias,
423 'scm': repo.alias,
410 'config': CONFIG['__file__'],
424 'config': CONFIG['__file__'],
411 'make_lock': None,
425 'make_lock': None,
412 'locked_by': [None, None]
426 'locked_by': [None, None]
413 }
427 }
414
428
415 Repository.inject_ui(repo, extras=extras)
429 Repository.inject_ui(repo, extras=extras)
416
430
417 if repo.alias == 'git':
431 if repo.alias == 'git':
418 repo.fetch(clone_uri)
432 repo.fetch(clone_uri)
419 else:
433 else:
420 repo.pull(clone_uri)
434 repo.pull(clone_uri)
421 self.mark_for_invalidation(dbrepo.repo_name)
435 self.mark_for_invalidation(dbrepo.repo_name)
422 except:
436 except:
423 log.error(traceback.format_exc())
437 log.error(traceback.format_exc())
424 raise
438 raise
425
439
426 def commit_change(self, repo, repo_name, cs, user, author, message,
440 def commit_change(self, repo, repo_name, cs, user, author, message,
427 content, f_path):
441 content, f_path):
428 """
442 """
429 Commits changes
443 Commits changes
430
444
431 :param repo: SCM instance
445 :param repo: SCM instance
432
446
433 """
447 """
434
448
435 if repo.alias == 'hg':
449 if repo.alias == 'hg':
436 from rhodecode.lib.vcs.backends.hg import \
450 from rhodecode.lib.vcs.backends.hg import \
437 MercurialInMemoryChangeset as IMC
451 MercurialInMemoryChangeset as IMC
438 elif repo.alias == 'git':
452 elif repo.alias == 'git':
439 from rhodecode.lib.vcs.backends.git import \
453 from rhodecode.lib.vcs.backends.git import \
440 GitInMemoryChangeset as IMC
454 GitInMemoryChangeset as IMC
441
455
442 # decoding here will force that we have proper encoded values
456 # decoding here will force that we have proper encoded values
443 # in any other case this will throw exceptions and deny commit
457 # in any other case this will throw exceptions and deny commit
444 content = safe_str(content)
458 content = safe_str(content)
445 path = safe_str(f_path)
459 path = safe_str(f_path)
446 # message and author needs to be unicode
460 # message and author needs to be unicode
447 # proper backend should then translate that into required type
461 # proper backend should then translate that into required type
448 message = safe_unicode(message)
462 message = safe_unicode(message)
449 author = safe_unicode(author)
463 author = safe_unicode(author)
450 m = IMC(repo)
464 m = IMC(repo)
451 m.change(FileNode(path, content))
465 m.change(FileNode(path, content))
452 tip = m.commit(message=message,
466 tip = m.commit(message=message,
453 author=author,
467 author=author,
454 parents=[cs], branch=cs.branch)
468 parents=[cs], branch=cs.branch)
455
469
456 action = 'push_local:%s' % tip.raw_id
470 action = 'push_local:%s' % tip.raw_id
457 action_logger(user, action, repo_name)
471 action_logger(user, action, repo_name)
458 self.mark_for_invalidation(repo_name)
472 self.mark_for_invalidation(repo_name)
459 return tip
473 return tip
460
474
461 def create_node(self, repo, repo_name, cs, user, author, message, content,
475 def create_node(self, repo, repo_name, cs, user, author, message, content,
462 f_path):
476 f_path):
463 if repo.alias == 'hg':
477 if repo.alias == 'hg':
464 from rhodecode.lib.vcs.backends.hg import MercurialInMemoryChangeset as IMC
478 from rhodecode.lib.vcs.backends.hg import MercurialInMemoryChangeset as IMC
465 elif repo.alias == 'git':
479 elif repo.alias == 'git':
466 from rhodecode.lib.vcs.backends.git import GitInMemoryChangeset as IMC
480 from rhodecode.lib.vcs.backends.git import GitInMemoryChangeset as IMC
467 # decoding here will force that we have proper encoded values
481 # decoding here will force that we have proper encoded values
468 # in any other case this will throw exceptions and deny commit
482 # in any other case this will throw exceptions and deny commit
469
483
470 if isinstance(content, (basestring,)):
484 if isinstance(content, (basestring,)):
471 content = safe_str(content)
485 content = safe_str(content)
472 elif isinstance(content, (file, cStringIO.OutputType,)):
486 elif isinstance(content, (file, cStringIO.OutputType,)):
473 content = content.read()
487 content = content.read()
474 else:
488 else:
475 raise Exception('Content is of unrecognized type %s' % (
489 raise Exception('Content is of unrecognized type %s' % (
476 type(content)
490 type(content)
477 ))
491 ))
478
492
479 message = safe_unicode(message)
493 message = safe_unicode(message)
480 author = safe_unicode(author)
494 author = safe_unicode(author)
481 path = safe_str(f_path)
495 path = safe_str(f_path)
482 m = IMC(repo)
496 m = IMC(repo)
483
497
484 if isinstance(cs, EmptyChangeset):
498 if isinstance(cs, EmptyChangeset):
485 # EmptyChangeset means we we're editing empty repository
499 # EmptyChangeset means we we're editing empty repository
486 parents = None
500 parents = None
487 else:
501 else:
488 parents = [cs]
502 parents = [cs]
489
503
490 m.add(FileNode(path, content=content))
504 m.add(FileNode(path, content=content))
491 tip = m.commit(message=message,
505 tip = m.commit(message=message,
492 author=author,
506 author=author,
493 parents=parents, branch=cs.branch)
507 parents=parents, branch=cs.branch)
494
508
495 action = 'push_local:%s' % tip.raw_id
509 action = 'push_local:%s' % tip.raw_id
496 action_logger(user, action, repo_name)
510 action_logger(user, action, repo_name)
497 self.mark_for_invalidation(repo_name)
511 self.mark_for_invalidation(repo_name)
498 return tip
512 return tip
499
513
500 def get_nodes(self, repo_name, revision, root_path='/', flat=True):
514 def get_nodes(self, repo_name, revision, root_path='/', flat=True):
501 """
515 """
502 recursive walk in root dir and return a set of all path in that dir
516 recursive walk in root dir and return a set of all path in that dir
503 based on repository walk function
517 based on repository walk function
504
518
505 :param repo_name: name of repository
519 :param repo_name: name of repository
506 :param revision: revision for which to list nodes
520 :param revision: revision for which to list nodes
507 :param root_path: root path to list
521 :param root_path: root path to list
508 :param flat: return as a list, if False returns a dict with decription
522 :param flat: return as a list, if False returns a dict with decription
509
523
510 """
524 """
511 _files = list()
525 _files = list()
512 _dirs = list()
526 _dirs = list()
513 try:
527 try:
514 _repo = self.__get_repo(repo_name)
528 _repo = self.__get_repo(repo_name)
515 changeset = _repo.scm_instance.get_changeset(revision)
529 changeset = _repo.scm_instance.get_changeset(revision)
516 root_path = root_path.lstrip('/')
530 root_path = root_path.lstrip('/')
517 for topnode, dirs, files in changeset.walk(root_path):
531 for topnode, dirs, files in changeset.walk(root_path):
518 for f in files:
532 for f in files:
519 _files.append(f.path if flat else {"name": f.path,
533 _files.append(f.path if flat else {"name": f.path,
520 "type": "file"})
534 "type": "file"})
521 for d in dirs:
535 for d in dirs:
522 _dirs.append(d.path if flat else {"name": d.path,
536 _dirs.append(d.path if flat else {"name": d.path,
523 "type": "dir"})
537 "type": "dir"})
524 except RepositoryError:
538 except RepositoryError:
525 log.debug(traceback.format_exc())
539 log.debug(traceback.format_exc())
526 raise
540 raise
527
541
528 return _dirs, _files
542 return _dirs, _files
529
543
530 def get_unread_journal(self):
544 def get_unread_journal(self):
531 return self.sa.query(UserLog).count()
545 return self.sa.query(UserLog).count()
532
546
533 def get_repo_landing_revs(self, repo=None):
547 def get_repo_landing_revs(self, repo=None):
534 """
548 """
535 Generates select option with tags branches and bookmarks (for hg only)
549 Generates select option with tags branches and bookmarks (for hg only)
536 grouped by type
550 grouped by type
537
551
538 :param repo:
552 :param repo:
539 :type repo:
553 :type repo:
540 """
554 """
541
555
542 hist_l = []
556 hist_l = []
543 choices = []
557 choices = []
544 repo = self.__get_repo(repo)
558 repo = self.__get_repo(repo)
545 hist_l.append(['tip', _('latest tip')])
559 hist_l.append(['tip', _('latest tip')])
546 choices.append('tip')
560 choices.append('tip')
547 if not repo:
561 if not repo:
548 return choices, hist_l
562 return choices, hist_l
549
563
550 repo = repo.scm_instance
564 repo = repo.scm_instance
551
565
552 branches_group = ([(k, k) for k, v in
566 branches_group = ([(k, k) for k, v in
553 repo.branches.iteritems()], _("Branches"))
567 repo.branches.iteritems()], _("Branches"))
554 hist_l.append(branches_group)
568 hist_l.append(branches_group)
555 choices.extend([x[0] for x in branches_group[0]])
569 choices.extend([x[0] for x in branches_group[0]])
556
570
557 if repo.alias == 'hg':
571 if repo.alias == 'hg':
558 bookmarks_group = ([(k, k) for k, v in
572 bookmarks_group = ([(k, k) for k, v in
559 repo.bookmarks.iteritems()], _("Bookmarks"))
573 repo.bookmarks.iteritems()], _("Bookmarks"))
560 hist_l.append(bookmarks_group)
574 hist_l.append(bookmarks_group)
561 choices.extend([x[0] for x in bookmarks_group[0]])
575 choices.extend([x[0] for x in bookmarks_group[0]])
562
576
563 tags_group = ([(k, k) for k, v in
577 tags_group = ([(k, k) for k, v in
564 repo.tags.iteritems()], _("Tags"))
578 repo.tags.iteritems()], _("Tags"))
565 hist_l.append(tags_group)
579 hist_l.append(tags_group)
566 choices.extend([x[0] for x in tags_group[0]])
580 choices.extend([x[0] for x in tags_group[0]])
567
581
568 return choices, hist_l
582 return choices, hist_l
569
583
570 def install_git_hook(self, repo, force_create=False):
584 def install_git_hook(self, repo, force_create=False):
571 """
585 """
572 Creates a rhodecode hook inside a git repository
586 Creates a rhodecode hook inside a git repository
573
587
574 :param repo: Instance of VCS repo
588 :param repo: Instance of VCS repo
575 :param force_create: Create even if same name hook exists
589 :param force_create: Create even if same name hook exists
576 """
590 """
577
591
578 loc = jn(repo.path, 'hooks')
592 loc = jn(repo.path, 'hooks')
579 if not repo.bare:
593 if not repo.bare:
580 loc = jn(repo.path, '.git', 'hooks')
594 loc = jn(repo.path, '.git', 'hooks')
581 if not os.path.isdir(loc):
595 if not os.path.isdir(loc):
582 os.makedirs(loc)
596 os.makedirs(loc)
583
597
584 tmpl_post = pkg_resources.resource_string(
598 tmpl_post = pkg_resources.resource_string(
585 'rhodecode', jn('config', 'post_receive_tmpl.py')
599 'rhodecode', jn('config', 'post_receive_tmpl.py')
586 )
600 )
587 tmpl_pre = pkg_resources.resource_string(
601 tmpl_pre = pkg_resources.resource_string(
588 'rhodecode', jn('config', 'pre_receive_tmpl.py')
602 'rhodecode', jn('config', 'pre_receive_tmpl.py')
589 )
603 )
590
604
591 for h_type, tmpl in [('pre', tmpl_pre), ('post', tmpl_post)]:
605 for h_type, tmpl in [('pre', tmpl_pre), ('post', tmpl_post)]:
592 _hook_file = jn(loc, '%s-receive' % h_type)
606 _hook_file = jn(loc, '%s-receive' % h_type)
593 _rhodecode_hook = False
607 _rhodecode_hook = False
594 log.debug('Installing git hook in repo %s' % repo)
608 log.debug('Installing git hook in repo %s' % repo)
595 if os.path.exists(_hook_file):
609 if os.path.exists(_hook_file):
596 # let's take a look at this hook, maybe it's rhodecode ?
610 # let's take a look at this hook, maybe it's rhodecode ?
597 log.debug('hook exists, checking if it is from rhodecode')
611 log.debug('hook exists, checking if it is from rhodecode')
598 _HOOK_VER_PAT = re.compile(r'^RC_HOOK_VER')
612 _HOOK_VER_PAT = re.compile(r'^RC_HOOK_VER')
599 with open(_hook_file, 'rb') as f:
613 with open(_hook_file, 'rb') as f:
600 data = f.read()
614 data = f.read()
601 matches = re.compile(r'(?:%s)\s*=\s*(.*)'
615 matches = re.compile(r'(?:%s)\s*=\s*(.*)'
602 % 'RC_HOOK_VER').search(data)
616 % 'RC_HOOK_VER').search(data)
603 if matches:
617 if matches:
604 try:
618 try:
605 ver = matches.groups()[0]
619 ver = matches.groups()[0]
606 log.debug('got %s it is rhodecode' % (ver))
620 log.debug('got %s it is rhodecode' % (ver))
607 _rhodecode_hook = True
621 _rhodecode_hook = True
608 except:
622 except:
609 log.error(traceback.format_exc())
623 log.error(traceback.format_exc())
610 else:
624 else:
611 # there is no hook in this dir, so we want to create one
625 # there is no hook in this dir, so we want to create one
612 _rhodecode_hook = True
626 _rhodecode_hook = True
613
627
614 if _rhodecode_hook or force_create:
628 if _rhodecode_hook or force_create:
615 log.debug('writing %s hook file !' % h_type)
629 log.debug('writing %s hook file !' % h_type)
616 with open(_hook_file, 'wb') as f:
630 with open(_hook_file, 'wb') as f:
617 tmpl = tmpl.replace('_TMPL_', rhodecode.__version__)
631 tmpl = tmpl.replace('_TMPL_', rhodecode.__version__)
618 f.write(tmpl)
632 f.write(tmpl)
619 os.chmod(_hook_file, 0755)
633 os.chmod(_hook_file, 0755)
620 else:
634 else:
621 log.debug('skipping writing hook file')
635 log.debug('skipping writing hook file')
@@ -1,745 +1,793 b''
1 """
1 """
2 Set of generic validators
2 Set of generic validators
3 """
3 """
4 import os
4 import os
5 import re
5 import re
6 import formencode
6 import formencode
7 import logging
7 import logging
8 from collections import defaultdict
8 from collections import defaultdict
9 from pylons.i18n.translation import _
9 from pylons.i18n.translation import _
10 from webhelpers.pylonslib.secure_form import authentication_token
10 from webhelpers.pylonslib.secure_form import authentication_token
11
11
12 from formencode.validators import (
12 from formencode.validators import (
13 UnicodeString, OneOf, Int, Number, Regex, Email, Bool, StringBoolean, Set,
13 UnicodeString, OneOf, Int, Number, Regex, Email, Bool, StringBoolean, Set,
14 NotEmpty, IPAddress, CIDR
14 NotEmpty, IPAddress, CIDR
15 )
15 )
16 from rhodecode.lib.compat import OrderedSet
16 from rhodecode.lib.compat import OrderedSet
17 from rhodecode.lib import ipaddr
17 from rhodecode.lib import ipaddr
18 from rhodecode.lib.utils import repo_name_slug
18 from rhodecode.lib.utils import repo_name_slug
19 from rhodecode.model.db import RepoGroup, Repository, UsersGroup, User,\
19 from rhodecode.model.db import RepoGroup, Repository, UsersGroup, User,\
20 ChangesetStatus
20 ChangesetStatus
21 from rhodecode.lib.exceptions import LdapImportError
21 from rhodecode.lib.exceptions import LdapImportError
22 from rhodecode.config.routing import ADMIN_PREFIX
22 from rhodecode.config.routing import ADMIN_PREFIX
23 from rhodecode.lib.auth import HasReposGroupPermissionAny
23 from rhodecode.lib.auth import HasReposGroupPermissionAny
24
24
25 # silence warnings and pylint
25 # silence warnings and pylint
26 UnicodeString, OneOf, Int, Number, Regex, Email, Bool, StringBoolean, Set, \
26 UnicodeString, OneOf, Int, Number, Regex, Email, Bool, StringBoolean, Set, \
27 NotEmpty, IPAddress, CIDR
27 NotEmpty, IPAddress, CIDR
28
28
29 log = logging.getLogger(__name__)
29 log = logging.getLogger(__name__)
30
30
31
31
32 class UniqueList(formencode.FancyValidator):
32 class UniqueList(formencode.FancyValidator):
33 """
33 """
34 Unique List !
34 Unique List !
35 """
35 """
36 messages = dict(
36 messages = dict(
37 empty=_('Value cannot be an empty list'),
37 empty=_('Value cannot be an empty list'),
38 missing_value=_('Value cannot be an empty list'),
38 missing_value=_('Value cannot be an empty list'),
39 )
39 )
40
40
41 def _to_python(self, value, state):
41 def _to_python(self, value, state):
42 if isinstance(value, list):
42 if isinstance(value, list):
43 return value
43 return value
44 elif isinstance(value, set):
44 elif isinstance(value, set):
45 return list(value)
45 return list(value)
46 elif isinstance(value, tuple):
46 elif isinstance(value, tuple):
47 return list(value)
47 return list(value)
48 elif value is None:
48 elif value is None:
49 return []
49 return []
50 else:
50 else:
51 return [value]
51 return [value]
52
52
53 def empty_value(self, value):
53 def empty_value(self, value):
54 return []
54 return []
55
55
56
56
57 class StateObj(object):
57 class StateObj(object):
58 """
58 """
59 this is needed to translate the messages using _() in validators
59 this is needed to translate the messages using _() in validators
60 """
60 """
61 _ = staticmethod(_)
61 _ = staticmethod(_)
62
62
63
63
64 def M(self, key, state=None, **kwargs):
64 def M(self, key, state=None, **kwargs):
65 """
65 """
66 returns string from self.message based on given key,
66 returns string from self.message based on given key,
67 passed kw params are used to substitute %(named)s params inside
67 passed kw params are used to substitute %(named)s params inside
68 translated strings
68 translated strings
69
69
70 :param msg:
70 :param msg:
71 :param state:
71 :param state:
72 """
72 """
73 if state is None:
73 if state is None:
74 state = StateObj()
74 state = StateObj()
75 else:
75 else:
76 state._ = staticmethod(_)
76 state._ = staticmethod(_)
77 #inject validator into state object
77 #inject validator into state object
78 return self.message(key, state, **kwargs)
78 return self.message(key, state, **kwargs)
79
79
80
80
81 def ValidUsername(edit=False, old_data={}):
81 def ValidUsername(edit=False, old_data={}):
82 class _validator(formencode.validators.FancyValidator):
82 class _validator(formencode.validators.FancyValidator):
83 messages = {
83 messages = {
84 'username_exists': _(u'Username "%(username)s" already exists'),
84 'username_exists': _(u'Username "%(username)s" already exists'),
85 'system_invalid_username':
85 'system_invalid_username':
86 _(u'Username "%(username)s" is forbidden'),
86 _(u'Username "%(username)s" is forbidden'),
87 'invalid_username':
87 'invalid_username':
88 _(u'Username may only contain alphanumeric characters '
88 _(u'Username may only contain alphanumeric characters '
89 'underscores, periods or dashes and must begin with '
89 'underscores, periods or dashes and must begin with '
90 'alphanumeric character')
90 'alphanumeric character')
91 }
91 }
92
92
93 def validate_python(self, value, state):
93 def validate_python(self, value, state):
94 if value in ['default', 'new_user']:
94 if value in ['default', 'new_user']:
95 msg = M(self, 'system_invalid_username', state, username=value)
95 msg = M(self, 'system_invalid_username', state, username=value)
96 raise formencode.Invalid(msg, value, state)
96 raise formencode.Invalid(msg, value, state)
97 #check if user is unique
97 #check if user is unique
98 old_un = None
98 old_un = None
99 if edit:
99 if edit:
100 old_un = User.get(old_data.get('user_id')).username
100 old_un = User.get(old_data.get('user_id')).username
101
101
102 if old_un != value or not edit:
102 if old_un != value or not edit:
103 if User.get_by_username(value, case_insensitive=True):
103 if User.get_by_username(value, case_insensitive=True):
104 msg = M(self, 'username_exists', state, username=value)
104 msg = M(self, 'username_exists', state, username=value)
105 raise formencode.Invalid(msg, value, state)
105 raise formencode.Invalid(msg, value, state)
106
106
107 if re.match(r'^[a-zA-Z0-9]{1}[a-zA-Z0-9\-\_\.]*$', value) is None:
107 if re.match(r'^[a-zA-Z0-9]{1}[a-zA-Z0-9\-\_\.]*$', value) is None:
108 msg = M(self, 'invalid_username', state)
108 msg = M(self, 'invalid_username', state)
109 raise formencode.Invalid(msg, value, state)
109 raise formencode.Invalid(msg, value, state)
110 return _validator
110 return _validator
111
111
112
112
113 def ValidRepoUser():
113 def ValidRepoUser():
114 class _validator(formencode.validators.FancyValidator):
114 class _validator(formencode.validators.FancyValidator):
115 messages = {
115 messages = {
116 'invalid_username': _(u'Username %(username)s is not valid')
116 'invalid_username': _(u'Username %(username)s is not valid')
117 }
117 }
118
118
119 def validate_python(self, value, state):
119 def validate_python(self, value, state):
120 try:
120 try:
121 User.query().filter(User.active == True)\
121 User.query().filter(User.active == True)\
122 .filter(User.username == value).one()
122 .filter(User.username == value).one()
123 except Exception:
123 except Exception:
124 msg = M(self, 'invalid_username', state, username=value)
124 msg = M(self, 'invalid_username', state, username=value)
125 raise formencode.Invalid(msg, value, state,
125 raise formencode.Invalid(msg, value, state,
126 error_dict=dict(username=msg)
126 error_dict=dict(username=msg)
127 )
127 )
128
128
129 return _validator
129 return _validator
130
130
131
131
132 def ValidUsersGroup(edit=False, old_data={}):
132 def ValidUsersGroup(edit=False, old_data={}):
133 class _validator(formencode.validators.FancyValidator):
133 class _validator(formencode.validators.FancyValidator):
134 messages = {
134 messages = {
135 'invalid_group': _(u'Invalid users group name'),
135 'invalid_group': _(u'Invalid users group name'),
136 'group_exist': _(u'Users group "%(usersgroup)s" already exists'),
136 'group_exist': _(u'Users group "%(usersgroup)s" already exists'),
137 'invalid_usersgroup_name':
137 'invalid_usersgroup_name':
138 _(u'users group name may only contain alphanumeric '
138 _(u'users group name may only contain alphanumeric '
139 'characters underscores, periods or dashes and must begin '
139 'characters underscores, periods or dashes and must begin '
140 'with alphanumeric character')
140 'with alphanumeric character')
141 }
141 }
142
142
143 def validate_python(self, value, state):
143 def validate_python(self, value, state):
144 if value in ['default']:
144 if value in ['default']:
145 msg = M(self, 'invalid_group', state)
145 msg = M(self, 'invalid_group', state)
146 raise formencode.Invalid(msg, value, state,
146 raise formencode.Invalid(msg, value, state,
147 error_dict=dict(users_group_name=msg)
147 error_dict=dict(users_group_name=msg)
148 )
148 )
149 #check if group is unique
149 #check if group is unique
150 old_ugname = None
150 old_ugname = None
151 if edit:
151 if edit:
152 old_id = old_data.get('users_group_id')
152 old_id = old_data.get('users_group_id')
153 old_ugname = UsersGroup.get(old_id).users_group_name
153 old_ugname = UsersGroup.get(old_id).users_group_name
154
154
155 if old_ugname != value or not edit:
155 if old_ugname != value or not edit:
156 is_existing_group = UsersGroup.get_by_group_name(value,
156 is_existing_group = UsersGroup.get_by_group_name(value,
157 case_insensitive=True)
157 case_insensitive=True)
158 if is_existing_group:
158 if is_existing_group:
159 msg = M(self, 'group_exist', state, usersgroup=value)
159 msg = M(self, 'group_exist', state, usersgroup=value)
160 raise formencode.Invalid(msg, value, state,
160 raise formencode.Invalid(msg, value, state,
161 error_dict=dict(users_group_name=msg)
161 error_dict=dict(users_group_name=msg)
162 )
162 )
163
163
164 if re.match(r'^[a-zA-Z0-9]{1}[a-zA-Z0-9\-\_\.]+$', value) is None:
164 if re.match(r'^[a-zA-Z0-9]{1}[a-zA-Z0-9\-\_\.]+$', value) is None:
165 msg = M(self, 'invalid_usersgroup_name', state)
165 msg = M(self, 'invalid_usersgroup_name', state)
166 raise formencode.Invalid(msg, value, state,
166 raise formencode.Invalid(msg, value, state,
167 error_dict=dict(users_group_name=msg)
167 error_dict=dict(users_group_name=msg)
168 )
168 )
169
169
170 return _validator
170 return _validator
171
171
172
172
173 def ValidReposGroup(edit=False, old_data={}):
173 def ValidReposGroup(edit=False, old_data={}):
174 class _validator(formencode.validators.FancyValidator):
174 class _validator(formencode.validators.FancyValidator):
175 messages = {
175 messages = {
176 'group_parent_id': _(u'Cannot assign this group as parent'),
176 'group_parent_id': _(u'Cannot assign this group as parent'),
177 'group_exists': _(u'Group "%(group_name)s" already exists'),
177 'group_exists': _(u'Group "%(group_name)s" already exists'),
178 'repo_exists':
178 'repo_exists':
179 _(u'Repository with name "%(group_name)s" already exists')
179 _(u'Repository with name "%(group_name)s" already exists')
180 }
180 }
181
181
182 def validate_python(self, value, state):
182 def validate_python(self, value, state):
183 # TODO WRITE VALIDATIONS
183 # TODO WRITE VALIDATIONS
184 group_name = value.get('group_name')
184 group_name = value.get('group_name')
185 group_parent_id = value.get('group_parent_id')
185 group_parent_id = value.get('group_parent_id')
186
186
187 # slugify repo group just in case :)
187 # slugify repo group just in case :)
188 slug = repo_name_slug(group_name)
188 slug = repo_name_slug(group_name)
189
189
190 # check for parent of self
190 # check for parent of self
191 parent_of_self = lambda: (
191 parent_of_self = lambda: (
192 old_data['group_id'] == int(group_parent_id)
192 old_data['group_id'] == int(group_parent_id)
193 if group_parent_id else False
193 if group_parent_id else False
194 )
194 )
195 if edit and parent_of_self():
195 if edit and parent_of_self():
196 msg = M(self, 'group_parent_id', state)
196 msg = M(self, 'group_parent_id', state)
197 raise formencode.Invalid(msg, value, state,
197 raise formencode.Invalid(msg, value, state,
198 error_dict=dict(group_parent_id=msg)
198 error_dict=dict(group_parent_id=msg)
199 )
199 )
200
200
201 old_gname = None
201 old_gname = None
202 if edit:
202 if edit:
203 old_gname = RepoGroup.get(old_data.get('group_id')).group_name
203 old_gname = RepoGroup.get(old_data.get('group_id')).group_name
204
204
205 if old_gname != group_name or not edit:
205 if old_gname != group_name or not edit:
206
206
207 # check group
207 # check group
208 gr = RepoGroup.query()\
208 gr = RepoGroup.query()\
209 .filter(RepoGroup.group_name == slug)\
209 .filter(RepoGroup.group_name == slug)\
210 .filter(RepoGroup.group_parent_id == group_parent_id)\
210 .filter(RepoGroup.group_parent_id == group_parent_id)\
211 .scalar()
211 .scalar()
212
212
213 if gr:
213 if gr:
214 msg = M(self, 'group_exists', state, group_name=slug)
214 msg = M(self, 'group_exists', state, group_name=slug)
215 raise formencode.Invalid(msg, value, state,
215 raise formencode.Invalid(msg, value, state,
216 error_dict=dict(group_name=msg)
216 error_dict=dict(group_name=msg)
217 )
217 )
218
218
219 # check for same repo
219 # check for same repo
220 repo = Repository.query()\
220 repo = Repository.query()\
221 .filter(Repository.repo_name == slug)\
221 .filter(Repository.repo_name == slug)\
222 .scalar()
222 .scalar()
223
223
224 if repo:
224 if repo:
225 msg = M(self, 'repo_exists', state, group_name=slug)
225 msg = M(self, 'repo_exists', state, group_name=slug)
226 raise formencode.Invalid(msg, value, state,
226 raise formencode.Invalid(msg, value, state,
227 error_dict=dict(group_name=msg)
227 error_dict=dict(group_name=msg)
228 )
228 )
229
229
230 return _validator
230 return _validator
231
231
232
232
233 def ValidPassword():
233 def ValidPassword():
234 class _validator(formencode.validators.FancyValidator):
234 class _validator(formencode.validators.FancyValidator):
235 messages = {
235 messages = {
236 'invalid_password':
236 'invalid_password':
237 _(u'Invalid characters (non-ascii) in password')
237 _(u'Invalid characters (non-ascii) in password')
238 }
238 }
239
239
240 def validate_python(self, value, state):
240 def validate_python(self, value, state):
241 try:
241 try:
242 (value or '').decode('ascii')
242 (value or '').decode('ascii')
243 except UnicodeError:
243 except UnicodeError:
244 msg = M(self, 'invalid_password', state)
244 msg = M(self, 'invalid_password', state)
245 raise formencode.Invalid(msg, value, state,)
245 raise formencode.Invalid(msg, value, state,)
246 return _validator
246 return _validator
247
247
248
248
249 def ValidPasswordsMatch():
249 def ValidPasswordsMatch():
250 class _validator(formencode.validators.FancyValidator):
250 class _validator(formencode.validators.FancyValidator):
251 messages = {
251 messages = {
252 'password_mismatch': _(u'Passwords do not match'),
252 'password_mismatch': _(u'Passwords do not match'),
253 }
253 }
254
254
255 def validate_python(self, value, state):
255 def validate_python(self, value, state):
256
256
257 pass_val = value.get('password') or value.get('new_password')
257 pass_val = value.get('password') or value.get('new_password')
258 if pass_val != value['password_confirmation']:
258 if pass_val != value['password_confirmation']:
259 msg = M(self, 'password_mismatch', state)
259 msg = M(self, 'password_mismatch', state)
260 raise formencode.Invalid(msg, value, state,
260 raise formencode.Invalid(msg, value, state,
261 error_dict=dict(password_confirmation=msg)
261 error_dict=dict(password_confirmation=msg)
262 )
262 )
263 return _validator
263 return _validator
264
264
265
265
266 def ValidAuth():
266 def ValidAuth():
267 class _validator(formencode.validators.FancyValidator):
267 class _validator(formencode.validators.FancyValidator):
268 messages = {
268 messages = {
269 'invalid_password': _(u'invalid password'),
269 'invalid_password': _(u'invalid password'),
270 'invalid_username': _(u'invalid user name'),
270 'invalid_username': _(u'invalid user name'),
271 'disabled_account': _(u'Your account is disabled')
271 'disabled_account': _(u'Your account is disabled')
272 }
272 }
273
273
274 def validate_python(self, value, state):
274 def validate_python(self, value, state):
275 from rhodecode.lib.auth import authenticate
275 from rhodecode.lib.auth import authenticate
276
276
277 password = value['password']
277 password = value['password']
278 username = value['username']
278 username = value['username']
279
279
280 if not authenticate(username, password):
280 if not authenticate(username, password):
281 user = User.get_by_username(username)
281 user = User.get_by_username(username)
282 if user and user.active is False:
282 if user and user.active is False:
283 log.warning('user %s is disabled' % username)
283 log.warning('user %s is disabled' % username)
284 msg = M(self, 'disabled_account', state)
284 msg = M(self, 'disabled_account', state)
285 raise formencode.Invalid(msg, value, state,
285 raise formencode.Invalid(msg, value, state,
286 error_dict=dict(username=msg)
286 error_dict=dict(username=msg)
287 )
287 )
288 else:
288 else:
289 log.warning('user %s failed to authenticate' % username)
289 log.warning('user %s failed to authenticate' % username)
290 msg = M(self, 'invalid_username', state)
290 msg = M(self, 'invalid_username', state)
291 msg2 = M(self, 'invalid_password', state)
291 msg2 = M(self, 'invalid_password', state)
292 raise formencode.Invalid(msg, value, state,
292 raise formencode.Invalid(msg, value, state,
293 error_dict=dict(username=msg, password=msg2)
293 error_dict=dict(username=msg, password=msg2)
294 )
294 )
295 return _validator
295 return _validator
296
296
297
297
298 def ValidAuthToken():
298 def ValidAuthToken():
299 class _validator(formencode.validators.FancyValidator):
299 class _validator(formencode.validators.FancyValidator):
300 messages = {
300 messages = {
301 'invalid_token': _(u'Token mismatch')
301 'invalid_token': _(u'Token mismatch')
302 }
302 }
303
303
304 def validate_python(self, value, state):
304 def validate_python(self, value, state):
305 if value != authentication_token():
305 if value != authentication_token():
306 msg = M(self, 'invalid_token', state)
306 msg = M(self, 'invalid_token', state)
307 raise formencode.Invalid(msg, value, state)
307 raise formencode.Invalid(msg, value, state)
308 return _validator
308 return _validator
309
309
310
310
311 def ValidRepoName(edit=False, old_data={}):
311 def ValidRepoName(edit=False, old_data={}):
312 class _validator(formencode.validators.FancyValidator):
312 class _validator(formencode.validators.FancyValidator):
313 messages = {
313 messages = {
314 'invalid_repo_name':
314 'invalid_repo_name':
315 _(u'Repository name %(repo)s is disallowed'),
315 _(u'Repository name %(repo)s is disallowed'),
316 'repository_exists':
316 'repository_exists':
317 _(u'Repository named %(repo)s already exists'),
317 _(u'Repository named %(repo)s already exists'),
318 'repository_in_group_exists': _(u'Repository "%(repo)s" already '
318 'repository_in_group_exists': _(u'Repository "%(repo)s" already '
319 'exists in group "%(group)s"'),
319 'exists in group "%(group)s"'),
320 'same_group_exists': _(u'Repositories group with name "%(repo)s" '
320 'same_group_exists': _(u'Repositories group with name "%(repo)s" '
321 'already exists')
321 'already exists')
322 }
322 }
323
323
324 def _to_python(self, value, state):
324 def _to_python(self, value, state):
325 repo_name = repo_name_slug(value.get('repo_name', ''))
325 repo_name = repo_name_slug(value.get('repo_name', ''))
326 repo_group = value.get('repo_group')
326 repo_group = value.get('repo_group')
327 if repo_group:
327 if repo_group:
328 gr = RepoGroup.get(repo_group)
328 gr = RepoGroup.get(repo_group)
329 group_path = gr.full_path
329 group_path = gr.full_path
330 group_name = gr.group_name
330 group_name = gr.group_name
331 # value needs to be aware of group name in order to check
331 # value needs to be aware of group name in order to check
332 # db key This is an actual just the name to store in the
332 # db key This is an actual just the name to store in the
333 # database
333 # database
334 repo_name_full = group_path + RepoGroup.url_sep() + repo_name
334 repo_name_full = group_path + RepoGroup.url_sep() + repo_name
335 else:
335 else:
336 group_name = group_path = ''
336 group_name = group_path = ''
337 repo_name_full = repo_name
337 repo_name_full = repo_name
338
338
339 value['repo_name'] = repo_name
339 value['repo_name'] = repo_name
340 value['repo_name_full'] = repo_name_full
340 value['repo_name_full'] = repo_name_full
341 value['group_path'] = group_path
341 value['group_path'] = group_path
342 value['group_name'] = group_name
342 value['group_name'] = group_name
343 return value
343 return value
344
344
345 def validate_python(self, value, state):
345 def validate_python(self, value, state):
346
346
347 repo_name = value.get('repo_name')
347 repo_name = value.get('repo_name')
348 repo_name_full = value.get('repo_name_full')
348 repo_name_full = value.get('repo_name_full')
349 group_path = value.get('group_path')
349 group_path = value.get('group_path')
350 group_name = value.get('group_name')
350 group_name = value.get('group_name')
351
351
352 if repo_name in [ADMIN_PREFIX, '']:
352 if repo_name in [ADMIN_PREFIX, '']:
353 msg = M(self, 'invalid_repo_name', state, repo=repo_name)
353 msg = M(self, 'invalid_repo_name', state, repo=repo_name)
354 raise formencode.Invalid(msg, value, state,
354 raise formencode.Invalid(msg, value, state,
355 error_dict=dict(repo_name=msg)
355 error_dict=dict(repo_name=msg)
356 )
356 )
357
357
358 rename = old_data.get('repo_name') != repo_name_full
358 rename = old_data.get('repo_name') != repo_name_full
359 create = not edit
359 create = not edit
360 if rename or create:
360 if rename or create:
361
361
362 if group_path != '':
362 if group_path != '':
363 if Repository.get_by_repo_name(repo_name_full):
363 if Repository.get_by_repo_name(repo_name_full):
364 msg = M(self, 'repository_in_group_exists', state,
364 msg = M(self, 'repository_in_group_exists', state,
365 repo=repo_name, group=group_name)
365 repo=repo_name, group=group_name)
366 raise formencode.Invalid(msg, value, state,
366 raise formencode.Invalid(msg, value, state,
367 error_dict=dict(repo_name=msg)
367 error_dict=dict(repo_name=msg)
368 )
368 )
369 elif RepoGroup.get_by_group_name(repo_name_full):
369 elif RepoGroup.get_by_group_name(repo_name_full):
370 msg = M(self, 'same_group_exists', state,
370 msg = M(self, 'same_group_exists', state,
371 repo=repo_name)
371 repo=repo_name)
372 raise formencode.Invalid(msg, value, state,
372 raise formencode.Invalid(msg, value, state,
373 error_dict=dict(repo_name=msg)
373 error_dict=dict(repo_name=msg)
374 )
374 )
375
375
376 elif Repository.get_by_repo_name(repo_name_full):
376 elif Repository.get_by_repo_name(repo_name_full):
377 msg = M(self, 'repository_exists', state,
377 msg = M(self, 'repository_exists', state,
378 repo=repo_name)
378 repo=repo_name)
379 raise formencode.Invalid(msg, value, state,
379 raise formencode.Invalid(msg, value, state,
380 error_dict=dict(repo_name=msg)
380 error_dict=dict(repo_name=msg)
381 )
381 )
382 return value
382 return value
383 return _validator
383 return _validator
384
384
385
385
386 def ValidForkName(*args, **kwargs):
386 def ValidForkName(*args, **kwargs):
387 return ValidRepoName(*args, **kwargs)
387 return ValidRepoName(*args, **kwargs)
388
388
389
389
390 def SlugifyName():
390 def SlugifyName():
391 class _validator(formencode.validators.FancyValidator):
391 class _validator(formencode.validators.FancyValidator):
392
392
393 def _to_python(self, value, state):
393 def _to_python(self, value, state):
394 return repo_name_slug(value)
394 return repo_name_slug(value)
395
395
396 def validate_python(self, value, state):
396 def validate_python(self, value, state):
397 pass
397 pass
398
398
399 return _validator
399 return _validator
400
400
401
401
402 def ValidCloneUri():
402 def ValidCloneUri():
403 from rhodecode.lib.utils import make_ui
403 from rhodecode.lib.utils import make_ui
404
404
405 def url_handler(repo_type, url, ui=None):
405 def url_handler(repo_type, url, ui=None):
406 if repo_type == 'hg':
406 if repo_type == 'hg':
407 from rhodecode.lib.vcs.backends.hg.repository import MercurialRepository
407 from rhodecode.lib.vcs.backends.hg.repository import MercurialRepository
408 from mercurial.httppeer import httppeer
408 from mercurial.httppeer import httppeer
409 if url.startswith('http'):
409 if url.startswith('http'):
410 ## initially check if it's at least the proper URL
410 ## initially check if it's at least the proper URL
411 ## or does it pass basic auth
411 ## or does it pass basic auth
412 MercurialRepository._check_url(url)
412 MercurialRepository._check_url(url)
413 httppeer(ui, url)._capabilities()
413 httppeer(ui, url)._capabilities()
414 elif url.startswith('svn+http'):
414 elif url.startswith('svn+http'):
415 from hgsubversion.svnrepo import svnremoterepo
415 from hgsubversion.svnrepo import svnremoterepo
416 svnremoterepo(ui, url).capabilities
416 svnremoterepo(ui, url).capabilities
417 elif url.startswith('git+http'):
417 elif url.startswith('git+http'):
418 raise NotImplementedError()
418 raise NotImplementedError()
419
419
420 elif repo_type == 'git':
420 elif repo_type == 'git':
421 from rhodecode.lib.vcs.backends.git.repository import GitRepository
421 from rhodecode.lib.vcs.backends.git.repository import GitRepository
422 if url.startswith('http'):
422 if url.startswith('http'):
423 ## initially check if it's at least the proper URL
423 ## initially check if it's at least the proper URL
424 ## or does it pass basic auth
424 ## or does it pass basic auth
425 GitRepository._check_url(url)
425 GitRepository._check_url(url)
426 elif url.startswith('svn+http'):
426 elif url.startswith('svn+http'):
427 raise NotImplementedError()
427 raise NotImplementedError()
428 elif url.startswith('hg+http'):
428 elif url.startswith('hg+http'):
429 raise NotImplementedError()
429 raise NotImplementedError()
430
430
431 class _validator(formencode.validators.FancyValidator):
431 class _validator(formencode.validators.FancyValidator):
432 messages = {
432 messages = {
433 'clone_uri': _(u'invalid clone url'),
433 'clone_uri': _(u'invalid clone url'),
434 'invalid_clone_uri': _(u'Invalid clone url, provide a '
434 'invalid_clone_uri': _(u'Invalid clone url, provide a '
435 'valid clone http(s)/svn+http(s) url')
435 'valid clone http(s)/svn+http(s) url')
436 }
436 }
437
437
438 def validate_python(self, value, state):
438 def validate_python(self, value, state):
439 repo_type = value.get('repo_type')
439 repo_type = value.get('repo_type')
440 url = value.get('clone_uri')
440 url = value.get('clone_uri')
441
441
442 if not url:
442 if not url:
443 pass
443 pass
444 else:
444 else:
445 try:
445 try:
446 url_handler(repo_type, url, make_ui('db', clear_session=False))
446 url_handler(repo_type, url, make_ui('db', clear_session=False))
447 except Exception:
447 except Exception:
448 log.exception('Url validation failed')
448 log.exception('Url validation failed')
449 msg = M(self, 'clone_uri')
449 msg = M(self, 'clone_uri')
450 raise formencode.Invalid(msg, value, state,
450 raise formencode.Invalid(msg, value, state,
451 error_dict=dict(clone_uri=msg)
451 error_dict=dict(clone_uri=msg)
452 )
452 )
453 return _validator
453 return _validator
454
454
455
455
456 def ValidForkType(old_data={}):
456 def ValidForkType(old_data={}):
457 class _validator(formencode.validators.FancyValidator):
457 class _validator(formencode.validators.FancyValidator):
458 messages = {
458 messages = {
459 'invalid_fork_type': _(u'Fork have to be the same type as parent')
459 'invalid_fork_type': _(u'Fork have to be the same type as parent')
460 }
460 }
461
461
462 def validate_python(self, value, state):
462 def validate_python(self, value, state):
463 if old_data['repo_type'] != value:
463 if old_data['repo_type'] != value:
464 msg = M(self, 'invalid_fork_type', state)
464 msg = M(self, 'invalid_fork_type', state)
465 raise formencode.Invalid(msg, value, state,
465 raise formencode.Invalid(msg, value, state,
466 error_dict=dict(repo_type=msg)
466 error_dict=dict(repo_type=msg)
467 )
467 )
468 return _validator
468 return _validator
469
469
470
470
471 def CanWriteGroup():
471 def CanWriteGroup():
472 class _validator(formencode.validators.FancyValidator):
472 class _validator(formencode.validators.FancyValidator):
473 messages = {
473 messages = {
474 'permission_denied': _(u"You don't have permissions "
474 'permission_denied': _(u"You don't have permissions "
475 "to create repository in this group")
475 "to create repository in this group")
476 }
476 }
477
477
478 def to_python(self, value, state):
479 #root location
480 if value in [-1, "-1"]:
481 return None
482 return value
483
478 def validate_python(self, value, state):
484 def validate_python(self, value, state):
479 gr = RepoGroup.get(value)
485 gr = RepoGroup.get(value)
480 if not HasReposGroupPermissionAny(
486 gr_name = gr.group_name if gr else None # None means ROOT location
481 'group.write', 'group.admin'
487 val = HasReposGroupPermissionAny('group.write', 'group.admin')
482 )(gr.group_name, 'get group of repo form'):
488 forbidden = not val(gr_name, 'can write into group validator')
489 #parent group need to be existing
490 if gr and forbidden:
483 msg = M(self, 'permission_denied', state)
491 msg = M(self, 'permission_denied', state)
484 raise formencode.Invalid(msg, value, state,
492 raise formencode.Invalid(msg, value, state,
485 error_dict=dict(repo_type=msg)
493 error_dict=dict(repo_type=msg)
486 )
494 )
487 return _validator
495 return _validator
488
496
489
497
498 def CanCreateGroup(can_create_in_root=False):
499 class _validator(formencode.validators.FancyValidator):
500 messages = {
501 'permission_denied': _(u"You don't have permissions "
502 "to create a group in this location")
503 }
504
505 def to_python(self, value, state):
506 #root location
507 if value in [-1, "-1"]:
508 return None
509 return value
510
511 def validate_python(self, value, state):
512 #TODO: REMOVE THIS !!
513 ################################
514 import ipdb;ipdb.set_trace()
515 print 'setting ipdb debuggin for rhodecode.model.validators._validator.validate_python'
516 ################################
517
518
519 gr = RepoGroup.get(value)
520 gr_name = gr.group_name if gr else None # None means ROOT location
521
522 if can_create_in_root and gr is None:
523 #we can create in root, we're fine no validations required
524 return
525
526 forbidden_in_root = gr is None and can_create_in_root is False
527 val = HasReposGroupPermissionAny('group.admin')
528 forbidden = not val(gr_name, 'can create group validator')
529 if forbidden_in_root or forbidden:
530 msg = M(self, 'permission_denied', state)
531 raise formencode.Invalid(msg, value, state,
532 error_dict=dict(group_parent_id=msg)
533 )
534
535 return _validator
536
537
490 def ValidPerms(type_='repo'):
538 def ValidPerms(type_='repo'):
491 if type_ == 'group':
539 if type_ == 'group':
492 EMPTY_PERM = 'group.none'
540 EMPTY_PERM = 'group.none'
493 elif type_ == 'repo':
541 elif type_ == 'repo':
494 EMPTY_PERM = 'repository.none'
542 EMPTY_PERM = 'repository.none'
495
543
496 class _validator(formencode.validators.FancyValidator):
544 class _validator(formencode.validators.FancyValidator):
497 messages = {
545 messages = {
498 'perm_new_member_name':
546 'perm_new_member_name':
499 _(u'This username or users group name is not valid')
547 _(u'This username or users group name is not valid')
500 }
548 }
501
549
502 def to_python(self, value, state):
550 def to_python(self, value, state):
503 perms_update = OrderedSet()
551 perms_update = OrderedSet()
504 perms_new = OrderedSet()
552 perms_new = OrderedSet()
505 # build a list of permission to update and new permission to create
553 # build a list of permission to update and new permission to create
506
554
507 #CLEAN OUT ORG VALUE FROM NEW MEMBERS, and group them using
555 #CLEAN OUT ORG VALUE FROM NEW MEMBERS, and group them using
508 new_perms_group = defaultdict(dict)
556 new_perms_group = defaultdict(dict)
509 for k, v in value.copy().iteritems():
557 for k, v in value.copy().iteritems():
510 if k.startswith('perm_new_member'):
558 if k.startswith('perm_new_member'):
511 del value[k]
559 del value[k]
512 _type, part = k.split('perm_new_member_')
560 _type, part = k.split('perm_new_member_')
513 args = part.split('_')
561 args = part.split('_')
514 if len(args) == 1:
562 if len(args) == 1:
515 new_perms_group[args[0]]['perm'] = v
563 new_perms_group[args[0]]['perm'] = v
516 elif len(args) == 2:
564 elif len(args) == 2:
517 _key, pos = args
565 _key, pos = args
518 new_perms_group[pos][_key] = v
566 new_perms_group[pos][_key] = v
519
567
520 # fill new permissions in order of how they were added
568 # fill new permissions in order of how they were added
521 for k in sorted(map(int, new_perms_group.keys())):
569 for k in sorted(map(int, new_perms_group.keys())):
522 perm_dict = new_perms_group[str(k)]
570 perm_dict = new_perms_group[str(k)]
523 new_member = perm_dict.get('name')
571 new_member = perm_dict.get('name')
524 new_perm = perm_dict.get('perm')
572 new_perm = perm_dict.get('perm')
525 new_type = perm_dict.get('type')
573 new_type = perm_dict.get('type')
526 if new_member and new_perm and new_type:
574 if new_member and new_perm and new_type:
527 perms_new.add((new_member, new_perm, new_type))
575 perms_new.add((new_member, new_perm, new_type))
528
576
529 for k, v in value.iteritems():
577 for k, v in value.iteritems():
530 if k.startswith('u_perm_') or k.startswith('g_perm_'):
578 if k.startswith('u_perm_') or k.startswith('g_perm_'):
531 member = k[7:]
579 member = k[7:]
532 t = {'u': 'user',
580 t = {'u': 'user',
533 'g': 'users_group'
581 'g': 'users_group'
534 }[k[0]]
582 }[k[0]]
535 if member == 'default':
583 if member == 'default':
536 if value.get('repo_private'):
584 if value.get('repo_private'):
537 # set none for default when updating to
585 # set none for default when updating to
538 # private repo
586 # private repo
539 v = EMPTY_PERM
587 v = EMPTY_PERM
540 perms_update.add((member, v, t))
588 perms_update.add((member, v, t))
541 #always set NONE when private flag is set
589 #always set NONE when private flag is set
542 if value.get('repo_private'):
590 if value.get('repo_private'):
543 perms_update.add(('default', EMPTY_PERM, 'user'))
591 perms_update.add(('default', EMPTY_PERM, 'user'))
544
592
545 value['perms_updates'] = list(perms_update)
593 value['perms_updates'] = list(perms_update)
546 value['perms_new'] = list(perms_new)
594 value['perms_new'] = list(perms_new)
547
595
548 # update permissions
596 # update permissions
549 for k, v, t in perms_new:
597 for k, v, t in perms_new:
550 try:
598 try:
551 if t is 'user':
599 if t is 'user':
552 self.user_db = User.query()\
600 self.user_db = User.query()\
553 .filter(User.active == True)\
601 .filter(User.active == True)\
554 .filter(User.username == k).one()
602 .filter(User.username == k).one()
555 if t is 'users_group':
603 if t is 'users_group':
556 self.user_db = UsersGroup.query()\
604 self.user_db = UsersGroup.query()\
557 .filter(UsersGroup.users_group_active == True)\
605 .filter(UsersGroup.users_group_active == True)\
558 .filter(UsersGroup.users_group_name == k).one()
606 .filter(UsersGroup.users_group_name == k).one()
559
607
560 except Exception:
608 except Exception:
561 log.exception('Updated permission failed')
609 log.exception('Updated permission failed')
562 msg = M(self, 'perm_new_member_type', state)
610 msg = M(self, 'perm_new_member_type', state)
563 raise formencode.Invalid(msg, value, state,
611 raise formencode.Invalid(msg, value, state,
564 error_dict=dict(perm_new_member_name=msg)
612 error_dict=dict(perm_new_member_name=msg)
565 )
613 )
566 return value
614 return value
567 return _validator
615 return _validator
568
616
569
617
570 def ValidSettings():
618 def ValidSettings():
571 class _validator(formencode.validators.FancyValidator):
619 class _validator(formencode.validators.FancyValidator):
572 def _to_python(self, value, state):
620 def _to_python(self, value, state):
573 # settings form for users that are not admin
621 # settings form for users that are not admin
574 # can't edit certain parameters, it's extra backup if they mangle
622 # can't edit certain parameters, it's extra backup if they mangle
575 # with forms
623 # with forms
576
624
577 forbidden_params = [
625 forbidden_params = [
578 'user', 'repo_type', 'repo_enable_locking',
626 'user', 'repo_type', 'repo_enable_locking',
579 'repo_enable_downloads', 'repo_enable_statistics'
627 'repo_enable_downloads', 'repo_enable_statistics'
580 ]
628 ]
581
629
582 for param in forbidden_params:
630 for param in forbidden_params:
583 if param in value:
631 if param in value:
584 del value[param]
632 del value[param]
585 return value
633 return value
586
634
587 def validate_python(self, value, state):
635 def validate_python(self, value, state):
588 pass
636 pass
589 return _validator
637 return _validator
590
638
591
639
592 def ValidPath():
640 def ValidPath():
593 class _validator(formencode.validators.FancyValidator):
641 class _validator(formencode.validators.FancyValidator):
594 messages = {
642 messages = {
595 'invalid_path': _(u'This is not a valid path')
643 'invalid_path': _(u'This is not a valid path')
596 }
644 }
597
645
598 def validate_python(self, value, state):
646 def validate_python(self, value, state):
599 if not os.path.isdir(value):
647 if not os.path.isdir(value):
600 msg = M(self, 'invalid_path', state)
648 msg = M(self, 'invalid_path', state)
601 raise formencode.Invalid(msg, value, state,
649 raise formencode.Invalid(msg, value, state,
602 error_dict=dict(paths_root_path=msg)
650 error_dict=dict(paths_root_path=msg)
603 )
651 )
604 return _validator
652 return _validator
605
653
606
654
607 def UniqSystemEmail(old_data={}):
655 def UniqSystemEmail(old_data={}):
608 class _validator(formencode.validators.FancyValidator):
656 class _validator(formencode.validators.FancyValidator):
609 messages = {
657 messages = {
610 'email_taken': _(u'This e-mail address is already taken')
658 'email_taken': _(u'This e-mail address is already taken')
611 }
659 }
612
660
613 def _to_python(self, value, state):
661 def _to_python(self, value, state):
614 return value.lower()
662 return value.lower()
615
663
616 def validate_python(self, value, state):
664 def validate_python(self, value, state):
617 if (old_data.get('email') or '').lower() != value:
665 if (old_data.get('email') or '').lower() != value:
618 user = User.get_by_email(value, case_insensitive=True)
666 user = User.get_by_email(value, case_insensitive=True)
619 if user:
667 if user:
620 msg = M(self, 'email_taken', state)
668 msg = M(self, 'email_taken', state)
621 raise formencode.Invalid(msg, value, state,
669 raise formencode.Invalid(msg, value, state,
622 error_dict=dict(email=msg)
670 error_dict=dict(email=msg)
623 )
671 )
624 return _validator
672 return _validator
625
673
626
674
627 def ValidSystemEmail():
675 def ValidSystemEmail():
628 class _validator(formencode.validators.FancyValidator):
676 class _validator(formencode.validators.FancyValidator):
629 messages = {
677 messages = {
630 'non_existing_email': _(u'e-mail "%(email)s" does not exist.')
678 'non_existing_email': _(u'e-mail "%(email)s" does not exist.')
631 }
679 }
632
680
633 def _to_python(self, value, state):
681 def _to_python(self, value, state):
634 return value.lower()
682 return value.lower()
635
683
636 def validate_python(self, value, state):
684 def validate_python(self, value, state):
637 user = User.get_by_email(value, case_insensitive=True)
685 user = User.get_by_email(value, case_insensitive=True)
638 if user is None:
686 if user is None:
639 msg = M(self, 'non_existing_email', state, email=value)
687 msg = M(self, 'non_existing_email', state, email=value)
640 raise formencode.Invalid(msg, value, state,
688 raise formencode.Invalid(msg, value, state,
641 error_dict=dict(email=msg)
689 error_dict=dict(email=msg)
642 )
690 )
643
691
644 return _validator
692 return _validator
645
693
646
694
647 def LdapLibValidator():
695 def LdapLibValidator():
648 class _validator(formencode.validators.FancyValidator):
696 class _validator(formencode.validators.FancyValidator):
649 messages = {
697 messages = {
650
698
651 }
699 }
652
700
653 def validate_python(self, value, state):
701 def validate_python(self, value, state):
654 try:
702 try:
655 import ldap
703 import ldap
656 ldap # pyflakes silence !
704 ldap # pyflakes silence !
657 except ImportError:
705 except ImportError:
658 raise LdapImportError()
706 raise LdapImportError()
659
707
660 return _validator
708 return _validator
661
709
662
710
663 def AttrLoginValidator():
711 def AttrLoginValidator():
664 class _validator(formencode.validators.FancyValidator):
712 class _validator(formencode.validators.FancyValidator):
665 messages = {
713 messages = {
666 'invalid_cn':
714 'invalid_cn':
667 _(u'The LDAP Login attribute of the CN must be specified - '
715 _(u'The LDAP Login attribute of the CN must be specified - '
668 'this is the name of the attribute that is equivalent '
716 'this is the name of the attribute that is equivalent '
669 'to "username"')
717 'to "username"')
670 }
718 }
671
719
672 def validate_python(self, value, state):
720 def validate_python(self, value, state):
673 if not value or not isinstance(value, (str, unicode)):
721 if not value or not isinstance(value, (str, unicode)):
674 msg = M(self, 'invalid_cn', state)
722 msg = M(self, 'invalid_cn', state)
675 raise formencode.Invalid(msg, value, state,
723 raise formencode.Invalid(msg, value, state,
676 error_dict=dict(ldap_attr_login=msg)
724 error_dict=dict(ldap_attr_login=msg)
677 )
725 )
678
726
679 return _validator
727 return _validator
680
728
681
729
682 def NotReviewedRevisions(repo_id):
730 def NotReviewedRevisions(repo_id):
683 class _validator(formencode.validators.FancyValidator):
731 class _validator(formencode.validators.FancyValidator):
684 messages = {
732 messages = {
685 'rev_already_reviewed':
733 'rev_already_reviewed':
686 _(u'Revisions %(revs)s are already part of pull request '
734 _(u'Revisions %(revs)s are already part of pull request '
687 'or have set status')
735 'or have set status')
688 }
736 }
689
737
690 def validate_python(self, value, state):
738 def validate_python(self, value, state):
691 # check revisions if they are not reviewed, or a part of another
739 # check revisions if they are not reviewed, or a part of another
692 # pull request
740 # pull request
693 statuses = ChangesetStatus.query()\
741 statuses = ChangesetStatus.query()\
694 .filter(ChangesetStatus.revision.in_(value))\
742 .filter(ChangesetStatus.revision.in_(value))\
695 .filter(ChangesetStatus.repo_id == repo_id)\
743 .filter(ChangesetStatus.repo_id == repo_id)\
696 .all()
744 .all()
697
745
698 errors = []
746 errors = []
699 for cs in statuses:
747 for cs in statuses:
700 if cs.pull_request_id:
748 if cs.pull_request_id:
701 errors.append(['pull_req', cs.revision[:12]])
749 errors.append(['pull_req', cs.revision[:12]])
702 elif cs.status:
750 elif cs.status:
703 errors.append(['status', cs.revision[:12]])
751 errors.append(['status', cs.revision[:12]])
704
752
705 if errors:
753 if errors:
706 revs = ','.join([x[1] for x in errors])
754 revs = ','.join([x[1] for x in errors])
707 msg = M(self, 'rev_already_reviewed', state, revs=revs)
755 msg = M(self, 'rev_already_reviewed', state, revs=revs)
708 raise formencode.Invalid(msg, value, state,
756 raise formencode.Invalid(msg, value, state,
709 error_dict=dict(revisions=revs)
757 error_dict=dict(revisions=revs)
710 )
758 )
711
759
712 return _validator
760 return _validator
713
761
714
762
715 def ValidIp():
763 def ValidIp():
716 class _validator(CIDR):
764 class _validator(CIDR):
717 messages = dict(
765 messages = dict(
718 badFormat=_('Please enter a valid IPv4 or IpV6 address'),
766 badFormat=_('Please enter a valid IPv4 or IpV6 address'),
719 illegalBits=_('The network size (bits) must be within the range'
767 illegalBits=_('The network size (bits) must be within the range'
720 ' of 0-32 (not %(bits)r)'))
768 ' of 0-32 (not %(bits)r)'))
721
769
722 def to_python(self, value, state):
770 def to_python(self, value, state):
723 v = super(_validator, self).to_python(value, state)
771 v = super(_validator, self).to_python(value, state)
724 v = v.strip()
772 v = v.strip()
725 net = ipaddr.IPNetwork(address=v)
773 net = ipaddr.IPNetwork(address=v)
726 if isinstance(net, ipaddr.IPv4Network):
774 if isinstance(net, ipaddr.IPv4Network):
727 #if IPv4 doesn't end with a mask, add /32
775 #if IPv4 doesn't end with a mask, add /32
728 if '/' not in value:
776 if '/' not in value:
729 v += '/32'
777 v += '/32'
730 if isinstance(net, ipaddr.IPv6Network):
778 if isinstance(net, ipaddr.IPv6Network):
731 #if IPv6 doesn't end with a mask, add /128
779 #if IPv6 doesn't end with a mask, add /128
732 if '/' not in value:
780 if '/' not in value:
733 v += '/128'
781 v += '/128'
734 return v
782 return v
735
783
736 def validate_python(self, value, state):
784 def validate_python(self, value, state):
737 try:
785 try:
738 addr = value.strip()
786 addr = value.strip()
739 #this raises an ValueError if address is not IpV4 or IpV6
787 #this raises an ValueError if address is not IpV4 or IpV6
740 ipaddr.IPNetwork(address=addr)
788 ipaddr.IPNetwork(address=addr)
741 except ValueError:
789 except ValueError:
742 raise formencode.Invalid(self.message('badFormat', state),
790 raise formencode.Invalid(self.message('badFormat', state),
743 value, state)
791 value, state)
744
792
745 return _validator
793 return _validator
@@ -1,4847 +1,4847 b''
1 html,body,div,span,applet,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,a,abbr,acronym,address,big,cite,code,del,dfn,em,font,img,ins,kbd,q,s,samp,small,strike,strong,sub,sup,tt,var,b,u,i,center,dl,dt,dd,ol,ul,li,fieldset,form,label,legend,table,caption,tbody,tfoot,thead,tr,th,td
1 html,body,div,span,applet,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,a,abbr,acronym,address,big,cite,code,del,dfn,em,font,img,ins,kbd,q,s,samp,small,strike,strong,sub,sup,tt,var,b,u,i,center,dl,dt,dd,ol,ul,li,fieldset,form,label,legend,table,caption,tbody,tfoot,thead,tr,th,td
2 {
2 {
3 border: 0;
3 border: 0;
4 outline: 0;
4 outline: 0;
5 font-size: 100%;
5 font-size: 100%;
6 vertical-align: baseline;
6 vertical-align: baseline;
7 background: transparent;
7 background: transparent;
8 margin: 0;
8 margin: 0;
9 padding: 0;
9 padding: 0;
10 }
10 }
11
11
12 body {
12 body {
13 line-height: 1;
13 line-height: 1;
14 height: 100%;
14 height: 100%;
15 background: url("../images/background.png") repeat scroll 0 0 #B0B0B0;
15 background: url("../images/background.png") repeat scroll 0 0 #B0B0B0;
16 font-family: Lucida Grande, Verdana, Lucida Sans Regular,
16 font-family: Lucida Grande, Verdana, Lucida Sans Regular,
17 Lucida Sans Unicode, Arial, sans-serif; font-size : 12px;
17 Lucida Sans Unicode, Arial, sans-serif; font-size : 12px;
18 color: #000;
18 color: #000;
19 margin: 0;
19 margin: 0;
20 padding: 0;
20 padding: 0;
21 font-size: 12px;
21 font-size: 12px;
22 }
22 }
23
23
24 ol,ul {
24 ol,ul {
25 list-style: none;
25 list-style: none;
26 }
26 }
27
27
28 blockquote,q {
28 blockquote,q {
29 quotes: none;
29 quotes: none;
30 }
30 }
31
31
32 blockquote:before,blockquote:after,q:before,q:after {
32 blockquote:before,blockquote:after,q:before,q:after {
33 content: none;
33 content: none;
34 }
34 }
35
35
36 :focus {
36 :focus {
37 outline: 0;
37 outline: 0;
38 }
38 }
39
39
40 del {
40 del {
41 text-decoration: line-through;
41 text-decoration: line-through;
42 }
42 }
43
43
44 table {
44 table {
45 border-collapse: collapse;
45 border-collapse: collapse;
46 border-spacing: 0;
46 border-spacing: 0;
47 }
47 }
48
48
49 html {
49 html {
50 height: 100%;
50 height: 100%;
51 }
51 }
52
52
53 a {
53 a {
54 color: #003367;
54 color: #003367;
55 text-decoration: none;
55 text-decoration: none;
56 cursor: pointer;
56 cursor: pointer;
57 }
57 }
58
58
59 a:hover {
59 a:hover {
60 color: #316293;
60 color: #316293;
61 text-decoration: underline;
61 text-decoration: underline;
62 }
62 }
63
63
64 h1,h2,h3,h4,h5,h6,
64 h1,h2,h3,h4,h5,h6,
65 div.h1,div.h2,div.h3,div.h4,div.h5,div.h6 {
65 div.h1,div.h2,div.h3,div.h4,div.h5,div.h6 {
66 color: #292929;
66 color: #292929;
67 font-weight: 700;
67 font-weight: 700;
68 }
68 }
69
69
70 h1,div.h1 {
70 h1,div.h1 {
71 font-size: 22px;
71 font-size: 22px;
72 }
72 }
73
73
74 h2,div.h2 {
74 h2,div.h2 {
75 font-size: 20px;
75 font-size: 20px;
76 }
76 }
77
77
78 h3,div.h3 {
78 h3,div.h3 {
79 font-size: 18px;
79 font-size: 18px;
80 }
80 }
81
81
82 h4,div.h4 {
82 h4,div.h4 {
83 font-size: 16px;
83 font-size: 16px;
84 }
84 }
85
85
86 h5,div.h5 {
86 h5,div.h5 {
87 font-size: 14px;
87 font-size: 14px;
88 }
88 }
89
89
90 h6,div.h6 {
90 h6,div.h6 {
91 font-size: 11px;
91 font-size: 11px;
92 }
92 }
93
93
94 ul.circle {
94 ul.circle {
95 list-style-type: circle;
95 list-style-type: circle;
96 }
96 }
97
97
98 ul.disc {
98 ul.disc {
99 list-style-type: disc;
99 list-style-type: disc;
100 }
100 }
101
101
102 ul.square {
102 ul.square {
103 list-style-type: square;
103 list-style-type: square;
104 }
104 }
105
105
106 ol.lower-roman {
106 ol.lower-roman {
107 list-style-type: lower-roman;
107 list-style-type: lower-roman;
108 }
108 }
109
109
110 ol.upper-roman {
110 ol.upper-roman {
111 list-style-type: upper-roman;
111 list-style-type: upper-roman;
112 }
112 }
113
113
114 ol.lower-alpha {
114 ol.lower-alpha {
115 list-style-type: lower-alpha;
115 list-style-type: lower-alpha;
116 }
116 }
117
117
118 ol.upper-alpha {
118 ol.upper-alpha {
119 list-style-type: upper-alpha;
119 list-style-type: upper-alpha;
120 }
120 }
121
121
122 ol.decimal {
122 ol.decimal {
123 list-style-type: decimal;
123 list-style-type: decimal;
124 }
124 }
125
125
126 div.color {
126 div.color {
127 clear: both;
127 clear: both;
128 overflow: hidden;
128 overflow: hidden;
129 position: absolute;
129 position: absolute;
130 background: #FFF;
130 background: #FFF;
131 margin: 7px 0 0 60px;
131 margin: 7px 0 0 60px;
132 padding: 1px 1px 1px 0;
132 padding: 1px 1px 1px 0;
133 }
133 }
134
134
135 div.color a {
135 div.color a {
136 width: 15px;
136 width: 15px;
137 height: 15px;
137 height: 15px;
138 display: block;
138 display: block;
139 float: left;
139 float: left;
140 margin: 0 0 0 1px;
140 margin: 0 0 0 1px;
141 padding: 0;
141 padding: 0;
142 }
142 }
143
143
144 div.options {
144 div.options {
145 clear: both;
145 clear: both;
146 overflow: hidden;
146 overflow: hidden;
147 position: absolute;
147 position: absolute;
148 background: #FFF;
148 background: #FFF;
149 margin: 7px 0 0 162px;
149 margin: 7px 0 0 162px;
150 padding: 0;
150 padding: 0;
151 }
151 }
152
152
153 div.options a {
153 div.options a {
154 height: 1%;
154 height: 1%;
155 display: block;
155 display: block;
156 text-decoration: none;
156 text-decoration: none;
157 margin: 0;
157 margin: 0;
158 padding: 3px 8px;
158 padding: 3px 8px;
159 }
159 }
160
160
161 .top-left-rounded-corner {
161 .top-left-rounded-corner {
162 -webkit-border-top-left-radius: 8px;
162 -webkit-border-top-left-radius: 8px;
163 -khtml-border-radius-topleft: 8px;
163 -khtml-border-radius-topleft: 8px;
164 -moz-border-radius-topleft: 8px;
164 -moz-border-radius-topleft: 8px;
165 border-top-left-radius: 8px;
165 border-top-left-radius: 8px;
166 }
166 }
167
167
168 .top-right-rounded-corner {
168 .top-right-rounded-corner {
169 -webkit-border-top-right-radius: 8px;
169 -webkit-border-top-right-radius: 8px;
170 -khtml-border-radius-topright: 8px;
170 -khtml-border-radius-topright: 8px;
171 -moz-border-radius-topright: 8px;
171 -moz-border-radius-topright: 8px;
172 border-top-right-radius: 8px;
172 border-top-right-radius: 8px;
173 }
173 }
174
174
175 .bottom-left-rounded-corner {
175 .bottom-left-rounded-corner {
176 -webkit-border-bottom-left-radius: 8px;
176 -webkit-border-bottom-left-radius: 8px;
177 -khtml-border-radius-bottomleft: 8px;
177 -khtml-border-radius-bottomleft: 8px;
178 -moz-border-radius-bottomleft: 8px;
178 -moz-border-radius-bottomleft: 8px;
179 border-bottom-left-radius: 8px;
179 border-bottom-left-radius: 8px;
180 }
180 }
181
181
182 .bottom-right-rounded-corner {
182 .bottom-right-rounded-corner {
183 -webkit-border-bottom-right-radius: 8px;
183 -webkit-border-bottom-right-radius: 8px;
184 -khtml-border-radius-bottomright: 8px;
184 -khtml-border-radius-bottomright: 8px;
185 -moz-border-radius-bottomright: 8px;
185 -moz-border-radius-bottomright: 8px;
186 border-bottom-right-radius: 8px;
186 border-bottom-right-radius: 8px;
187 }
187 }
188
188
189 .top-left-rounded-corner-mid {
189 .top-left-rounded-corner-mid {
190 -webkit-border-top-left-radius: 4px;
190 -webkit-border-top-left-radius: 4px;
191 -khtml-border-radius-topleft: 4px;
191 -khtml-border-radius-topleft: 4px;
192 -moz-border-radius-topleft: 4px;
192 -moz-border-radius-topleft: 4px;
193 border-top-left-radius: 4px;
193 border-top-left-radius: 4px;
194 }
194 }
195
195
196 .top-right-rounded-corner-mid {
196 .top-right-rounded-corner-mid {
197 -webkit-border-top-right-radius: 4px;
197 -webkit-border-top-right-radius: 4px;
198 -khtml-border-radius-topright: 4px;
198 -khtml-border-radius-topright: 4px;
199 -moz-border-radius-topright: 4px;
199 -moz-border-radius-topright: 4px;
200 border-top-right-radius: 4px;
200 border-top-right-radius: 4px;
201 }
201 }
202
202
203 .bottom-left-rounded-corner-mid {
203 .bottom-left-rounded-corner-mid {
204 -webkit-border-bottom-left-radius: 4px;
204 -webkit-border-bottom-left-radius: 4px;
205 -khtml-border-radius-bottomleft: 4px;
205 -khtml-border-radius-bottomleft: 4px;
206 -moz-border-radius-bottomleft: 4px;
206 -moz-border-radius-bottomleft: 4px;
207 border-bottom-left-radius: 4px;
207 border-bottom-left-radius: 4px;
208 }
208 }
209
209
210 .bottom-right-rounded-corner-mid {
210 .bottom-right-rounded-corner-mid {
211 -webkit-border-bottom-right-radius: 4px;
211 -webkit-border-bottom-right-radius: 4px;
212 -khtml-border-radius-bottomright: 4px;
212 -khtml-border-radius-bottomright: 4px;
213 -moz-border-radius-bottomright: 4px;
213 -moz-border-radius-bottomright: 4px;
214 border-bottom-right-radius: 4px;
214 border-bottom-right-radius: 4px;
215 }
215 }
216
216
217 .help-block {
217 .help-block {
218 color: #999999;
218 color: #999999;
219 display: block;
219 display: block;
220 margin-bottom: 0;
220 margin-bottom: 0;
221 margin-top: 5px;
221 margin-top: 5px;
222 }
222 }
223
223
224 .empty_data{
224 .empty_data{
225 color:#B9B9B9;
225 color:#B9B9B9;
226 }
226 }
227
227
228 a.permalink{
228 a.permalink{
229 visibility: hidden;
229 visibility: hidden;
230 }
230 }
231
231
232 a.permalink:hover{
232 a.permalink:hover{
233 text-decoration: none;
233 text-decoration: none;
234 }
234 }
235
235
236 h1:hover > a.permalink,
236 h1:hover > a.permalink,
237 h2:hover > a.permalink,
237 h2:hover > a.permalink,
238 h3:hover > a.permalink,
238 h3:hover > a.permalink,
239 h4:hover > a.permalink,
239 h4:hover > a.permalink,
240 h5:hover > a.permalink,
240 h5:hover > a.permalink,
241 h6:hover > a.permalink,
241 h6:hover > a.permalink,
242 div:hover > a.permalink {
242 div:hover > a.permalink {
243 visibility: visible;
243 visibility: visible;
244 }
244 }
245
245
246 #header {
246 #header {
247 margin: 0;
247 margin: 0;
248 padding: 0 10px;
248 padding: 0 10px;
249 }
249 }
250
250
251 #header ul#logged-user {
251 #header ul#logged-user {
252 margin-bottom: 5px !important;
252 margin-bottom: 5px !important;
253 -webkit-border-radius: 0px 0px 8px 8px;
253 -webkit-border-radius: 0px 0px 8px 8px;
254 -khtml-border-radius: 0px 0px 8px 8px;
254 -khtml-border-radius: 0px 0px 8px 8px;
255 -moz-border-radius: 0px 0px 8px 8px;
255 -moz-border-radius: 0px 0px 8px 8px;
256 border-radius: 0px 0px 8px 8px;
256 border-radius: 0px 0px 8px 8px;
257 height: 37px;
257 height: 37px;
258 background-color: #003B76;
258 background-color: #003B76;
259 background-repeat: repeat-x;
259 background-repeat: repeat-x;
260 background-image: -khtml-gradient(linear, left top, left bottom, from(#003B76), to(#00376E) );
260 background-image: -khtml-gradient(linear, left top, left bottom, from(#003B76), to(#00376E) );
261 background-image: -moz-linear-gradient(top, #003b76, #00376e);
261 background-image: -moz-linear-gradient(top, #003b76, #00376e);
262 background-image: -ms-linear-gradient(top, #003b76, #00376e);
262 background-image: -ms-linear-gradient(top, #003b76, #00376e);
263 background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #003b76), color-stop(100%, #00376e) );
263 background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #003b76), color-stop(100%, #00376e) );
264 background-image: -webkit-linear-gradient(top, #003b76, #00376e);
264 background-image: -webkit-linear-gradient(top, #003b76, #00376e);
265 background-image: -o-linear-gradient(top, #003b76, #00376e);
265 background-image: -o-linear-gradient(top, #003b76, #00376e);
266 background-image: linear-gradient(top, #003b76, #00376e);
266 background-image: linear-gradient(top, #003b76, #00376e);
267 filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#003b76',endColorstr='#00376e', GradientType=0 );
267 filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#003b76',endColorstr='#00376e', GradientType=0 );
268 box-shadow: 0 2px 2px rgba(0, 0, 0, 0.6);
268 box-shadow: 0 2px 2px rgba(0, 0, 0, 0.6);
269 }
269 }
270
270
271 #header ul#logged-user li {
271 #header ul#logged-user li {
272 list-style: none;
272 list-style: none;
273 float: left;
273 float: left;
274 margin: 8px 0 0;
274 margin: 8px 0 0;
275 padding: 4px 12px;
275 padding: 4px 12px;
276 border-left: 1px solid #316293;
276 border-left: 1px solid #316293;
277 }
277 }
278
278
279 #header ul#logged-user li.first {
279 #header ul#logged-user li.first {
280 border-left: none;
280 border-left: none;
281 margin: 4px;
281 margin: 4px;
282 }
282 }
283
283
284 #header ul#logged-user li.first div.gravatar {
284 #header ul#logged-user li.first div.gravatar {
285 margin-top: -2px;
285 margin-top: -2px;
286 }
286 }
287
287
288 #header ul#logged-user li.first div.account {
288 #header ul#logged-user li.first div.account {
289 padding-top: 4px;
289 padding-top: 4px;
290 float: left;
290 float: left;
291 }
291 }
292
292
293 #header ul#logged-user li.last {
293 #header ul#logged-user li.last {
294 border-right: none;
294 border-right: none;
295 }
295 }
296
296
297 #header ul#logged-user li a {
297 #header ul#logged-user li a {
298 color: #fff;
298 color: #fff;
299 font-weight: 700;
299 font-weight: 700;
300 text-decoration: none;
300 text-decoration: none;
301 }
301 }
302
302
303 #header ul#logged-user li a:hover {
303 #header ul#logged-user li a:hover {
304 text-decoration: underline;
304 text-decoration: underline;
305 }
305 }
306
306
307 #header ul#logged-user li.highlight a {
307 #header ul#logged-user li.highlight a {
308 color: #fff;
308 color: #fff;
309 }
309 }
310
310
311 #header ul#logged-user li.highlight a:hover {
311 #header ul#logged-user li.highlight a:hover {
312 color: #FFF;
312 color: #FFF;
313 }
313 }
314
314
315 #header #header-inner {
315 #header #header-inner {
316 min-height: 44px;
316 min-height: 44px;
317 clear: both;
317 clear: both;
318 position: relative;
318 position: relative;
319 background-color: #003B76;
319 background-color: #003B76;
320 background-repeat: repeat-x;
320 background-repeat: repeat-x;
321 background-image: -khtml-gradient(linear, left top, left bottom, from(#003B76), to(#00376E) );
321 background-image: -khtml-gradient(linear, left top, left bottom, from(#003B76), to(#00376E) );
322 background-image: -moz-linear-gradient(top, #003b76, #00376e);
322 background-image: -moz-linear-gradient(top, #003b76, #00376e);
323 background-image: -ms-linear-gradient(top, #003b76, #00376e);
323 background-image: -ms-linear-gradient(top, #003b76, #00376e);
324 background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #003b76),color-stop(100%, #00376e) );
324 background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #003b76),color-stop(100%, #00376e) );
325 background-image: -webkit-linear-gradient(top, #003b76, #00376e);
325 background-image: -webkit-linear-gradient(top, #003b76, #00376e);
326 background-image: -o-linear-gradient(top, #003b76, #00376e);
326 background-image: -o-linear-gradient(top, #003b76, #00376e);
327 background-image: linear-gradient(top, #003b76, #00376e);
327 background-image: linear-gradient(top, #003b76, #00376e);
328 filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#003b76',endColorstr='#00376e', GradientType=0 );
328 filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#003b76',endColorstr='#00376e', GradientType=0 );
329 margin: 0;
329 margin: 0;
330 padding: 0;
330 padding: 0;
331 display: block;
331 display: block;
332 box-shadow: 0 2px 2px rgba(0, 0, 0, 0.6);
332 box-shadow: 0 2px 2px rgba(0, 0, 0, 0.6);
333 -webkit-border-radius: 4px 4px 4px 4px;
333 -webkit-border-radius: 4px 4px 4px 4px;
334 -khtml-border-radius: 4px 4px 4px 4px;
334 -khtml-border-radius: 4px 4px 4px 4px;
335 -moz-border-radius: 4px 4px 4px 4px;
335 -moz-border-radius: 4px 4px 4px 4px;
336 border-radius: 4px 4px 4px 4px;
336 border-radius: 4px 4px 4px 4px;
337 }
337 }
338 #header #header-inner.hover{
338 #header #header-inner.hover{
339 position: fixed !important;
339 position: fixed !important;
340 width: 100% !important;
340 width: 100% !important;
341 margin-left: -10px !important;
341 margin-left: -10px !important;
342 z-index: 10000;
342 z-index: 10000;
343 -webkit-border-radius: 0px 0px 0px 0px;
343 -webkit-border-radius: 0px 0px 0px 0px;
344 -khtml-border-radius: 0px 0px 0px 0px;
344 -khtml-border-radius: 0px 0px 0px 0px;
345 -moz-border-radius: 0px 0px 0px 0px;
345 -moz-border-radius: 0px 0px 0px 0px;
346 border-radius: 0px 0px 0px 0px;
346 border-radius: 0px 0px 0px 0px;
347 }
347 }
348
348
349 .ie7 #header #header-inner.hover,
349 .ie7 #header #header-inner.hover,
350 .ie8 #header #header-inner.hover,
350 .ie8 #header #header-inner.hover,
351 .ie9 #header #header-inner.hover
351 .ie9 #header #header-inner.hover
352 {
352 {
353 z-index: auto !important;
353 z-index: auto !important;
354 }
354 }
355
355
356 .header-pos-fix, .anchor{
356 .header-pos-fix, .anchor{
357 margin-top: -46px;
357 margin-top: -46px;
358 padding-top: 46px;
358 padding-top: 46px;
359 }
359 }
360
360
361 #header #header-inner #home a {
361 #header #header-inner #home a {
362 height: 40px;
362 height: 40px;
363 width: 46px;
363 width: 46px;
364 display: block;
364 display: block;
365 background: url("../images/button_home.png");
365 background: url("../images/button_home.png");
366 background-position: 0 0;
366 background-position: 0 0;
367 margin: 0;
367 margin: 0;
368 padding: 0;
368 padding: 0;
369 }
369 }
370
370
371 #header #header-inner #home a:hover {
371 #header #header-inner #home a:hover {
372 background-position: 0 -40px;
372 background-position: 0 -40px;
373 }
373 }
374
374
375 #header #header-inner #logo {
375 #header #header-inner #logo {
376 float: left;
376 float: left;
377 position: absolute;
377 position: absolute;
378 }
378 }
379
379
380 #header #header-inner #logo h1 {
380 #header #header-inner #logo h1 {
381 color: #FFF;
381 color: #FFF;
382 font-size: 20px;
382 font-size: 20px;
383 margin: 12px 0 0 13px;
383 margin: 12px 0 0 13px;
384 padding: 0;
384 padding: 0;
385 }
385 }
386
386
387 #header #header-inner #logo a {
387 #header #header-inner #logo a {
388 color: #fff;
388 color: #fff;
389 text-decoration: none;
389 text-decoration: none;
390 }
390 }
391
391
392 #header #header-inner #logo a:hover {
392 #header #header-inner #logo a:hover {
393 color: #bfe3ff;
393 color: #bfe3ff;
394 }
394 }
395
395
396 #header #header-inner #quick,#header #header-inner #quick ul {
396 #header #header-inner #quick,#header #header-inner #quick ul {
397 position: relative;
397 position: relative;
398 float: right;
398 float: right;
399 list-style-type: none;
399 list-style-type: none;
400 list-style-position: outside;
400 list-style-position: outside;
401 margin: 8px 8px 0 0;
401 margin: 8px 8px 0 0;
402 padding: 0;
402 padding: 0;
403 }
403 }
404
404
405 #header #header-inner #quick li {
405 #header #header-inner #quick li {
406 position: relative;
406 position: relative;
407 float: left;
407 float: left;
408 margin: 0 5px 0 0;
408 margin: 0 5px 0 0;
409 padding: 0;
409 padding: 0;
410 }
410 }
411
411
412 #header #header-inner #quick li a.menu_link {
412 #header #header-inner #quick li a.menu_link {
413 top: 0;
413 top: 0;
414 left: 0;
414 left: 0;
415 height: 1%;
415 height: 1%;
416 display: block;
416 display: block;
417 clear: both;
417 clear: both;
418 overflow: hidden;
418 overflow: hidden;
419 color: #FFF;
419 color: #FFF;
420 font-weight: 700;
420 font-weight: 700;
421 text-decoration: none;
421 text-decoration: none;
422 background: #369;
422 background: #369;
423 padding: 0;
423 padding: 0;
424 -webkit-border-radius: 4px 4px 4px 4px;
424 -webkit-border-radius: 4px 4px 4px 4px;
425 -khtml-border-radius: 4px 4px 4px 4px;
425 -khtml-border-radius: 4px 4px 4px 4px;
426 -moz-border-radius: 4px 4px 4px 4px;
426 -moz-border-radius: 4px 4px 4px 4px;
427 border-radius: 4px 4px 4px 4px;
427 border-radius: 4px 4px 4px 4px;
428 }
428 }
429
429
430 #header #header-inner #quick li span.short {
430 #header #header-inner #quick li span.short {
431 padding: 9px 6px 8px 6px;
431 padding: 9px 6px 8px 6px;
432 }
432 }
433
433
434 #header #header-inner #quick li span {
434 #header #header-inner #quick li span {
435 top: 0;
435 top: 0;
436 right: 0;
436 right: 0;
437 height: 1%;
437 height: 1%;
438 display: block;
438 display: block;
439 float: left;
439 float: left;
440 border-left: 1px solid #3f6f9f;
440 border-left: 1px solid #3f6f9f;
441 margin: 0;
441 margin: 0;
442 padding: 10px 12px 8px 10px;
442 padding: 10px 12px 8px 10px;
443 }
443 }
444
444
445 #header #header-inner #quick li span.normal {
445 #header #header-inner #quick li span.normal {
446 border: none;
446 border: none;
447 padding: 10px 12px 8px;
447 padding: 10px 12px 8px;
448 }
448 }
449
449
450 #header #header-inner #quick li span.icon {
450 #header #header-inner #quick li span.icon {
451 top: 0;
451 top: 0;
452 left: 0;
452 left: 0;
453 border-left: none;
453 border-left: none;
454 border-right: 1px solid #2e5c89;
454 border-right: 1px solid #2e5c89;
455 padding: 8px 6px 4px;
455 padding: 8px 6px 4px;
456 min-width: 16px;
456 min-width: 16px;
457 min-height: 16px;
457 min-height: 16px;
458 }
458 }
459
459
460 #header #header-inner #quick li span.icon_short {
460 #header #header-inner #quick li span.icon_short {
461 top: 0;
461 top: 0;
462 left: 0;
462 left: 0;
463 border-left: none;
463 border-left: none;
464 border-right: 1px solid #2e5c89;
464 border-right: 1px solid #2e5c89;
465 padding: 8px 6px 4px;
465 padding: 8px 6px 4px;
466 }
466 }
467
467
468 #header #header-inner #quick li span.icon img,#header #header-inner #quick li span.icon_short img
468 #header #header-inner #quick li span.icon img,#header #header-inner #quick li span.icon_short img
469 {
469 {
470 margin: 0px -2px 0px 0px;
470 margin: 0px -2px 0px 0px;
471 }
471 }
472
472
473 #header #header-inner #quick li.current a,
473 #header #header-inner #quick li.current a,
474 #header #header-inner #quick li a:hover {
474 #header #header-inner #quick li a:hover {
475 background: #4e4e4e no-repeat top left;
475 background: #4e4e4e no-repeat top left;
476 }
476 }
477
477
478 #header #header-inner #quick li.current a span,
478 #header #header-inner #quick li.current a span,
479 #header #header-inner #quick li a:hover span {
479 #header #header-inner #quick li a:hover span {
480 border-left: 1px solid #545454;
480 border-left: 1px solid #545454;
481 }
481 }
482
482
483 #header #header-inner #quick li.current a span.icon,
483 #header #header-inner #quick li.current a span.icon,
484 #header #header-inner #quick li.current a span.icon_short,
484 #header #header-inner #quick li.current a span.icon_short,
485 #header #header-inner #quick li a:hover span.icon,
485 #header #header-inner #quick li a:hover span.icon,
486 #header #header-inner #quick li a:hover span.icon_short
486 #header #header-inner #quick li a:hover span.icon_short
487 {
487 {
488 border-left: none;
488 border-left: none;
489 border-right: 1px solid #464646;
489 border-right: 1px solid #464646;
490 }
490 }
491
491
492 #header #header-inner #quick ul {
492 #header #header-inner #quick ul {
493 top: 29px;
493 top: 29px;
494 right: 0;
494 right: 0;
495 min-width: 200px;
495 min-width: 200px;
496 display: none;
496 display: none;
497 position: absolute;
497 position: absolute;
498 background: #FFF;
498 background: #FFF;
499 border: 1px solid #666;
499 border: 1px solid #666;
500 border-top: 1px solid #003367;
500 border-top: 1px solid #003367;
501 z-index: 100;
501 z-index: 100;
502 margin: 0px 0px 0px 0px;
502 margin: 0px 0px 0px 0px;
503 padding: 0;
503 padding: 0;
504 }
504 }
505
505
506 #header #header-inner #quick ul.repo_switcher {
506 #header #header-inner #quick ul.repo_switcher {
507 max-height: 275px;
507 max-height: 275px;
508 overflow-x: hidden;
508 overflow-x: hidden;
509 overflow-y: auto;
509 overflow-y: auto;
510 }
510 }
511
511
512 #header #header-inner #quick ul.repo_switcher li.qfilter_rs {
512 #header #header-inner #quick ul.repo_switcher li.qfilter_rs {
513 float: none;
513 float: none;
514 margin: 0;
514 margin: 0;
515 border-bottom: 2px solid #003367;
515 border-bottom: 2px solid #003367;
516 }
516 }
517
517
518 #header #header-inner #quick .repo_switcher_type {
518 #header #header-inner #quick .repo_switcher_type {
519 position: absolute;
519 position: absolute;
520 left: 0;
520 left: 0;
521 top: 9px;
521 top: 9px;
522 }
522 }
523
523
524 #header #header-inner #quick li ul li {
524 #header #header-inner #quick li ul li {
525 border-bottom: 1px solid #ddd;
525 border-bottom: 1px solid #ddd;
526 }
526 }
527
527
528 #header #header-inner #quick li ul li a {
528 #header #header-inner #quick li ul li a {
529 width: 182px;
529 width: 182px;
530 height: auto;
530 height: auto;
531 display: block;
531 display: block;
532 float: left;
532 float: left;
533 background: #FFF;
533 background: #FFF;
534 color: #003367;
534 color: #003367;
535 font-weight: 400;
535 font-weight: 400;
536 margin: 0;
536 margin: 0;
537 padding: 7px 9px;
537 padding: 7px 9px;
538 }
538 }
539
539
540 #header #header-inner #quick li ul li a:hover {
540 #header #header-inner #quick li ul li a:hover {
541 color: #000;
541 color: #000;
542 background: #FFF;
542 background: #FFF;
543 }
543 }
544
544
545 #header #header-inner #quick ul ul {
545 #header #header-inner #quick ul ul {
546 top: auto;
546 top: auto;
547 }
547 }
548
548
549 #header #header-inner #quick li ul ul {
549 #header #header-inner #quick li ul ul {
550 right: 200px;
550 right: 200px;
551 max-height: 290px;
551 max-height: 290px;
552 overflow: auto;
552 overflow: auto;
553 overflow-x: hidden;
553 overflow-x: hidden;
554 white-space: normal;
554 white-space: normal;
555 }
555 }
556
556
557 #header #header-inner #quick li ul li a.journal,#header #header-inner #quick li ul li a.journal:hover
557 #header #header-inner #quick li ul li a.journal,#header #header-inner #quick li ul li a.journal:hover
558 {
558 {
559 background: url("../images/icons/book.png") no-repeat scroll 4px 9px
559 background: url("../images/icons/book.png") no-repeat scroll 4px 9px
560 #FFF;
560 #FFF;
561 width: 167px;
561 width: 167px;
562 margin: 0;
562 margin: 0;
563 padding: 12px 9px 7px 24px;
563 padding: 12px 9px 7px 24px;
564 }
564 }
565
565
566 #header #header-inner #quick li ul li a.private_repo,#header #header-inner #quick li ul li a.private_repo:hover
566 #header #header-inner #quick li ul li a.private_repo,#header #header-inner #quick li ul li a.private_repo:hover
567 {
567 {
568 background: url("../images/icons/lock.png") no-repeat scroll 4px 9px
568 background: url("../images/icons/lock.png") no-repeat scroll 4px 9px
569 #FFF;
569 #FFF;
570 min-width: 167px;
570 min-width: 167px;
571 margin: 0;
571 margin: 0;
572 padding: 12px 9px 7px 24px;
572 padding: 12px 9px 7px 24px;
573 }
573 }
574
574
575 #header #header-inner #quick li ul li a.public_repo,#header #header-inner #quick li ul li a.public_repo:hover
575 #header #header-inner #quick li ul li a.public_repo,#header #header-inner #quick li ul li a.public_repo:hover
576 {
576 {
577 background: url("../images/icons/lock_open.png") no-repeat scroll 4px
577 background: url("../images/icons/lock_open.png") no-repeat scroll 4px
578 9px #FFF;
578 9px #FFF;
579 min-width: 167px;
579 min-width: 167px;
580 margin: 0;
580 margin: 0;
581 padding: 12px 9px 7px 24px;
581 padding: 12px 9px 7px 24px;
582 }
582 }
583
583
584 #header #header-inner #quick li ul li a.hg,#header #header-inner #quick li ul li a.hg:hover
584 #header #header-inner #quick li ul li a.hg,#header #header-inner #quick li ul li a.hg:hover
585 {
585 {
586 background: url("../images/icons/hgicon.png") no-repeat scroll 4px 9px
586 background: url("../images/icons/hgicon.png") no-repeat scroll 4px 9px
587 #FFF;
587 #FFF;
588 min-width: 167px;
588 min-width: 167px;
589 margin: 0 0 0 14px;
589 margin: 0 0 0 14px;
590 padding: 12px 9px 7px 24px;
590 padding: 12px 9px 7px 24px;
591 }
591 }
592
592
593 #header #header-inner #quick li ul li a.git,#header #header-inner #quick li ul li a.git:hover
593 #header #header-inner #quick li ul li a.git,#header #header-inner #quick li ul li a.git:hover
594 {
594 {
595 background: url("../images/icons/giticon.png") no-repeat scroll 4px 9px
595 background: url("../images/icons/giticon.png") no-repeat scroll 4px 9px
596 #FFF;
596 #FFF;
597 min-width: 167px;
597 min-width: 167px;
598 margin: 0 0 0 14px;
598 margin: 0 0 0 14px;
599 padding: 12px 9px 7px 24px;
599 padding: 12px 9px 7px 24px;
600 }
600 }
601
601
602 #header #header-inner #quick li ul li a.repos,#header #header-inner #quick li ul li a.repos:hover
602 #header #header-inner #quick li ul li a.repos,#header #header-inner #quick li ul li a.repos:hover
603 {
603 {
604 background: url("../images/icons/database_edit.png") no-repeat scroll
604 background: url("../images/icons/database_edit.png") no-repeat scroll
605 4px 9px #FFF;
605 4px 9px #FFF;
606 width: 167px;
606 width: 167px;
607 margin: 0;
607 margin: 0;
608 padding: 12px 9px 7px 24px;
608 padding: 12px 9px 7px 24px;
609 }
609 }
610
610
611 #header #header-inner #quick li ul li a.repos_groups,#header #header-inner #quick li ul li a.repos_groups:hover
611 #header #header-inner #quick li ul li a.repos_groups,#header #header-inner #quick li ul li a.repos_groups:hover
612 {
612 {
613 background: url("../images/icons/database_link.png") no-repeat scroll
613 background: url("../images/icons/database_link.png") no-repeat scroll
614 4px 9px #FFF;
614 4px 9px #FFF;
615 width: 167px;
615 width: 167px;
616 margin: 0;
616 margin: 0;
617 padding: 12px 9px 7px 24px;
617 padding: 12px 9px 7px 24px;
618 }
618 }
619
619
620 #header #header-inner #quick li ul li a.users,#header #header-inner #quick li ul li a.users:hover
620 #header #header-inner #quick li ul li a.users,#header #header-inner #quick li ul li a.users:hover
621 {
621 {
622 background: #FFF url("../images/icons/user_edit.png") no-repeat 4px 9px;
622 background: #FFF url("../images/icons/user_edit.png") no-repeat 4px 9px;
623 width: 167px;
623 width: 167px;
624 margin: 0;
624 margin: 0;
625 padding: 12px 9px 7px 24px;
625 padding: 12px 9px 7px 24px;
626 }
626 }
627
627
628 #header #header-inner #quick li ul li a.groups,#header #header-inner #quick li ul li a.groups:hover
628 #header #header-inner #quick li ul li a.groups,#header #header-inner #quick li ul li a.groups:hover
629 {
629 {
630 background: #FFF url("../images/icons/group_edit.png") no-repeat 4px 9px;
630 background: #FFF url("../images/icons/group_edit.png") no-repeat 4px 9px;
631 width: 167px;
631 width: 167px;
632 margin: 0;
632 margin: 0;
633 padding: 12px 9px 7px 24px;
633 padding: 12px 9px 7px 24px;
634 }
634 }
635
635
636 #header #header-inner #quick li ul li a.defaults,#header #header-inner #quick li ul li a.defaults:hover
636 #header #header-inner #quick li ul li a.defaults,#header #header-inner #quick li ul li a.defaults:hover
637 {
637 {
638 background: #FFF url("../images/icons/wrench.png") no-repeat 4px 9px;
638 background: #FFF url("../images/icons/wrench.png") no-repeat 4px 9px;
639 width: 167px;
639 width: 167px;
640 margin: 0;
640 margin: 0;
641 padding: 12px 9px 7px 24px;
641 padding: 12px 9px 7px 24px;
642 }
642 }
643
643
644 #header #header-inner #quick li ul li a.settings,#header #header-inner #quick li ul li a.settings:hover
644 #header #header-inner #quick li ul li a.settings,#header #header-inner #quick li ul li a.settings:hover
645 {
645 {
646 background: #FFF url("../images/icons/cog.png") no-repeat 4px 9px;
646 background: #FFF url("../images/icons/cog.png") no-repeat 4px 9px;
647 width: 167px;
647 width: 167px;
648 margin: 0;
648 margin: 0;
649 padding: 12px 9px 7px 24px;
649 padding: 12px 9px 7px 24px;
650 }
650 }
651
651
652 #header #header-inner #quick li ul li a.permissions,#header #header-inner #quick li ul li a.permissions:hover
652 #header #header-inner #quick li ul li a.permissions,#header #header-inner #quick li ul li a.permissions:hover
653 {
653 {
654 background: #FFF url("../images/icons/key.png") no-repeat 4px 9px;
654 background: #FFF url("../images/icons/key.png") no-repeat 4px 9px;
655 width: 167px;
655 width: 167px;
656 margin: 0;
656 margin: 0;
657 padding: 12px 9px 7px 24px;
657 padding: 12px 9px 7px 24px;
658 }
658 }
659
659
660 #header #header-inner #quick li ul li a.ldap,#header #header-inner #quick li ul li a.ldap:hover
660 #header #header-inner #quick li ul li a.ldap,#header #header-inner #quick li ul li a.ldap:hover
661 {
661 {
662 background: #FFF url("../images/icons/server_key.png") no-repeat 4px 9px;
662 background: #FFF url("../images/icons/server_key.png") no-repeat 4px 9px;
663 width: 167px;
663 width: 167px;
664 margin: 0;
664 margin: 0;
665 padding: 12px 9px 7px 24px;
665 padding: 12px 9px 7px 24px;
666 }
666 }
667
667
668 #header #header-inner #quick li ul li a.fork,#header #header-inner #quick li ul li a.fork:hover
668 #header #header-inner #quick li ul li a.fork,#header #header-inner #quick li ul li a.fork:hover
669 {
669 {
670 background: #FFF url("../images/icons/arrow_divide.png") no-repeat 4px
670 background: #FFF url("../images/icons/arrow_divide.png") no-repeat 4px
671 9px;
671 9px;
672 width: 167px;
672 width: 167px;
673 margin: 0;
673 margin: 0;
674 padding: 12px 9px 7px 24px;
674 padding: 12px 9px 7px 24px;
675 }
675 }
676
676
677 #header #header-inner #quick li ul li a.locking_add,#header #header-inner #quick li ul li a.locking_add:hover
677 #header #header-inner #quick li ul li a.locking_add,#header #header-inner #quick li ul li a.locking_add:hover
678 {
678 {
679 background: #FFF url("../images/icons/lock_add.png") no-repeat 4px
679 background: #FFF url("../images/icons/lock_add.png") no-repeat 4px
680 9px;
680 9px;
681 width: 167px;
681 width: 167px;
682 margin: 0;
682 margin: 0;
683 padding: 12px 9px 7px 24px;
683 padding: 12px 9px 7px 24px;
684 }
684 }
685
685
686 #header #header-inner #quick li ul li a.locking_del,#header #header-inner #quick li ul li a.locking_del:hover
686 #header #header-inner #quick li ul li a.locking_del,#header #header-inner #quick li ul li a.locking_del:hover
687 {
687 {
688 background: #FFF url("../images/icons/lock_delete.png") no-repeat 4px
688 background: #FFF url("../images/icons/lock_delete.png") no-repeat 4px
689 9px;
689 9px;
690 width: 167px;
690 width: 167px;
691 margin: 0;
691 margin: 0;
692 padding: 12px 9px 7px 24px;
692 padding: 12px 9px 7px 24px;
693 }
693 }
694
694
695 #header #header-inner #quick li ul li a.pull_request,#header #header-inner #quick li ul li a.pull_request:hover
695 #header #header-inner #quick li ul li a.pull_request,#header #header-inner #quick li ul li a.pull_request:hover
696 {
696 {
697 background: #FFF url("../images/icons/arrow_join.png") no-repeat 4px
697 background: #FFF url("../images/icons/arrow_join.png") no-repeat 4px
698 9px;
698 9px;
699 width: 167px;
699 width: 167px;
700 margin: 0;
700 margin: 0;
701 padding: 12px 9px 7px 24px;
701 padding: 12px 9px 7px 24px;
702 }
702 }
703
703
704 #header #header-inner #quick li ul li a.compare_request,#header #header-inner #quick li ul li a.compare_request:hover
704 #header #header-inner #quick li ul li a.compare_request,#header #header-inner #quick li ul li a.compare_request:hover
705 {
705 {
706 background: #FFF url("../images/icons/arrow_inout.png") no-repeat 4px
706 background: #FFF url("../images/icons/arrow_inout.png") no-repeat 4px
707 9px;
707 9px;
708 width: 167px;
708 width: 167px;
709 margin: 0;
709 margin: 0;
710 padding: 12px 9px 7px 24px;
710 padding: 12px 9px 7px 24px;
711 }
711 }
712
712
713 #header #header-inner #quick li ul li a.search,#header #header-inner #quick li ul li a.search:hover
713 #header #header-inner #quick li ul li a.search,#header #header-inner #quick li ul li a.search:hover
714 {
714 {
715 background: #FFF url("../images/icons/search_16.png") no-repeat 4px 9px;
715 background: #FFF url("../images/icons/search_16.png") no-repeat 4px 9px;
716 width: 167px;
716 width: 167px;
717 margin: 0;
717 margin: 0;
718 padding: 12px 9px 7px 24px;
718 padding: 12px 9px 7px 24px;
719 }
719 }
720
720
721 #header #header-inner #quick li ul li a.delete,#header #header-inner #quick li ul li a.delete:hover
721 #header #header-inner #quick li ul li a.delete,#header #header-inner #quick li ul li a.delete:hover
722 {
722 {
723 background: #FFF url("../images/icons/delete.png") no-repeat 4px 9px;
723 background: #FFF url("../images/icons/delete.png") no-repeat 4px 9px;
724 width: 167px;
724 width: 167px;
725 margin: 0;
725 margin: 0;
726 padding: 12px 9px 7px 24px;
726 padding: 12px 9px 7px 24px;
727 }
727 }
728
728
729 #header #header-inner #quick li ul li a.branches,#header #header-inner #quick li ul li a.branches:hover
729 #header #header-inner #quick li ul li a.branches,#header #header-inner #quick li ul li a.branches:hover
730 {
730 {
731 background: #FFF url("../images/icons/arrow_branch.png") no-repeat 4px
731 background: #FFF url("../images/icons/arrow_branch.png") no-repeat 4px
732 9px;
732 9px;
733 width: 167px;
733 width: 167px;
734 margin: 0;
734 margin: 0;
735 padding: 12px 9px 7px 24px;
735 padding: 12px 9px 7px 24px;
736 }
736 }
737
737
738 #header #header-inner #quick li ul li a.tags,
738 #header #header-inner #quick li ul li a.tags,
739 #header #header-inner #quick li ul li a.tags:hover{
739 #header #header-inner #quick li ul li a.tags:hover{
740 background: #FFF url("../images/icons/tag_blue.png") no-repeat 4px 9px;
740 background: #FFF url("../images/icons/tag_blue.png") no-repeat 4px 9px;
741 width: 167px;
741 width: 167px;
742 margin: 0;
742 margin: 0;
743 padding: 12px 9px 7px 24px;
743 padding: 12px 9px 7px 24px;
744 }
744 }
745
745
746 #header #header-inner #quick li ul li a.bookmarks,
746 #header #header-inner #quick li ul li a.bookmarks,
747 #header #header-inner #quick li ul li a.bookmarks:hover{
747 #header #header-inner #quick li ul li a.bookmarks:hover{
748 background: #FFF url("../images/icons/tag_green.png") no-repeat 4px 9px;
748 background: #FFF url("../images/icons/tag_green.png") no-repeat 4px 9px;
749 width: 167px;
749 width: 167px;
750 margin: 0;
750 margin: 0;
751 padding: 12px 9px 7px 24px;
751 padding: 12px 9px 7px 24px;
752 }
752 }
753
753
754 #header #header-inner #quick li ul li a.admin,
754 #header #header-inner #quick li ul li a.admin,
755 #header #header-inner #quick li ul li a.admin:hover{
755 #header #header-inner #quick li ul li a.admin:hover{
756 background: #FFF url("../images/icons/cog_edit.png") no-repeat 4px 9px;
756 background: #FFF url("../images/icons/cog_edit.png") no-repeat 4px 9px;
757 width: 167px;
757 width: 167px;
758 margin: 0;
758 margin: 0;
759 padding: 12px 9px 7px 24px;
759 padding: 12px 9px 7px 24px;
760 }
760 }
761
761
762 .groups_breadcrumbs a {
762 .groups_breadcrumbs a {
763 color: #fff;
763 color: #fff;
764 }
764 }
765
765
766 .groups_breadcrumbs a:hover {
766 .groups_breadcrumbs a:hover {
767 color: #bfe3ff;
767 color: #bfe3ff;
768 text-decoration: none;
768 text-decoration: none;
769 }
769 }
770
770
771 td.quick_repo_menu {
771 td.quick_repo_menu {
772 background: #FFF url("../images/vertical-indicator.png") 8px 50% no-repeat !important;
772 background: #FFF url("../images/vertical-indicator.png") 8px 50% no-repeat !important;
773 cursor: pointer;
773 cursor: pointer;
774 width: 8px;
774 width: 8px;
775 border: 1px solid transparent;
775 border: 1px solid transparent;
776 }
776 }
777
777
778 td.quick_repo_menu.active {
778 td.quick_repo_menu.active {
779 background: url("../images/dt-arrow-dn.png") no-repeat scroll 5px 50% #FFFFFF !important;
779 background: url("../images/dt-arrow-dn.png") no-repeat scroll 5px 50% #FFFFFF !important;
780 border: 1px solid #003367;
780 border: 1px solid #003367;
781 box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
781 box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
782 cursor: pointer;
782 cursor: pointer;
783 }
783 }
784
784
785 td.quick_repo_menu .menu_items {
785 td.quick_repo_menu .menu_items {
786 margin-top: 10px;
786 margin-top: 10px;
787 margin-left:-6px;
787 margin-left:-6px;
788 width: 150px;
788 width: 150px;
789 position: absolute;
789 position: absolute;
790 background-color: #FFF;
790 background-color: #FFF;
791 background: none repeat scroll 0 0 #FFFFFF;
791 background: none repeat scroll 0 0 #FFFFFF;
792 border-color: #003367 #666666 #666666;
792 border-color: #003367 #666666 #666666;
793 border-right: 1px solid #666666;
793 border-right: 1px solid #666666;
794 border-style: solid;
794 border-style: solid;
795 border-width: 1px;
795 border-width: 1px;
796 box-shadow: 2px 8px 4px rgba(0, 0, 0, 0.2);
796 box-shadow: 2px 8px 4px rgba(0, 0, 0, 0.2);
797 border-top-style: none;
797 border-top-style: none;
798 }
798 }
799
799
800 td.quick_repo_menu .menu_items li {
800 td.quick_repo_menu .menu_items li {
801 padding: 0 !important;
801 padding: 0 !important;
802 }
802 }
803
803
804 td.quick_repo_menu .menu_items a {
804 td.quick_repo_menu .menu_items a {
805 display: block;
805 display: block;
806 padding: 4px 12px 4px 8px;
806 padding: 4px 12px 4px 8px;
807 }
807 }
808
808
809 td.quick_repo_menu .menu_items a:hover {
809 td.quick_repo_menu .menu_items a:hover {
810 background-color: #EEE;
810 background-color: #EEE;
811 text-decoration: none;
811 text-decoration: none;
812 }
812 }
813
813
814 td.quick_repo_menu .menu_items .icon img {
814 td.quick_repo_menu .menu_items .icon img {
815 margin-bottom: -2px;
815 margin-bottom: -2px;
816 }
816 }
817
817
818 td.quick_repo_menu .menu_items.hidden {
818 td.quick_repo_menu .menu_items.hidden {
819 display: none;
819 display: none;
820 }
820 }
821
821
822 .yui-dt-first th {
822 .yui-dt-first th {
823 text-align: left;
823 text-align: left;
824 }
824 }
825
825
826 /*
826 /*
827 Copyright (c) 2011, Yahoo! Inc. All rights reserved.
827 Copyright (c) 2011, Yahoo! Inc. All rights reserved.
828 Code licensed under the BSD License:
828 Code licensed under the BSD License:
829 http://developer.yahoo.com/yui/license.html
829 http://developer.yahoo.com/yui/license.html
830 version: 2.9.0
830 version: 2.9.0
831 */
831 */
832 .yui-skin-sam .yui-dt-mask {
832 .yui-skin-sam .yui-dt-mask {
833 position: absolute;
833 position: absolute;
834 z-index: 9500;
834 z-index: 9500;
835 }
835 }
836 .yui-dt-tmp {
836 .yui-dt-tmp {
837 position: absolute;
837 position: absolute;
838 left: -9000px;
838 left: -9000px;
839 }
839 }
840 .yui-dt-scrollable .yui-dt-bd { overflow: auto }
840 .yui-dt-scrollable .yui-dt-bd { overflow: auto }
841 .yui-dt-scrollable .yui-dt-hd {
841 .yui-dt-scrollable .yui-dt-hd {
842 overflow: hidden;
842 overflow: hidden;
843 position: relative;
843 position: relative;
844 }
844 }
845 .yui-dt-scrollable .yui-dt-bd thead tr,
845 .yui-dt-scrollable .yui-dt-bd thead tr,
846 .yui-dt-scrollable .yui-dt-bd thead th {
846 .yui-dt-scrollable .yui-dt-bd thead th {
847 position: absolute;
847 position: absolute;
848 left: -1500px;
848 left: -1500px;
849 }
849 }
850 .yui-dt-scrollable tbody { -moz-outline: 0 }
850 .yui-dt-scrollable tbody { -moz-outline: 0 }
851 .yui-skin-sam thead .yui-dt-sortable { cursor: pointer }
851 .yui-skin-sam thead .yui-dt-sortable { cursor: pointer }
852 .yui-skin-sam thead .yui-dt-draggable { cursor: move }
852 .yui-skin-sam thead .yui-dt-draggable { cursor: move }
853 .yui-dt-coltarget {
853 .yui-dt-coltarget {
854 position: absolute;
854 position: absolute;
855 z-index: 999;
855 z-index: 999;
856 }
856 }
857 .yui-dt-hd { zoom: 1 }
857 .yui-dt-hd { zoom: 1 }
858 th.yui-dt-resizeable .yui-dt-resizerliner { position: relative }
858 th.yui-dt-resizeable .yui-dt-resizerliner { position: relative }
859 .yui-dt-resizer {
859 .yui-dt-resizer {
860 position: absolute;
860 position: absolute;
861 right: 0;
861 right: 0;
862 bottom: 0;
862 bottom: 0;
863 height: 100%;
863 height: 100%;
864 cursor: e-resize;
864 cursor: e-resize;
865 cursor: col-resize;
865 cursor: col-resize;
866 background-color: #CCC;
866 background-color: #CCC;
867 opacity: 0;
867 opacity: 0;
868 filter: alpha(opacity=0);
868 filter: alpha(opacity=0);
869 }
869 }
870 .yui-dt-resizerproxy {
870 .yui-dt-resizerproxy {
871 visibility: hidden;
871 visibility: hidden;
872 position: absolute;
872 position: absolute;
873 z-index: 9000;
873 z-index: 9000;
874 background-color: #CCC;
874 background-color: #CCC;
875 opacity: 0;
875 opacity: 0;
876 filter: alpha(opacity=0);
876 filter: alpha(opacity=0);
877 }
877 }
878 th.yui-dt-hidden .yui-dt-liner,
878 th.yui-dt-hidden .yui-dt-liner,
879 td.yui-dt-hidden .yui-dt-liner,
879 td.yui-dt-hidden .yui-dt-liner,
880 th.yui-dt-hidden .yui-dt-resizer { display: none }
880 th.yui-dt-hidden .yui-dt-resizer { display: none }
881 .yui-dt-editor,
881 .yui-dt-editor,
882 .yui-dt-editor-shim {
882 .yui-dt-editor-shim {
883 position: absolute;
883 position: absolute;
884 z-index: 9000;
884 z-index: 9000;
885 }
885 }
886 .yui-skin-sam .yui-dt table {
886 .yui-skin-sam .yui-dt table {
887 margin: 0;
887 margin: 0;
888 padding: 0;
888 padding: 0;
889 font-family: arial;
889 font-family: arial;
890 font-size: inherit;
890 font-size: inherit;
891 border-collapse: separate;
891 border-collapse: separate;
892 *border-collapse: collapse;
892 *border-collapse: collapse;
893 border-spacing: 0;
893 border-spacing: 0;
894 border: 1px solid #7f7f7f;
894 border: 1px solid #7f7f7f;
895 }
895 }
896 .yui-skin-sam .yui-dt thead { border-spacing: 0 }
896 .yui-skin-sam .yui-dt thead { border-spacing: 0 }
897 .yui-skin-sam .yui-dt caption {
897 .yui-skin-sam .yui-dt caption {
898 color: #000;
898 color: #000;
899 font-size: 85%;
899 font-size: 85%;
900 font-weight: normal;
900 font-weight: normal;
901 font-style: italic;
901 font-style: italic;
902 line-height: 1;
902 line-height: 1;
903 padding: 1em 0;
903 padding: 1em 0;
904 text-align: center;
904 text-align: center;
905 }
905 }
906 .yui-skin-sam .yui-dt th { background: #d8d8da url(../images/sprite.png) repeat-x 0 0 }
906 .yui-skin-sam .yui-dt th { background: #d8d8da url(../images/sprite.png) repeat-x 0 0 }
907 .yui-skin-sam .yui-dt th,
907 .yui-skin-sam .yui-dt th,
908 .yui-skin-sam .yui-dt th a {
908 .yui-skin-sam .yui-dt th a {
909 font-weight: normal;
909 font-weight: normal;
910 text-decoration: none;
910 text-decoration: none;
911 color: #000;
911 color: #000;
912 vertical-align: bottom;
912 vertical-align: bottom;
913 }
913 }
914 .yui-skin-sam .yui-dt th {
914 .yui-skin-sam .yui-dt th {
915 margin: 0;
915 margin: 0;
916 padding: 0;
916 padding: 0;
917 border: 0;
917 border: 0;
918 border-right: 1px solid #cbcbcb;
918 border-right: 1px solid #cbcbcb;
919 }
919 }
920 .yui-skin-sam .yui-dt tr.yui-dt-first td { border-top: 1px solid #7f7f7f }
920 .yui-skin-sam .yui-dt tr.yui-dt-first td { border-top: 1px solid #7f7f7f }
921 .yui-skin-sam .yui-dt th .yui-dt-liner { white-space: nowrap }
921 .yui-skin-sam .yui-dt th .yui-dt-liner { white-space: nowrap }
922 .yui-skin-sam .yui-dt-liner {
922 .yui-skin-sam .yui-dt-liner {
923 margin: 0;
923 margin: 0;
924 padding: 0;
924 padding: 0;
925 }
925 }
926 .yui-skin-sam .yui-dt-coltarget {
926 .yui-skin-sam .yui-dt-coltarget {
927 width: 5px;
927 width: 5px;
928 background-color: red;
928 background-color: red;
929 }
929 }
930 .yui-skin-sam .yui-dt td {
930 .yui-skin-sam .yui-dt td {
931 margin: 0;
931 margin: 0;
932 padding: 0;
932 padding: 0;
933 border: 0;
933 border: 0;
934 border-right: 1px solid #cbcbcb;
934 border-right: 1px solid #cbcbcb;
935 text-align: left;
935 text-align: left;
936 }
936 }
937 .yui-skin-sam .yui-dt-list td { border-right: 0 }
937 .yui-skin-sam .yui-dt-list td { border-right: 0 }
938 .yui-skin-sam .yui-dt-resizer { width: 6px }
938 .yui-skin-sam .yui-dt-resizer { width: 6px }
939 .yui-skin-sam .yui-dt-mask {
939 .yui-skin-sam .yui-dt-mask {
940 background-color: #000;
940 background-color: #000;
941 opacity: .25;
941 opacity: .25;
942 filter: alpha(opacity=25);
942 filter: alpha(opacity=25);
943 }
943 }
944 .yui-skin-sam .yui-dt-message { background-color: #FFF }
944 .yui-skin-sam .yui-dt-message { background-color: #FFF }
945 .yui-skin-sam .yui-dt-scrollable table { border: 0 }
945 .yui-skin-sam .yui-dt-scrollable table { border: 0 }
946 .yui-skin-sam .yui-dt-scrollable .yui-dt-hd {
946 .yui-skin-sam .yui-dt-scrollable .yui-dt-hd {
947 border-left: 1px solid #7f7f7f;
947 border-left: 1px solid #7f7f7f;
948 border-top: 1px solid #7f7f7f;
948 border-top: 1px solid #7f7f7f;
949 border-right: 1px solid #7f7f7f;
949 border-right: 1px solid #7f7f7f;
950 }
950 }
951 .yui-skin-sam .yui-dt-scrollable .yui-dt-bd {
951 .yui-skin-sam .yui-dt-scrollable .yui-dt-bd {
952 border-left: 1px solid #7f7f7f;
952 border-left: 1px solid #7f7f7f;
953 border-bottom: 1px solid #7f7f7f;
953 border-bottom: 1px solid #7f7f7f;
954 border-right: 1px solid #7f7f7f;
954 border-right: 1px solid #7f7f7f;
955 background-color: #FFF;
955 background-color: #FFF;
956 }
956 }
957 .yui-skin-sam .yui-dt-scrollable .yui-dt-data tr.yui-dt-last td { border-bottom: 1px solid #7f7f7f }
957 .yui-skin-sam .yui-dt-scrollable .yui-dt-data tr.yui-dt-last td { border-bottom: 1px solid #7f7f7f }
958 .yui-skin-sam th.yui-dt-asc,
958 .yui-skin-sam th.yui-dt-asc,
959 .yui-skin-sam th.yui-dt-desc { background: url(../images/sprite.png) repeat-x 0 -100px }
959 .yui-skin-sam th.yui-dt-desc { background: url(../images/sprite.png) repeat-x 0 -100px }
960 .yui-skin-sam th.yui-dt-sortable .yui-dt-label { margin-right: 10px }
960 .yui-skin-sam th.yui-dt-sortable .yui-dt-label { margin-right: 10px }
961 .yui-skin-sam th.yui-dt-asc .yui-dt-liner { background: url(../images/dt-arrow-up.png) no-repeat right }
961 .yui-skin-sam th.yui-dt-asc .yui-dt-liner { background: url(../images/dt-arrow-up.png) no-repeat right }
962 .yui-skin-sam th.yui-dt-desc .yui-dt-liner { background: url(../images/dt-arrow-dn.png) no-repeat right }
962 .yui-skin-sam th.yui-dt-desc .yui-dt-liner { background: url(../images/dt-arrow-dn.png) no-repeat right }
963 tbody .yui-dt-editable { cursor: pointer }
963 tbody .yui-dt-editable { cursor: pointer }
964 .yui-dt-editor {
964 .yui-dt-editor {
965 text-align: left;
965 text-align: left;
966 background-color: #f2f2f2;
966 background-color: #f2f2f2;
967 border: 1px solid #808080;
967 border: 1px solid #808080;
968 padding: 6px;
968 padding: 6px;
969 }
969 }
970 .yui-dt-editor label {
970 .yui-dt-editor label {
971 padding-left: 4px;
971 padding-left: 4px;
972 padding-right: 6px;
972 padding-right: 6px;
973 }
973 }
974 .yui-dt-editor .yui-dt-button {
974 .yui-dt-editor .yui-dt-button {
975 padding-top: 6px;
975 padding-top: 6px;
976 text-align: right;
976 text-align: right;
977 }
977 }
978 .yui-dt-editor .yui-dt-button button {
978 .yui-dt-editor .yui-dt-button button {
979 background: url(../images/sprite.png) repeat-x 0 0;
979 background: url(../images/sprite.png) repeat-x 0 0;
980 border: 1px solid #999;
980 border: 1px solid #999;
981 width: 4em;
981 width: 4em;
982 height: 1.8em;
982 height: 1.8em;
983 margin-left: 6px;
983 margin-left: 6px;
984 }
984 }
985 .yui-dt-editor .yui-dt-button button.yui-dt-default {
985 .yui-dt-editor .yui-dt-button button.yui-dt-default {
986 background: url(../images/sprite.png) repeat-x 0 -1400px;
986 background: url(../images/sprite.png) repeat-x 0 -1400px;
987 background-color: #5584e0;
987 background-color: #5584e0;
988 border: 1px solid #304369;
988 border: 1px solid #304369;
989 color: #FFF;
989 color: #FFF;
990 }
990 }
991 .yui-dt-editor .yui-dt-button button:hover {
991 .yui-dt-editor .yui-dt-button button:hover {
992 background: url(../images/sprite.png) repeat-x 0 -1300px;
992 background: url(../images/sprite.png) repeat-x 0 -1300px;
993 color: #000;
993 color: #000;
994 }
994 }
995 .yui-dt-editor .yui-dt-button button:active {
995 .yui-dt-editor .yui-dt-button button:active {
996 background: url(../images/sprite.png) repeat-x 0 -1700px;
996 background: url(../images/sprite.png) repeat-x 0 -1700px;
997 color: #000;
997 color: #000;
998 }
998 }
999 .yui-skin-sam tr.yui-dt-even { background-color: #FFF }
999 .yui-skin-sam tr.yui-dt-even { background-color: #FFF }
1000 .yui-skin-sam tr.yui-dt-odd { background-color: #edf5ff }
1000 .yui-skin-sam tr.yui-dt-odd { background-color: #edf5ff }
1001 .yui-skin-sam tr.yui-dt-even td.yui-dt-asc,
1001 .yui-skin-sam tr.yui-dt-even td.yui-dt-asc,
1002 .yui-skin-sam tr.yui-dt-even td.yui-dt-desc { background-color: #edf5ff }
1002 .yui-skin-sam tr.yui-dt-even td.yui-dt-desc { background-color: #edf5ff }
1003 .yui-skin-sam tr.yui-dt-odd td.yui-dt-asc,
1003 .yui-skin-sam tr.yui-dt-odd td.yui-dt-asc,
1004 .yui-skin-sam tr.yui-dt-odd td.yui-dt-desc { background-color: #dbeaff }
1004 .yui-skin-sam tr.yui-dt-odd td.yui-dt-desc { background-color: #dbeaff }
1005 .yui-skin-sam .yui-dt-list tr.yui-dt-even { background-color: #FFF }
1005 .yui-skin-sam .yui-dt-list tr.yui-dt-even { background-color: #FFF }
1006 .yui-skin-sam .yui-dt-list tr.yui-dt-odd { background-color: #FFF }
1006 .yui-skin-sam .yui-dt-list tr.yui-dt-odd { background-color: #FFF }
1007 .yui-skin-sam .yui-dt-list tr.yui-dt-even td.yui-dt-asc,
1007 .yui-skin-sam .yui-dt-list tr.yui-dt-even td.yui-dt-asc,
1008 .yui-skin-sam .yui-dt-list tr.yui-dt-even td.yui-dt-desc { background-color: #edf5ff }
1008 .yui-skin-sam .yui-dt-list tr.yui-dt-even td.yui-dt-desc { background-color: #edf5ff }
1009 .yui-skin-sam .yui-dt-list tr.yui-dt-odd td.yui-dt-asc,
1009 .yui-skin-sam .yui-dt-list tr.yui-dt-odd td.yui-dt-asc,
1010 .yui-skin-sam .yui-dt-list tr.yui-dt-odd td.yui-dt-desc { background-color: #edf5ff }
1010 .yui-skin-sam .yui-dt-list tr.yui-dt-odd td.yui-dt-desc { background-color: #edf5ff }
1011 .yui-skin-sam th.yui-dt-highlighted,
1011 .yui-skin-sam th.yui-dt-highlighted,
1012 .yui-skin-sam th.yui-dt-highlighted a { background-color: #b2d2ff }
1012 .yui-skin-sam th.yui-dt-highlighted a { background-color: #b2d2ff }
1013 .yui-skin-sam tr.yui-dt-highlighted,
1013 .yui-skin-sam tr.yui-dt-highlighted,
1014 .yui-skin-sam tr.yui-dt-highlighted td.yui-dt-asc,
1014 .yui-skin-sam tr.yui-dt-highlighted td.yui-dt-asc,
1015 .yui-skin-sam tr.yui-dt-highlighted td.yui-dt-desc,
1015 .yui-skin-sam tr.yui-dt-highlighted td.yui-dt-desc,
1016 .yui-skin-sam tr.yui-dt-even td.yui-dt-highlighted,
1016 .yui-skin-sam tr.yui-dt-even td.yui-dt-highlighted,
1017 .yui-skin-sam tr.yui-dt-odd td.yui-dt-highlighted {
1017 .yui-skin-sam tr.yui-dt-odd td.yui-dt-highlighted {
1018 cursor: pointer;
1018 cursor: pointer;
1019 background-color: #b2d2ff;
1019 background-color: #b2d2ff;
1020 }
1020 }
1021 .yui-skin-sam .yui-dt-list th.yui-dt-highlighted,
1021 .yui-skin-sam .yui-dt-list th.yui-dt-highlighted,
1022 .yui-skin-sam .yui-dt-list th.yui-dt-highlighted a { background-color: #b2d2ff }
1022 .yui-skin-sam .yui-dt-list th.yui-dt-highlighted a { background-color: #b2d2ff }
1023 .yui-skin-sam .yui-dt-list tr.yui-dt-highlighted,
1023 .yui-skin-sam .yui-dt-list tr.yui-dt-highlighted,
1024 .yui-skin-sam .yui-dt-list tr.yui-dt-highlighted td.yui-dt-asc,
1024 .yui-skin-sam .yui-dt-list tr.yui-dt-highlighted td.yui-dt-asc,
1025 .yui-skin-sam .yui-dt-list tr.yui-dt-highlighted td.yui-dt-desc,
1025 .yui-skin-sam .yui-dt-list tr.yui-dt-highlighted td.yui-dt-desc,
1026 .yui-skin-sam .yui-dt-list tr.yui-dt-even td.yui-dt-highlighted,
1026 .yui-skin-sam .yui-dt-list tr.yui-dt-even td.yui-dt-highlighted,
1027 .yui-skin-sam .yui-dt-list tr.yui-dt-odd td.yui-dt-highlighted {
1027 .yui-skin-sam .yui-dt-list tr.yui-dt-odd td.yui-dt-highlighted {
1028 cursor: pointer;
1028 cursor: pointer;
1029 background-color: #b2d2ff;
1029 background-color: #b2d2ff;
1030 }
1030 }
1031 .yui-skin-sam th.yui-dt-selected,
1031 .yui-skin-sam th.yui-dt-selected,
1032 .yui-skin-sam th.yui-dt-selected a { background-color: #446cd7 }
1032 .yui-skin-sam th.yui-dt-selected a { background-color: #446cd7 }
1033 .yui-skin-sam tr.yui-dt-selected td,
1033 .yui-skin-sam tr.yui-dt-selected td,
1034 .yui-skin-sam tr.yui-dt-selected td.yui-dt-asc,
1034 .yui-skin-sam tr.yui-dt-selected td.yui-dt-asc,
1035 .yui-skin-sam tr.yui-dt-selected td.yui-dt-desc {
1035 .yui-skin-sam tr.yui-dt-selected td.yui-dt-desc {
1036 background-color: #426fd9;
1036 background-color: #426fd9;
1037 color: #FFF;
1037 color: #FFF;
1038 }
1038 }
1039 .yui-skin-sam tr.yui-dt-even td.yui-dt-selected,
1039 .yui-skin-sam tr.yui-dt-even td.yui-dt-selected,
1040 .yui-skin-sam tr.yui-dt-odd td.yui-dt-selected {
1040 .yui-skin-sam tr.yui-dt-odd td.yui-dt-selected {
1041 background-color: #446cd7;
1041 background-color: #446cd7;
1042 color: #FFF;
1042 color: #FFF;
1043 }
1043 }
1044 .yui-skin-sam .yui-dt-list th.yui-dt-selected,
1044 .yui-skin-sam .yui-dt-list th.yui-dt-selected,
1045 .yui-skin-sam .yui-dt-list th.yui-dt-selected a { background-color: #446cd7 }
1045 .yui-skin-sam .yui-dt-list th.yui-dt-selected a { background-color: #446cd7 }
1046 .yui-skin-sam .yui-dt-list tr.yui-dt-selected td,
1046 .yui-skin-sam .yui-dt-list tr.yui-dt-selected td,
1047 .yui-skin-sam .yui-dt-list tr.yui-dt-selected td.yui-dt-asc,
1047 .yui-skin-sam .yui-dt-list tr.yui-dt-selected td.yui-dt-asc,
1048 .yui-skin-sam .yui-dt-list tr.yui-dt-selected td.yui-dt-desc {
1048 .yui-skin-sam .yui-dt-list tr.yui-dt-selected td.yui-dt-desc {
1049 background-color: #426fd9;
1049 background-color: #426fd9;
1050 color: #FFF;
1050 color: #FFF;
1051 }
1051 }
1052 .yui-skin-sam .yui-dt-list tr.yui-dt-even td.yui-dt-selected,
1052 .yui-skin-sam .yui-dt-list tr.yui-dt-even td.yui-dt-selected,
1053 .yui-skin-sam .yui-dt-list tr.yui-dt-odd td.yui-dt-selected {
1053 .yui-skin-sam .yui-dt-list tr.yui-dt-odd td.yui-dt-selected {
1054 background-color: #446cd7;
1054 background-color: #446cd7;
1055 color: #FFF;
1055 color: #FFF;
1056 }
1056 }
1057 .yui-skin-sam .yui-dt-paginator {
1057 .yui-skin-sam .yui-dt-paginator {
1058 display: block;
1058 display: block;
1059 margin: 6px 0;
1059 margin: 6px 0;
1060 white-space: nowrap;
1060 white-space: nowrap;
1061 }
1061 }
1062 .yui-skin-sam .yui-dt-paginator .yui-dt-first,
1062 .yui-skin-sam .yui-dt-paginator .yui-dt-first,
1063 .yui-skin-sam .yui-dt-paginator .yui-dt-last,
1063 .yui-skin-sam .yui-dt-paginator .yui-dt-last,
1064 .yui-skin-sam .yui-dt-paginator .yui-dt-selected { padding: 2px 6px }
1064 .yui-skin-sam .yui-dt-paginator .yui-dt-selected { padding: 2px 6px }
1065 .yui-skin-sam .yui-dt-paginator a.yui-dt-first,
1065 .yui-skin-sam .yui-dt-paginator a.yui-dt-first,
1066 .yui-skin-sam .yui-dt-paginator a.yui-dt-last { text-decoration: none }
1066 .yui-skin-sam .yui-dt-paginator a.yui-dt-last { text-decoration: none }
1067 .yui-skin-sam .yui-dt-paginator .yui-dt-previous,
1067 .yui-skin-sam .yui-dt-paginator .yui-dt-previous,
1068 .yui-skin-sam .yui-dt-paginator .yui-dt-next { display: none }
1068 .yui-skin-sam .yui-dt-paginator .yui-dt-next { display: none }
1069 .yui-skin-sam a.yui-dt-page {
1069 .yui-skin-sam a.yui-dt-page {
1070 border: 1px solid #cbcbcb;
1070 border: 1px solid #cbcbcb;
1071 padding: 2px 6px;
1071 padding: 2px 6px;
1072 text-decoration: none;
1072 text-decoration: none;
1073 background-color: #fff;
1073 background-color: #fff;
1074 }
1074 }
1075 .yui-skin-sam .yui-dt-selected {
1075 .yui-skin-sam .yui-dt-selected {
1076 border: 1px solid #fff;
1076 border: 1px solid #fff;
1077 background-color: #fff;
1077 background-color: #fff;
1078 }
1078 }
1079
1079
1080 #content #left {
1080 #content #left {
1081 left: 0;
1081 left: 0;
1082 width: 280px;
1082 width: 280px;
1083 position: absolute;
1083 position: absolute;
1084 }
1084 }
1085
1085
1086 #content #right {
1086 #content #right {
1087 margin: 0 60px 10px 290px;
1087 margin: 0 60px 10px 290px;
1088 }
1088 }
1089
1089
1090 #content div.box {
1090 #content div.box {
1091 clear: both;
1091 clear: both;
1092 overflow: hidden;
1092 overflow: hidden;
1093 background: #fff;
1093 background: #fff;
1094 margin: 0 0 10px;
1094 margin: 0 0 10px;
1095 padding: 0 0 10px;
1095 padding: 0 0 10px;
1096 -webkit-border-radius: 4px 4px 4px 4px;
1096 -webkit-border-radius: 4px 4px 4px 4px;
1097 -khtml-border-radius: 4px 4px 4px 4px;
1097 -khtml-border-radius: 4px 4px 4px 4px;
1098 -moz-border-radius: 4px 4px 4px 4px;
1098 -moz-border-radius: 4px 4px 4px 4px;
1099 border-radius: 4px 4px 4px 4px;
1099 border-radius: 4px 4px 4px 4px;
1100 box-shadow: 0 2px 2px rgba(0, 0, 0, 0.6);
1100 box-shadow: 0 2px 2px rgba(0, 0, 0, 0.6);
1101 }
1101 }
1102
1102
1103 #content div.box-left {
1103 #content div.box-left {
1104 width: 49%;
1104 width: 49%;
1105 clear: none;
1105 clear: none;
1106 float: left;
1106 float: left;
1107 margin: 0 0 10px;
1107 margin: 0 0 10px;
1108 }
1108 }
1109
1109
1110 #content div.box-right {
1110 #content div.box-right {
1111 width: 49%;
1111 width: 49%;
1112 clear: none;
1112 clear: none;
1113 float: right;
1113 float: right;
1114 margin: 0 0 10px;
1114 margin: 0 0 10px;
1115 }
1115 }
1116
1116
1117 #content div.box div.title {
1117 #content div.box div.title {
1118 clear: both;
1118 clear: both;
1119 overflow: hidden;
1119 overflow: hidden;
1120 background-color: #003B76;
1120 background-color: #003B76;
1121 background-repeat: repeat-x;
1121 background-repeat: repeat-x;
1122 background-image: -khtml-gradient(linear, left top, left bottom, from(#003B76), to(#00376E) );
1122 background-image: -khtml-gradient(linear, left top, left bottom, from(#003B76), to(#00376E) );
1123 background-image: -moz-linear-gradient(top, #003b76, #00376e);
1123 background-image: -moz-linear-gradient(top, #003b76, #00376e);
1124 background-image: -ms-linear-gradient(top, #003b76, #00376e);
1124 background-image: -ms-linear-gradient(top, #003b76, #00376e);
1125 background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #003b76), color-stop(100%, #00376e) );
1125 background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #003b76), color-stop(100%, #00376e) );
1126 background-image: -webkit-linear-gradient(top, #003b76, #00376e);
1126 background-image: -webkit-linear-gradient(top, #003b76, #00376e);
1127 background-image: -o-linear-gradient(top, #003b76, #00376e);
1127 background-image: -o-linear-gradient(top, #003b76, #00376e);
1128 background-image: linear-gradient(top, #003b76, #00376e);
1128 background-image: linear-gradient(top, #003b76, #00376e);
1129 filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#003b76', endColorstr='#00376e', GradientType=0 );
1129 filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#003b76', endColorstr='#00376e', GradientType=0 );
1130 margin: 0 0 20px;
1130 margin: 0 0 20px;
1131 padding: 0;
1131 padding: 0;
1132 }
1132 }
1133
1133
1134 #content div.box div.title h5 {
1134 #content div.box div.title h5 {
1135 float: left;
1135 float: left;
1136 border: none;
1136 border: none;
1137 color: #fff;
1137 color: #fff;
1138 text-transform: uppercase;
1138 text-transform: uppercase;
1139 margin: 0;
1139 margin: 0;
1140 padding: 11px 0 11px 10px;
1140 padding: 11px 0 11px 10px;
1141 }
1141 }
1142
1142
1143 #content div.box div.title .link-white{
1143 #content div.box div.title .link-white{
1144 color: #FFFFFF;
1144 color: #FFFFFF;
1145 }
1145 }
1146
1146
1147 #content div.box div.title .link-white.current{
1147 #content div.box div.title .link-white.current{
1148 color: #BFE3FF;
1148 color: #BFE3FF;
1149 }
1149 }
1150
1150
1151 #content div.box div.title ul.links li {
1151 #content div.box div.title ul.links li {
1152 list-style: none;
1152 list-style: none;
1153 float: left;
1153 float: left;
1154 margin: 0;
1154 margin: 0;
1155 padding: 0;
1155 padding: 0;
1156 }
1156 }
1157
1157
1158 #content div.box div.title ul.links li a {
1158 #content div.box div.title ul.links li a {
1159 border-left: 1px solid #316293;
1159 border-left: 1px solid #316293;
1160 color: #FFFFFF;
1160 color: #FFFFFF;
1161 display: block;
1161 display: block;
1162 float: left;
1162 float: left;
1163 font-size: 13px;
1163 font-size: 13px;
1164 font-weight: 700;
1164 font-weight: 700;
1165 height: 1%;
1165 height: 1%;
1166 margin: 0;
1166 margin: 0;
1167 padding: 11px 22px 12px;
1167 padding: 11px 22px 12px;
1168 text-decoration: none;
1168 text-decoration: none;
1169 }
1169 }
1170
1170
1171 #content div.box h1,#content div.box h2,#content div.box h3,#content div.box h4,#content div.box h5,#content div.box h6,
1171 #content div.box h1,#content div.box h2,#content div.box h3,#content div.box h4,#content div.box h5,#content div.box h6,
1172 #content div.box div.h1,#content div.box div.h2,#content div.box div.h3,#content div.box div.h4,#content div.box div.h5,#content div.box div.h6
1172 #content div.box div.h1,#content div.box div.h2,#content div.box div.h3,#content div.box div.h4,#content div.box div.h5,#content div.box div.h6
1173
1173
1174 {
1174 {
1175 clear: both;
1175 clear: both;
1176 overflow: hidden;
1176 overflow: hidden;
1177 border-bottom: 1px solid #DDD;
1177 border-bottom: 1px solid #DDD;
1178 margin: 10px 20px;
1178 margin: 10px 20px;
1179 padding: 0 0 15px;
1179 padding: 0 0 15px;
1180 }
1180 }
1181
1181
1182 #content div.box p {
1182 #content div.box p {
1183 color: #5f5f5f;
1183 color: #5f5f5f;
1184 font-size: 12px;
1184 font-size: 12px;
1185 line-height: 150%;
1185 line-height: 150%;
1186 margin: 0 24px 10px;
1186 margin: 0 24px 10px;
1187 padding: 0;
1187 padding: 0;
1188 }
1188 }
1189
1189
1190 #content div.box blockquote {
1190 #content div.box blockquote {
1191 border-left: 4px solid #DDD;
1191 border-left: 4px solid #DDD;
1192 color: #5f5f5f;
1192 color: #5f5f5f;
1193 font-size: 11px;
1193 font-size: 11px;
1194 line-height: 150%;
1194 line-height: 150%;
1195 margin: 0 34px;
1195 margin: 0 34px;
1196 padding: 0 0 0 14px;
1196 padding: 0 0 0 14px;
1197 }
1197 }
1198
1198
1199 #content div.box blockquote p {
1199 #content div.box blockquote p {
1200 margin: 10px 0;
1200 margin: 10px 0;
1201 padding: 0;
1201 padding: 0;
1202 }
1202 }
1203
1203
1204 #content div.box dl {
1204 #content div.box dl {
1205 margin: 10px 0px;
1205 margin: 10px 0px;
1206 }
1206 }
1207
1207
1208 #content div.box dt {
1208 #content div.box dt {
1209 font-size: 12px;
1209 font-size: 12px;
1210 margin: 0;
1210 margin: 0;
1211 }
1211 }
1212
1212
1213 #content div.box dd {
1213 #content div.box dd {
1214 font-size: 12px;
1214 font-size: 12px;
1215 margin: 0;
1215 margin: 0;
1216 padding: 8px 0 8px 15px;
1216 padding: 8px 0 8px 15px;
1217 }
1217 }
1218
1218
1219 #content div.box li {
1219 #content div.box li {
1220 font-size: 12px;
1220 font-size: 12px;
1221 padding: 4px 0;
1221 padding: 4px 0;
1222 }
1222 }
1223
1223
1224 #content div.box ul.disc,#content div.box ul.circle {
1224 #content div.box ul.disc,#content div.box ul.circle {
1225 margin: 10px 24px 10px 38px;
1225 margin: 10px 24px 10px 38px;
1226 }
1226 }
1227
1227
1228 #content div.box ul.square {
1228 #content div.box ul.square {
1229 margin: 10px 24px 10px 40px;
1229 margin: 10px 24px 10px 40px;
1230 }
1230 }
1231
1231
1232 #content div.box img.left {
1232 #content div.box img.left {
1233 border: none;
1233 border: none;
1234 float: left;
1234 float: left;
1235 margin: 10px 10px 10px 0;
1235 margin: 10px 10px 10px 0;
1236 }
1236 }
1237
1237
1238 #content div.box img.right {
1238 #content div.box img.right {
1239 border: none;
1239 border: none;
1240 float: right;
1240 float: right;
1241 margin: 10px 0 10px 10px;
1241 margin: 10px 0 10px 10px;
1242 }
1242 }
1243
1243
1244 #content div.box div.messages {
1244 #content div.box div.messages {
1245 clear: both;
1245 clear: both;
1246 overflow: hidden;
1246 overflow: hidden;
1247 margin: 0 20px;
1247 margin: 0 20px;
1248 padding: 0;
1248 padding: 0;
1249 }
1249 }
1250
1250
1251 #content div.box div.message {
1251 #content div.box div.message {
1252 clear: both;
1252 clear: both;
1253 overflow: hidden;
1253 overflow: hidden;
1254 margin: 0;
1254 margin: 0;
1255 padding: 5px 0;
1255 padding: 5px 0;
1256 white-space: pre-wrap;
1256 white-space: pre-wrap;
1257 }
1257 }
1258 #content div.box div.expand {
1258 #content div.box div.expand {
1259 width: 110%;
1259 width: 110%;
1260 height:14px;
1260 height:14px;
1261 font-size:10px;
1261 font-size:10px;
1262 text-align:center;
1262 text-align:center;
1263 cursor: pointer;
1263 cursor: pointer;
1264 color:#666;
1264 color:#666;
1265
1265
1266 background:-webkit-gradient(linear,0% 50%,100% 50%,color-stop(0%,rgba(255,255,255,0)),color-stop(100%,rgba(64,96,128,0.1)));
1266 background:-webkit-gradient(linear,0% 50%,100% 50%,color-stop(0%,rgba(255,255,255,0)),color-stop(100%,rgba(64,96,128,0.1)));
1267 background:-webkit-linear-gradient(top,rgba(255,255,255,0),rgba(64,96,128,0.1));
1267 background:-webkit-linear-gradient(top,rgba(255,255,255,0),rgba(64,96,128,0.1));
1268 background:-moz-linear-gradient(top,rgba(255,255,255,0),rgba(64,96,128,0.1));
1268 background:-moz-linear-gradient(top,rgba(255,255,255,0),rgba(64,96,128,0.1));
1269 background:-o-linear-gradient(top,rgba(255,255,255,0),rgba(64,96,128,0.1));
1269 background:-o-linear-gradient(top,rgba(255,255,255,0),rgba(64,96,128,0.1));
1270 background:-ms-linear-gradient(top,rgba(255,255,255,0),rgba(64,96,128,0.1));
1270 background:-ms-linear-gradient(top,rgba(255,255,255,0),rgba(64,96,128,0.1));
1271 background:linear-gradient(top,rgba(255,255,255,0),rgba(64,96,128,0.1));
1271 background:linear-gradient(top,rgba(255,255,255,0),rgba(64,96,128,0.1));
1272
1272
1273 display: none;
1273 display: none;
1274 }
1274 }
1275 #content div.box div.expand .expandtext {
1275 #content div.box div.expand .expandtext {
1276 background-color: #ffffff;
1276 background-color: #ffffff;
1277 padding: 2px;
1277 padding: 2px;
1278 border-radius: 2px;
1278 border-radius: 2px;
1279 }
1279 }
1280
1280
1281 #content div.box div.message a {
1281 #content div.box div.message a {
1282 font-weight: 400 !important;
1282 font-weight: 400 !important;
1283 }
1283 }
1284
1284
1285 #content div.box div.message div.image {
1285 #content div.box div.message div.image {
1286 float: left;
1286 float: left;
1287 margin: 9px 0 0 5px;
1287 margin: 9px 0 0 5px;
1288 padding: 6px;
1288 padding: 6px;
1289 }
1289 }
1290
1290
1291 #content div.box div.message div.image img {
1291 #content div.box div.message div.image img {
1292 vertical-align: middle;
1292 vertical-align: middle;
1293 margin: 0;
1293 margin: 0;
1294 }
1294 }
1295
1295
1296 #content div.box div.message div.text {
1296 #content div.box div.message div.text {
1297 float: left;
1297 float: left;
1298 margin: 0;
1298 margin: 0;
1299 padding: 9px 6px;
1299 padding: 9px 6px;
1300 }
1300 }
1301
1301
1302 #content div.box div.message div.dismiss a {
1302 #content div.box div.message div.dismiss a {
1303 height: 16px;
1303 height: 16px;
1304 width: 16px;
1304 width: 16px;
1305 display: block;
1305 display: block;
1306 background: url("../images/icons/cross.png") no-repeat;
1306 background: url("../images/icons/cross.png") no-repeat;
1307 margin: 15px 14px 0 0;
1307 margin: 15px 14px 0 0;
1308 padding: 0;
1308 padding: 0;
1309 }
1309 }
1310
1310
1311 #content div.box div.message div.text h1,#content div.box div.message div.text h2,#content div.box div.message div.text h3,#content div.box div.message div.text h4,#content div.box div.message div.text h5,#content div.box div.message div.text h6
1311 #content div.box div.message div.text h1,#content div.box div.message div.text h2,#content div.box div.message div.text h3,#content div.box div.message div.text h4,#content div.box div.message div.text h5,#content div.box div.message div.text h6
1312 {
1312 {
1313 border: none;
1313 border: none;
1314 margin: 0;
1314 margin: 0;
1315 padding: 0;
1315 padding: 0;
1316 }
1316 }
1317
1317
1318 #content div.box div.message div.text span {
1318 #content div.box div.message div.text span {
1319 height: 1%;
1319 height: 1%;
1320 display: block;
1320 display: block;
1321 margin: 0;
1321 margin: 0;
1322 padding: 5px 0 0;
1322 padding: 5px 0 0;
1323 }
1323 }
1324
1324
1325 #content div.box div.message-error {
1325 #content div.box div.message-error {
1326 height: 1%;
1326 height: 1%;
1327 clear: both;
1327 clear: both;
1328 overflow: hidden;
1328 overflow: hidden;
1329 background: #FBE3E4;
1329 background: #FBE3E4;
1330 border: 1px solid #FBC2C4;
1330 border: 1px solid #FBC2C4;
1331 color: #860006;
1331 color: #860006;
1332 }
1332 }
1333
1333
1334 #content div.box div.message-error h6 {
1334 #content div.box div.message-error h6 {
1335 color: #860006;
1335 color: #860006;
1336 }
1336 }
1337
1337
1338 #content div.box div.message-warning {
1338 #content div.box div.message-warning {
1339 height: 1%;
1339 height: 1%;
1340 clear: both;
1340 clear: both;
1341 overflow: hidden;
1341 overflow: hidden;
1342 background: #FFF6BF;
1342 background: #FFF6BF;
1343 border: 1px solid #FFD324;
1343 border: 1px solid #FFD324;
1344 color: #5f5200;
1344 color: #5f5200;
1345 }
1345 }
1346
1346
1347 #content div.box div.message-warning h6 {
1347 #content div.box div.message-warning h6 {
1348 color: #5f5200;
1348 color: #5f5200;
1349 }
1349 }
1350
1350
1351 #content div.box div.message-notice {
1351 #content div.box div.message-notice {
1352 height: 1%;
1352 height: 1%;
1353 clear: both;
1353 clear: both;
1354 overflow: hidden;
1354 overflow: hidden;
1355 background: #8FBDE0;
1355 background: #8FBDE0;
1356 border: 1px solid #6BACDE;
1356 border: 1px solid #6BACDE;
1357 color: #003863;
1357 color: #003863;
1358 }
1358 }
1359
1359
1360 #content div.box div.message-notice h6 {
1360 #content div.box div.message-notice h6 {
1361 color: #003863;
1361 color: #003863;
1362 }
1362 }
1363
1363
1364 #content div.box div.message-success {
1364 #content div.box div.message-success {
1365 height: 1%;
1365 height: 1%;
1366 clear: both;
1366 clear: both;
1367 overflow: hidden;
1367 overflow: hidden;
1368 background: #E6EFC2;
1368 background: #E6EFC2;
1369 border: 1px solid #C6D880;
1369 border: 1px solid #C6D880;
1370 color: #4e6100;
1370 color: #4e6100;
1371 }
1371 }
1372
1372
1373 #content div.box div.message-success h6 {
1373 #content div.box div.message-success h6 {
1374 color: #4e6100;
1374 color: #4e6100;
1375 }
1375 }
1376
1376
1377 #content div.box div.form div.fields div.field {
1377 #content div.box div.form div.fields div.field {
1378 height: 1%;
1378 height: 1%;
1379 border-bottom: 1px solid #DDD;
1379 border-bottom: 1px solid #DDD;
1380 clear: both;
1380 clear: both;
1381 margin: 0;
1381 margin: 0;
1382 padding: 10px 0;
1382 padding: 10px 0;
1383 }
1383 }
1384
1384
1385 #content div.box div.form div.fields div.field-first {
1385 #content div.box div.form div.fields div.field-first {
1386 padding: 0 0 10px;
1386 padding: 0 0 10px;
1387 }
1387 }
1388
1388
1389 #content div.box div.form div.fields div.field-noborder {
1389 #content div.box div.form div.fields div.field-noborder {
1390 border-bottom: 0 !important;
1390 border-bottom: 0 !important;
1391 }
1391 }
1392
1392
1393 #content div.box div.form div.fields div.field span.error-message {
1393 #content div.box div.form div.fields div.field span.error-message {
1394 height: 1%;
1394 height: 1%;
1395 display: inline-block;
1395 display: inline-block;
1396 color: red;
1396 color: red;
1397 margin: 8px 0 0 4px;
1397 margin: 8px 0 0 4px;
1398 padding: 0;
1398 padding: 0;
1399 }
1399 }
1400
1400
1401 #content div.box div.form div.fields div.field span.success {
1401 #content div.box div.form div.fields div.field span.success {
1402 height: 1%;
1402 height: 1%;
1403 display: block;
1403 display: block;
1404 color: #316309;
1404 color: #316309;
1405 margin: 8px 0 0;
1405 margin: 8px 0 0;
1406 padding: 0;
1406 padding: 0;
1407 }
1407 }
1408
1408
1409 #content div.box div.form div.fields div.field div.label {
1409 #content div.box div.form div.fields div.field div.label {
1410 left: 70px;
1410 left: 70px;
1411 width: 155px;
1411 width: 155px;
1412 position: absolute;
1412 position: absolute;
1413 margin: 0;
1413 margin: 0;
1414 padding: 5px 0 0 0px;
1414 padding: 5px 0 0 0px;
1415 }
1415 }
1416
1416
1417 #content div.box div.form div.fields div.field div.label-summary {
1417 #content div.box div.form div.fields div.field div.label-summary {
1418 left: 30px;
1418 left: 30px;
1419 width: 155px;
1419 width: 155px;
1420 position: absolute;
1420 position: absolute;
1421 margin: 0;
1421 margin: 0;
1422 padding: 0px 0 0 0px;
1422 padding: 0px 0 0 0px;
1423 }
1423 }
1424
1424
1425 #content div.box-left div.form div.fields div.field div.label,
1425 #content div.box-left div.form div.fields div.field div.label,
1426 #content div.box-right div.form div.fields div.field div.label,
1426 #content div.box-right div.form div.fields div.field div.label,
1427 #content div.box-left div.form div.fields div.field div.label,
1427 #content div.box-left div.form div.fields div.field div.label,
1428 #content div.box-left div.form div.fields div.field div.label-summary,
1428 #content div.box-left div.form div.fields div.field div.label-summary,
1429 #content div.box-right div.form div.fields div.field div.label-summary,
1429 #content div.box-right div.form div.fields div.field div.label-summary,
1430 #content div.box-left div.form div.fields div.field div.label-summary
1430 #content div.box-left div.form div.fields div.field div.label-summary
1431 {
1431 {
1432 clear: both;
1432 clear: both;
1433 overflow: hidden;
1433 overflow: hidden;
1434 left: 0;
1434 left: 0;
1435 width: auto;
1435 width: auto;
1436 position: relative;
1436 position: relative;
1437 margin: 0;
1437 margin: 0;
1438 padding: 0 0 8px;
1438 padding: 0 0 8px;
1439 }
1439 }
1440
1440
1441 #content div.box div.form div.fields div.field div.label-select {
1441 #content div.box div.form div.fields div.field div.label-select {
1442 padding: 5px 0 0 5px;
1442 padding: 5px 0 0 5px;
1443 }
1443 }
1444
1444
1445 #content div.box-left div.form div.fields div.field div.label-select,
1445 #content div.box-left div.form div.fields div.field div.label-select,
1446 #content div.box-right div.form div.fields div.field div.label-select
1446 #content div.box-right div.form div.fields div.field div.label-select
1447 {
1447 {
1448 padding: 0 0 8px;
1448 padding: 0 0 8px;
1449 }
1449 }
1450
1450
1451 #content div.box-left div.form div.fields div.field div.label-textarea,
1451 #content div.box-left div.form div.fields div.field div.label-textarea,
1452 #content div.box-right div.form div.fields div.field div.label-textarea
1452 #content div.box-right div.form div.fields div.field div.label-textarea
1453 {
1453 {
1454 padding: 0 0 8px !important;
1454 padding: 0 0 8px !important;
1455 }
1455 }
1456
1456
1457 #content div.box div.form div.fields div.field div.label label,div.label label
1457 #content div.box div.form div.fields div.field div.label label,div.label label
1458 {
1458 {
1459 color: #393939;
1459 color: #393939;
1460 font-weight: 700;
1460 font-weight: 700;
1461 }
1461 }
1462 #content div.box div.form div.fields div.field div.label label,div.label-summary label
1462 #content div.box div.form div.fields div.field div.label label,div.label-summary label
1463 {
1463 {
1464 color: #393939;
1464 color: #393939;
1465 font-weight: 700;
1465 font-weight: 700;
1466 }
1466 }
1467 #content div.box div.form div.fields div.field div.input {
1467 #content div.box div.form div.fields div.field div.input {
1468 margin: 0 0 0 200px;
1468 margin: 0 0 0 200px;
1469 }
1469 }
1470
1470
1471 #content div.box div.form div.fields div.field div.input.summary {
1471 #content div.box div.form div.fields div.field div.input.summary {
1472 margin: 0 0 0 110px;
1472 margin: 0 0 0 110px;
1473 }
1473 }
1474 #content div.box div.form div.fields div.field div.input.summary-short {
1474 #content div.box div.form div.fields div.field div.input.summary-short {
1475 margin: 0 0 0 110px;
1475 margin: 0 0 0 110px;
1476 }
1476 }
1477 #content div.box div.form div.fields div.field div.file {
1477 #content div.box div.form div.fields div.field div.file {
1478 margin: 0 0 0 200px;
1478 margin: 0 0 0 200px;
1479 }
1479 }
1480
1480
1481 #content div.box-left div.form div.fields div.field div.input,#content div.box-right div.form div.fields div.field div.input
1481 #content div.box-left div.form div.fields div.field div.input,#content div.box-right div.form div.fields div.field div.input
1482 {
1482 {
1483 margin: 0 0 0 0px;
1483 margin: 0 0 0 0px;
1484 }
1484 }
1485
1485
1486 #content div.box div.form div.fields div.field div.input input,
1486 #content div.box div.form div.fields div.field div.input input,
1487 .reviewer_ac input {
1487 .reviewer_ac input {
1488 background: #FFF;
1488 background: #FFF;
1489 border-top: 1px solid #b3b3b3;
1489 border-top: 1px solid #b3b3b3;
1490 border-left: 1px solid #b3b3b3;
1490 border-left: 1px solid #b3b3b3;
1491 border-right: 1px solid #eaeaea;
1491 border-right: 1px solid #eaeaea;
1492 border-bottom: 1px solid #eaeaea;
1492 border-bottom: 1px solid #eaeaea;
1493 color: #000;
1493 color: #000;
1494 font-size: 11px;
1494 font-size: 11px;
1495 margin: 0;
1495 margin: 0;
1496 padding: 7px 7px 6px;
1496 padding: 7px 7px 6px;
1497 }
1497 }
1498
1498
1499 #content div.box div.form div.fields div.field div.input input#clone_url,
1499 #content div.box div.form div.fields div.field div.input input#clone_url,
1500 #content div.box div.form div.fields div.field div.input input#clone_url_id
1500 #content div.box div.form div.fields div.field div.input input#clone_url_id
1501 {
1501 {
1502 font-size: 16px;
1502 font-size: 16px;
1503 padding: 2px;
1503 padding: 2px;
1504 }
1504 }
1505
1505
1506 #content div.box div.form div.fields div.field div.file input {
1506 #content div.box div.form div.fields div.field div.file input {
1507 background: none repeat scroll 0 0 #FFFFFF;
1507 background: none repeat scroll 0 0 #FFFFFF;
1508 border-color: #B3B3B3 #EAEAEA #EAEAEA #B3B3B3;
1508 border-color: #B3B3B3 #EAEAEA #EAEAEA #B3B3B3;
1509 border-style: solid;
1509 border-style: solid;
1510 border-width: 1px;
1510 border-width: 1px;
1511 color: #000000;
1511 color: #000000;
1512 font-size: 11px;
1512 font-size: 11px;
1513 margin: 0;
1513 margin: 0;
1514 padding: 7px 7px 6px;
1514 padding: 7px 7px 6px;
1515 }
1515 }
1516
1516
1517 input.disabled {
1517 input.disabled {
1518 background-color: #F5F5F5 !important;
1518 background-color: #F5F5F5 !important;
1519 }
1519 }
1520 #content div.box div.form div.fields div.field div.input input.small {
1520 #content div.box div.form div.fields div.field div.input input.small {
1521 width: 30%;
1521 width: 30%;
1522 }
1522 }
1523
1523
1524 #content div.box div.form div.fields div.field div.input input.medium {
1524 #content div.box div.form div.fields div.field div.input input.medium {
1525 width: 55%;
1525 width: 55%;
1526 }
1526 }
1527
1527
1528 #content div.box div.form div.fields div.field div.input input.large {
1528 #content div.box div.form div.fields div.field div.input input.large {
1529 width: 85%;
1529 width: 85%;
1530 }
1530 }
1531
1531
1532 #content div.box div.form div.fields div.field div.input input.date {
1532 #content div.box div.form div.fields div.field div.input input.date {
1533 width: 177px;
1533 width: 177px;
1534 }
1534 }
1535
1535
1536 #content div.box div.form div.fields div.field div.input input.button {
1536 #content div.box div.form div.fields div.field div.input input.button {
1537 background: #D4D0C8;
1537 background: #D4D0C8;
1538 border-top: 1px solid #FFF;
1538 border-top: 1px solid #FFF;
1539 border-left: 1px solid #FFF;
1539 border-left: 1px solid #FFF;
1540 border-right: 1px solid #404040;
1540 border-right: 1px solid #404040;
1541 border-bottom: 1px solid #404040;
1541 border-bottom: 1px solid #404040;
1542 color: #000;
1542 color: #000;
1543 margin: 0;
1543 margin: 0;
1544 padding: 4px 8px;
1544 padding: 4px 8px;
1545 }
1545 }
1546
1546
1547 #content div.box div.form div.fields div.field div.textarea {
1547 #content div.box div.form div.fields div.field div.textarea {
1548 border-top: 1px solid #b3b3b3;
1548 border-top: 1px solid #b3b3b3;
1549 border-left: 1px solid #b3b3b3;
1549 border-left: 1px solid #b3b3b3;
1550 border-right: 1px solid #eaeaea;
1550 border-right: 1px solid #eaeaea;
1551 border-bottom: 1px solid #eaeaea;
1551 border-bottom: 1px solid #eaeaea;
1552 margin: 0 0 0 200px;
1552 margin: 0 0 0 200px;
1553 padding: 10px;
1553 padding: 10px;
1554 }
1554 }
1555
1555
1556 #content div.box div.form div.fields div.field div.textarea-editor {
1556 #content div.box div.form div.fields div.field div.textarea-editor {
1557 border: 1px solid #ddd;
1557 border: 1px solid #ddd;
1558 padding: 0;
1558 padding: 0;
1559 }
1559 }
1560
1560
1561 #content div.box div.form div.fields div.field div.textarea textarea {
1561 #content div.box div.form div.fields div.field div.textarea textarea {
1562 width: 100%;
1562 width: 100%;
1563 height: 220px;
1563 height: 220px;
1564 overflow: hidden;
1564 overflow: hidden;
1565 background: #FFF;
1565 background: #FFF;
1566 color: #000;
1566 color: #000;
1567 font-size: 11px;
1567 font-size: 11px;
1568 outline: none;
1568 outline: none;
1569 border-width: 0;
1569 border-width: 0;
1570 margin: 0;
1570 margin: 0;
1571 padding: 0;
1571 padding: 0;
1572 }
1572 }
1573
1573
1574 #content div.box-left div.form div.fields div.field div.textarea textarea,#content div.box-right div.form div.fields div.field div.textarea textarea
1574 #content div.box-left div.form div.fields div.field div.textarea textarea,#content div.box-right div.form div.fields div.field div.textarea textarea
1575 {
1575 {
1576 width: 100%;
1576 width: 100%;
1577 height: 100px;
1577 height: 100px;
1578 }
1578 }
1579
1579
1580 #content div.box div.form div.fields div.field div.textarea table {
1580 #content div.box div.form div.fields div.field div.textarea table {
1581 width: 100%;
1581 width: 100%;
1582 border: none;
1582 border: none;
1583 margin: 0;
1583 margin: 0;
1584 padding: 0;
1584 padding: 0;
1585 }
1585 }
1586
1586
1587 #content div.box div.form div.fields div.field div.textarea table td {
1587 #content div.box div.form div.fields div.field div.textarea table td {
1588 background: #DDD;
1588 background: #DDD;
1589 border: none;
1589 border: none;
1590 padding: 0;
1590 padding: 0;
1591 }
1591 }
1592
1592
1593 #content div.box div.form div.fields div.field div.textarea table td table
1593 #content div.box div.form div.fields div.field div.textarea table td table
1594 {
1594 {
1595 width: auto;
1595 width: auto;
1596 border: none;
1596 border: none;
1597 margin: 0;
1597 margin: 0;
1598 padding: 0;
1598 padding: 0;
1599 }
1599 }
1600
1600
1601 #content div.box div.form div.fields div.field div.textarea table td table td
1601 #content div.box div.form div.fields div.field div.textarea table td table td
1602 {
1602 {
1603 font-size: 11px;
1603 font-size: 11px;
1604 padding: 5px 5px 5px 0;
1604 padding: 5px 5px 5px 0;
1605 }
1605 }
1606
1606
1607 #content div.box div.form div.fields div.field input[type=text]:focus,
1607 #content div.box div.form div.fields div.field input[type=text]:focus,
1608 #content div.box div.form div.fields div.field input[type=password]:focus,
1608 #content div.box div.form div.fields div.field input[type=password]:focus,
1609 #content div.box div.form div.fields div.field input[type=file]:focus,
1609 #content div.box div.form div.fields div.field input[type=file]:focus,
1610 #content div.box div.form div.fields div.field textarea:focus,
1610 #content div.box div.form div.fields div.field textarea:focus,
1611 #content div.box div.form div.fields div.field select:focus,
1611 #content div.box div.form div.fields div.field select:focus,
1612 .reviewer_ac input:focus
1612 .reviewer_ac input:focus
1613 {
1613 {
1614 background: #f6f6f6;
1614 background: #f6f6f6;
1615 border-color: #666;
1615 border-color: #666;
1616 }
1616 }
1617
1617
1618 .reviewer_ac {
1618 .reviewer_ac {
1619 padding:10px
1619 padding:10px
1620 }
1620 }
1621
1621
1622 div.form div.fields div.field div.button {
1622 div.form div.fields div.field div.button {
1623 margin: 0;
1623 margin: 0;
1624 padding: 0 0 0 8px;
1624 padding: 0 0 0 8px;
1625 }
1625 }
1626 #content div.box table.noborder {
1626 #content div.box table.noborder {
1627 border: 1px solid transparent;
1627 border: 1px solid transparent;
1628 }
1628 }
1629
1629
1630 #content div.box table {
1630 #content div.box table {
1631 width: 100%;
1631 width: 100%;
1632 border-collapse: separate;
1632 border-collapse: separate;
1633 margin: 0;
1633 margin: 0;
1634 padding: 0;
1634 padding: 0;
1635 border: 1px solid #eee;
1635 border: 1px solid #eee;
1636 -webkit-border-radius: 4px;
1636 -webkit-border-radius: 4px;
1637 -moz-border-radius: 4px;
1637 -moz-border-radius: 4px;
1638 border-radius: 4px;
1638 border-radius: 4px;
1639 }
1639 }
1640
1640
1641 #content div.box table th {
1641 #content div.box table th {
1642 background: #eee;
1642 background: #eee;
1643 border-bottom: 1px solid #ddd;
1643 border-bottom: 1px solid #ddd;
1644 padding: 5px 0px 5px 5px;
1644 padding: 5px 0px 5px 5px;
1645 text-align: left;
1645 text-align: left;
1646 }
1646 }
1647
1647
1648 #content div.box table th.left {
1648 #content div.box table th.left {
1649 text-align: left;
1649 text-align: left;
1650 }
1650 }
1651
1651
1652 #content div.box table th.right {
1652 #content div.box table th.right {
1653 text-align: right;
1653 text-align: right;
1654 }
1654 }
1655
1655
1656 #content div.box table th.center {
1656 #content div.box table th.center {
1657 text-align: center;
1657 text-align: center;
1658 }
1658 }
1659
1659
1660 #content div.box table th.selected {
1660 #content div.box table th.selected {
1661 vertical-align: middle;
1661 vertical-align: middle;
1662 padding: 0;
1662 padding: 0;
1663 }
1663 }
1664
1664
1665 #content div.box table td {
1665 #content div.box table td {
1666 background: #fff;
1666 background: #fff;
1667 border-bottom: 1px solid #cdcdcd;
1667 border-bottom: 1px solid #cdcdcd;
1668 vertical-align: middle;
1668 vertical-align: middle;
1669 padding: 5px;
1669 padding: 5px;
1670 }
1670 }
1671
1671
1672 #content div.box table tr.selected td {
1672 #content div.box table tr.selected td {
1673 background: #FFC;
1673 background: #FFC;
1674 }
1674 }
1675
1675
1676 #content div.box table td.selected {
1676 #content div.box table td.selected {
1677 width: 3%;
1677 width: 3%;
1678 text-align: center;
1678 text-align: center;
1679 vertical-align: middle;
1679 vertical-align: middle;
1680 padding: 0;
1680 padding: 0;
1681 }
1681 }
1682
1682
1683 #content div.box table td.action {
1683 #content div.box table td.action {
1684 width: 45%;
1684 width: 45%;
1685 text-align: left;
1685 text-align: left;
1686 }
1686 }
1687
1687
1688 #content div.box table td.date {
1688 #content div.box table td.date {
1689 width: 33%;
1689 width: 33%;
1690 text-align: center;
1690 text-align: center;
1691 }
1691 }
1692
1692
1693 #content div.box div.action {
1693 #content div.box div.action {
1694 float: right;
1694 float: right;
1695 background: #FFF;
1695 background: #FFF;
1696 text-align: right;
1696 text-align: right;
1697 margin: 10px 0 0;
1697 margin: 10px 0 0;
1698 padding: 0;
1698 padding: 0;
1699 }
1699 }
1700
1700
1701 #content div.box div.action select {
1701 #content div.box div.action select {
1702 font-size: 11px;
1702 font-size: 11px;
1703 margin: 0;
1703 margin: 0;
1704 }
1704 }
1705
1705
1706 #content div.box div.action .ui-selectmenu {
1706 #content div.box div.action .ui-selectmenu {
1707 margin: 0;
1707 margin: 0;
1708 padding: 0;
1708 padding: 0;
1709 }
1709 }
1710
1710
1711 #content div.box div.pagination {
1711 #content div.box div.pagination {
1712 height: 1%;
1712 height: 1%;
1713 clear: both;
1713 clear: both;
1714 overflow: hidden;
1714 overflow: hidden;
1715 margin: 10px 0 0;
1715 margin: 10px 0 0;
1716 padding: 0;
1716 padding: 0;
1717 }
1717 }
1718
1718
1719 #content div.box div.pagination ul.pager {
1719 #content div.box div.pagination ul.pager {
1720 float: right;
1720 float: right;
1721 text-align: right;
1721 text-align: right;
1722 margin: 0;
1722 margin: 0;
1723 padding: 0;
1723 padding: 0;
1724 }
1724 }
1725
1725
1726 #content div.box div.pagination ul.pager li {
1726 #content div.box div.pagination ul.pager li {
1727 height: 1%;
1727 height: 1%;
1728 float: left;
1728 float: left;
1729 list-style: none;
1729 list-style: none;
1730 background: #ebebeb url("../images/pager.png") repeat-x;
1730 background: #ebebeb url("../images/pager.png") repeat-x;
1731 border-top: 1px solid #dedede;
1731 border-top: 1px solid #dedede;
1732 border-left: 1px solid #cfcfcf;
1732 border-left: 1px solid #cfcfcf;
1733 border-right: 1px solid #c4c4c4;
1733 border-right: 1px solid #c4c4c4;
1734 border-bottom: 1px solid #c4c4c4;
1734 border-bottom: 1px solid #c4c4c4;
1735 color: #4A4A4A;
1735 color: #4A4A4A;
1736 font-weight: 700;
1736 font-weight: 700;
1737 margin: 0 0 0 4px;
1737 margin: 0 0 0 4px;
1738 padding: 0;
1738 padding: 0;
1739 }
1739 }
1740
1740
1741 #content div.box div.pagination ul.pager li.separator {
1741 #content div.box div.pagination ul.pager li.separator {
1742 padding: 6px;
1742 padding: 6px;
1743 }
1743 }
1744
1744
1745 #content div.box div.pagination ul.pager li.current {
1745 #content div.box div.pagination ul.pager li.current {
1746 background: #b4b4b4 url("../images/pager_selected.png") repeat-x;
1746 background: #b4b4b4 url("../images/pager_selected.png") repeat-x;
1747 border-top: 1px solid #ccc;
1747 border-top: 1px solid #ccc;
1748 border-left: 1px solid #bebebe;
1748 border-left: 1px solid #bebebe;
1749 border-right: 1px solid #b1b1b1;
1749 border-right: 1px solid #b1b1b1;
1750 border-bottom: 1px solid #afafaf;
1750 border-bottom: 1px solid #afafaf;
1751 color: #515151;
1751 color: #515151;
1752 padding: 6px;
1752 padding: 6px;
1753 }
1753 }
1754
1754
1755 #content div.box div.pagination ul.pager li a {
1755 #content div.box div.pagination ul.pager li a {
1756 height: 1%;
1756 height: 1%;
1757 display: block;
1757 display: block;
1758 float: left;
1758 float: left;
1759 color: #515151;
1759 color: #515151;
1760 text-decoration: none;
1760 text-decoration: none;
1761 margin: 0;
1761 margin: 0;
1762 padding: 6px;
1762 padding: 6px;
1763 }
1763 }
1764
1764
1765 #content div.box div.pagination ul.pager li a:hover,#content div.box div.pagination ul.pager li a:active
1765 #content div.box div.pagination ul.pager li a:hover,#content div.box div.pagination ul.pager li a:active
1766 {
1766 {
1767 background: #b4b4b4 url("../images/pager_selected.png") repeat-x;
1767 background: #b4b4b4 url("../images/pager_selected.png") repeat-x;
1768 border-top: 1px solid #ccc;
1768 border-top: 1px solid #ccc;
1769 border-left: 1px solid #bebebe;
1769 border-left: 1px solid #bebebe;
1770 border-right: 1px solid #b1b1b1;
1770 border-right: 1px solid #b1b1b1;
1771 border-bottom: 1px solid #afafaf;
1771 border-bottom: 1px solid #afafaf;
1772 margin: -1px;
1772 margin: -1px;
1773 }
1773 }
1774
1774
1775 #content div.box div.pagination-wh {
1775 #content div.box div.pagination-wh {
1776 height: 1%;
1776 height: 1%;
1777 clear: both;
1777 clear: both;
1778 overflow: hidden;
1778 overflow: hidden;
1779 text-align: right;
1779 text-align: right;
1780 margin: 10px 0 0;
1780 margin: 10px 0 0;
1781 padding: 0;
1781 padding: 0;
1782 }
1782 }
1783
1783
1784 #content div.box div.pagination-right {
1784 #content div.box div.pagination-right {
1785 float: right;
1785 float: right;
1786 }
1786 }
1787
1787
1788 #content div.box div.pagination-wh a,
1788 #content div.box div.pagination-wh a,
1789 #content div.box div.pagination-wh span.pager_dotdot,
1789 #content div.box div.pagination-wh span.pager_dotdot,
1790 #content div.box div.pagination-wh span.yui-pg-previous,
1790 #content div.box div.pagination-wh span.yui-pg-previous,
1791 #content div.box div.pagination-wh span.yui-pg-last,
1791 #content div.box div.pagination-wh span.yui-pg-last,
1792 #content div.box div.pagination-wh span.yui-pg-next,
1792 #content div.box div.pagination-wh span.yui-pg-next,
1793 #content div.box div.pagination-wh span.yui-pg-first
1793 #content div.box div.pagination-wh span.yui-pg-first
1794 {
1794 {
1795 height: 1%;
1795 height: 1%;
1796 float: left;
1796 float: left;
1797 background: #ebebeb url("../images/pager.png") repeat-x;
1797 background: #ebebeb url("../images/pager.png") repeat-x;
1798 border-top: 1px solid #dedede;
1798 border-top: 1px solid #dedede;
1799 border-left: 1px solid #cfcfcf;
1799 border-left: 1px solid #cfcfcf;
1800 border-right: 1px solid #c4c4c4;
1800 border-right: 1px solid #c4c4c4;
1801 border-bottom: 1px solid #c4c4c4;
1801 border-bottom: 1px solid #c4c4c4;
1802 color: #4A4A4A;
1802 color: #4A4A4A;
1803 font-weight: 700;
1803 font-weight: 700;
1804 margin: 0 0 0 4px;
1804 margin: 0 0 0 4px;
1805 padding: 6px;
1805 padding: 6px;
1806 }
1806 }
1807
1807
1808 #content div.box div.pagination-wh span.pager_curpage {
1808 #content div.box div.pagination-wh span.pager_curpage {
1809 height: 1%;
1809 height: 1%;
1810 float: left;
1810 float: left;
1811 background: #b4b4b4 url("../images/pager_selected.png") repeat-x;
1811 background: #b4b4b4 url("../images/pager_selected.png") repeat-x;
1812 border-top: 1px solid #ccc;
1812 border-top: 1px solid #ccc;
1813 border-left: 1px solid #bebebe;
1813 border-left: 1px solid #bebebe;
1814 border-right: 1px solid #b1b1b1;
1814 border-right: 1px solid #b1b1b1;
1815 border-bottom: 1px solid #afafaf;
1815 border-bottom: 1px solid #afafaf;
1816 color: #515151;
1816 color: #515151;
1817 font-weight: 700;
1817 font-weight: 700;
1818 margin: 0 0 0 4px;
1818 margin: 0 0 0 4px;
1819 padding: 6px;
1819 padding: 6px;
1820 }
1820 }
1821
1821
1822 #content div.box div.pagination-wh a:hover,#content div.box div.pagination-wh a:active
1822 #content div.box div.pagination-wh a:hover,#content div.box div.pagination-wh a:active
1823 {
1823 {
1824 background: #b4b4b4 url("../images/pager_selected.png") repeat-x;
1824 background: #b4b4b4 url("../images/pager_selected.png") repeat-x;
1825 border-top: 1px solid #ccc;
1825 border-top: 1px solid #ccc;
1826 border-left: 1px solid #bebebe;
1826 border-left: 1px solid #bebebe;
1827 border-right: 1px solid #b1b1b1;
1827 border-right: 1px solid #b1b1b1;
1828 border-bottom: 1px solid #afafaf;
1828 border-bottom: 1px solid #afafaf;
1829 text-decoration: none;
1829 text-decoration: none;
1830 }
1830 }
1831
1831
1832 #content div.box div.traffic div.legend {
1832 #content div.box div.traffic div.legend {
1833 clear: both;
1833 clear: both;
1834 overflow: hidden;
1834 overflow: hidden;
1835 border-bottom: 1px solid #ddd;
1835 border-bottom: 1px solid #ddd;
1836 margin: 0 0 10px;
1836 margin: 0 0 10px;
1837 padding: 0 0 10px;
1837 padding: 0 0 10px;
1838 }
1838 }
1839
1839
1840 #content div.box div.traffic div.legend h6 {
1840 #content div.box div.traffic div.legend h6 {
1841 float: left;
1841 float: left;
1842 border: none;
1842 border: none;
1843 margin: 0;
1843 margin: 0;
1844 padding: 0;
1844 padding: 0;
1845 }
1845 }
1846
1846
1847 #content div.box div.traffic div.legend li {
1847 #content div.box div.traffic div.legend li {
1848 list-style: none;
1848 list-style: none;
1849 float: left;
1849 float: left;
1850 font-size: 11px;
1850 font-size: 11px;
1851 margin: 0;
1851 margin: 0;
1852 padding: 0 8px 0 4px;
1852 padding: 0 8px 0 4px;
1853 }
1853 }
1854
1854
1855 #content div.box div.traffic div.legend li.visits {
1855 #content div.box div.traffic div.legend li.visits {
1856 border-left: 12px solid #edc240;
1856 border-left: 12px solid #edc240;
1857 }
1857 }
1858
1858
1859 #content div.box div.traffic div.legend li.pageviews {
1859 #content div.box div.traffic div.legend li.pageviews {
1860 border-left: 12px solid #afd8f8;
1860 border-left: 12px solid #afd8f8;
1861 }
1861 }
1862
1862
1863 #content div.box div.traffic table {
1863 #content div.box div.traffic table {
1864 width: auto;
1864 width: auto;
1865 }
1865 }
1866
1866
1867 #content div.box div.traffic table td {
1867 #content div.box div.traffic table td {
1868 background: transparent;
1868 background: transparent;
1869 border: none;
1869 border: none;
1870 padding: 2px 3px 3px;
1870 padding: 2px 3px 3px;
1871 }
1871 }
1872
1872
1873 #content div.box div.traffic table td.legendLabel {
1873 #content div.box div.traffic table td.legendLabel {
1874 padding: 0 3px 2px;
1874 padding: 0 3px 2px;
1875 }
1875 }
1876
1876
1877 #summary {
1877 #summary {
1878
1878
1879 }
1879 }
1880
1880
1881 #summary .metatag {
1881 #summary .metatag {
1882 display: inline-block;
1882 display: inline-block;
1883 padding: 3px 5px;
1883 padding: 3px 5px;
1884 margin-bottom: 3px;
1884 margin-bottom: 3px;
1885 margin-right: 1px;
1885 margin-right: 1px;
1886 border-radius: 5px;
1886 border-radius: 5px;
1887 }
1887 }
1888
1888
1889 #content div.box #summary p {
1889 #content div.box #summary p {
1890 margin-bottom: -5px;
1890 margin-bottom: -5px;
1891 width: 600px;
1891 width: 600px;
1892 white-space: pre-wrap;
1892 white-space: pre-wrap;
1893 }
1893 }
1894
1894
1895 #content div.box #summary p:last-child {
1895 #content div.box #summary p:last-child {
1896 margin-bottom: 9px;
1896 margin-bottom: 9px;
1897 }
1897 }
1898
1898
1899 #content div.box #summary p:first-of-type {
1899 #content div.box #summary p:first-of-type {
1900 margin-top: 9px;
1900 margin-top: 9px;
1901 }
1901 }
1902
1902
1903 .metatag {
1903 .metatag {
1904 display: inline-block;
1904 display: inline-block;
1905 margin-right: 1px;
1905 margin-right: 1px;
1906 -webkit-border-radius: 4px 4px 4px 4px;
1906 -webkit-border-radius: 4px 4px 4px 4px;
1907 -khtml-border-radius: 4px 4px 4px 4px;
1907 -khtml-border-radius: 4px 4px 4px 4px;
1908 -moz-border-radius: 4px 4px 4px 4px;
1908 -moz-border-radius: 4px 4px 4px 4px;
1909 border-radius: 4px 4px 4px 4px;
1909 border-radius: 4px 4px 4px 4px;
1910
1910
1911 border: solid 1px #9CF;
1911 border: solid 1px #9CF;
1912 padding: 2px 3px 2px 3px !important;
1912 padding: 2px 3px 2px 3px !important;
1913 background-color: #DEF;
1913 background-color: #DEF;
1914 }
1914 }
1915
1915
1916 .metatag[tag="dead"] {
1916 .metatag[tag="dead"] {
1917 background-color: #E44;
1917 background-color: #E44;
1918 }
1918 }
1919
1919
1920 .metatag[tag="stale"] {
1920 .metatag[tag="stale"] {
1921 background-color: #EA4;
1921 background-color: #EA4;
1922 }
1922 }
1923
1923
1924 .metatag[tag="featured"] {
1924 .metatag[tag="featured"] {
1925 background-color: #AEA;
1925 background-color: #AEA;
1926 }
1926 }
1927
1927
1928 .metatag[tag="requires"] {
1928 .metatag[tag="requires"] {
1929 background-color: #9CF;
1929 background-color: #9CF;
1930 }
1930 }
1931
1931
1932 .metatag[tag="recommends"] {
1932 .metatag[tag="recommends"] {
1933 background-color: #BDF;
1933 background-color: #BDF;
1934 }
1934 }
1935
1935
1936 .metatag[tag="lang"] {
1936 .metatag[tag="lang"] {
1937 background-color: #FAF474;
1937 background-color: #FAF474;
1938 }
1938 }
1939
1939
1940 .metatag[tag="license"] {
1940 .metatag[tag="license"] {
1941 border: solid 1px #9CF;
1941 border: solid 1px #9CF;
1942 background-color: #DEF;
1942 background-color: #DEF;
1943 target-new: tab !important;
1943 target-new: tab !important;
1944 }
1944 }
1945 .metatag[tag="see"] {
1945 .metatag[tag="see"] {
1946 border: solid 1px #CBD;
1946 border: solid 1px #CBD;
1947 background-color: #EDF;
1947 background-color: #EDF;
1948 }
1948 }
1949
1949
1950 a.metatag[tag="license"]:hover {
1950 a.metatag[tag="license"]:hover {
1951 background-color: #003367;
1951 background-color: #003367;
1952 color: #FFF;
1952 color: #FFF;
1953 text-decoration: none;
1953 text-decoration: none;
1954 }
1954 }
1955
1955
1956 #summary .desc {
1956 #summary .desc {
1957 white-space: pre;
1957 white-space: pre;
1958 width: 100%;
1958 width: 100%;
1959 }
1959 }
1960
1960
1961 #summary .repo_name {
1961 #summary .repo_name {
1962 font-size: 1.6em;
1962 font-size: 1.6em;
1963 font-weight: bold;
1963 font-weight: bold;
1964 vertical-align: baseline;
1964 vertical-align: baseline;
1965 clear: right
1965 clear: right
1966 }
1966 }
1967
1967
1968 #footer {
1968 #footer {
1969 clear: both;
1969 clear: both;
1970 overflow: hidden;
1970 overflow: hidden;
1971 text-align: right;
1971 text-align: right;
1972 margin: 0;
1972 margin: 0;
1973 padding: 0 10px 4px;
1973 padding: 0 10px 4px;
1974 margin: -10px 0 0;
1974 margin: -10px 0 0;
1975 }
1975 }
1976
1976
1977 #footer div#footer-inner {
1977 #footer div#footer-inner {
1978 background-color: #003B76;
1978 background-color: #003B76;
1979 background-repeat : repeat-x;
1979 background-repeat : repeat-x;
1980 background-image : -khtml-gradient( linear, left top, left bottom, from(#003B76), to(#00376E));
1980 background-image : -khtml-gradient( linear, left top, left bottom, from(#003B76), to(#00376E));
1981 background-image : -moz-linear-gradient(top, #003b76, #00376e);
1981 background-image : -moz-linear-gradient(top, #003b76, #00376e);
1982 background-image : -ms-linear-gradient( top, #003b76, #00376e);
1982 background-image : -ms-linear-gradient( top, #003b76, #00376e);
1983 background-image : -webkit-gradient( linear, left top, left bottom, color-stop( 0%, #003b76), color-stop( 100%, #00376e));
1983 background-image : -webkit-gradient( linear, left top, left bottom, color-stop( 0%, #003b76), color-stop( 100%, #00376e));
1984 background-image : -webkit-linear-gradient( top, #003b76, #00376e));
1984 background-image : -webkit-linear-gradient( top, #003b76, #00376e));
1985 background-image : -o-linear-gradient( top, #003b76, #00376e));
1985 background-image : -o-linear-gradient( top, #003b76, #00376e));
1986 background-image : linear-gradient( top, #003b76, #00376e);
1986 background-image : linear-gradient( top, #003b76, #00376e);
1987 filter :progid : DXImageTransform.Microsoft.gradient ( startColorstr = '#003b76', endColorstr = '#00376e', GradientType = 0);
1987 filter :progid : DXImageTransform.Microsoft.gradient ( startColorstr = '#003b76', endColorstr = '#00376e', GradientType = 0);
1988 box-shadow: 0 2px 2px rgba(0, 0, 0, 0.6);
1988 box-shadow: 0 2px 2px rgba(0, 0, 0, 0.6);
1989 -webkit-border-radius: 4px 4px 4px 4px;
1989 -webkit-border-radius: 4px 4px 4px 4px;
1990 -khtml-border-radius: 4px 4px 4px 4px;
1990 -khtml-border-radius: 4px 4px 4px 4px;
1991 -moz-border-radius: 4px 4px 4px 4px;
1991 -moz-border-radius: 4px 4px 4px 4px;
1992 border-radius: 4px 4px 4px 4px;
1992 border-radius: 4px 4px 4px 4px;
1993 }
1993 }
1994
1994
1995 #footer div#footer-inner p {
1995 #footer div#footer-inner p {
1996 padding: 15px 25px 15px 0;
1996 padding: 15px 25px 15px 0;
1997 color: #FFF;
1997 color: #FFF;
1998 font-weight: 700;
1998 font-weight: 700;
1999 }
1999 }
2000
2000
2001 #footer div#footer-inner .footer-link {
2001 #footer div#footer-inner .footer-link {
2002 float: left;
2002 float: left;
2003 padding-left: 10px;
2003 padding-left: 10px;
2004 }
2004 }
2005
2005
2006 #footer div#footer-inner .footer-link a,#footer div#footer-inner .footer-link-right a
2006 #footer div#footer-inner .footer-link a,#footer div#footer-inner .footer-link-right a
2007 {
2007 {
2008 color: #FFF;
2008 color: #FFF;
2009 }
2009 }
2010
2010
2011 #login div.title {
2011 #login div.title {
2012 clear: both;
2012 clear: both;
2013 overflow: hidden;
2013 overflow: hidden;
2014 position: relative;
2014 position: relative;
2015 background-color: #003B76;
2015 background-color: #003B76;
2016 background-repeat : repeat-x;
2016 background-repeat : repeat-x;
2017 background-image : -khtml-gradient( linear, left top, left bottom, from(#003B76), to(#00376E));
2017 background-image : -khtml-gradient( linear, left top, left bottom, from(#003B76), to(#00376E));
2018 background-image : -moz-linear-gradient( top, #003b76, #00376e);
2018 background-image : -moz-linear-gradient( top, #003b76, #00376e);
2019 background-image : -ms-linear-gradient( top, #003b76, #00376e);
2019 background-image : -ms-linear-gradient( top, #003b76, #00376e);
2020 background-image : -webkit-gradient( linear, left top, left bottom, color-stop( 0%, #003b76), color-stop( 100%, #00376e));
2020 background-image : -webkit-gradient( linear, left top, left bottom, color-stop( 0%, #003b76), color-stop( 100%, #00376e));
2021 background-image : -webkit-linear-gradient( top, #003b76, #00376e));
2021 background-image : -webkit-linear-gradient( top, #003b76, #00376e));
2022 background-image : -o-linear-gradient( top, #003b76, #00376e));
2022 background-image : -o-linear-gradient( top, #003b76, #00376e));
2023 background-image : linear-gradient( top, #003b76, #00376e);
2023 background-image : linear-gradient( top, #003b76, #00376e);
2024 filter : progid : DXImageTransform.Microsoft.gradient ( startColorstr = '#003b76', endColorstr = '#00376e', GradientType = 0);
2024 filter : progid : DXImageTransform.Microsoft.gradient ( startColorstr = '#003b76', endColorstr = '#00376e', GradientType = 0);
2025 margin: 0 auto;
2025 margin: 0 auto;
2026 padding: 0;
2026 padding: 0;
2027 }
2027 }
2028
2028
2029 #login div.inner {
2029 #login div.inner {
2030 background: #FFF url("../images/login.png") no-repeat top left;
2030 background: #FFF url("../images/login.png") no-repeat top left;
2031 border-top: none;
2031 border-top: none;
2032 border-bottom: none;
2032 border-bottom: none;
2033 margin: 0 auto;
2033 margin: 0 auto;
2034 padding: 20px;
2034 padding: 20px;
2035 }
2035 }
2036
2036
2037 #login div.form div.fields div.field div.label {
2037 #login div.form div.fields div.field div.label {
2038 width: 173px;
2038 width: 173px;
2039 float: left;
2039 float: left;
2040 text-align: right;
2040 text-align: right;
2041 margin: 2px 10px 0 0;
2041 margin: 2px 10px 0 0;
2042 padding: 5px 0 0 5px;
2042 padding: 5px 0 0 5px;
2043 }
2043 }
2044
2044
2045 #login div.form div.fields div.field div.input input {
2045 #login div.form div.fields div.field div.input input {
2046 background: #FFF;
2046 background: #FFF;
2047 border-top: 1px solid #b3b3b3;
2047 border-top: 1px solid #b3b3b3;
2048 border-left: 1px solid #b3b3b3;
2048 border-left: 1px solid #b3b3b3;
2049 border-right: 1px solid #eaeaea;
2049 border-right: 1px solid #eaeaea;
2050 border-bottom: 1px solid #eaeaea;
2050 border-bottom: 1px solid #eaeaea;
2051 color: #000;
2051 color: #000;
2052 font-size: 11px;
2052 font-size: 11px;
2053 margin: 0;
2053 margin: 0;
2054 padding: 7px 7px 6px;
2054 padding: 7px 7px 6px;
2055 }
2055 }
2056
2056
2057 #login div.form div.fields div.buttons {
2057 #login div.form div.fields div.buttons {
2058 clear: both;
2058 clear: both;
2059 overflow: hidden;
2059 overflow: hidden;
2060 border-top: 1px solid #DDD;
2060 border-top: 1px solid #DDD;
2061 text-align: right;
2061 text-align: right;
2062 margin: 0;
2062 margin: 0;
2063 padding: 10px 0 0;
2063 padding: 10px 0 0;
2064 }
2064 }
2065
2065
2066 #login div.form div.links {
2066 #login div.form div.links {
2067 clear: both;
2067 clear: both;
2068 overflow: hidden;
2068 overflow: hidden;
2069 margin: 10px 0 0;
2069 margin: 10px 0 0;
2070 padding: 0 0 2px;
2070 padding: 0 0 2px;
2071 }
2071 }
2072
2072
2073 .user-menu{
2073 .user-menu{
2074 margin: 0px !important;
2074 margin: 0px !important;
2075 float: left;
2075 float: left;
2076 }
2076 }
2077
2077
2078 .user-menu .container{
2078 .user-menu .container{
2079 padding:0px 4px 0px 4px;
2079 padding:0px 4px 0px 4px;
2080 margin: 0px 0px 0px 0px;
2080 margin: 0px 0px 0px 0px;
2081 }
2081 }
2082
2082
2083 .user-menu .gravatar{
2083 .user-menu .gravatar{
2084 margin: 0px 0px 0px 0px;
2084 margin: 0px 0px 0px 0px;
2085 cursor: pointer;
2085 cursor: pointer;
2086 }
2086 }
2087 .user-menu .gravatar.enabled{
2087 .user-menu .gravatar.enabled{
2088 background-color: #FDF784 !important;
2088 background-color: #FDF784 !important;
2089 }
2089 }
2090 .user-menu .gravatar:hover{
2090 .user-menu .gravatar:hover{
2091 background-color: #FDF784 !important;
2091 background-color: #FDF784 !important;
2092 }
2092 }
2093 #quick_login{
2093 #quick_login{
2094 min-height: 80px;
2094 min-height: 80px;
2095 padding: 4px;
2095 padding: 4px;
2096 position: absolute;
2096 position: absolute;
2097 right: 0;
2097 right: 0;
2098 width: 278px;
2098 width: 278px;
2099 background-color: #003B76;
2099 background-color: #003B76;
2100 background-repeat: repeat-x;
2100 background-repeat: repeat-x;
2101 background-image: -khtml-gradient(linear, left top, left bottom, from(#003B76), to(#00376E) );
2101 background-image: -khtml-gradient(linear, left top, left bottom, from(#003B76), to(#00376E) );
2102 background-image: -moz-linear-gradient(top, #003b76, #00376e);
2102 background-image: -moz-linear-gradient(top, #003b76, #00376e);
2103 background-image: -ms-linear-gradient(top, #003b76, #00376e);
2103 background-image: -ms-linear-gradient(top, #003b76, #00376e);
2104 background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #003b76), color-stop(100%, #00376e) );
2104 background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #003b76), color-stop(100%, #00376e) );
2105 background-image: -webkit-linear-gradient(top, #003b76, #00376e);
2105 background-image: -webkit-linear-gradient(top, #003b76, #00376e);
2106 background-image: -o-linear-gradient(top, #003b76, #00376e);
2106 background-image: -o-linear-gradient(top, #003b76, #00376e);
2107 background-image: linear-gradient(top, #003b76, #00376e);
2107 background-image: linear-gradient(top, #003b76, #00376e);
2108 filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#003b76', endColorstr='#00376e', GradientType=0 );
2108 filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#003b76', endColorstr='#00376e', GradientType=0 );
2109
2109
2110 z-index: 999;
2110 z-index: 999;
2111 -webkit-border-radius: 0px 0px 4px 4px;
2111 -webkit-border-radius: 0px 0px 4px 4px;
2112 -khtml-border-radius: 0px 0px 4px 4px;
2112 -khtml-border-radius: 0px 0px 4px 4px;
2113 -moz-border-radius: 0px 0px 4px 4px;
2113 -moz-border-radius: 0px 0px 4px 4px;
2114 border-radius: 0px 0px 4px 4px;
2114 border-radius: 0px 0px 4px 4px;
2115 box-shadow: 0 2px 2px rgba(0, 0, 0, 0.6);
2115 box-shadow: 0 2px 2px rgba(0, 0, 0, 0.6);
2116 }
2116 }
2117 #quick_login h4{
2117 #quick_login h4{
2118 color: #fff;
2118 color: #fff;
2119 padding: 5px 0px 5px 14px;
2119 padding: 5px 0px 5px 14px;
2120 }
2120 }
2121
2121
2122 #quick_login .password_forgoten {
2122 #quick_login .password_forgoten {
2123 padding-right: 10px;
2123 padding-right: 10px;
2124 padding-top: 0px;
2124 padding-top: 0px;
2125 text-align: left;
2125 text-align: left;
2126 }
2126 }
2127
2127
2128 #quick_login .password_forgoten a {
2128 #quick_login .password_forgoten a {
2129 font-size: 10px;
2129 font-size: 10px;
2130 color: #fff;
2130 color: #fff;
2131 }
2131 }
2132
2132
2133 #quick_login .register {
2133 #quick_login .register {
2134 padding-right: 10px;
2134 padding-right: 10px;
2135 padding-top: 5px;
2135 padding-top: 5px;
2136 text-align: left;
2136 text-align: left;
2137 }
2137 }
2138
2138
2139 #quick_login .register a {
2139 #quick_login .register a {
2140 font-size: 10px;
2140 font-size: 10px;
2141 color: #fff;
2141 color: #fff;
2142 }
2142 }
2143
2143
2144 #quick_login .submit {
2144 #quick_login .submit {
2145 margin: -20px 0 0 0px;
2145 margin: -20px 0 0 0px;
2146 position: absolute;
2146 position: absolute;
2147 right: 15px;
2147 right: 15px;
2148 }
2148 }
2149
2149
2150 #quick_login .links_left{
2150 #quick_login .links_left{
2151 float: left;
2151 float: left;
2152 }
2152 }
2153 #quick_login .links_right{
2153 #quick_login .links_right{
2154 float: right;
2154 float: right;
2155 }
2155 }
2156 #quick_login .full_name{
2156 #quick_login .full_name{
2157 color: #FFFFFF;
2157 color: #FFFFFF;
2158 font-weight: bold;
2158 font-weight: bold;
2159 padding: 3px;
2159 padding: 3px;
2160 }
2160 }
2161 #quick_login .big_gravatar{
2161 #quick_login .big_gravatar{
2162 padding:4px 0px 0px 6px;
2162 padding:4px 0px 0px 6px;
2163 }
2163 }
2164 #quick_login .inbox{
2164 #quick_login .inbox{
2165 padding:4px 0px 0px 6px;
2165 padding:4px 0px 0px 6px;
2166 color: #FFFFFF;
2166 color: #FFFFFF;
2167 font-weight: bold;
2167 font-weight: bold;
2168 }
2168 }
2169 #quick_login .inbox a{
2169 #quick_login .inbox a{
2170 color: #FFFFFF;
2170 color: #FFFFFF;
2171 }
2171 }
2172 #quick_login .email,#quick_login .email a{
2172 #quick_login .email,#quick_login .email a{
2173 color: #FFFFFF;
2173 color: #FFFFFF;
2174 padding: 3px;
2174 padding: 3px;
2175
2175
2176 }
2176 }
2177 #quick_login .links .logout{
2177 #quick_login .links .logout{
2178
2178
2179 }
2179 }
2180
2180
2181 #quick_login div.form div.fields {
2181 #quick_login div.form div.fields {
2182 padding-top: 2px;
2182 padding-top: 2px;
2183 padding-left: 10px;
2183 padding-left: 10px;
2184 }
2184 }
2185
2185
2186 #quick_login div.form div.fields div.field {
2186 #quick_login div.form div.fields div.field {
2187 padding: 5px;
2187 padding: 5px;
2188 }
2188 }
2189
2189
2190 #quick_login div.form div.fields div.field div.label label {
2190 #quick_login div.form div.fields div.field div.label label {
2191 color: #fff;
2191 color: #fff;
2192 padding-bottom: 3px;
2192 padding-bottom: 3px;
2193 }
2193 }
2194
2194
2195 #quick_login div.form div.fields div.field div.input input {
2195 #quick_login div.form div.fields div.field div.input input {
2196 width: 236px;
2196 width: 236px;
2197 background: #FFF;
2197 background: #FFF;
2198 border-top: 1px solid #b3b3b3;
2198 border-top: 1px solid #b3b3b3;
2199 border-left: 1px solid #b3b3b3;
2199 border-left: 1px solid #b3b3b3;
2200 border-right: 1px solid #eaeaea;
2200 border-right: 1px solid #eaeaea;
2201 border-bottom: 1px solid #eaeaea;
2201 border-bottom: 1px solid #eaeaea;
2202 color: #000;
2202 color: #000;
2203 font-size: 11px;
2203 font-size: 11px;
2204 margin: 0;
2204 margin: 0;
2205 padding: 5px 7px 4px;
2205 padding: 5px 7px 4px;
2206 }
2206 }
2207
2207
2208 #quick_login div.form div.fields div.buttons {
2208 #quick_login div.form div.fields div.buttons {
2209 clear: both;
2209 clear: both;
2210 overflow: hidden;
2210 overflow: hidden;
2211 text-align: right;
2211 text-align: right;
2212 margin: 0;
2212 margin: 0;
2213 padding: 5px 14px 0px 5px;
2213 padding: 5px 14px 0px 5px;
2214 }
2214 }
2215
2215
2216 #quick_login div.form div.links {
2216 #quick_login div.form div.links {
2217 clear: both;
2217 clear: both;
2218 overflow: hidden;
2218 overflow: hidden;
2219 margin: 10px 0 0;
2219 margin: 10px 0 0;
2220 padding: 0 0 2px;
2220 padding: 0 0 2px;
2221 }
2221 }
2222
2222
2223 #quick_login ol.links{
2223 #quick_login ol.links{
2224 display: block;
2224 display: block;
2225 font-weight: bold;
2225 font-weight: bold;
2226 list-style: none outside none;
2226 list-style: none outside none;
2227 text-align: right;
2227 text-align: right;
2228 }
2228 }
2229 #quick_login ol.links li{
2229 #quick_login ol.links li{
2230 line-height: 27px;
2230 line-height: 27px;
2231 margin: 0;
2231 margin: 0;
2232 padding: 0;
2232 padding: 0;
2233 color: #fff;
2233 color: #fff;
2234 display: block;
2234 display: block;
2235 float:none !important;
2235 float:none !important;
2236 }
2236 }
2237
2237
2238 #quick_login ol.links li a{
2238 #quick_login ol.links li a{
2239 color: #fff;
2239 color: #fff;
2240 display: block;
2240 display: block;
2241 padding: 2px;
2241 padding: 2px;
2242 }
2242 }
2243 #quick_login ol.links li a:HOVER{
2243 #quick_login ol.links li a:HOVER{
2244 background-color: inherit !important;
2244 background-color: inherit !important;
2245 }
2245 }
2246
2246
2247 #register div.title {
2247 #register div.title {
2248 clear: both;
2248 clear: both;
2249 overflow: hidden;
2249 overflow: hidden;
2250 position: relative;
2250 position: relative;
2251 background-color: #003B76;
2251 background-color: #003B76;
2252 background-repeat: repeat-x;
2252 background-repeat: repeat-x;
2253 background-image: -khtml-gradient(linear, left top, left bottom, from(#003B76), to(#00376E) );
2253 background-image: -khtml-gradient(linear, left top, left bottom, from(#003B76), to(#00376E) );
2254 background-image: -moz-linear-gradient(top, #003b76, #00376e);
2254 background-image: -moz-linear-gradient(top, #003b76, #00376e);
2255 background-image: -ms-linear-gradient(top, #003b76, #00376e);
2255 background-image: -ms-linear-gradient(top, #003b76, #00376e);
2256 background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #003b76), color-stop(100%, #00376e) );
2256 background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #003b76), color-stop(100%, #00376e) );
2257 background-image: -webkit-linear-gradient(top, #003b76, #00376e);
2257 background-image: -webkit-linear-gradient(top, #003b76, #00376e);
2258 background-image: -o-linear-gradient(top, #003b76, #00376e);
2258 background-image: -o-linear-gradient(top, #003b76, #00376e);
2259 background-image: linear-gradient(top, #003b76, #00376e);
2259 background-image: linear-gradient(top, #003b76, #00376e);
2260 filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#003b76',
2260 filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#003b76',
2261 endColorstr='#00376e', GradientType=0 );
2261 endColorstr='#00376e', GradientType=0 );
2262 margin: 0 auto;
2262 margin: 0 auto;
2263 padding: 0;
2263 padding: 0;
2264 }
2264 }
2265
2265
2266 #register div.inner {
2266 #register div.inner {
2267 background: #FFF;
2267 background: #FFF;
2268 border-top: none;
2268 border-top: none;
2269 border-bottom: none;
2269 border-bottom: none;
2270 margin: 0 auto;
2270 margin: 0 auto;
2271 padding: 20px;
2271 padding: 20px;
2272 }
2272 }
2273
2273
2274 #register div.form div.fields div.field div.label {
2274 #register div.form div.fields div.field div.label {
2275 width: 135px;
2275 width: 135px;
2276 float: left;
2276 float: left;
2277 text-align: right;
2277 text-align: right;
2278 margin: 2px 10px 0 0;
2278 margin: 2px 10px 0 0;
2279 padding: 5px 0 0 5px;
2279 padding: 5px 0 0 5px;
2280 }
2280 }
2281
2281
2282 #register div.form div.fields div.field div.input input {
2282 #register div.form div.fields div.field div.input input {
2283 width: 300px;
2283 width: 300px;
2284 background: #FFF;
2284 background: #FFF;
2285 border-top: 1px solid #b3b3b3;
2285 border-top: 1px solid #b3b3b3;
2286 border-left: 1px solid #b3b3b3;
2286 border-left: 1px solid #b3b3b3;
2287 border-right: 1px solid #eaeaea;
2287 border-right: 1px solid #eaeaea;
2288 border-bottom: 1px solid #eaeaea;
2288 border-bottom: 1px solid #eaeaea;
2289 color: #000;
2289 color: #000;
2290 font-size: 11px;
2290 font-size: 11px;
2291 margin: 0;
2291 margin: 0;
2292 padding: 7px 7px 6px;
2292 padding: 7px 7px 6px;
2293 }
2293 }
2294
2294
2295 #register div.form div.fields div.buttons {
2295 #register div.form div.fields div.buttons {
2296 clear: both;
2296 clear: both;
2297 overflow: hidden;
2297 overflow: hidden;
2298 border-top: 1px solid #DDD;
2298 border-top: 1px solid #DDD;
2299 text-align: left;
2299 text-align: left;
2300 margin: 0;
2300 margin: 0;
2301 padding: 10px 0 0 150px;
2301 padding: 10px 0 0 150px;
2302 }
2302 }
2303
2303
2304 #register div.form div.activation_msg {
2304 #register div.form div.activation_msg {
2305 padding-top: 4px;
2305 padding-top: 4px;
2306 padding-bottom: 4px;
2306 padding-bottom: 4px;
2307 }
2307 }
2308
2308
2309 #journal .journal_day {
2309 #journal .journal_day {
2310 font-size: 20px;
2310 font-size: 20px;
2311 padding: 10px 0px;
2311 padding: 10px 0px;
2312 border-bottom: 2px solid #DDD;
2312 border-bottom: 2px solid #DDD;
2313 margin-left: 10px;
2313 margin-left: 10px;
2314 margin-right: 10px;
2314 margin-right: 10px;
2315 }
2315 }
2316
2316
2317 #journal .journal_container {
2317 #journal .journal_container {
2318 padding: 5px;
2318 padding: 5px;
2319 clear: both;
2319 clear: both;
2320 margin: 0px 5px 0px 10px;
2320 margin: 0px 5px 0px 10px;
2321 }
2321 }
2322
2322
2323 #journal .journal_action_container {
2323 #journal .journal_action_container {
2324 padding-left: 38px;
2324 padding-left: 38px;
2325 }
2325 }
2326
2326
2327 #journal .journal_user {
2327 #journal .journal_user {
2328 color: #747474;
2328 color: #747474;
2329 font-size: 14px;
2329 font-size: 14px;
2330 font-weight: bold;
2330 font-weight: bold;
2331 height: 30px;
2331 height: 30px;
2332 }
2332 }
2333
2333
2334 #journal .journal_user.deleted {
2334 #journal .journal_user.deleted {
2335 color: #747474;
2335 color: #747474;
2336 font-size: 14px;
2336 font-size: 14px;
2337 font-weight: normal;
2337 font-weight: normal;
2338 height: 30px;
2338 height: 30px;
2339 font-style: italic;
2339 font-style: italic;
2340 }
2340 }
2341
2341
2342
2342
2343 #journal .journal_icon {
2343 #journal .journal_icon {
2344 clear: both;
2344 clear: both;
2345 float: left;
2345 float: left;
2346 padding-right: 4px;
2346 padding-right: 4px;
2347 padding-top: 3px;
2347 padding-top: 3px;
2348 }
2348 }
2349
2349
2350 #journal .journal_action {
2350 #journal .journal_action {
2351 padding-top: 4px;
2351 padding-top: 4px;
2352 min-height: 2px;
2352 min-height: 2px;
2353 float: left
2353 float: left
2354 }
2354 }
2355
2355
2356 #journal .journal_action_params {
2356 #journal .journal_action_params {
2357 clear: left;
2357 clear: left;
2358 padding-left: 22px;
2358 padding-left: 22px;
2359 }
2359 }
2360
2360
2361 #journal .journal_repo {
2361 #journal .journal_repo {
2362 float: left;
2362 float: left;
2363 margin-left: 6px;
2363 margin-left: 6px;
2364 padding-top: 3px;
2364 padding-top: 3px;
2365 }
2365 }
2366
2366
2367 #journal .date {
2367 #journal .date {
2368 clear: both;
2368 clear: both;
2369 color: #777777;
2369 color: #777777;
2370 font-size: 11px;
2370 font-size: 11px;
2371 padding-left: 22px;
2371 padding-left: 22px;
2372 }
2372 }
2373
2373
2374 #journal .journal_repo .journal_repo_name {
2374 #journal .journal_repo .journal_repo_name {
2375 font-weight: bold;
2375 font-weight: bold;
2376 font-size: 1.1em;
2376 font-size: 1.1em;
2377 }
2377 }
2378
2378
2379 #journal .compare_view {
2379 #journal .compare_view {
2380 padding: 5px 0px 5px 0px;
2380 padding: 5px 0px 5px 0px;
2381 width: 95px;
2381 width: 95px;
2382 }
2382 }
2383
2383
2384 .journal_highlight {
2384 .journal_highlight {
2385 font-weight: bold;
2385 font-weight: bold;
2386 padding: 0 2px;
2386 padding: 0 2px;
2387 vertical-align: bottom;
2387 vertical-align: bottom;
2388 }
2388 }
2389
2389
2390 .trending_language_tbl,.trending_language_tbl td {
2390 .trending_language_tbl,.trending_language_tbl td {
2391 border: 0 !important;
2391 border: 0 !important;
2392 margin: 0 !important;
2392 margin: 0 !important;
2393 padding: 0 !important;
2393 padding: 0 !important;
2394 }
2394 }
2395
2395
2396 .trending_language_tbl,.trending_language_tbl tr {
2396 .trending_language_tbl,.trending_language_tbl tr {
2397 border-spacing: 1px;
2397 border-spacing: 1px;
2398 }
2398 }
2399
2399
2400 .trending_language {
2400 .trending_language {
2401 background-color: #003367;
2401 background-color: #003367;
2402 color: #FFF;
2402 color: #FFF;
2403 display: block;
2403 display: block;
2404 min-width: 20px;
2404 min-width: 20px;
2405 text-decoration: none;
2405 text-decoration: none;
2406 height: 12px;
2406 height: 12px;
2407 margin-bottom: 0px;
2407 margin-bottom: 0px;
2408 margin-left: 5px;
2408 margin-left: 5px;
2409 white-space: pre;
2409 white-space: pre;
2410 padding: 3px;
2410 padding: 3px;
2411 }
2411 }
2412
2412
2413 h3.files_location {
2413 h3.files_location {
2414 font-size: 1.8em;
2414 font-size: 1.8em;
2415 font-weight: 700;
2415 font-weight: 700;
2416 border-bottom: none !important;
2416 border-bottom: none !important;
2417 margin: 10px 0 !important;
2417 margin: 10px 0 !important;
2418 }
2418 }
2419
2419
2420 #files_data dl dt {
2420 #files_data dl dt {
2421 float: left;
2421 float: left;
2422 width: 60px;
2422 width: 60px;
2423 margin: 0 !important;
2423 margin: 0 !important;
2424 padding: 5px;
2424 padding: 5px;
2425 }
2425 }
2426
2426
2427 #files_data dl dd {
2427 #files_data dl dd {
2428 margin: 0 !important;
2428 margin: 0 !important;
2429 padding: 5px !important;
2429 padding: 5px !important;
2430 }
2430 }
2431
2431
2432 .file_history{
2432 .file_history{
2433 padding-top:10px;
2433 padding-top:10px;
2434 font-size:16px;
2434 font-size:16px;
2435 }
2435 }
2436 .file_author{
2436 .file_author{
2437 float: left;
2437 float: left;
2438 }
2438 }
2439
2439
2440 .file_author .item{
2440 .file_author .item{
2441 float:left;
2441 float:left;
2442 padding:5px;
2442 padding:5px;
2443 color: #888;
2443 color: #888;
2444 }
2444 }
2445
2445
2446 .tablerow0 {
2446 .tablerow0 {
2447 background-color: #F8F8F8;
2447 background-color: #F8F8F8;
2448 }
2448 }
2449
2449
2450 .tablerow1 {
2450 .tablerow1 {
2451 background-color: #FFFFFF;
2451 background-color: #FFFFFF;
2452 }
2452 }
2453
2453
2454 .changeset_id {
2454 .changeset_id {
2455 font-family: monospace;
2455 font-family: monospace;
2456 color: #666666;
2456 color: #666666;
2457 }
2457 }
2458
2458
2459 .changeset_hash {
2459 .changeset_hash {
2460 color: #000000;
2460 color: #000000;
2461 }
2461 }
2462
2462
2463 #changeset_content {
2463 #changeset_content {
2464 border-left: 1px solid #CCC;
2464 border-left: 1px solid #CCC;
2465 border-right: 1px solid #CCC;
2465 border-right: 1px solid #CCC;
2466 border-bottom: 1px solid #CCC;
2466 border-bottom: 1px solid #CCC;
2467 padding: 5px;
2467 padding: 5px;
2468 }
2468 }
2469
2469
2470 #changeset_compare_view_content {
2470 #changeset_compare_view_content {
2471 border: 1px solid #CCC;
2471 border: 1px solid #CCC;
2472 padding: 5px;
2472 padding: 5px;
2473 }
2473 }
2474
2474
2475 #changeset_content .container {
2475 #changeset_content .container {
2476 min-height: 100px;
2476 min-height: 100px;
2477 font-size: 1.2em;
2477 font-size: 1.2em;
2478 overflow: hidden;
2478 overflow: hidden;
2479 }
2479 }
2480
2480
2481 #changeset_compare_view_content .compare_view_commits {
2481 #changeset_compare_view_content .compare_view_commits {
2482 width: auto !important;
2482 width: auto !important;
2483 }
2483 }
2484
2484
2485 #changeset_compare_view_content .compare_view_commits td {
2485 #changeset_compare_view_content .compare_view_commits td {
2486 padding: 0px 0px 0px 12px !important;
2486 padding: 0px 0px 0px 12px !important;
2487 }
2487 }
2488
2488
2489 #changeset_content .container .right {
2489 #changeset_content .container .right {
2490 float: right;
2490 float: right;
2491 width: 20%;
2491 width: 20%;
2492 text-align: right;
2492 text-align: right;
2493 }
2493 }
2494
2494
2495 #changeset_content .container .left .message {
2495 #changeset_content .container .left .message {
2496 white-space: pre-wrap;
2496 white-space: pre-wrap;
2497 }
2497 }
2498 #changeset_content .container .left .message a:hover {
2498 #changeset_content .container .left .message a:hover {
2499 text-decoration: none;
2499 text-decoration: none;
2500 }
2500 }
2501 .cs_files .cur_cs {
2501 .cs_files .cur_cs {
2502 margin: 10px 2px;
2502 margin: 10px 2px;
2503 font-weight: bold;
2503 font-weight: bold;
2504 }
2504 }
2505
2505
2506 .cs_files .node {
2506 .cs_files .node {
2507 float: left;
2507 float: left;
2508 }
2508 }
2509
2509
2510 .cs_files .changes {
2510 .cs_files .changes {
2511 float: right;
2511 float: right;
2512 color:#003367;
2512 color:#003367;
2513
2513
2514 }
2514 }
2515
2515
2516 .cs_files .changes .added {
2516 .cs_files .changes .added {
2517 background-color: #BBFFBB;
2517 background-color: #BBFFBB;
2518 float: left;
2518 float: left;
2519 text-align: center;
2519 text-align: center;
2520 font-size: 9px;
2520 font-size: 9px;
2521 padding: 2px 0px 2px 0px;
2521 padding: 2px 0px 2px 0px;
2522 }
2522 }
2523
2523
2524 .cs_files .changes .deleted {
2524 .cs_files .changes .deleted {
2525 background-color: #FF8888;
2525 background-color: #FF8888;
2526 float: left;
2526 float: left;
2527 text-align: center;
2527 text-align: center;
2528 font-size: 9px;
2528 font-size: 9px;
2529 padding: 2px 0px 2px 0px;
2529 padding: 2px 0px 2px 0px;
2530 }
2530 }
2531 /*new binary*/
2531 /*new binary*/
2532 .cs_files .changes .bin1 {
2532 .cs_files .changes .bin1 {
2533 background-color: #BBFFBB;
2533 background-color: #BBFFBB;
2534 float: left;
2534 float: left;
2535 text-align: center;
2535 text-align: center;
2536 font-size: 9px;
2536 font-size: 9px;
2537 padding: 2px 0px 2px 0px;
2537 padding: 2px 0px 2px 0px;
2538 }
2538 }
2539
2539
2540 /*deleted binary*/
2540 /*deleted binary*/
2541 .cs_files .changes .bin2 {
2541 .cs_files .changes .bin2 {
2542 background-color: #FF8888;
2542 background-color: #FF8888;
2543 float: left;
2543 float: left;
2544 text-align: center;
2544 text-align: center;
2545 font-size: 9px;
2545 font-size: 9px;
2546 padding: 2px 0px 2px 0px;
2546 padding: 2px 0px 2px 0px;
2547 }
2547 }
2548
2548
2549 /*mod binary*/
2549 /*mod binary*/
2550 .cs_files .changes .bin3 {
2550 .cs_files .changes .bin3 {
2551 background-color: #DDDDDD;
2551 background-color: #DDDDDD;
2552 float: left;
2552 float: left;
2553 text-align: center;
2553 text-align: center;
2554 font-size: 9px;
2554 font-size: 9px;
2555 padding: 2px 0px 2px 0px;
2555 padding: 2px 0px 2px 0px;
2556 }
2556 }
2557
2557
2558 /*rename file*/
2558 /*rename file*/
2559 .cs_files .changes .bin4 {
2559 .cs_files .changes .bin4 {
2560 background-color: #6D99FF;
2560 background-color: #6D99FF;
2561 float: left;
2561 float: left;
2562 text-align: center;
2562 text-align: center;
2563 font-size: 9px;
2563 font-size: 9px;
2564 padding: 2px 0px 2px 0px;
2564 padding: 2px 0px 2px 0px;
2565 }
2565 }
2566
2566
2567
2567
2568 .cs_files .cs_added,.cs_files .cs_A {
2568 .cs_files .cs_added,.cs_files .cs_A {
2569 background: url("../images/icons/page_white_add.png") no-repeat scroll
2569 background: url("../images/icons/page_white_add.png") no-repeat scroll
2570 3px;
2570 3px;
2571 height: 16px;
2571 height: 16px;
2572 padding-left: 20px;
2572 padding-left: 20px;
2573 margin-top: 7px;
2573 margin-top: 7px;
2574 text-align: left;
2574 text-align: left;
2575 }
2575 }
2576
2576
2577 .cs_files .cs_changed,.cs_files .cs_M {
2577 .cs_files .cs_changed,.cs_files .cs_M {
2578 background: url("../images/icons/page_white_edit.png") no-repeat scroll
2578 background: url("../images/icons/page_white_edit.png") no-repeat scroll
2579 3px;
2579 3px;
2580 height: 16px;
2580 height: 16px;
2581 padding-left: 20px;
2581 padding-left: 20px;
2582 margin-top: 7px;
2582 margin-top: 7px;
2583 text-align: left;
2583 text-align: left;
2584 }
2584 }
2585
2585
2586 .cs_files .cs_removed,.cs_files .cs_D {
2586 .cs_files .cs_removed,.cs_files .cs_D {
2587 background: url("../images/icons/page_white_delete.png") no-repeat
2587 background: url("../images/icons/page_white_delete.png") no-repeat
2588 scroll 3px;
2588 scroll 3px;
2589 height: 16px;
2589 height: 16px;
2590 padding-left: 20px;
2590 padding-left: 20px;
2591 margin-top: 7px;
2591 margin-top: 7px;
2592 text-align: left;
2592 text-align: left;
2593 }
2593 }
2594
2594
2595 #graph {
2595 #graph {
2596 overflow: hidden;
2596 overflow: hidden;
2597 }
2597 }
2598
2598
2599 #graph_nodes {
2599 #graph_nodes {
2600 float: left;
2600 float: left;
2601 margin-right: 0px;
2601 margin-right: 0px;
2602 margin-top: 0px;
2602 margin-top: 0px;
2603 }
2603 }
2604
2604
2605 #graph_content {
2605 #graph_content {
2606 width: 80%;
2606 width: 80%;
2607 float: left;
2607 float: left;
2608 }
2608 }
2609
2609
2610 #graph_content .container_header {
2610 #graph_content .container_header {
2611 border-bottom: 1px solid #DDD;
2611 border-bottom: 1px solid #DDD;
2612 padding: 10px;
2612 padding: 10px;
2613 height: 25px;
2613 height: 25px;
2614 }
2614 }
2615
2615
2616 #graph_content #rev_range_container {
2616 #graph_content #rev_range_container {
2617 float: left;
2617 float: left;
2618 margin: 0px 0px 0px 3px;
2618 margin: 0px 0px 0px 3px;
2619 }
2619 }
2620
2620
2621 #graph_content #rev_range_clear {
2621 #graph_content #rev_range_clear {
2622 float: left;
2622 float: left;
2623 margin: 0px 0px 0px 3px;
2623 margin: 0px 0px 0px 3px;
2624 }
2624 }
2625
2625
2626 #graph_content .container {
2626 #graph_content .container {
2627 border-bottom: 1px solid #DDD;
2627 border-bottom: 1px solid #DDD;
2628 height: 56px;
2628 height: 56px;
2629 overflow: hidden;
2629 overflow: hidden;
2630 }
2630 }
2631
2631
2632 #graph_content .container .right {
2632 #graph_content .container .right {
2633 float: right;
2633 float: right;
2634 width: 23%;
2634 width: 23%;
2635 text-align: right;
2635 text-align: right;
2636 }
2636 }
2637
2637
2638 #graph_content .container .left {
2638 #graph_content .container .left {
2639 float: left;
2639 float: left;
2640 width: 25%;
2640 width: 25%;
2641 padding-left: 5px;
2641 padding-left: 5px;
2642 }
2642 }
2643
2643
2644 #graph_content .container .mid {
2644 #graph_content .container .mid {
2645 float: left;
2645 float: left;
2646 width: 49%;
2646 width: 49%;
2647 }
2647 }
2648
2648
2649
2649
2650 #graph_content .container .left .date {
2650 #graph_content .container .left .date {
2651 color: #666;
2651 color: #666;
2652 padding-left: 22px;
2652 padding-left: 22px;
2653 font-size: 10px;
2653 font-size: 10px;
2654 }
2654 }
2655
2655
2656 #graph_content .container .left .author {
2656 #graph_content .container .left .author {
2657 height: 22px;
2657 height: 22px;
2658 }
2658 }
2659
2659
2660 #graph_content .container .left .author .user {
2660 #graph_content .container .left .author .user {
2661 color: #444444;
2661 color: #444444;
2662 float: left;
2662 float: left;
2663 margin-left: -4px;
2663 margin-left: -4px;
2664 margin-top: 4px;
2664 margin-top: 4px;
2665 }
2665 }
2666
2666
2667 #graph_content .container .mid .message {
2667 #graph_content .container .mid .message {
2668 white-space: pre-wrap;
2668 white-space: pre-wrap;
2669 }
2669 }
2670
2670
2671 #graph_content .container .mid .message a:hover{
2671 #graph_content .container .mid .message a:hover{
2672 text-decoration: none;
2672 text-decoration: none;
2673 }
2673 }
2674
2674
2675 .revision-link
2675 .revision-link
2676 {
2676 {
2677 color:#3F6F9F;
2677 color:#3F6F9F;
2678 font-weight: bold !important;
2678 font-weight: bold !important;
2679 }
2679 }
2680
2680
2681 .issue-tracker-link{
2681 .issue-tracker-link{
2682 color:#3F6F9F;
2682 color:#3F6F9F;
2683 font-weight: bold !important;
2683 font-weight: bold !important;
2684 }
2684 }
2685
2685
2686 .changeset-status-container{
2686 .changeset-status-container{
2687 padding-right: 5px;
2687 padding-right: 5px;
2688 margin-top:1px;
2688 margin-top:1px;
2689 float:right;
2689 float:right;
2690 height:14px;
2690 height:14px;
2691 }
2691 }
2692 .code-header .changeset-status-container{
2692 .code-header .changeset-status-container{
2693 float:left;
2693 float:left;
2694 padding:2px 0px 0px 2px;
2694 padding:2px 0px 0px 2px;
2695 }
2695 }
2696 .changeset-status-container .changeset-status-lbl{
2696 .changeset-status-container .changeset-status-lbl{
2697 color: rgb(136, 136, 136);
2697 color: rgb(136, 136, 136);
2698 float: left;
2698 float: left;
2699 padding: 3px 4px 0px 0px
2699 padding: 3px 4px 0px 0px
2700 }
2700 }
2701 .code-header .changeset-status-container .changeset-status-lbl{
2701 .code-header .changeset-status-container .changeset-status-lbl{
2702 float: left;
2702 float: left;
2703 padding: 0px 4px 0px 0px;
2703 padding: 0px 4px 0px 0px;
2704 }
2704 }
2705 .changeset-status-container .changeset-status-ico{
2705 .changeset-status-container .changeset-status-ico{
2706 float: left;
2706 float: left;
2707 }
2707 }
2708 .code-header .changeset-status-container .changeset-status-ico, .container .changeset-status-ico{
2708 .code-header .changeset-status-container .changeset-status-ico, .container .changeset-status-ico{
2709 float: left;
2709 float: left;
2710 }
2710 }
2711 .right .comments-container{
2711 .right .comments-container{
2712 padding-right: 5px;
2712 padding-right: 5px;
2713 margin-top:1px;
2713 margin-top:1px;
2714 float:right;
2714 float:right;
2715 height:14px;
2715 height:14px;
2716 }
2716 }
2717
2717
2718 .right .comments-cnt{
2718 .right .comments-cnt{
2719 float: left;
2719 float: left;
2720 color: rgb(136, 136, 136);
2720 color: rgb(136, 136, 136);
2721 padding-right: 2px;
2721 padding-right: 2px;
2722 }
2722 }
2723
2723
2724 .right .changes{
2724 .right .changes{
2725 clear: both;
2725 clear: both;
2726 }
2726 }
2727
2727
2728 .right .changes .changed_total {
2728 .right .changes .changed_total {
2729 display: block;
2729 display: block;
2730 float: right;
2730 float: right;
2731 text-align: center;
2731 text-align: center;
2732 min-width: 45px;
2732 min-width: 45px;
2733 cursor: pointer;
2733 cursor: pointer;
2734 color: #444444;
2734 color: #444444;
2735 background: #FEA;
2735 background: #FEA;
2736 -webkit-border-radius: 0px 0px 0px 6px;
2736 -webkit-border-radius: 0px 0px 0px 6px;
2737 -moz-border-radius: 0px 0px 0px 6px;
2737 -moz-border-radius: 0px 0px 0px 6px;
2738 border-radius: 0px 0px 0px 6px;
2738 border-radius: 0px 0px 0px 6px;
2739 padding: 1px;
2739 padding: 1px;
2740 }
2740 }
2741
2741
2742 .right .changes .added,.changed,.removed {
2742 .right .changes .added,.changed,.removed {
2743 display: block;
2743 display: block;
2744 padding: 1px;
2744 padding: 1px;
2745 color: #444444;
2745 color: #444444;
2746 float: right;
2746 float: right;
2747 text-align: center;
2747 text-align: center;
2748 min-width: 15px;
2748 min-width: 15px;
2749 }
2749 }
2750
2750
2751 .right .changes .added {
2751 .right .changes .added {
2752 background: #CFC;
2752 background: #CFC;
2753 }
2753 }
2754
2754
2755 .right .changes .changed {
2755 .right .changes .changed {
2756 background: #FEA;
2756 background: #FEA;
2757 }
2757 }
2758
2758
2759 .right .changes .removed {
2759 .right .changes .removed {
2760 background: #FAA;
2760 background: #FAA;
2761 }
2761 }
2762
2762
2763 .right .merge {
2763 .right .merge {
2764 padding: 1px 3px 1px 3px;
2764 padding: 1px 3px 1px 3px;
2765 background-color: #fca062;
2765 background-color: #fca062;
2766 font-size: 10px;
2766 font-size: 10px;
2767 font-weight: bold;
2767 font-weight: bold;
2768 color: #ffffff;
2768 color: #ffffff;
2769 text-transform: uppercase;
2769 text-transform: uppercase;
2770 white-space: nowrap;
2770 white-space: nowrap;
2771 -webkit-border-radius: 3px;
2771 -webkit-border-radius: 3px;
2772 -moz-border-radius: 3px;
2772 -moz-border-radius: 3px;
2773 border-radius: 3px;
2773 border-radius: 3px;
2774 margin-right: 2px;
2774 margin-right: 2px;
2775 }
2775 }
2776
2776
2777 .right .parent {
2777 .right .parent {
2778 color: #666666;
2778 color: #666666;
2779 clear:both;
2779 clear:both;
2780 }
2780 }
2781 .right .logtags{
2781 .right .logtags{
2782 padding: 2px 2px 2px 2px;
2782 padding: 2px 2px 2px 2px;
2783 }
2783 }
2784 .right .logtags .branchtag,.right .logtags .tagtag,.right .logtags .booktag{
2784 .right .logtags .branchtag,.right .logtags .tagtag,.right .logtags .booktag{
2785 margin: 0px 2px;
2785 margin: 0px 2px;
2786 }
2786 }
2787
2787
2788 .right .logtags .branchtag,
2788 .right .logtags .branchtag,
2789 .logtags .branchtag,
2789 .logtags .branchtag,
2790 .spantag {
2790 .spantag {
2791 padding: 1px 3px 1px 3px;
2791 padding: 1px 3px 1px 3px;
2792 background-color: #bfbfbf;
2792 background-color: #bfbfbf;
2793 font-size: 10px;
2793 font-size: 10px;
2794 font-weight: bold;
2794 font-weight: bold;
2795 color: #ffffff;
2795 color: #ffffff;
2796 white-space: nowrap;
2796 white-space: nowrap;
2797 -webkit-border-radius: 3px;
2797 -webkit-border-radius: 3px;
2798 -moz-border-radius: 3px;
2798 -moz-border-radius: 3px;
2799 border-radius: 3px;
2799 border-radius: 3px;
2800 }
2800 }
2801 .right .logtags .branchtag a:hover,.logtags .branchtag a{
2801 .right .logtags .branchtag a:hover,.logtags .branchtag a{
2802 color: #ffffff;
2802 color: #ffffff;
2803 }
2803 }
2804 .right .logtags .branchtag a:hover,.logtags .branchtag a:hover{
2804 .right .logtags .branchtag a:hover,.logtags .branchtag a:hover{
2805 text-decoration: none;
2805 text-decoration: none;
2806 color: #ffffff;
2806 color: #ffffff;
2807 }
2807 }
2808 .right .logtags .tagtag,.logtags .tagtag {
2808 .right .logtags .tagtag,.logtags .tagtag {
2809 padding: 1px 3px 1px 3px;
2809 padding: 1px 3px 1px 3px;
2810 background-color: #62cffc;
2810 background-color: #62cffc;
2811 font-size: 10px;
2811 font-size: 10px;
2812 font-weight: bold;
2812 font-weight: bold;
2813 color: #ffffff;
2813 color: #ffffff;
2814 white-space: nowrap;
2814 white-space: nowrap;
2815 -webkit-border-radius: 3px;
2815 -webkit-border-radius: 3px;
2816 -moz-border-radius: 3px;
2816 -moz-border-radius: 3px;
2817 border-radius: 3px;
2817 border-radius: 3px;
2818 }
2818 }
2819 .right .logtags .tagtag a:hover,.logtags .tagtag a{
2819 .right .logtags .tagtag a:hover,.logtags .tagtag a{
2820 color: #ffffff;
2820 color: #ffffff;
2821 }
2821 }
2822 .right .logtags .tagtag a:hover,.logtags .tagtag a:hover{
2822 .right .logtags .tagtag a:hover,.logtags .tagtag a:hover{
2823 text-decoration: none;
2823 text-decoration: none;
2824 color: #ffffff;
2824 color: #ffffff;
2825 }
2825 }
2826 .right .logbooks .bookbook,.logbooks .bookbook,.right .logtags .bookbook,.logtags .bookbook {
2826 .right .logbooks .bookbook,.logbooks .bookbook,.right .logtags .bookbook,.logtags .bookbook {
2827 padding: 1px 3px 1px 3px;
2827 padding: 1px 3px 1px 3px;
2828 background-color: #46A546;
2828 background-color: #46A546;
2829 font-size: 10px;
2829 font-size: 10px;
2830 font-weight: bold;
2830 font-weight: bold;
2831 color: #ffffff;
2831 color: #ffffff;
2832 text-transform: uppercase;
2832 text-transform: uppercase;
2833 white-space: nowrap;
2833 white-space: nowrap;
2834 -webkit-border-radius: 3px;
2834 -webkit-border-radius: 3px;
2835 -moz-border-radius: 3px;
2835 -moz-border-radius: 3px;
2836 border-radius: 3px;
2836 border-radius: 3px;
2837 }
2837 }
2838 .right .logbooks .bookbook,.logbooks .bookbook a,.right .logtags .bookbook,.logtags .bookbook a{
2838 .right .logbooks .bookbook,.logbooks .bookbook a,.right .logtags .bookbook,.logtags .bookbook a{
2839 color: #ffffff;
2839 color: #ffffff;
2840 }
2840 }
2841 .right .logbooks .bookbook,.logbooks .bookbook a:hover,.right .logtags .bookbook,.logtags .bookbook a:hover{
2841 .right .logbooks .bookbook,.logbooks .bookbook a:hover,.right .logtags .bookbook,.logtags .bookbook a:hover{
2842 text-decoration: none;
2842 text-decoration: none;
2843 color: #ffffff;
2843 color: #ffffff;
2844 }
2844 }
2845 div.browserblock {
2845 div.browserblock {
2846 overflow: hidden;
2846 overflow: hidden;
2847 border: 1px solid #ccc;
2847 border: 1px solid #ccc;
2848 background: #f8f8f8;
2848 background: #f8f8f8;
2849 font-size: 100%;
2849 font-size: 100%;
2850 line-height: 125%;
2850 line-height: 125%;
2851 padding: 0;
2851 padding: 0;
2852 -webkit-border-radius: 6px 6px 0px 0px;
2852 -webkit-border-radius: 6px 6px 0px 0px;
2853 -moz-border-radius: 6px 6px 0px 0px;
2853 -moz-border-radius: 6px 6px 0px 0px;
2854 border-radius: 6px 6px 0px 0px;
2854 border-radius: 6px 6px 0px 0px;
2855 }
2855 }
2856
2856
2857 div.browserblock .browser-header {
2857 div.browserblock .browser-header {
2858 background: #FFF;
2858 background: #FFF;
2859 padding: 10px 0px 15px 0px;
2859 padding: 10px 0px 15px 0px;
2860 width: 100%;
2860 width: 100%;
2861 }
2861 }
2862
2862
2863 div.browserblock .browser-nav {
2863 div.browserblock .browser-nav {
2864 float: left
2864 float: left
2865 }
2865 }
2866
2866
2867 div.browserblock .browser-branch {
2867 div.browserblock .browser-branch {
2868 float: left;
2868 float: left;
2869 }
2869 }
2870
2870
2871 div.browserblock .browser-branch label {
2871 div.browserblock .browser-branch label {
2872 color: #4A4A4A;
2872 color: #4A4A4A;
2873 vertical-align: text-top;
2873 vertical-align: text-top;
2874 }
2874 }
2875
2875
2876 div.browserblock .browser-header span {
2876 div.browserblock .browser-header span {
2877 margin-left: 5px;
2877 margin-left: 5px;
2878 font-weight: 700;
2878 font-weight: 700;
2879 }
2879 }
2880
2880
2881 div.browserblock .browser-search {
2881 div.browserblock .browser-search {
2882 clear: both;
2882 clear: both;
2883 padding: 8px 8px 0px 5px;
2883 padding: 8px 8px 0px 5px;
2884 height: 20px;
2884 height: 20px;
2885 }
2885 }
2886
2886
2887 div.browserblock #node_filter_box {
2887 div.browserblock #node_filter_box {
2888
2888
2889 }
2889 }
2890
2890
2891 div.browserblock .search_activate {
2891 div.browserblock .search_activate {
2892 float: left
2892 float: left
2893 }
2893 }
2894
2894
2895 div.browserblock .add_node {
2895 div.browserblock .add_node {
2896 float: left;
2896 float: left;
2897 padding-left: 5px;
2897 padding-left: 5px;
2898 }
2898 }
2899
2899
2900 div.browserblock .search_activate a:hover,div.browserblock .add_node a:hover
2900 div.browserblock .search_activate a:hover,div.browserblock .add_node a:hover
2901 {
2901 {
2902 text-decoration: none !important;
2902 text-decoration: none !important;
2903 }
2903 }
2904
2904
2905 div.browserblock .browser-body {
2905 div.browserblock .browser-body {
2906 background: #EEE;
2906 background: #EEE;
2907 border-top: 1px solid #CCC;
2907 border-top: 1px solid #CCC;
2908 }
2908 }
2909
2909
2910 table.code-browser {
2910 table.code-browser {
2911 border-collapse: collapse;
2911 border-collapse: collapse;
2912 width: 100%;
2912 width: 100%;
2913 }
2913 }
2914
2914
2915 table.code-browser tr {
2915 table.code-browser tr {
2916 margin: 3px;
2916 margin: 3px;
2917 }
2917 }
2918
2918
2919 table.code-browser thead th {
2919 table.code-browser thead th {
2920 background-color: #EEE;
2920 background-color: #EEE;
2921 height: 20px;
2921 height: 20px;
2922 font-size: 1.1em;
2922 font-size: 1.1em;
2923 font-weight: 700;
2923 font-weight: 700;
2924 text-align: left;
2924 text-align: left;
2925 padding-left: 10px;
2925 padding-left: 10px;
2926 }
2926 }
2927
2927
2928 table.code-browser tbody td {
2928 table.code-browser tbody td {
2929 padding-left: 10px;
2929 padding-left: 10px;
2930 height: 20px;
2930 height: 20px;
2931 }
2931 }
2932
2932
2933 table.code-browser .browser-file {
2933 table.code-browser .browser-file {
2934 background: url("../images/icons/document_16.png") no-repeat scroll 3px;
2934 background: url("../images/icons/document_16.png") no-repeat scroll 3px;
2935 height: 16px;
2935 height: 16px;
2936 padding-left: 20px;
2936 padding-left: 20px;
2937 text-align: left;
2937 text-align: left;
2938 }
2938 }
2939 .diffblock .changeset_header {
2939 .diffblock .changeset_header {
2940 height: 16px;
2940 height: 16px;
2941 }
2941 }
2942 .diffblock .changeset_file {
2942 .diffblock .changeset_file {
2943 background: url("../images/icons/file.png") no-repeat scroll 3px;
2943 background: url("../images/icons/file.png") no-repeat scroll 3px;
2944 text-align: left;
2944 text-align: left;
2945 float: left;
2945 float: left;
2946 padding: 2px 0px 2px 22px;
2946 padding: 2px 0px 2px 22px;
2947 }
2947 }
2948 .diffblock .diff-menu-wrapper{
2948 .diffblock .diff-menu-wrapper{
2949 float: left;
2949 float: left;
2950 }
2950 }
2951
2951
2952 .diffblock .diff-menu{
2952 .diffblock .diff-menu{
2953 position: absolute;
2953 position: absolute;
2954 background: none repeat scroll 0 0 #FFFFFF;
2954 background: none repeat scroll 0 0 #FFFFFF;
2955 border-color: #003367 #666666 #666666;
2955 border-color: #003367 #666666 #666666;
2956 border-right: 1px solid #666666;
2956 border-right: 1px solid #666666;
2957 border-style: solid solid solid;
2957 border-style: solid solid solid;
2958 border-width: 1px;
2958 border-width: 1px;
2959 box-shadow: 2px 8px 4px rgba(0, 0, 0, 0.2);
2959 box-shadow: 2px 8px 4px rgba(0, 0, 0, 0.2);
2960 margin-top:5px;
2960 margin-top:5px;
2961 margin-left:1px;
2961 margin-left:1px;
2962
2962
2963 }
2963 }
2964 .diffblock .diff-actions {
2964 .diffblock .diff-actions {
2965 padding: 2px 0px 0px 2px;
2965 padding: 2px 0px 0px 2px;
2966 float: left;
2966 float: left;
2967 }
2967 }
2968 .diffblock .diff-menu ul li {
2968 .diffblock .diff-menu ul li {
2969 padding: 0px 0px 0px 0px !important;
2969 padding: 0px 0px 0px 0px !important;
2970 }
2970 }
2971 .diffblock .diff-menu ul li a{
2971 .diffblock .diff-menu ul li a{
2972 display: block;
2972 display: block;
2973 padding: 3px 8px 3px 8px !important;
2973 padding: 3px 8px 3px 8px !important;
2974 }
2974 }
2975 .diffblock .diff-menu ul li a:hover{
2975 .diffblock .diff-menu ul li a:hover{
2976 text-decoration: none;
2976 text-decoration: none;
2977 background-color: #EEEEEE;
2977 background-color: #EEEEEE;
2978 }
2978 }
2979 table.code-browser .browser-dir {
2979 table.code-browser .browser-dir {
2980 background: url("../images/icons/folder_16.png") no-repeat scroll 3px;
2980 background: url("../images/icons/folder_16.png") no-repeat scroll 3px;
2981 height: 16px;
2981 height: 16px;
2982 padding-left: 20px;
2982 padding-left: 20px;
2983 text-align: left;
2983 text-align: left;
2984 }
2984 }
2985
2985
2986 table.code-browser .submodule-dir {
2986 table.code-browser .submodule-dir {
2987 background: url("../images/icons/disconnect.png") no-repeat scroll 3px;
2987 background: url("../images/icons/disconnect.png") no-repeat scroll 3px;
2988 height: 16px;
2988 height: 16px;
2989 padding-left: 20px;
2989 padding-left: 20px;
2990 text-align: left;
2990 text-align: left;
2991 }
2991 }
2992
2992
2993
2993
2994 .box .search {
2994 .box .search {
2995 clear: both;
2995 clear: both;
2996 overflow: hidden;
2996 overflow: hidden;
2997 margin: 0;
2997 margin: 0;
2998 padding: 0 20px 10px;
2998 padding: 0 20px 10px;
2999 }
2999 }
3000
3000
3001 .box .search div.search_path {
3001 .box .search div.search_path {
3002 background: none repeat scroll 0 0 #EEE;
3002 background: none repeat scroll 0 0 #EEE;
3003 border: 1px solid #CCC;
3003 border: 1px solid #CCC;
3004 color: blue;
3004 color: blue;
3005 margin-bottom: 10px;
3005 margin-bottom: 10px;
3006 padding: 10px 0;
3006 padding: 10px 0;
3007 }
3007 }
3008
3008
3009 .box .search div.search_path div.link {
3009 .box .search div.search_path div.link {
3010 font-weight: 700;
3010 font-weight: 700;
3011 margin-left: 25px;
3011 margin-left: 25px;
3012 }
3012 }
3013
3013
3014 .box .search div.search_path div.link a {
3014 .box .search div.search_path div.link a {
3015 color: #003367;
3015 color: #003367;
3016 cursor: pointer;
3016 cursor: pointer;
3017 text-decoration: none;
3017 text-decoration: none;
3018 }
3018 }
3019
3019
3020 #path_unlock {
3020 #path_unlock {
3021 color: red;
3021 color: red;
3022 font-size: 1.2em;
3022 font-size: 1.2em;
3023 padding-left: 4px;
3023 padding-left: 4px;
3024 }
3024 }
3025
3025
3026 .info_box span {
3026 .info_box span {
3027 margin-left: 3px;
3027 margin-left: 3px;
3028 margin-right: 3px;
3028 margin-right: 3px;
3029 }
3029 }
3030
3030
3031 .info_box .rev {
3031 .info_box .rev {
3032 color: #003367;
3032 color: #003367;
3033 font-size: 1.6em;
3033 font-size: 1.6em;
3034 font-weight: bold;
3034 font-weight: bold;
3035 vertical-align: sub;
3035 vertical-align: sub;
3036 }
3036 }
3037
3037
3038 .info_box input#at_rev,.info_box input#size {
3038 .info_box input#at_rev,.info_box input#size {
3039 background: #FFF;
3039 background: #FFF;
3040 border-top: 1px solid #b3b3b3;
3040 border-top: 1px solid #b3b3b3;
3041 border-left: 1px solid #b3b3b3;
3041 border-left: 1px solid #b3b3b3;
3042 border-right: 1px solid #eaeaea;
3042 border-right: 1px solid #eaeaea;
3043 border-bottom: 1px solid #eaeaea;
3043 border-bottom: 1px solid #eaeaea;
3044 color: #000;
3044 color: #000;
3045 font-size: 12px;
3045 font-size: 12px;
3046 margin: 0;
3046 margin: 0;
3047 padding: 1px 5px 1px;
3047 padding: 1px 5px 1px;
3048 }
3048 }
3049
3049
3050 .info_box input#view {
3050 .info_box input#view {
3051 text-align: center;
3051 text-align: center;
3052 padding: 4px 3px 2px 2px;
3052 padding: 4px 3px 2px 2px;
3053 }
3053 }
3054
3054
3055 .yui-overlay,.yui-panel-container {
3055 .yui-overlay,.yui-panel-container {
3056 visibility: hidden;
3056 visibility: hidden;
3057 position: absolute;
3057 position: absolute;
3058 z-index: 2;
3058 z-index: 2;
3059 }
3059 }
3060
3060
3061 #tip-box {
3061 #tip-box {
3062 position: absolute;
3062 position: absolute;
3063
3063
3064 background-color: #FFF;
3064 background-color: #FFF;
3065 border: 2px solid #003367;
3065 border: 2px solid #003367;
3066 font: 100% sans-serif;
3066 font: 100% sans-serif;
3067 width: auto;
3067 width: auto;
3068 opacity: 1px;
3068 opacity: 1px;
3069 padding: 8px;
3069 padding: 8px;
3070
3070
3071 white-space: pre-wrap;
3071 white-space: pre-wrap;
3072 -webkit-border-radius: 8px 8px 8px 8px;
3072 -webkit-border-radius: 8px 8px 8px 8px;
3073 -khtml-border-radius: 8px 8px 8px 8px;
3073 -khtml-border-radius: 8px 8px 8px 8px;
3074 -moz-border-radius: 8px 8px 8px 8px;
3074 -moz-border-radius: 8px 8px 8px 8px;
3075 border-radius: 8px 8px 8px 8px;
3075 border-radius: 8px 8px 8px 8px;
3076 box-shadow: 0 2px 2px rgba(0, 0, 0, 0.6);
3076 box-shadow: 0 2px 2px rgba(0, 0, 0, 0.6);
3077 -moz-box-shadow: 0 2px 2px rgba(0, 0, 0, 0.6);
3077 -moz-box-shadow: 0 2px 2px rgba(0, 0, 0, 0.6);
3078 -webkit-box-shadow: 0 2px 2px rgba(0, 0, 0, 0.6);
3078 -webkit-box-shadow: 0 2px 2px rgba(0, 0, 0, 0.6);
3079 }
3079 }
3080
3080
3081 .hl-tip-box {
3081 .hl-tip-box {
3082 visibility: hidden;
3082 visibility: hidden;
3083 position: absolute;
3083 position: absolute;
3084 color: #666;
3084 color: #666;
3085 background-color: #FFF;
3085 background-color: #FFF;
3086 border: 2px solid #003367;
3086 border: 2px solid #003367;
3087 font: 100% sans-serif;
3087 font: 100% sans-serif;
3088 width: auto;
3088 width: auto;
3089 opacity: 1px;
3089 opacity: 1px;
3090 padding: 8px;
3090 padding: 8px;
3091 white-space: pre-wrap;
3091 white-space: pre-wrap;
3092 -webkit-border-radius: 8px 8px 8px 8px;
3092 -webkit-border-radius: 8px 8px 8px 8px;
3093 -khtml-border-radius: 8px 8px 8px 8px;
3093 -khtml-border-radius: 8px 8px 8px 8px;
3094 -moz-border-radius: 8px 8px 8px 8px;
3094 -moz-border-radius: 8px 8px 8px 8px;
3095 border-radius: 8px 8px 8px 8px;
3095 border-radius: 8px 8px 8px 8px;
3096 box-shadow: 0 2px 2px rgba(0, 0, 0, 0.6);
3096 box-shadow: 0 2px 2px rgba(0, 0, 0, 0.6);
3097 }
3097 }
3098
3098
3099
3099
3100 .mentions-container{
3100 .mentions-container{
3101 width: 90% !important;
3101 width: 90% !important;
3102 }
3102 }
3103 .mentions-container .yui-ac-content{
3103 .mentions-container .yui-ac-content{
3104 width: 100% !important;
3104 width: 100% !important;
3105 }
3105 }
3106
3106
3107 .ac {
3107 .ac {
3108 vertical-align: top;
3108 vertical-align: top;
3109 }
3109 }
3110
3110
3111 .ac .yui-ac {
3111 .ac .yui-ac {
3112 position: inherit;
3112 position: inherit;
3113 font-size: 100%;
3113 font-size: 100%;
3114 }
3114 }
3115
3115
3116 .ac .perm_ac {
3116 .ac .perm_ac {
3117 width: 20em;
3117 width: 20em;
3118 }
3118 }
3119
3119
3120 .ac .yui-ac-input {
3120 .ac .yui-ac-input {
3121 width: 100%;
3121 width: 100%;
3122 }
3122 }
3123
3123
3124 .ac .yui-ac-container {
3124 .ac .yui-ac-container {
3125 position: absolute;
3125 position: absolute;
3126 top: 1.6em;
3126 top: 1.6em;
3127 width: auto;
3127 width: auto;
3128 }
3128 }
3129
3129
3130 .ac .yui-ac-content {
3130 .ac .yui-ac-content {
3131 position: absolute;
3131 position: absolute;
3132 border: 1px solid gray;
3132 border: 1px solid gray;
3133 background: #fff;
3133 background: #fff;
3134 z-index: 9050;
3134 z-index: 9050;
3135
3135
3136 }
3136 }
3137
3137
3138 .ac .yui-ac-shadow {
3138 .ac .yui-ac-shadow {
3139 position: absolute;
3139 position: absolute;
3140 width: 100%;
3140 width: 100%;
3141 background: #000;
3141 background: #000;
3142 -moz-opacity: 0.1px;
3142 -moz-opacity: 0.1px;
3143 opacity: .10;
3143 opacity: .10;
3144 filter: alpha(opacity = 10);
3144 filter: alpha(opacity = 10);
3145 z-index: 9049;
3145 z-index: 9049;
3146 margin: .3em;
3146 margin: .3em;
3147 }
3147 }
3148
3148
3149 .ac .yui-ac-content ul {
3149 .ac .yui-ac-content ul {
3150 width: 100%;
3150 width: 100%;
3151 margin: 0;
3151 margin: 0;
3152 padding: 0;
3152 padding: 0;
3153 z-index: 9050;
3153 z-index: 9050;
3154 }
3154 }
3155
3155
3156 .ac .yui-ac-content li {
3156 .ac .yui-ac-content li {
3157 cursor: default;
3157 cursor: default;
3158 white-space: nowrap;
3158 white-space: nowrap;
3159 margin: 0;
3159 margin: 0;
3160 padding: 2px 5px;
3160 padding: 2px 5px;
3161 height: 18px;
3161 height: 18px;
3162 z-index: 9050;
3162 z-index: 9050;
3163 display: block;
3163 display: block;
3164 width: auto !important;
3164 width: auto !important;
3165 }
3165 }
3166
3166
3167 .ac .yui-ac-content li .ac-container-wrap{
3167 .ac .yui-ac-content li .ac-container-wrap{
3168 width: auto;
3168 width: auto;
3169 }
3169 }
3170
3170
3171 .ac .yui-ac-content li.yui-ac-prehighlight {
3171 .ac .yui-ac-content li.yui-ac-prehighlight {
3172 background: #B3D4FF;
3172 background: #B3D4FF;
3173 z-index: 9050;
3173 z-index: 9050;
3174 }
3174 }
3175
3175
3176 .ac .yui-ac-content li.yui-ac-highlight {
3176 .ac .yui-ac-content li.yui-ac-highlight {
3177 background: #556CB5;
3177 background: #556CB5;
3178 color: #FFF;
3178 color: #FFF;
3179 z-index: 9050;
3179 z-index: 9050;
3180 }
3180 }
3181 .ac .yui-ac-bd{
3181 .ac .yui-ac-bd{
3182 z-index: 9050;
3182 z-index: 9050;
3183 }
3183 }
3184
3184
3185 .follow {
3185 .follow {
3186 background: url("../images/icons/heart_add.png") no-repeat scroll 3px;
3186 background: url("../images/icons/heart_add.png") no-repeat scroll 3px;
3187 height: 16px;
3187 height: 16px;
3188 width: 20px;
3188 width: 20px;
3189 cursor: pointer;
3189 cursor: pointer;
3190 display: block;
3190 display: block;
3191 float: right;
3191 float: right;
3192 margin-top: 2px;
3192 margin-top: 2px;
3193 }
3193 }
3194
3194
3195 .following {
3195 .following {
3196 background: url("../images/icons/heart_delete.png") no-repeat scroll 3px;
3196 background: url("../images/icons/heart_delete.png") no-repeat scroll 3px;
3197 height: 16px;
3197 height: 16px;
3198 width: 20px;
3198 width: 20px;
3199 cursor: pointer;
3199 cursor: pointer;
3200 display: block;
3200 display: block;
3201 float: right;
3201 float: right;
3202 margin-top: 2px;
3202 margin-top: 2px;
3203 }
3203 }
3204
3204
3205 .locking_locked{
3205 .locking_locked{
3206 background: #FFF url("../images/icons/block_16.png") no-repeat scroll 3px;
3206 background: #FFF url("../images/icons/block_16.png") no-repeat scroll 3px;
3207 height: 16px;
3207 height: 16px;
3208 width: 20px;
3208 width: 20px;
3209 cursor: pointer;
3209 cursor: pointer;
3210 display: block;
3210 display: block;
3211 float: right;
3211 float: right;
3212 margin-top: 2px;
3212 margin-top: 2px;
3213 }
3213 }
3214
3214
3215 .locking_unlocked{
3215 .locking_unlocked{
3216 background: #FFF url("../images/icons/accept.png") no-repeat scroll 3px;
3216 background: #FFF url("../images/icons/accept.png") no-repeat scroll 3px;
3217 height: 16px;
3217 height: 16px;
3218 width: 20px;
3218 width: 20px;
3219 cursor: pointer;
3219 cursor: pointer;
3220 display: block;
3220 display: block;
3221 float: right;
3221 float: right;
3222 margin-top: 2px;
3222 margin-top: 2px;
3223 }
3223 }
3224
3224
3225 .currently_following {
3225 .currently_following {
3226 padding-left: 10px;
3226 padding-left: 10px;
3227 padding-bottom: 5px;
3227 padding-bottom: 5px;
3228 }
3228 }
3229
3229
3230 .add_icon {
3230 .add_icon {
3231 background: url("../images/icons/add.png") no-repeat scroll 3px;
3231 background: url("../images/icons/add.png") no-repeat scroll 3px;
3232 padding-left: 20px;
3232 padding-left: 20px;
3233 padding-top: 0px;
3233 padding-top: 0px;
3234 text-align: left;
3234 text-align: left;
3235 }
3235 }
3236
3236
3237 .accept_icon {
3237 .accept_icon {
3238 background: url("../images/icons/accept.png") no-repeat scroll 3px;
3238 background: url("../images/icons/accept.png") no-repeat scroll 3px;
3239 padding-left: 20px;
3239 padding-left: 20px;
3240 padding-top: 0px;
3240 padding-top: 0px;
3241 text-align: left;
3241 text-align: left;
3242 }
3242 }
3243
3243
3244 .edit_icon {
3244 .edit_icon {
3245 background: url("../images/icons/application_form_edit.png") no-repeat scroll 3px;
3245 background: url("../images/icons/application_form_edit.png") no-repeat scroll 3px;
3246 padding-left: 20px;
3246 padding-left: 20px;
3247 padding-top: 0px;
3247 padding-top: 0px;
3248 text-align: left;
3248 text-align: left;
3249 }
3249 }
3250
3250
3251 .delete_icon {
3251 .delete_icon {
3252 background: url("../images/icons/delete.png") no-repeat scroll 3px;
3252 background: url("../images/icons/delete.png") no-repeat scroll 3px;
3253 padding-left: 20px;
3253 padding-left: 20px;
3254 padding-top: 0px;
3254 padding-top: 0px;
3255 text-align: left;
3255 text-align: left;
3256 }
3256 }
3257
3257
3258 .refresh_icon {
3258 .refresh_icon {
3259 background: url("../images/icons/arrow_refresh.png") no-repeat scroll
3259 background: url("../images/icons/arrow_refresh.png") no-repeat scroll
3260 3px;
3260 3px;
3261 padding-left: 20px;
3261 padding-left: 20px;
3262 padding-top: 0px;
3262 padding-top: 0px;
3263 text-align: left;
3263 text-align: left;
3264 }
3264 }
3265
3265
3266 .pull_icon {
3266 .pull_icon {
3267 background: url("../images/icons/connect.png") no-repeat scroll 3px;
3267 background: url("../images/icons/connect.png") no-repeat scroll 3px;
3268 padding-left: 20px;
3268 padding-left: 20px;
3269 padding-top: 0px;
3269 padding-top: 0px;
3270 text-align: left;
3270 text-align: left;
3271 }
3271 }
3272
3272
3273 .rss_icon {
3273 .rss_icon {
3274 background: url("../images/icons/rss_16.png") no-repeat scroll 3px;
3274 background: url("../images/icons/rss_16.png") no-repeat scroll 3px;
3275 padding-left: 20px;
3275 padding-left: 20px;
3276 padding-top: 4px;
3276 padding-top: 4px;
3277 text-align: left;
3277 text-align: left;
3278 font-size: 8px
3278 font-size: 8px
3279 }
3279 }
3280
3280
3281 .atom_icon {
3281 .atom_icon {
3282 background: url("../images/icons/atom.png") no-repeat scroll 3px;
3282 background: url("../images/icons/atom.png") no-repeat scroll 3px;
3283 padding-left: 20px;
3283 padding-left: 20px;
3284 padding-top: 4px;
3284 padding-top: 4px;
3285 text-align: left;
3285 text-align: left;
3286 font-size: 8px
3286 font-size: 8px
3287 }
3287 }
3288
3288
3289 .archive_icon {
3289 .archive_icon {
3290 background: url("../images/icons/compress.png") no-repeat scroll 3px;
3290 background: url("../images/icons/compress.png") no-repeat scroll 3px;
3291 padding-left: 20px;
3291 padding-left: 20px;
3292 text-align: left;
3292 text-align: left;
3293 padding-top: 1px;
3293 padding-top: 1px;
3294 }
3294 }
3295
3295
3296 .start_following_icon {
3296 .start_following_icon {
3297 background: url("../images/icons/heart_add.png") no-repeat scroll 3px;
3297 background: url("../images/icons/heart_add.png") no-repeat scroll 3px;
3298 padding-left: 20px;
3298 padding-left: 20px;
3299 text-align: left;
3299 text-align: left;
3300 padding-top: 0px;
3300 padding-top: 0px;
3301 }
3301 }
3302
3302
3303 .stop_following_icon {
3303 .stop_following_icon {
3304 background: url("../images/icons/heart_delete.png") no-repeat scroll 3px;
3304 background: url("../images/icons/heart_delete.png") no-repeat scroll 3px;
3305 padding-left: 20px;
3305 padding-left: 20px;
3306 text-align: left;
3306 text-align: left;
3307 padding-top: 0px;
3307 padding-top: 0px;
3308 }
3308 }
3309
3309
3310 .action_button {
3310 .action_button {
3311 border: 0;
3311 border: 0;
3312 display: inline;
3312 display: inline;
3313 }
3313 }
3314
3314
3315 .action_button:hover {
3315 .action_button:hover {
3316 border: 0;
3316 border: 0;
3317 text-decoration: underline;
3317 text-decoration: underline;
3318 cursor: pointer;
3318 cursor: pointer;
3319 }
3319 }
3320
3320
3321 #switch_repos {
3321 #switch_repos {
3322 position: absolute;
3322 position: absolute;
3323 height: 25px;
3323 height: 25px;
3324 z-index: 1;
3324 z-index: 1;
3325 }
3325 }
3326
3326
3327 #switch_repos select {
3327 #switch_repos select {
3328 min-width: 150px;
3328 min-width: 150px;
3329 max-height: 250px;
3329 max-height: 250px;
3330 z-index: 1;
3330 z-index: 1;
3331 }
3331 }
3332
3332
3333 .breadcrumbs {
3333 .breadcrumbs {
3334 border: medium none;
3334 border: medium none;
3335 color: #FFF;
3335 color: #FFF;
3336 float: left;
3336 float: left;
3337 font-weight: 700;
3337 font-weight: 700;
3338 font-size: 14px;
3338 font-size: 14px;
3339 margin: 0;
3339 margin: 0;
3340 padding: 11px 0 11px 10px;
3340 padding: 11px 0 11px 10px;
3341 }
3341 }
3342
3342
3343 .breadcrumbs .hash {
3343 .breadcrumbs .hash {
3344 text-transform: none;
3344 text-transform: none;
3345 color: #fff;
3345 color: #fff;
3346 }
3346 }
3347
3347
3348 .breadcrumbs a {
3348 .breadcrumbs a {
3349 color: #FFF;
3349 color: #FFF;
3350 }
3350 }
3351
3351
3352 .flash_msg {
3352 .flash_msg {
3353
3353
3354 }
3354 }
3355
3355
3356 .flash_msg ul {
3356 .flash_msg ul {
3357
3357
3358 }
3358 }
3359
3359
3360 .error_red {
3360 .error_red {
3361 color:red;
3361 color:red;
3362 }
3362 }
3363
3363
3364 .error_msg {
3364 .error_msg {
3365 background-color: #c43c35;
3365 background-color: #c43c35;
3366 background-repeat: repeat-x;
3366 background-repeat: repeat-x;
3367 background-image: -khtml-gradient(linear, left top, left bottom, from(#ee5f5b), to(#c43c35) );
3367 background-image: -khtml-gradient(linear, left top, left bottom, from(#ee5f5b), to(#c43c35) );
3368 background-image: -moz-linear-gradient(top, #ee5f5b, #c43c35);
3368 background-image: -moz-linear-gradient(top, #ee5f5b, #c43c35);
3369 background-image: -ms-linear-gradient(top, #ee5f5b, #c43c35);
3369 background-image: -ms-linear-gradient(top, #ee5f5b, #c43c35);
3370 background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #ee5f5b), color-stop(100%, #c43c35) );
3370 background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #ee5f5b), color-stop(100%, #c43c35) );
3371 background-image: -webkit-linear-gradient(top, #ee5f5b, #c43c35);
3371 background-image: -webkit-linear-gradient(top, #ee5f5b, #c43c35);
3372 background-image: -o-linear-gradient(top, #ee5f5b, #c43c35);
3372 background-image: -o-linear-gradient(top, #ee5f5b, #c43c35);
3373 background-image: linear-gradient(top, #ee5f5b, #c43c35);
3373 background-image: linear-gradient(top, #ee5f5b, #c43c35);
3374 filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ee5f5b',endColorstr='#c43c35', GradientType=0 );
3374 filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ee5f5b',endColorstr='#c43c35', GradientType=0 );
3375 border-color: #c43c35 #c43c35 #882a25;
3375 border-color: #c43c35 #c43c35 #882a25;
3376 }
3376 }
3377
3377
3378 .warning_msg {
3378 .warning_msg {
3379 color: #404040 !important;
3379 color: #404040 !important;
3380 background-color: #eedc94;
3380 background-color: #eedc94;
3381 background-repeat: repeat-x;
3381 background-repeat: repeat-x;
3382 background-image: -khtml-gradient(linear, left top, left bottom, from(#fceec1), to(#eedc94) );
3382 background-image: -khtml-gradient(linear, left top, left bottom, from(#fceec1), to(#eedc94) );
3383 background-image: -moz-linear-gradient(top, #fceec1, #eedc94);
3383 background-image: -moz-linear-gradient(top, #fceec1, #eedc94);
3384 background-image: -ms-linear-gradient(top, #fceec1, #eedc94);
3384 background-image: -ms-linear-gradient(top, #fceec1, #eedc94);
3385 background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #fceec1), color-stop(100%, #eedc94) );
3385 background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #fceec1), color-stop(100%, #eedc94) );
3386 background-image: -webkit-linear-gradient(top, #fceec1, #eedc94);
3386 background-image: -webkit-linear-gradient(top, #fceec1, #eedc94);
3387 background-image: -o-linear-gradient(top, #fceec1, #eedc94);
3387 background-image: -o-linear-gradient(top, #fceec1, #eedc94);
3388 background-image: linear-gradient(top, #fceec1, #eedc94);
3388 background-image: linear-gradient(top, #fceec1, #eedc94);
3389 filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fceec1', endColorstr='#eedc94', GradientType=0 );
3389 filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fceec1', endColorstr='#eedc94', GradientType=0 );
3390 border-color: #eedc94 #eedc94 #e4c652;
3390 border-color: #eedc94 #eedc94 #e4c652;
3391 }
3391 }
3392
3392
3393 .success_msg {
3393 .success_msg {
3394 background-color: #57a957;
3394 background-color: #57a957;
3395 background-repeat: repeat-x !important;
3395 background-repeat: repeat-x !important;
3396 background-image: -khtml-gradient(linear, left top, left bottom, from(#62c462), to(#57a957) );
3396 background-image: -khtml-gradient(linear, left top, left bottom, from(#62c462), to(#57a957) );
3397 background-image: -moz-linear-gradient(top, #62c462, #57a957);
3397 background-image: -moz-linear-gradient(top, #62c462, #57a957);
3398 background-image: -ms-linear-gradient(top, #62c462, #57a957);
3398 background-image: -ms-linear-gradient(top, #62c462, #57a957);
3399 background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #62c462), color-stop(100%, #57a957) );
3399 background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #62c462), color-stop(100%, #57a957) );
3400 background-image: -webkit-linear-gradient(top, #62c462, #57a957);
3400 background-image: -webkit-linear-gradient(top, #62c462, #57a957);
3401 background-image: -o-linear-gradient(top, #62c462, #57a957);
3401 background-image: -o-linear-gradient(top, #62c462, #57a957);
3402 background-image: linear-gradient(top, #62c462, #57a957);
3402 background-image: linear-gradient(top, #62c462, #57a957);
3403 filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#62c462', endColorstr='#57a957', GradientType=0 );
3403 filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#62c462', endColorstr='#57a957', GradientType=0 );
3404 border-color: #57a957 #57a957 #3d773d;
3404 border-color: #57a957 #57a957 #3d773d;
3405 }
3405 }
3406
3406
3407 .notice_msg {
3407 .notice_msg {
3408 background-color: #339bb9;
3408 background-color: #339bb9;
3409 background-repeat: repeat-x;
3409 background-repeat: repeat-x;
3410 background-image: -khtml-gradient(linear, left top, left bottom, from(#5bc0de), to(#339bb9) );
3410 background-image: -khtml-gradient(linear, left top, left bottom, from(#5bc0de), to(#339bb9) );
3411 background-image: -moz-linear-gradient(top, #5bc0de, #339bb9);
3411 background-image: -moz-linear-gradient(top, #5bc0de, #339bb9);
3412 background-image: -ms-linear-gradient(top, #5bc0de, #339bb9);
3412 background-image: -ms-linear-gradient(top, #5bc0de, #339bb9);
3413 background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #5bc0de), color-stop(100%, #339bb9) );
3413 background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #5bc0de), color-stop(100%, #339bb9) );
3414 background-image: -webkit-linear-gradient(top, #5bc0de, #339bb9);
3414 background-image: -webkit-linear-gradient(top, #5bc0de, #339bb9);
3415 background-image: -o-linear-gradient(top, #5bc0de, #339bb9);
3415 background-image: -o-linear-gradient(top, #5bc0de, #339bb9);
3416 background-image: linear-gradient(top, #5bc0de, #339bb9);
3416 background-image: linear-gradient(top, #5bc0de, #339bb9);
3417 filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#5bc0de', endColorstr='#339bb9', GradientType=0 );
3417 filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#5bc0de', endColorstr='#339bb9', GradientType=0 );
3418 border-color: #339bb9 #339bb9 #22697d;
3418 border-color: #339bb9 #339bb9 #22697d;
3419 }
3419 }
3420
3420
3421 .success_msg,.error_msg,.notice_msg,.warning_msg {
3421 .success_msg,.error_msg,.notice_msg,.warning_msg {
3422 font-size: 12px;
3422 font-size: 12px;
3423 font-weight: 700;
3423 font-weight: 700;
3424 min-height: 14px;
3424 min-height: 14px;
3425 line-height: 14px;
3425 line-height: 14px;
3426 margin-bottom: 10px;
3426 margin-bottom: 10px;
3427 margin-top: 0;
3427 margin-top: 0;
3428 display: block;
3428 display: block;
3429 overflow: auto;
3429 overflow: auto;
3430 padding: 6px 10px 6px 10px;
3430 padding: 6px 10px 6px 10px;
3431 border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
3431 border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
3432 position: relative;
3432 position: relative;
3433 color: #FFF;
3433 color: #FFF;
3434 border-width: 1px;
3434 border-width: 1px;
3435 border-style: solid;
3435 border-style: solid;
3436 -webkit-border-radius: 4px;
3436 -webkit-border-radius: 4px;
3437 -moz-border-radius: 4px;
3437 -moz-border-radius: 4px;
3438 border-radius: 4px;
3438 border-radius: 4px;
3439 -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25);
3439 -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25);
3440 -moz-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25);
3440 -moz-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25);
3441 box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25);
3441 box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25);
3442 }
3442 }
3443
3443
3444 #msg_close {
3444 #msg_close {
3445 background: transparent url("../icons/cross_grey_small.png") no-repeat scroll 0 0;
3445 background: transparent url("../icons/cross_grey_small.png") no-repeat scroll 0 0;
3446 cursor: pointer;
3446 cursor: pointer;
3447 height: 16px;
3447 height: 16px;
3448 position: absolute;
3448 position: absolute;
3449 right: 5px;
3449 right: 5px;
3450 top: 5px;
3450 top: 5px;
3451 width: 16px;
3451 width: 16px;
3452 }
3452 }
3453 div#legend_data{
3453 div#legend_data{
3454 padding-left:10px;
3454 padding-left:10px;
3455 }
3455 }
3456 div#legend_container table{
3456 div#legend_container table{
3457 border: none !important;
3457 border: none !important;
3458 }
3458 }
3459 div#legend_container table,div#legend_choices table {
3459 div#legend_container table,div#legend_choices table {
3460 width: auto !important;
3460 width: auto !important;
3461 }
3461 }
3462
3462
3463 table#permissions_manage {
3463 table#permissions_manage {
3464 width: 0 !important;
3464 width: 0 !important;
3465 }
3465 }
3466
3466
3467 table#permissions_manage span.private_repo_msg {
3467 table#permissions_manage span.private_repo_msg {
3468 font-size: 0.8em;
3468 font-size: 0.8em;
3469 opacity: 0.6px;
3469 opacity: 0.6px;
3470 }
3470 }
3471
3471
3472 table#permissions_manage td.private_repo_msg {
3472 table#permissions_manage td.private_repo_msg {
3473 font-size: 0.8em;
3473 font-size: 0.8em;
3474 }
3474 }
3475
3475
3476 table#permissions_manage tr#add_perm_input td {
3476 table#permissions_manage tr#add_perm_input td {
3477 vertical-align: middle;
3477 vertical-align: middle;
3478 }
3478 }
3479
3479
3480 div.gravatar {
3480 div.gravatar {
3481 background-color: #FFF;
3481 background-color: #FFF;
3482 float: left;
3482 float: left;
3483 margin-right: 0.7em;
3483 margin-right: 0.7em;
3484 padding: 1px 1px 1px 1px;
3484 padding: 1px 1px 1px 1px;
3485 line-height:0;
3485 line-height:0;
3486 -webkit-border-radius: 3px;
3486 -webkit-border-radius: 3px;
3487 -khtml-border-radius: 3px;
3487 -khtml-border-radius: 3px;
3488 -moz-border-radius: 3px;
3488 -moz-border-radius: 3px;
3489 border-radius: 3px;
3489 border-radius: 3px;
3490 }
3490 }
3491
3491
3492 div.gravatar img {
3492 div.gravatar img {
3493 -webkit-border-radius: 2px;
3493 -webkit-border-radius: 2px;
3494 -khtml-border-radius: 2px;
3494 -khtml-border-radius: 2px;
3495 -moz-border-radius: 2px;
3495 -moz-border-radius: 2px;
3496 border-radius: 2px;
3496 border-radius: 2px;
3497 }
3497 }
3498
3498
3499 #header,#content,#footer {
3499 #header,#content,#footer {
3500 min-width: 978px;
3500 min-width: 978px;
3501 }
3501 }
3502
3502
3503 #content {
3503 #content {
3504 clear: both;
3504 clear: both;
3505 overflow: hidden;
3505 overflow: hidden;
3506 padding: 54px 10px 14px 10px;
3506 padding: 54px 10px 14px 10px;
3507 }
3507 }
3508
3508
3509 #content div.box div.title div.search {
3509 #content div.box div.title div.search {
3510
3510
3511 border-left: 1px solid #316293;
3511 border-left: 1px solid #316293;
3512 }
3512 }
3513
3513
3514 #content div.box div.title div.search div.input input {
3514 #content div.box div.title div.search div.input input {
3515 border: 1px solid #316293;
3515 border: 1px solid #316293;
3516 }
3516 }
3517
3517
3518 .ui-btn{
3518 .ui-btn{
3519 color: #515151;
3519 color: #515151;
3520 background-color: #DADADA;
3520 background-color: #DADADA;
3521 background-repeat: repeat-x;
3521 background-repeat: repeat-x;
3522 background-image: -khtml-gradient(linear, left top, left bottom, from(#F4F4F4),to(#DADADA) );
3522 background-image: -khtml-gradient(linear, left top, left bottom, from(#F4F4F4),to(#DADADA) );
3523 background-image: -moz-linear-gradient(top, #F4F4F4, #DADADA);
3523 background-image: -moz-linear-gradient(top, #F4F4F4, #DADADA);
3524 background-image: -ms-linear-gradient(top, #F4F4F4, #DADADA);
3524 background-image: -ms-linear-gradient(top, #F4F4F4, #DADADA);
3525 background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #F4F4F4),color-stop(100%, #DADADA) );
3525 background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #F4F4F4),color-stop(100%, #DADADA) );
3526 background-image: -webkit-linear-gradient(top, #F4F4F4, #DADADA) );
3526 background-image: -webkit-linear-gradient(top, #F4F4F4, #DADADA) );
3527 background-image: -o-linear-gradient(top, #F4F4F4, #DADADA) );
3527 background-image: -o-linear-gradient(top, #F4F4F4, #DADADA) );
3528 background-image: linear-gradient(top, #F4F4F4, #DADADA);
3528 background-image: linear-gradient(top, #F4F4F4, #DADADA);
3529 filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#F4F4F4', endColorstr='#DADADA', GradientType=0);
3529 filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#F4F4F4', endColorstr='#DADADA', GradientType=0);
3530
3530
3531 border-top: 1px solid #DDD;
3531 border-top: 1px solid #DDD;
3532 border-left: 1px solid #c6c6c6;
3532 border-left: 1px solid #c6c6c6;
3533 border-right: 1px solid #DDD;
3533 border-right: 1px solid #DDD;
3534 border-bottom: 1px solid #c6c6c6;
3534 border-bottom: 1px solid #c6c6c6;
3535 color: #515151;
3535 color: #515151;
3536 outline: none;
3536 outline: none;
3537 margin: 0px 3px 3px 0px;
3537 margin: 0px 3px 3px 0px;
3538 -webkit-border-radius: 4px 4px 4px 4px !important;
3538 -webkit-border-radius: 4px 4px 4px 4px !important;
3539 -khtml-border-radius: 4px 4px 4px 4px !important;
3539 -khtml-border-radius: 4px 4px 4px 4px !important;
3540 -moz-border-radius: 4px 4px 4px 4px !important;
3540 -moz-border-radius: 4px 4px 4px 4px !important;
3541 border-radius: 4px 4px 4px 4px !important;
3541 border-radius: 4px 4px 4px 4px !important;
3542 cursor: pointer !important;
3542 cursor: pointer !important;
3543 padding: 3px 3px 3px 3px;
3543 padding: 3px 3px 3px 3px;
3544 background-position: 0 -15px;
3544 background-position: 0 -15px;
3545
3545
3546 }
3546 }
3547 .ui-btn.xsmall{
3547 .ui-btn.xsmall{
3548 padding: 1px 2px 1px 1px;
3548 padding: 1px 2px 1px 1px;
3549 }
3549 }
3550
3550
3551 .ui-btn.large{
3551 .ui-btn.large{
3552 padding: 6px 12px;
3552 padding: 6px 12px;
3553 }
3553 }
3554
3554
3555 .ui-btn.clone{
3555 .ui-btn.clone{
3556 padding: 5px 2px 6px 1px;
3556 padding: 5px 2px 6px 1px;
3557 margin: 0px -4px 3px 0px;
3557 margin: 0px -4px 3px 0px;
3558 -webkit-border-radius: 4px 0px 0px 4px !important;
3558 -webkit-border-radius: 4px 0px 0px 4px !important;
3559 -khtml-border-radius: 4px 0px 0px 4px !important;
3559 -khtml-border-radius: 4px 0px 0px 4px !important;
3560 -moz-border-radius: 4px 0px 0px 4px !important;
3560 -moz-border-radius: 4px 0px 0px 4px !important;
3561 border-radius: 4px 0px 0px 4px !important;
3561 border-radius: 4px 0px 0px 4px !important;
3562 width: 100px;
3562 width: 100px;
3563 text-align: center;
3563 text-align: center;
3564 float: left;
3564 float: left;
3565 position: absolute;
3565 position: absolute;
3566 }
3566 }
3567 .ui-btn:focus {
3567 .ui-btn:focus {
3568 outline: none;
3568 outline: none;
3569 }
3569 }
3570 .ui-btn:hover{
3570 .ui-btn:hover{
3571 background-position: 0 0px;
3571 background-position: 0 0px;
3572 text-decoration: none;
3572 text-decoration: none;
3573 color: #515151;
3573 color: #515151;
3574 box-shadow: 0 1px 2px rgba(0, 0, 0, 0.25), 0 0 3px #FFFFFF !important;
3574 box-shadow: 0 1px 2px rgba(0, 0, 0, 0.25), 0 0 3px #FFFFFF !important;
3575 }
3575 }
3576
3576
3577 .ui-btn.red{
3577 .ui-btn.red{
3578 color:#fff;
3578 color:#fff;
3579 background-color: #c43c35;
3579 background-color: #c43c35;
3580 background-repeat: repeat-x;
3580 background-repeat: repeat-x;
3581 background-image: -khtml-gradient(linear, left top, left bottom, from(#ee5f5b), to(#c43c35));
3581 background-image: -khtml-gradient(linear, left top, left bottom, from(#ee5f5b), to(#c43c35));
3582 background-image: -moz-linear-gradient(top, #ee5f5b, #c43c35);
3582 background-image: -moz-linear-gradient(top, #ee5f5b, #c43c35);
3583 background-image: -ms-linear-gradient(top, #ee5f5b, #c43c35);
3583 background-image: -ms-linear-gradient(top, #ee5f5b, #c43c35);
3584 background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #ee5f5b), color-stop(100%, #c43c35));
3584 background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #ee5f5b), color-stop(100%, #c43c35));
3585 background-image: -webkit-linear-gradient(top, #ee5f5b, #c43c35);
3585 background-image: -webkit-linear-gradient(top, #ee5f5b, #c43c35);
3586 background-image: -o-linear-gradient(top, #ee5f5b, #c43c35);
3586 background-image: -o-linear-gradient(top, #ee5f5b, #c43c35);
3587 background-image: linear-gradient(top, #ee5f5b, #c43c35);
3587 background-image: linear-gradient(top, #ee5f5b, #c43c35);
3588 filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ee5f5b', endColorstr='#c43c35', GradientType=0);
3588 filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ee5f5b', endColorstr='#c43c35', GradientType=0);
3589 border-color: #c43c35 #c43c35 #882a25;
3589 border-color: #c43c35 #c43c35 #882a25;
3590 border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
3590 border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
3591 }
3591 }
3592
3592
3593
3593
3594 .ui-btn.blue{
3594 .ui-btn.blue{
3595 color:#fff;
3595 color:#fff;
3596 background-color: #339bb9;
3596 background-color: #339bb9;
3597 background-repeat: repeat-x;
3597 background-repeat: repeat-x;
3598 background-image: -khtml-gradient(linear, left top, left bottom, from(#5bc0de), to(#339bb9));
3598 background-image: -khtml-gradient(linear, left top, left bottom, from(#5bc0de), to(#339bb9));
3599 background-image: -moz-linear-gradient(top, #5bc0de, #339bb9);
3599 background-image: -moz-linear-gradient(top, #5bc0de, #339bb9);
3600 background-image: -ms-linear-gradient(top, #5bc0de, #339bb9);
3600 background-image: -ms-linear-gradient(top, #5bc0de, #339bb9);
3601 background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #5bc0de), color-stop(100%, #339bb9));
3601 background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #5bc0de), color-stop(100%, #339bb9));
3602 background-image: -webkit-linear-gradient(top, #5bc0de, #339bb9);
3602 background-image: -webkit-linear-gradient(top, #5bc0de, #339bb9);
3603 background-image: -o-linear-gradient(top, #5bc0de, #339bb9);
3603 background-image: -o-linear-gradient(top, #5bc0de, #339bb9);
3604 background-image: linear-gradient(top, #5bc0de, #339bb9);
3604 background-image: linear-gradient(top, #5bc0de, #339bb9);
3605 filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#5bc0de', endColorstr='#339bb9', GradientType=0);
3605 filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#5bc0de', endColorstr='#339bb9', GradientType=0);
3606 border-color: #339bb9 #339bb9 #22697d;
3606 border-color: #339bb9 #339bb9 #22697d;
3607 border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
3607 border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
3608 }
3608 }
3609
3609
3610 .ui-btn.green{
3610 .ui-btn.green{
3611 background-color: #57a957;
3611 background-color: #57a957;
3612 background-repeat: repeat-x;
3612 background-repeat: repeat-x;
3613 background-image: -khtml-gradient(linear, left top, left bottom, from(#62c462), to(#57a957));
3613 background-image: -khtml-gradient(linear, left top, left bottom, from(#62c462), to(#57a957));
3614 background-image: -moz-linear-gradient(top, #62c462, #57a957);
3614 background-image: -moz-linear-gradient(top, #62c462, #57a957);
3615 background-image: -ms-linear-gradient(top, #62c462, #57a957);
3615 background-image: -ms-linear-gradient(top, #62c462, #57a957);
3616 background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #62c462), color-stop(100%, #57a957));
3616 background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #62c462), color-stop(100%, #57a957));
3617 background-image: -webkit-linear-gradient(top, #62c462, #57a957);
3617 background-image: -webkit-linear-gradient(top, #62c462, #57a957);
3618 background-image: -o-linear-gradient(top, #62c462, #57a957);
3618 background-image: -o-linear-gradient(top, #62c462, #57a957);
3619 background-image: linear-gradient(top, #62c462, #57a957);
3619 background-image: linear-gradient(top, #62c462, #57a957);
3620 filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#62c462', endColorstr='#57a957', GradientType=0);
3620 filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#62c462', endColorstr='#57a957', GradientType=0);
3621 border-color: #57a957 #57a957 #3d773d;
3621 border-color: #57a957 #57a957 #3d773d;
3622 border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
3622 border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
3623 }
3623 }
3624
3624
3625 .ui-btn.blue.hidden{
3625 .ui-btn.blue.hidden{
3626 display: none;
3626 display: none;
3627 }
3627 }
3628
3628
3629 .ui-btn.active{
3629 .ui-btn.active{
3630 font-weight: bold;
3630 font-weight: bold;
3631 }
3631 }
3632
3632
3633 ins,div.options a:hover {
3633 ins,div.options a:hover {
3634 text-decoration: none;
3634 text-decoration: none;
3635 }
3635 }
3636
3636
3637 img,
3637 img,
3638 #header #header-inner #quick li a:hover span.normal,
3638 #header #header-inner #quick li a:hover span.normal,
3639 #header #header-inner #quick li ul li.last,
3639 #header #header-inner #quick li ul li.last,
3640 #content div.box div.form div.fields div.field div.textarea table td table td a,
3640 #content div.box div.form div.fields div.field div.textarea table td table td a,
3641 #clone_url,
3641 #clone_url,
3642 #clone_url_id
3642 #clone_url_id
3643 {
3643 {
3644 border: none;
3644 border: none;
3645 }
3645 }
3646
3646
3647 img.icon,.right .merge img {
3647 img.icon,.right .merge img {
3648 vertical-align: bottom;
3648 vertical-align: bottom;
3649 }
3649 }
3650
3650
3651 #header ul#logged-user,#content div.box div.title ul.links,
3651 #header ul#logged-user,#content div.box div.title ul.links,
3652 #content div.box div.message div.dismiss,
3652 #content div.box div.message div.dismiss,
3653 #content div.box div.traffic div.legend ul
3653 #content div.box div.traffic div.legend ul
3654 {
3654 {
3655 float: right;
3655 float: right;
3656 margin: 0;
3656 margin: 0;
3657 padding: 0;
3657 padding: 0;
3658 }
3658 }
3659
3659
3660 #header #header-inner #home,#header #header-inner #logo,
3660 #header #header-inner #home,#header #header-inner #logo,
3661 #content div.box ul.left,#content div.box ol.left,
3661 #content div.box ul.left,#content div.box ol.left,
3662 #content div.box div.pagination-left,div#commit_history,
3662 #content div.box div.pagination-left,div#commit_history,
3663 div#legend_data,div#legend_container,div#legend_choices
3663 div#legend_data,div#legend_container,div#legend_choices
3664 {
3664 {
3665 float: left;
3665 float: left;
3666 }
3666 }
3667
3667
3668 #header #header-inner #quick li #quick_login,
3668 #header #header-inner #quick li #quick_login,
3669 #header #header-inner #quick li:hover ul ul,
3669 #header #header-inner #quick li:hover ul ul,
3670 #header #header-inner #quick li:hover ul ul ul,
3670 #header #header-inner #quick li:hover ul ul ul,
3671 #header #header-inner #quick li:hover ul ul ul ul,
3671 #header #header-inner #quick li:hover ul ul ul ul,
3672 #content #left #menu ul.closed,#content #left #menu li ul.collapsed,.yui-tt-shadow
3672 #content #left #menu ul.closed,#content #left #menu li ul.collapsed,.yui-tt-shadow
3673 {
3673 {
3674 display: none;
3674 display: none;
3675 }
3675 }
3676
3676
3677 #header #header-inner #quick li:hover #quick_login,
3677 #header #header-inner #quick li:hover #quick_login,
3678 #header #header-inner #quick li:hover ul,#header #header-inner #quick li li:hover ul,#header #header-inner #quick li li li:hover ul,#header #header-inner #quick li li li li:hover ul,#content #left #menu ul.opened,#content #left #menu li ul.expanded
3678 #header #header-inner #quick li:hover ul,#header #header-inner #quick li li:hover ul,#header #header-inner #quick li li li:hover ul,#header #header-inner #quick li li li li:hover ul,#content #left #menu ul.opened,#content #left #menu li ul.expanded
3679 {
3679 {
3680 display: block;
3680 display: block;
3681 }
3681 }
3682
3682
3683 #content div.graph {
3683 #content div.graph {
3684 padding: 0 10px 10px;
3684 padding: 0 10px 10px;
3685 }
3685 }
3686
3686
3687 #content div.box div.title ul.links li a:hover,#content div.box div.title ul.links li.ui-tabs-selected a
3687 #content div.box div.title ul.links li a:hover,#content div.box div.title ul.links li.ui-tabs-selected a
3688 {
3688 {
3689 color: #bfe3ff;
3689 color: #bfe3ff;
3690 }
3690 }
3691
3691
3692 #content div.box ol.lower-roman,#content div.box ol.upper-roman,#content div.box ol.lower-alpha,#content div.box ol.upper-alpha,#content div.box ol.decimal
3692 #content div.box ol.lower-roman,#content div.box ol.upper-roman,#content div.box ol.lower-alpha,#content div.box ol.upper-alpha,#content div.box ol.decimal
3693 {
3693 {
3694 margin: 10px 24px 10px 44px;
3694 margin: 10px 24px 10px 44px;
3695 }
3695 }
3696
3696
3697 #content div.box div.form,#content div.box div.table,#content div.box div.traffic
3697 #content div.box div.form,#content div.box div.table,#content div.box div.traffic
3698 {
3698 {
3699 clear: both;
3699 clear: both;
3700 overflow: hidden;
3700 overflow: hidden;
3701 margin: 0;
3701 margin: 0;
3702 padding: 0 20px 10px;
3702 padding: 0 20px 10px;
3703 }
3703 }
3704
3704
3705 #content div.box div.form div.fields,#login div.form,#login div.form div.fields,#register div.form,#register div.form div.fields
3705 #content div.box div.form div.fields,#login div.form,#login div.form div.fields,#register div.form,#register div.form div.fields
3706 {
3706 {
3707 clear: both;
3707 clear: both;
3708 overflow: hidden;
3708 overflow: hidden;
3709 margin: 0;
3709 margin: 0;
3710 padding: 0;
3710 padding: 0;
3711 }
3711 }
3712
3712
3713 #content div.box div.form div.fields div.field div.label span,#login div.form div.fields div.field div.label span,#register div.form div.fields div.field div.label span
3713 #content div.box div.form div.fields div.field div.label span,#login div.form div.fields div.field div.label span,#register div.form div.fields div.field div.label span
3714 {
3714 {
3715 height: 1%;
3715 height: 1%;
3716 display: block;
3716 display: block;
3717 color: #363636;
3717 color: #363636;
3718 margin: 0;
3718 margin: 0;
3719 padding: 2px 0 0;
3719 padding: 2px 0 0;
3720 }
3720 }
3721
3721
3722 #content div.box div.form div.fields div.field div.input input.error,#login div.form div.fields div.field div.input input.error,#register div.form div.fields div.field div.input input.error
3722 #content div.box div.form div.fields div.field div.input input.error,#login div.form div.fields div.field div.input input.error,#register div.form div.fields div.field div.input input.error
3723 {
3723 {
3724 background: #FBE3E4;
3724 background: #FBE3E4;
3725 border-top: 1px solid #e1b2b3;
3725 border-top: 1px solid #e1b2b3;
3726 border-left: 1px solid #e1b2b3;
3726 border-left: 1px solid #e1b2b3;
3727 border-right: 1px solid #FBC2C4;
3727 border-right: 1px solid #FBC2C4;
3728 border-bottom: 1px solid #FBC2C4;
3728 border-bottom: 1px solid #FBC2C4;
3729 }
3729 }
3730
3730
3731 #content div.box div.form div.fields div.field div.input input.success,#login div.form div.fields div.field div.input input.success,#register div.form div.fields div.field div.input input.success
3731 #content div.box div.form div.fields div.field div.input input.success,#login div.form div.fields div.field div.input input.success,#register div.form div.fields div.field div.input input.success
3732 {
3732 {
3733 background: #E6EFC2;
3733 background: #E6EFC2;
3734 border-top: 1px solid #cebb98;
3734 border-top: 1px solid #cebb98;
3735 border-left: 1px solid #cebb98;
3735 border-left: 1px solid #cebb98;
3736 border-right: 1px solid #c6d880;
3736 border-right: 1px solid #c6d880;
3737 border-bottom: 1px solid #c6d880;
3737 border-bottom: 1px solid #c6d880;
3738 }
3738 }
3739
3739
3740 #content div.box-left div.form div.fields div.field div.textarea,#content div.box-right div.form div.fields div.field div.textarea,#content div.box div.form div.fields div.field div.select select,#content div.box table th.selected input,#content div.box table td.selected input
3740 #content div.box-left div.form div.fields div.field div.textarea,#content div.box-right div.form div.fields div.field div.textarea,#content div.box div.form div.fields div.field div.select select,#content div.box table th.selected input,#content div.box table td.selected input
3741 {
3741 {
3742 margin: 0;
3742 margin: 0;
3743 }
3743 }
3744
3744
3745 #content div.box-left div.form div.fields div.field div.select,#content div.box-left div.form div.fields div.field div.checkboxes,#content div.box-left div.form div.fields div.field div.radios,#content div.box-right div.form div.fields div.field div.select,#content div.box-right div.form div.fields div.field div.checkboxes,#content div.box-right div.form div.fields div.field div.radios
3745 #content div.box-left div.form div.fields div.field div.select,#content div.box-left div.form div.fields div.field div.checkboxes,#content div.box-left div.form div.fields div.field div.radios,#content div.box-right div.form div.fields div.field div.select,#content div.box-right div.form div.fields div.field div.checkboxes,#content div.box-right div.form div.fields div.field div.radios
3746 {
3746 {
3747 margin: 0 0 0 0px !important;
3747 margin: 0 0 0 0px !important;
3748 padding: 0;
3748 padding: 0;
3749 }
3749 }
3750
3750
3751 #content div.box div.form div.fields div.field div.select,#content div.box div.form div.fields div.field div.checkboxes,#content div.box div.form div.fields div.field div.radios
3751 #content div.box div.form div.fields div.field div.select,#content div.box div.form div.fields div.field div.checkboxes,#content div.box div.form div.fields div.field div.radios
3752 {
3752 {
3753 margin: 0 0 0 200px;
3753 margin: 0 0 0 200px;
3754 padding: 0;
3754 padding: 0;
3755 }
3755 }
3756
3756
3757 #content div.box div.form div.fields div.field div.select a:hover,#content div.box div.form div.fields div.field div.select a.ui-selectmenu:hover,#content div.box div.action a:hover
3757 #content div.box div.form div.fields div.field div.select a:hover,#content div.box div.form div.fields div.field div.select a.ui-selectmenu:hover,#content div.box div.action a:hover
3758 {
3758 {
3759 color: #000;
3759 color: #000;
3760 text-decoration: none;
3760 text-decoration: none;
3761 }
3761 }
3762
3762
3763 #content div.box div.form div.fields div.field div.select a.ui-selectmenu-focus,#content div.box div.action a.ui-selectmenu-focus
3763 #content div.box div.form div.fields div.field div.select a.ui-selectmenu-focus,#content div.box div.action a.ui-selectmenu-focus
3764 {
3764 {
3765 border: 1px solid #666;
3765 border: 1px solid #666;
3766 }
3766 }
3767
3767
3768 #content div.box div.form div.fields div.field div.checkboxes div.checkbox,#content div.box div.form div.fields div.field div.radios div.radio
3768 #content div.box div.form div.fields div.field div.checkboxes div.checkbox,#content div.box div.form div.fields div.field div.radios div.radio
3769 {
3769 {
3770 clear: both;
3770 clear: both;
3771 overflow: hidden;
3771 overflow: hidden;
3772 margin: 0;
3772 margin: 0;
3773 padding: 8px 0 2px;
3773 padding: 8px 0 2px;
3774 }
3774 }
3775
3775
3776 #content div.box div.form div.fields div.field div.checkboxes div.checkbox input,#content div.box div.form div.fields div.field div.radios div.radio input
3776 #content div.box div.form div.fields div.field div.checkboxes div.checkbox input,#content div.box div.form div.fields div.field div.radios div.radio input
3777 {
3777 {
3778 float: left;
3778 float: left;
3779 margin: 0;
3779 margin: 0;
3780 }
3780 }
3781
3781
3782 #content div.box div.form div.fields div.field div.checkboxes div.checkbox label,#content div.box div.form div.fields div.field div.radios div.radio label
3782 #content div.box div.form div.fields div.field div.checkboxes div.checkbox label,#content div.box div.form div.fields div.field div.radios div.radio label
3783 {
3783 {
3784 height: 1%;
3784 height: 1%;
3785 display: block;
3785 display: block;
3786 float: left;
3786 float: left;
3787 margin: 2px 0 0 4px;
3787 margin: 2px 0 0 4px;
3788 }
3788 }
3789
3789
3790 div.form div.fields div.field div.button input,
3790 div.form div.fields div.field div.button input,
3791 #content div.box div.form div.fields div.buttons input
3791 #content div.box div.form div.fields div.buttons input
3792 div.form div.fields div.buttons input,
3792 div.form div.fields div.buttons input,
3793 #content div.box div.action div.button input {
3793 #content div.box div.action div.button input {
3794 /*color: #000;*/
3794 /*color: #000;*/
3795 font-size: 11px;
3795 font-size: 11px;
3796 font-weight: 700;
3796 font-weight: 700;
3797 margin: 0;
3797 margin: 0;
3798 }
3798 }
3799
3799
3800 input.ui-button {
3800 input.ui-button {
3801 background: #e5e3e3 url("../images/button.png") repeat-x;
3801 background: #e5e3e3 url("../images/button.png") repeat-x;
3802 border-top: 1px solid #DDD;
3802 border-top: 1px solid #DDD;
3803 border-left: 1px solid #c6c6c6;
3803 border-left: 1px solid #c6c6c6;
3804 border-right: 1px solid #DDD;
3804 border-right: 1px solid #DDD;
3805 border-bottom: 1px solid #c6c6c6;
3805 border-bottom: 1px solid #c6c6c6;
3806 color: #515151 !important;
3806 color: #515151 !important;
3807 outline: none;
3807 outline: none;
3808 margin: 0;
3808 margin: 0;
3809 padding: 6px 12px;
3809 padding: 6px 12px;
3810 -webkit-border-radius: 4px 4px 4px 4px;
3810 -webkit-border-radius: 4px 4px 4px 4px;
3811 -khtml-border-radius: 4px 4px 4px 4px;
3811 -khtml-border-radius: 4px 4px 4px 4px;
3812 -moz-border-radius: 4px 4px 4px 4px;
3812 -moz-border-radius: 4px 4px 4px 4px;
3813 border-radius: 4px 4px 4px 4px;
3813 border-radius: 4px 4px 4px 4px;
3814 box-shadow: 0 1px 0 #ececec;
3814 box-shadow: 0 1px 0 #ececec;
3815 cursor: pointer;
3815 cursor: pointer;
3816 }
3816 }
3817
3817
3818 input.ui-button:hover {
3818 input.ui-button:hover {
3819 background: #b4b4b4 url("../images/button_selected.png") repeat-x;
3819 background: #b4b4b4 url("../images/button_selected.png") repeat-x;
3820 border-top: 1px solid #ccc;
3820 border-top: 1px solid #ccc;
3821 border-left: 1px solid #bebebe;
3821 border-left: 1px solid #bebebe;
3822 border-right: 1px solid #b1b1b1;
3822 border-right: 1px solid #b1b1b1;
3823 border-bottom: 1px solid #afafaf;
3823 border-bottom: 1px solid #afafaf;
3824 }
3824 }
3825
3825
3826 div.form div.fields div.field div.highlight,#content div.box div.form div.fields div.buttons div.highlight
3826 div.form div.fields div.field div.highlight,#content div.box div.form div.fields div.buttons div.highlight
3827 {
3827 {
3828 display: inline;
3828 display: inline;
3829 }
3829 }
3830
3830
3831 #content div.box div.form div.fields div.buttons,div.form div.fields div.buttons
3831 #content div.box div.form div.fields div.buttons,div.form div.fields div.buttons
3832 {
3832 {
3833 margin: 10px 0 0 200px;
3833 margin: 10px 0 0 200px;
3834 padding: 0;
3834 padding: 0;
3835 }
3835 }
3836
3836
3837 #content div.box-left div.form div.fields div.buttons,#content div.box-right div.form div.fields div.buttons,div.box-left div.form div.fields div.buttons,div.box-right div.form div.fields div.buttons
3837 #content div.box-left div.form div.fields div.buttons,#content div.box-right div.form div.fields div.buttons,div.box-left div.form div.fields div.buttons,div.box-right div.form div.fields div.buttons
3838 {
3838 {
3839 margin: 10px 0 0;
3839 margin: 10px 0 0;
3840 }
3840 }
3841
3841
3842 #content div.box table td.user,#content div.box table td.address {
3842 #content div.box table td.user,#content div.box table td.address {
3843 width: 10%;
3843 width: 10%;
3844 text-align: center;
3844 text-align: center;
3845 }
3845 }
3846
3846
3847 #content div.box div.action div.button,#login div.form div.fields div.field div.input div.link,#register div.form div.fields div.field div.input div.link
3847 #content div.box div.action div.button,#login div.form div.fields div.field div.input div.link,#register div.form div.fields div.field div.input div.link
3848 {
3848 {
3849 text-align: right;
3849 text-align: right;
3850 margin: 6px 0 0;
3850 margin: 6px 0 0;
3851 padding: 0;
3851 padding: 0;
3852 }
3852 }
3853
3853
3854 #content div.box div.action div.button input.ui-state-hover,#login div.form div.fields div.buttons input.ui-state-hover,#register div.form div.fields div.buttons input.ui-state-hover
3854 #content div.box div.action div.button input.ui-state-hover,#login div.form div.fields div.buttons input.ui-state-hover,#register div.form div.fields div.buttons input.ui-state-hover
3855 {
3855 {
3856 background: #b4b4b4 url("../images/button_selected.png") repeat-x;
3856 background: #b4b4b4 url("../images/button_selected.png") repeat-x;
3857 border-top: 1px solid #ccc;
3857 border-top: 1px solid #ccc;
3858 border-left: 1px solid #bebebe;
3858 border-left: 1px solid #bebebe;
3859 border-right: 1px solid #b1b1b1;
3859 border-right: 1px solid #b1b1b1;
3860 border-bottom: 1px solid #afafaf;
3860 border-bottom: 1px solid #afafaf;
3861 color: #515151;
3861 color: #515151;
3862 margin: 0;
3862 margin: 0;
3863 padding: 6px 12px;
3863 padding: 6px 12px;
3864 }
3864 }
3865
3865
3866 #content div.box div.pagination div.results,#content div.box div.pagination-wh div.results
3866 #content div.box div.pagination div.results,#content div.box div.pagination-wh div.results
3867 {
3867 {
3868 text-align: left;
3868 text-align: left;
3869 float: left;
3869 float: left;
3870 margin: 0;
3870 margin: 0;
3871 padding: 0;
3871 padding: 0;
3872 }
3872 }
3873
3873
3874 #content div.box div.pagination div.results span,#content div.box div.pagination-wh div.results span
3874 #content div.box div.pagination div.results span,#content div.box div.pagination-wh div.results span
3875 {
3875 {
3876 height: 1%;
3876 height: 1%;
3877 display: block;
3877 display: block;
3878 float: left;
3878 float: left;
3879 background: #ebebeb url("../images/pager.png") repeat-x;
3879 background: #ebebeb url("../images/pager.png") repeat-x;
3880 border-top: 1px solid #dedede;
3880 border-top: 1px solid #dedede;
3881 border-left: 1px solid #cfcfcf;
3881 border-left: 1px solid #cfcfcf;
3882 border-right: 1px solid #c4c4c4;
3882 border-right: 1px solid #c4c4c4;
3883 border-bottom: 1px solid #c4c4c4;
3883 border-bottom: 1px solid #c4c4c4;
3884 color: #4A4A4A;
3884 color: #4A4A4A;
3885 font-weight: 700;
3885 font-weight: 700;
3886 margin: 0;
3886 margin: 0;
3887 padding: 6px 8px;
3887 padding: 6px 8px;
3888 }
3888 }
3889
3889
3890 #content div.box div.pagination ul.pager li.disabled,#content div.box div.pagination-wh a.disabled
3890 #content div.box div.pagination ul.pager li.disabled,#content div.box div.pagination-wh a.disabled
3891 {
3891 {
3892 color: #B4B4B4;
3892 color: #B4B4B4;
3893 padding: 6px;
3893 padding: 6px;
3894 }
3894 }
3895
3895
3896 #login,#register {
3896 #login,#register {
3897 width: 520px;
3897 width: 520px;
3898 margin: 10% auto 0;
3898 margin: 10% auto 0;
3899 padding: 0;
3899 padding: 0;
3900 }
3900 }
3901
3901
3902 #login div.color,#register div.color {
3902 #login div.color,#register div.color {
3903 clear: both;
3903 clear: both;
3904 overflow: hidden;
3904 overflow: hidden;
3905 background: #FFF;
3905 background: #FFF;
3906 margin: 10px auto 0;
3906 margin: 10px auto 0;
3907 padding: 3px 3px 3px 0;
3907 padding: 3px 3px 3px 0;
3908 }
3908 }
3909
3909
3910 #login div.color a,#register div.color a {
3910 #login div.color a,#register div.color a {
3911 width: 20px;
3911 width: 20px;
3912 height: 20px;
3912 height: 20px;
3913 display: block;
3913 display: block;
3914 float: left;
3914 float: left;
3915 margin: 0 0 0 3px;
3915 margin: 0 0 0 3px;
3916 padding: 0;
3916 padding: 0;
3917 }
3917 }
3918
3918
3919 #login div.title h5,#register div.title h5 {
3919 #login div.title h5,#register div.title h5 {
3920 color: #fff;
3920 color: #fff;
3921 margin: 10px;
3921 margin: 10px;
3922 padding: 0;
3922 padding: 0;
3923 }
3923 }
3924
3924
3925 #login div.form div.fields div.field,#register div.form div.fields div.field
3925 #login div.form div.fields div.field,#register div.form div.fields div.field
3926 {
3926 {
3927 clear: both;
3927 clear: both;
3928 overflow: hidden;
3928 overflow: hidden;
3929 margin: 0;
3929 margin: 0;
3930 padding: 0 0 10px;
3930 padding: 0 0 10px;
3931 }
3931 }
3932
3932
3933 #login div.form div.fields div.field span.error-message,#register div.form div.fields div.field span.error-message
3933 #login div.form div.fields div.field span.error-message,#register div.form div.fields div.field span.error-message
3934 {
3934 {
3935 height: 1%;
3935 height: 1%;
3936 display: block;
3936 display: block;
3937 color: red;
3937 color: red;
3938 margin: 8px 0 0;
3938 margin: 8px 0 0;
3939 padding: 0;
3939 padding: 0;
3940 max-width: 320px;
3940 max-width: 320px;
3941 }
3941 }
3942
3942
3943 #login div.form div.fields div.field div.label label,#register div.form div.fields div.field div.label label
3943 #login div.form div.fields div.field div.label label,#register div.form div.fields div.field div.label label
3944 {
3944 {
3945 color: #000;
3945 color: #000;
3946 font-weight: 700;
3946 font-weight: 700;
3947 }
3947 }
3948
3948
3949 #login div.form div.fields div.field div.input,#register div.form div.fields div.field div.input
3949 #login div.form div.fields div.field div.input,#register div.form div.fields div.field div.input
3950 {
3950 {
3951 float: left;
3951 float: left;
3952 margin: 0;
3952 margin: 0;
3953 padding: 0;
3953 padding: 0;
3954 }
3954 }
3955
3955
3956 #login div.form div.fields div.field div.checkbox,#register div.form div.fields div.field div.checkbox
3956 #login div.form div.fields div.field div.checkbox,#register div.form div.fields div.field div.checkbox
3957 {
3957 {
3958 margin: 0 0 0 184px;
3958 margin: 0 0 0 184px;
3959 padding: 0;
3959 padding: 0;
3960 }
3960 }
3961
3961
3962 #login div.form div.fields div.field div.checkbox label,#register div.form div.fields div.field div.checkbox label
3962 #login div.form div.fields div.field div.checkbox label,#register div.form div.fields div.field div.checkbox label
3963 {
3963 {
3964 color: #565656;
3964 color: #565656;
3965 font-weight: 700;
3965 font-weight: 700;
3966 }
3966 }
3967
3967
3968 #login div.form div.fields div.buttons input,#register div.form div.fields div.buttons input
3968 #login div.form div.fields div.buttons input,#register div.form div.fields div.buttons input
3969 {
3969 {
3970 color: #000;
3970 color: #000;
3971 font-size: 1em;
3971 font-size: 1em;
3972 font-weight: 700;
3972 font-weight: 700;
3973 margin: 0;
3973 margin: 0;
3974 }
3974 }
3975
3975
3976 #changeset_content .container .wrapper,#graph_content .container .wrapper
3976 #changeset_content .container .wrapper,#graph_content .container .wrapper
3977 {
3977 {
3978 width: 600px;
3978 width: 600px;
3979 }
3979 }
3980
3980
3981 #changeset_content .container .left {
3981 #changeset_content .container .left {
3982 float: left;
3982 float: left;
3983 width: 75%;
3983 width: 75%;
3984 padding-left: 5px;
3984 padding-left: 5px;
3985 }
3985 }
3986
3986
3987 #changeset_content .container .left .date,.ac .match {
3987 #changeset_content .container .left .date,.ac .match {
3988 font-weight: 700;
3988 font-weight: 700;
3989 padding-top: 5px;
3989 padding-top: 5px;
3990 padding-bottom: 5px;
3990 padding-bottom: 5px;
3991 }
3991 }
3992
3992
3993 div#legend_container table td,div#legend_choices table td {
3993 div#legend_container table td,div#legend_choices table td {
3994 border: none !important;
3994 border: none !important;
3995 height: 20px !important;
3995 height: 20px !important;
3996 padding: 0 !important;
3996 padding: 0 !important;
3997 }
3997 }
3998
3998
3999 .q_filter_box {
3999 .q_filter_box {
4000 -webkit-box-shadow: rgba(0,0,0,0.07) 0 1px 2px inset;
4000 -webkit-box-shadow: rgba(0,0,0,0.07) 0 1px 2px inset;
4001 -webkit-border-radius: 4px;
4001 -webkit-border-radius: 4px;
4002 -moz-border-radius: 4px;
4002 -moz-border-radius: 4px;
4003 border-radius: 4px;
4003 border-radius: 4px;
4004 border: 0 none;
4004 border: 0 none;
4005 color: #AAAAAA;
4005 color: #AAAAAA;
4006 margin-bottom: -4px;
4006 margin-bottom: -4px;
4007 margin-top: -4px;
4007 margin-top: -4px;
4008 padding-left: 3px;
4008 padding-left: 3px;
4009 }
4009 }
4010
4010
4011 #node_filter {
4011 #node_filter {
4012 border: 0px solid #545454;
4012 border: 0px solid #545454;
4013 color: #AAAAAA;
4013 color: #AAAAAA;
4014 padding-left: 3px;
4014 padding-left: 3px;
4015 }
4015 }
4016
4016
4017
4017
4018 .group_members_wrap{
4018 .group_members_wrap{
4019 min-height: 85px;
4019 min-height: 85px;
4020 padding-left: 20px;
4020 padding-left: 20px;
4021 }
4021 }
4022
4022
4023 .group_members .group_member{
4023 .group_members .group_member{
4024 height: 30px;
4024 height: 30px;
4025 padding:0px 0px 0px 0px;
4025 padding:0px 0px 0px 0px;
4026 }
4026 }
4027
4027
4028 .reviewers_member{
4028 .reviewers_member{
4029 height: 15px;
4029 height: 15px;
4030 padding:0px 0px 0px 10px;
4030 padding:0px 0px 0px 10px;
4031 }
4031 }
4032
4032
4033 .emails_wrap{
4033 .emails_wrap{
4034 padding: 0px 20px;
4034 padding: 0px 20px;
4035 }
4035 }
4036
4036
4037 .emails_wrap .email_entry{
4037 .emails_wrap .email_entry{
4038 height: 30px;
4038 height: 30px;
4039 padding:0px 0px 0px 10px;
4039 padding:0px 0px 0px 10px;
4040 }
4040 }
4041 .emails_wrap .email_entry .email{
4041 .emails_wrap .email_entry .email{
4042 float: left
4042 float: left
4043 }
4043 }
4044 .emails_wrap .email_entry .email_action{
4044 .emails_wrap .email_entry .email_action{
4045 float: left
4045 float: left
4046 }
4046 }
4047
4047
4048 .ips_wrap{
4048 .ips_wrap{
4049 padding: 0px 20px;
4049 padding: 0px 20px;
4050 }
4050 }
4051
4051
4052 .ips_wrap .ip_entry{
4052 .ips_wrap .ip_entry{
4053 height: 30px;
4053 height: 30px;
4054 padding:0px 0px 0px 10px;
4054 padding:0px 0px 0px 10px;
4055 }
4055 }
4056 .ips_wrap .ip_entry .ip{
4056 .ips_wrap .ip_entry .ip{
4057 float: left
4057 float: left
4058 }
4058 }
4059 .ips_wrap .ip_entry .ip_action{
4059 .ips_wrap .ip_entry .ip_action{
4060 float: left
4060 float: left
4061 }
4061 }
4062
4062
4063
4063
4064 /*README STYLE*/
4064 /*README STYLE*/
4065
4065
4066 div.readme {
4066 div.readme {
4067 padding:0px;
4067 padding:0px;
4068 }
4068 }
4069
4069
4070 div.readme h2 {
4070 div.readme h2 {
4071 font-weight: normal;
4071 font-weight: normal;
4072 }
4072 }
4073
4073
4074 div.readme .readme_box {
4074 div.readme .readme_box {
4075 background-color: #fafafa;
4075 background-color: #fafafa;
4076 }
4076 }
4077
4077
4078 div.readme .readme_box {
4078 div.readme .readme_box {
4079 clear:both;
4079 clear:both;
4080 overflow:hidden;
4080 overflow:hidden;
4081 margin:0;
4081 margin:0;
4082 padding:0 20px 10px;
4082 padding:0 20px 10px;
4083 }
4083 }
4084
4084
4085 div.readme .readme_box h1, div.readme .readme_box h2, div.readme .readme_box h3, div.readme .readme_box h4, div.readme .readme_box h5, div.readme .readme_box h6 {
4085 div.readme .readme_box h1, div.readme .readme_box h2, div.readme .readme_box h3, div.readme .readme_box h4, div.readme .readme_box h5, div.readme .readme_box h6 {
4086 border-bottom: 0 !important;
4086 border-bottom: 0 !important;
4087 margin: 0 !important;
4087 margin: 0 !important;
4088 padding: 0 !important;
4088 padding: 0 !important;
4089 line-height: 1.5em !important;
4089 line-height: 1.5em !important;
4090 }
4090 }
4091
4091
4092
4092
4093 div.readme .readme_box h1:first-child {
4093 div.readme .readme_box h1:first-child {
4094 padding-top: .25em !important;
4094 padding-top: .25em !important;
4095 }
4095 }
4096
4096
4097 div.readme .readme_box h2, div.readme .readme_box h3 {
4097 div.readme .readme_box h2, div.readme .readme_box h3 {
4098 margin: 1em 0 !important;
4098 margin: 1em 0 !important;
4099 }
4099 }
4100
4100
4101 div.readme .readme_box h2 {
4101 div.readme .readme_box h2 {
4102 margin-top: 1.5em !important;
4102 margin-top: 1.5em !important;
4103 border-top: 4px solid #e0e0e0 !important;
4103 border-top: 4px solid #e0e0e0 !important;
4104 padding-top: .5em !important;
4104 padding-top: .5em !important;
4105 }
4105 }
4106
4106
4107 div.readme .readme_box p {
4107 div.readme .readme_box p {
4108 color: black !important;
4108 color: black !important;
4109 margin: 1em 0 !important;
4109 margin: 1em 0 !important;
4110 line-height: 1.5em !important;
4110 line-height: 1.5em !important;
4111 }
4111 }
4112
4112
4113 div.readme .readme_box ul {
4113 div.readme .readme_box ul {
4114 list-style: disc !important;
4114 list-style: disc !important;
4115 margin: 1em 0 1em 2em !important;
4115 margin: 1em 0 1em 2em !important;
4116 }
4116 }
4117
4117
4118 div.readme .readme_box ol {
4118 div.readme .readme_box ol {
4119 list-style: decimal;
4119 list-style: decimal;
4120 margin: 1em 0 1em 2em !important;
4120 margin: 1em 0 1em 2em !important;
4121 }
4121 }
4122
4122
4123 div.readme .readme_box pre, code {
4123 div.readme .readme_box pre, code {
4124 font: 12px "Bitstream Vera Sans Mono","Courier",monospace;
4124 font: 12px "Bitstream Vera Sans Mono","Courier",monospace;
4125 }
4125 }
4126
4126
4127 div.readme .readme_box code {
4127 div.readme .readme_box code {
4128 font-size: 12px !important;
4128 font-size: 12px !important;
4129 background-color: ghostWhite !important;
4129 background-color: ghostWhite !important;
4130 color: #444 !important;
4130 color: #444 !important;
4131 padding: 0 .2em !important;
4131 padding: 0 .2em !important;
4132 border: 1px solid #dedede !important;
4132 border: 1px solid #dedede !important;
4133 }
4133 }
4134
4134
4135 div.readme .readme_box pre code {
4135 div.readme .readme_box pre code {
4136 padding: 0 !important;
4136 padding: 0 !important;
4137 font-size: 12px !important;
4137 font-size: 12px !important;
4138 background-color: #eee !important;
4138 background-color: #eee !important;
4139 border: none !important;
4139 border: none !important;
4140 }
4140 }
4141
4141
4142 div.readme .readme_box pre {
4142 div.readme .readme_box pre {
4143 margin: 1em 0;
4143 margin: 1em 0;
4144 font-size: 12px;
4144 font-size: 12px;
4145 background-color: #eee;
4145 background-color: #eee;
4146 border: 1px solid #ddd;
4146 border: 1px solid #ddd;
4147 padding: 5px;
4147 padding: 5px;
4148 color: #444;
4148 color: #444;
4149 overflow: auto;
4149 overflow: auto;
4150 -webkit-box-shadow: rgba(0,0,0,0.07) 0 1px 2px inset;
4150 -webkit-box-shadow: rgba(0,0,0,0.07) 0 1px 2px inset;
4151 -webkit-border-radius: 3px;
4151 -webkit-border-radius: 3px;
4152 -moz-border-radius: 3px;
4152 -moz-border-radius: 3px;
4153 border-radius: 3px;
4153 border-radius: 3px;
4154 }
4154 }
4155
4155
4156 div.readme .readme_box table {
4156 div.readme .readme_box table {
4157 display: table;
4157 display: table;
4158 border-collapse: separate;
4158 border-collapse: separate;
4159 border-spacing: 2px;
4159 border-spacing: 2px;
4160 border-color: gray;
4160 border-color: gray;
4161 width: auto !important;
4161 width: auto !important;
4162 }
4162 }
4163
4163
4164
4164
4165 /** RST STYLE **/
4165 /** RST STYLE **/
4166
4166
4167
4167
4168 div.rst-block {
4168 div.rst-block {
4169 padding:0px;
4169 padding:0px;
4170 }
4170 }
4171
4171
4172 div.rst-block h2 {
4172 div.rst-block h2 {
4173 font-weight: normal;
4173 font-weight: normal;
4174 }
4174 }
4175
4175
4176 div.rst-block {
4176 div.rst-block {
4177 background-color: #fafafa;
4177 background-color: #fafafa;
4178 }
4178 }
4179
4179
4180 div.rst-block {
4180 div.rst-block {
4181 clear:both;
4181 clear:both;
4182 overflow:hidden;
4182 overflow:hidden;
4183 margin:0;
4183 margin:0;
4184 padding:0 20px 10px;
4184 padding:0 20px 10px;
4185 }
4185 }
4186
4186
4187 div.rst-block h1, div.rst-block h2, div.rst-block h3, div.rst-block h4, div.rst-block h5, div.rst-block h6 {
4187 div.rst-block h1, div.rst-block h2, div.rst-block h3, div.rst-block h4, div.rst-block h5, div.rst-block h6 {
4188 border-bottom: 0 !important;
4188 border-bottom: 0 !important;
4189 margin: 0 !important;
4189 margin: 0 !important;
4190 padding: 0 !important;
4190 padding: 0 !important;
4191 line-height: 1.5em !important;
4191 line-height: 1.5em !important;
4192 }
4192 }
4193
4193
4194
4194
4195 div.rst-block h1:first-child {
4195 div.rst-block h1:first-child {
4196 padding-top: .25em !important;
4196 padding-top: .25em !important;
4197 }
4197 }
4198
4198
4199 div.rst-block h2, div.rst-block h3 {
4199 div.rst-block h2, div.rst-block h3 {
4200 margin: 1em 0 !important;
4200 margin: 1em 0 !important;
4201 }
4201 }
4202
4202
4203 div.rst-block h2 {
4203 div.rst-block h2 {
4204 margin-top: 1.5em !important;
4204 margin-top: 1.5em !important;
4205 border-top: 4px solid #e0e0e0 !important;
4205 border-top: 4px solid #e0e0e0 !important;
4206 padding-top: .5em !important;
4206 padding-top: .5em !important;
4207 }
4207 }
4208
4208
4209 div.rst-block p {
4209 div.rst-block p {
4210 color: black !important;
4210 color: black !important;
4211 margin: 1em 0 !important;
4211 margin: 1em 0 !important;
4212 line-height: 1.5em !important;
4212 line-height: 1.5em !important;
4213 }
4213 }
4214
4214
4215 div.rst-block ul {
4215 div.rst-block ul {
4216 list-style: disc !important;
4216 list-style: disc !important;
4217 margin: 1em 0 1em 2em !important;
4217 margin: 1em 0 1em 2em !important;
4218 }
4218 }
4219
4219
4220 div.rst-block ol {
4220 div.rst-block ol {
4221 list-style: decimal;
4221 list-style: decimal;
4222 margin: 1em 0 1em 2em !important;
4222 margin: 1em 0 1em 2em !important;
4223 }
4223 }
4224
4224
4225 div.rst-block pre, code {
4225 div.rst-block pre, code {
4226 font: 12px "Bitstream Vera Sans Mono","Courier",monospace;
4226 font: 12px "Bitstream Vera Sans Mono","Courier",monospace;
4227 }
4227 }
4228
4228
4229 div.rst-block code {
4229 div.rst-block code {
4230 font-size: 12px !important;
4230 font-size: 12px !important;
4231 background-color: ghostWhite !important;
4231 background-color: ghostWhite !important;
4232 color: #444 !important;
4232 color: #444 !important;
4233 padding: 0 .2em !important;
4233 padding: 0 .2em !important;
4234 border: 1px solid #dedede !important;
4234 border: 1px solid #dedede !important;
4235 }
4235 }
4236
4236
4237 div.rst-block pre code {
4237 div.rst-block pre code {
4238 padding: 0 !important;
4238 padding: 0 !important;
4239 font-size: 12px !important;
4239 font-size: 12px !important;
4240 background-color: #eee !important;
4240 background-color: #eee !important;
4241 border: none !important;
4241 border: none !important;
4242 }
4242 }
4243
4243
4244 div.rst-block pre {
4244 div.rst-block pre {
4245 margin: 1em 0;
4245 margin: 1em 0;
4246 font-size: 12px;
4246 font-size: 12px;
4247 background-color: #eee;
4247 background-color: #eee;
4248 border: 1px solid #ddd;
4248 border: 1px solid #ddd;
4249 padding: 5px;
4249 padding: 5px;
4250 color: #444;
4250 color: #444;
4251 overflow: auto;
4251 overflow: auto;
4252 -webkit-box-shadow: rgba(0,0,0,0.07) 0 1px 2px inset;
4252 -webkit-box-shadow: rgba(0,0,0,0.07) 0 1px 2px inset;
4253 -webkit-border-radius: 3px;
4253 -webkit-border-radius: 3px;
4254 -moz-border-radius: 3px;
4254 -moz-border-radius: 3px;
4255 border-radius: 3px;
4255 border-radius: 3px;
4256 }
4256 }
4257
4257
4258
4258
4259 /** comment main **/
4259 /** comment main **/
4260 .comments {
4260 .comments {
4261 padding:10px 20px;
4261 padding:10px 20px;
4262 }
4262 }
4263
4263
4264 .comments .comment {
4264 .comments .comment {
4265 border: 1px solid #ddd;
4265 border: 1px solid #ddd;
4266 margin-top: 10px;
4266 margin-top: 10px;
4267 -webkit-border-radius: 4px;
4267 -webkit-border-radius: 4px;
4268 -moz-border-radius: 4px;
4268 -moz-border-radius: 4px;
4269 border-radius: 4px;
4269 border-radius: 4px;
4270 }
4270 }
4271
4271
4272 .comments .comment .meta {
4272 .comments .comment .meta {
4273 background: #f8f8f8;
4273 background: #f8f8f8;
4274 padding: 4px;
4274 padding: 4px;
4275 border-bottom: 1px solid #ddd;
4275 border-bottom: 1px solid #ddd;
4276 height: 18px;
4276 height: 18px;
4277 }
4277 }
4278
4278
4279 .comments .comment .meta img {
4279 .comments .comment .meta img {
4280 vertical-align: middle;
4280 vertical-align: middle;
4281 }
4281 }
4282
4282
4283 .comments .comment .meta .user {
4283 .comments .comment .meta .user {
4284 font-weight: bold;
4284 font-weight: bold;
4285 float: left;
4285 float: left;
4286 padding: 4px 2px 2px 2px;
4286 padding: 4px 2px 2px 2px;
4287 }
4287 }
4288
4288
4289 .comments .comment .meta .date {
4289 .comments .comment .meta .date {
4290 float: left;
4290 float: left;
4291 padding:4px 4px 0px 4px;
4291 padding:4px 4px 0px 4px;
4292 }
4292 }
4293
4293
4294 .comments .comment .text {
4294 .comments .comment .text {
4295 background-color: #FAFAFA;
4295 background-color: #FAFAFA;
4296 }
4296 }
4297 .comment .text div.rst-block p {
4297 .comment .text div.rst-block p {
4298 margin: 0.5em 0px !important;
4298 margin: 0.5em 0px !important;
4299 }
4299 }
4300
4300
4301 .comments .comments-number{
4301 .comments .comments-number{
4302 padding:0px 0px 10px 0px;
4302 padding:0px 0px 10px 0px;
4303 font-weight: bold;
4303 font-weight: bold;
4304 color: #666;
4304 color: #666;
4305 font-size: 16px;
4305 font-size: 16px;
4306 }
4306 }
4307
4307
4308 /** comment form **/
4308 /** comment form **/
4309
4309
4310 .status-block{
4310 .status-block{
4311 height:80px;
4311 height:80px;
4312 clear:both
4312 clear:both
4313 }
4313 }
4314
4314
4315 .comment-form .clearfix{
4315 .comment-form .clearfix{
4316 background: #EEE;
4316 background: #EEE;
4317 -webkit-border-radius: 4px;
4317 -webkit-border-radius: 4px;
4318 -moz-border-radius: 4px;
4318 -moz-border-radius: 4px;
4319 border-radius: 4px;
4319 border-radius: 4px;
4320 padding: 10px;
4320 padding: 10px;
4321 }
4321 }
4322
4322
4323 div.comment-form {
4323 div.comment-form {
4324 margin-top: 20px;
4324 margin-top: 20px;
4325 }
4325 }
4326
4326
4327 .comment-form strong {
4327 .comment-form strong {
4328 display: block;
4328 display: block;
4329 margin-bottom: 15px;
4329 margin-bottom: 15px;
4330 }
4330 }
4331
4331
4332 .comment-form textarea {
4332 .comment-form textarea {
4333 width: 100%;
4333 width: 100%;
4334 height: 100px;
4334 height: 100px;
4335 font-family: 'Monaco', 'Courier', 'Courier New', monospace;
4335 font-family: 'Monaco', 'Courier', 'Courier New', monospace;
4336 }
4336 }
4337
4337
4338 form.comment-form {
4338 form.comment-form {
4339 margin-top: 10px;
4339 margin-top: 10px;
4340 margin-left: 10px;
4340 margin-left: 10px;
4341 }
4341 }
4342
4342
4343 .comment-form-submit {
4343 .comment-form-submit {
4344 margin-top: 5px;
4344 margin-top: 5px;
4345 margin-left: 525px;
4345 margin-left: 525px;
4346 }
4346 }
4347
4347
4348 .file-comments {
4348 .file-comments {
4349 display: none;
4349 display: none;
4350 }
4350 }
4351
4351
4352 .comment-form .comment {
4352 .comment-form .comment {
4353 margin-left: 10px;
4353 margin-left: 10px;
4354 }
4354 }
4355
4355
4356 .comment-form .comment-help{
4356 .comment-form .comment-help{
4357 padding: 0px 0px 5px 0px;
4357 padding: 0px 0px 5px 0px;
4358 color: #666;
4358 color: #666;
4359 }
4359 }
4360
4360
4361 .comment-form .comment-button{
4361 .comment-form .comment-button{
4362 padding-top:5px;
4362 padding-top:5px;
4363 }
4363 }
4364
4364
4365 .add-another-button {
4365 .add-another-button {
4366 margin-left: 10px;
4366 margin-left: 10px;
4367 margin-top: 10px;
4367 margin-top: 10px;
4368 margin-bottom: 10px;
4368 margin-bottom: 10px;
4369 }
4369 }
4370
4370
4371 .comment .buttons {
4371 .comment .buttons {
4372 float: right;
4372 float: right;
4373 padding:2px 2px 0px 0px;
4373 padding:2px 2px 0px 0px;
4374 }
4374 }
4375
4375
4376
4376
4377 .show-inline-comments{
4377 .show-inline-comments{
4378 position: relative;
4378 position: relative;
4379 top:1px
4379 top:1px
4380 }
4380 }
4381
4381
4382 /** comment inline form **/
4382 /** comment inline form **/
4383 .comment-inline-form .overlay{
4383 .comment-inline-form .overlay{
4384 display: none;
4384 display: none;
4385 }
4385 }
4386 .comment-inline-form .overlay.submitting{
4386 .comment-inline-form .overlay.submitting{
4387 display:block;
4387 display:block;
4388 background: none repeat scroll 0 0 white;
4388 background: none repeat scroll 0 0 white;
4389 font-size: 16px;
4389 font-size: 16px;
4390 opacity: 0.5;
4390 opacity: 0.5;
4391 position: absolute;
4391 position: absolute;
4392 text-align: center;
4392 text-align: center;
4393 vertical-align: top;
4393 vertical-align: top;
4394
4394
4395 }
4395 }
4396 .comment-inline-form .overlay.submitting .overlay-text{
4396 .comment-inline-form .overlay.submitting .overlay-text{
4397 width:100%;
4397 width:100%;
4398 margin-top:5%;
4398 margin-top:5%;
4399 }
4399 }
4400
4400
4401 .comment-inline-form .clearfix{
4401 .comment-inline-form .clearfix{
4402 background: #EEE;
4402 background: #EEE;
4403 -webkit-border-radius: 4px;
4403 -webkit-border-radius: 4px;
4404 -moz-border-radius: 4px;
4404 -moz-border-radius: 4px;
4405 border-radius: 4px;
4405 border-radius: 4px;
4406 padding: 5px;
4406 padding: 5px;
4407 }
4407 }
4408
4408
4409 div.comment-inline-form {
4409 div.comment-inline-form {
4410 padding:4px 0px 6px 0px;
4410 padding:4px 0px 6px 0px;
4411 }
4411 }
4412
4412
4413
4413
4414 tr.hl-comment{
4414 tr.hl-comment{
4415 /*
4415 /*
4416 background-color: #FFFFCC !important;
4416 background-color: #FFFFCC !important;
4417 */
4417 */
4418 }
4418 }
4419
4419
4420 /*
4420 /*
4421 tr.hl-comment pre {
4421 tr.hl-comment pre {
4422 border-top: 2px solid #FFEE33;
4422 border-top: 2px solid #FFEE33;
4423 border-left: 2px solid #FFEE33;
4423 border-left: 2px solid #FFEE33;
4424 border-right: 2px solid #FFEE33;
4424 border-right: 2px solid #FFEE33;
4425 }
4425 }
4426 */
4426 */
4427
4427
4428 .comment-inline-form strong {
4428 .comment-inline-form strong {
4429 display: block;
4429 display: block;
4430 margin-bottom: 15px;
4430 margin-bottom: 15px;
4431 }
4431 }
4432
4432
4433 .comment-inline-form textarea {
4433 .comment-inline-form textarea {
4434 width: 100%;
4434 width: 100%;
4435 height: 100px;
4435 height: 100px;
4436 font-family: 'Monaco', 'Courier', 'Courier New', monospace;
4436 font-family: 'Monaco', 'Courier', 'Courier New', monospace;
4437 }
4437 }
4438
4438
4439 form.comment-inline-form {
4439 form.comment-inline-form {
4440 margin-top: 10px;
4440 margin-top: 10px;
4441 margin-left: 10px;
4441 margin-left: 10px;
4442 }
4442 }
4443
4443
4444 .comment-inline-form-submit {
4444 .comment-inline-form-submit {
4445 margin-top: 5px;
4445 margin-top: 5px;
4446 margin-left: 525px;
4446 margin-left: 525px;
4447 }
4447 }
4448
4448
4449 .file-comments {
4449 .file-comments {
4450 display: none;
4450 display: none;
4451 }
4451 }
4452
4452
4453 .comment-inline-form .comment {
4453 .comment-inline-form .comment {
4454 margin-left: 10px;
4454 margin-left: 10px;
4455 }
4455 }
4456
4456
4457 .comment-inline-form .comment-help{
4457 .comment-inline-form .comment-help{
4458 padding: 0px 0px 2px 0px;
4458 padding: 0px 0px 2px 0px;
4459 color: #666666;
4459 color: #666666;
4460 font-size: 10px;
4460 font-size: 10px;
4461 }
4461 }
4462
4462
4463 .comment-inline-form .comment-button{
4463 .comment-inline-form .comment-button{
4464 padding-top:5px;
4464 padding-top:5px;
4465 }
4465 }
4466
4466
4467 /** comment inline **/
4467 /** comment inline **/
4468 .inline-comments {
4468 .inline-comments {
4469 padding:10px 20px;
4469 padding:10px 20px;
4470 }
4470 }
4471
4471
4472 .inline-comments div.rst-block {
4472 .inline-comments div.rst-block {
4473 clear:both;
4473 clear:both;
4474 overflow:hidden;
4474 overflow:hidden;
4475 margin:0;
4475 margin:0;
4476 padding:0 20px 0px;
4476 padding:0 20px 0px;
4477 }
4477 }
4478 .inline-comments .comment {
4478 .inline-comments .comment {
4479 border: 1px solid #ddd;
4479 border: 1px solid #ddd;
4480 -webkit-border-radius: 4px;
4480 -webkit-border-radius: 4px;
4481 -moz-border-radius: 4px;
4481 -moz-border-radius: 4px;
4482 border-radius: 4px;
4482 border-radius: 4px;
4483 margin: 3px 3px 5px 5px;
4483 margin: 3px 3px 5px 5px;
4484 background-color: #FAFAFA;
4484 background-color: #FAFAFA;
4485 }
4485 }
4486 .inline-comments .add-comment {
4486 .inline-comments .add-comment {
4487 padding: 2px 4px 8px 5px;
4487 padding: 2px 4px 8px 5px;
4488 }
4488 }
4489
4489
4490 .inline-comments .comment-wrapp{
4490 .inline-comments .comment-wrapp{
4491 padding:1px;
4491 padding:1px;
4492 }
4492 }
4493 .inline-comments .comment .meta {
4493 .inline-comments .comment .meta {
4494 background: #f8f8f8;
4494 background: #f8f8f8;
4495 padding: 4px;
4495 padding: 4px;
4496 border-bottom: 1px solid #ddd;
4496 border-bottom: 1px solid #ddd;
4497 height: 20px;
4497 height: 20px;
4498 }
4498 }
4499
4499
4500 .inline-comments .comment .meta img {
4500 .inline-comments .comment .meta img {
4501 vertical-align: middle;
4501 vertical-align: middle;
4502 }
4502 }
4503
4503
4504 .inline-comments .comment .meta .user {
4504 .inline-comments .comment .meta .user {
4505 font-weight: bold;
4505 font-weight: bold;
4506 float:left;
4506 float:left;
4507 padding: 3px;
4507 padding: 3px;
4508 }
4508 }
4509
4509
4510 .inline-comments .comment .meta .date {
4510 .inline-comments .comment .meta .date {
4511 float:left;
4511 float:left;
4512 padding: 3px;
4512 padding: 3px;
4513 }
4513 }
4514
4514
4515 .inline-comments .comment .text {
4515 .inline-comments .comment .text {
4516 background-color: #FAFAFA;
4516 background-color: #FAFAFA;
4517 }
4517 }
4518
4518
4519 .inline-comments .comments-number{
4519 .inline-comments .comments-number{
4520 padding:0px 0px 10px 0px;
4520 padding:0px 0px 10px 0px;
4521 font-weight: bold;
4521 font-weight: bold;
4522 color: #666;
4522 color: #666;
4523 font-size: 16px;
4523 font-size: 16px;
4524 }
4524 }
4525 .inline-comments-button .add-comment{
4525 .inline-comments-button .add-comment{
4526 margin:2px 0px 8px 5px !important
4526 margin:2px 0px 8px 5px !important
4527 }
4527 }
4528
4528
4529
4529
4530 .notification-paginator{
4530 .notification-paginator{
4531 padding: 0px 0px 4px 16px;
4531 padding: 0px 0px 4px 16px;
4532 float: left;
4532 float: left;
4533 }
4533 }
4534
4534
4535 .menu_link_user{
4535 .menu_link_user{
4536 padding: 10px 8px 8px 8px !important;
4536 padding: 10px 8px 8px 8px !important;
4537 }
4537 }
4538
4538
4539 .menu_link_notifications {
4539 .menu_link_notifications {
4540 padding: 4px 4px !important;
4540 padding: 4px 4px !important;
4541 margin: 7px 4px 0px 0px !important;
4541 margin: 7px 4px 0px 0px !important;
4542 text-align: center;
4542 text-align: center;
4543 color:#888 !important;
4543 color:#888 !important;
4544 font-size: 10px;
4544 font-size: 10px;
4545 background-color: #DEDEDE !important;
4545 background-color: #DEDEDE !important;
4546 border-radius: 4px !important;
4546 border-radius: 4px !important;
4547 -webkit-border-radius: 4px !important;
4547 -webkit-border-radius: 4px !important;
4548 -moz-border-radius: 4px !important;
4548 -moz-border-radius: 4px !important;
4549 }
4549 }
4550
4550
4551 .notification-header{
4551 .notification-header{
4552 padding-top:6px;
4552 padding-top:6px;
4553 }
4553 }
4554 .notification-header .desc{
4554 .notification-header .desc{
4555 font-size: 16px;
4555 font-size: 16px;
4556 height: 24px;
4556 height: 24px;
4557 float: left
4557 float: left
4558 }
4558 }
4559 .notification-list .container.unread{
4559 .notification-list .container.unread{
4560 background: none repeat scroll 0 0 rgba(255, 255, 180, 0.6);
4560 background: none repeat scroll 0 0 rgba(255, 255, 180, 0.6);
4561 }
4561 }
4562 .notification-header .gravatar{
4562 .notification-header .gravatar{
4563 background: none repeat scroll 0 0 transparent;
4563 background: none repeat scroll 0 0 transparent;
4564 padding: 0px 0px 0px 8px;
4564 padding: 0px 0px 0px 8px;
4565 }
4565 }
4566 .notification-list .container .notification-header .desc{
4566 .notification-list .container .notification-header .desc{
4567 font-weight: bold;
4567 font-weight: bold;
4568 font-size: 17px;
4568 font-size: 17px;
4569 }
4569 }
4570 .notification-table{
4570 .notification-table{
4571 border: 1px solid #ccc;
4571 border: 1px solid #ccc;
4572 -webkit-border-radius: 6px 6px 6px 6px;
4572 -webkit-border-radius: 6px 6px 6px 6px;
4573 -moz-border-radius: 6px 6px 6px 6px;
4573 -moz-border-radius: 6px 6px 6px 6px;
4574 border-radius: 6px 6px 6px 6px;
4574 border-radius: 6px 6px 6px 6px;
4575 clear: both;
4575 clear: both;
4576 margin: 0px 20px 0px 20px;
4576 margin: 0px 20px 0px 20px;
4577 }
4577 }
4578 .notification-header .delete-notifications{
4578 .notification-header .delete-notifications{
4579 float: right;
4579 float: right;
4580 padding-top: 8px;
4580 padding-top: 8px;
4581 cursor: pointer;
4581 cursor: pointer;
4582 }
4582 }
4583 .notification-header .read-notifications{
4583 .notification-header .read-notifications{
4584 float: right;
4584 float: right;
4585 padding-top: 8px;
4585 padding-top: 8px;
4586 cursor: pointer;
4586 cursor: pointer;
4587 }
4587 }
4588 .notification-subject{
4588 .notification-subject{
4589 clear:both;
4589 clear:both;
4590 border-bottom: 1px solid #eee;
4590 border-bottom: 1px solid #eee;
4591 padding:5px 0px 5px 38px;
4591 padding:5px 0px 5px 38px;
4592 }
4592 }
4593
4593
4594 .notification-body{
4594 .notification-body{
4595 clear:both;
4595 clear:both;
4596 margin: 34px 2px 2px 8px
4596 margin: 34px 2px 2px 8px
4597 }
4597 }
4598
4598
4599 /****
4599 /****
4600 PULL REQUESTS
4600 PULL REQUESTS
4601 *****/
4601 *****/
4602 .pullrequests_section_head {
4602 .pullrequests_section_head {
4603 padding:10px 10px 10px 0px;
4603 padding:10px 10px 10px 0px;
4604 font-size:16px;
4604 font-size:16px;
4605 font-weight: bold;
4605 font-weight: bold;
4606 }
4606 }
4607
4607
4608 /****
4608 /****
4609 PERMS
4609 PERMS
4610 *****/
4610 *****/
4611 #perms .perms_section_head {
4611 #perms .perms_section_head {
4612 padding:10px 10px 10px 0px;
4612 padding:10px 10px 10px 0px;
4613 font-size:16px;
4613 font-size:16px;
4614 font-weight: bold;
4614 font-weight: bold;
4615 }
4615 }
4616
4616
4617 #perms .perm_tag{
4617 #perms .perm_tag{
4618 padding: 1px 3px 1px 3px;
4618 padding: 1px 3px 1px 3px;
4619 font-size: 10px;
4619 font-size: 10px;
4620 font-weight: bold;
4620 font-weight: bold;
4621 text-transform: uppercase;
4621 text-transform: uppercase;
4622 white-space: nowrap;
4622 white-space: nowrap;
4623 -webkit-border-radius: 3px;
4623 -webkit-border-radius: 3px;
4624 -moz-border-radius: 3px;
4624 -moz-border-radius: 3px;
4625 border-radius: 3px;
4625 border-radius: 3px;
4626 }
4626 }
4627
4627
4628 #perms .perm_tag.admin{
4628 #perms .perm_tag.admin{
4629 background-color: #B94A48;
4629 background-color: #B94A48;
4630 color: #ffffff;
4630 color: #ffffff;
4631 }
4631 }
4632
4632
4633 #perms .perm_tag.write{
4633 #perms .perm_tag.write{
4634 background-color: #B94A48;
4634 background-color: #DB7525;
4635 color: #ffffff;
4635 color: #ffffff;
4636 }
4636 }
4637
4637
4638 #perms .perm_tag.read{
4638 #perms .perm_tag.read{
4639 background-color: #468847;
4639 background-color: #468847;
4640 color: #ffffff;
4640 color: #ffffff;
4641 }
4641 }
4642
4642
4643 #perms .perm_tag.none{
4643 #perms .perm_tag.none{
4644 background-color: #bfbfbf;
4644 background-color: #bfbfbf;
4645 color: #ffffff;
4645 color: #ffffff;
4646 }
4646 }
4647
4647
4648 .perm-gravatar{
4648 .perm-gravatar{
4649 vertical-align:middle;
4649 vertical-align:middle;
4650 padding:2px;
4650 padding:2px;
4651 }
4651 }
4652 .perm-gravatar-ac{
4652 .perm-gravatar-ac{
4653 vertical-align:middle;
4653 vertical-align:middle;
4654 padding:2px;
4654 padding:2px;
4655 width: 14px;
4655 width: 14px;
4656 height: 14px;
4656 height: 14px;
4657 }
4657 }
4658
4658
4659 /*****************************************************************************
4659 /*****************************************************************************
4660 DIFFS CSS
4660 DIFFS CSS
4661 ******************************************************************************/
4661 ******************************************************************************/
4662
4662
4663 div.diffblock {
4663 div.diffblock {
4664 overflow: auto;
4664 overflow: auto;
4665 padding: 0px;
4665 padding: 0px;
4666 border: 1px solid #ccc;
4666 border: 1px solid #ccc;
4667 background: #f8f8f8;
4667 background: #f8f8f8;
4668 font-size: 100%;
4668 font-size: 100%;
4669 line-height: 100%;
4669 line-height: 100%;
4670 /* new */
4670 /* new */
4671 line-height: 125%;
4671 line-height: 125%;
4672 -webkit-border-radius: 6px 6px 0px 0px;
4672 -webkit-border-radius: 6px 6px 0px 0px;
4673 -moz-border-radius: 6px 6px 0px 0px;
4673 -moz-border-radius: 6px 6px 0px 0px;
4674 border-radius: 6px 6px 0px 0px;
4674 border-radius: 6px 6px 0px 0px;
4675 }
4675 }
4676 div.diffblock.margined{
4676 div.diffblock.margined{
4677 margin: 0px 20px 0px 20px;
4677 margin: 0px 20px 0px 20px;
4678 }
4678 }
4679 div.diffblock .code-header{
4679 div.diffblock .code-header{
4680 border-bottom: 1px solid #CCCCCC;
4680 border-bottom: 1px solid #CCCCCC;
4681 background: #EEEEEE;
4681 background: #EEEEEE;
4682 padding:10px 0 10px 0;
4682 padding:10px 0 10px 0;
4683 height: 14px;
4683 height: 14px;
4684 }
4684 }
4685
4685
4686 div.diffblock .code-header.banner{
4686 div.diffblock .code-header.banner{
4687 border-bottom: 1px solid #CCCCCC;
4687 border-bottom: 1px solid #CCCCCC;
4688 background: #EEEEEE;
4688 background: #EEEEEE;
4689 height: 14px;
4689 height: 14px;
4690 margin: 0px 95px 0px 95px;
4690 margin: 0px 95px 0px 95px;
4691 padding: 3px 3px 11px 3px;
4691 padding: 3px 3px 11px 3px;
4692 }
4692 }
4693
4693
4694 div.diffblock .code-header.cv{
4694 div.diffblock .code-header.cv{
4695 height: 34px;
4695 height: 34px;
4696 }
4696 }
4697 div.diffblock .code-header-title{
4697 div.diffblock .code-header-title{
4698 padding: 0px 0px 10px 5px !important;
4698 padding: 0px 0px 10px 5px !important;
4699 margin: 0 !important;
4699 margin: 0 !important;
4700 }
4700 }
4701 div.diffblock .code-header .hash{
4701 div.diffblock .code-header .hash{
4702 float: left;
4702 float: left;
4703 padding: 2px 0 0 2px;
4703 padding: 2px 0 0 2px;
4704 }
4704 }
4705 div.diffblock .code-header .date{
4705 div.diffblock .code-header .date{
4706 float:left;
4706 float:left;
4707 text-transform: uppercase;
4707 text-transform: uppercase;
4708 padding: 2px 0px 0px 2px;
4708 padding: 2px 0px 0px 2px;
4709 }
4709 }
4710 div.diffblock .code-header div{
4710 div.diffblock .code-header div{
4711 margin-left:4px;
4711 margin-left:4px;
4712 font-weight: bold;
4712 font-weight: bold;
4713 font-size: 14px;
4713 font-size: 14px;
4714 }
4714 }
4715
4715
4716 div.diffblock .parents {
4716 div.diffblock .parents {
4717 float: left;
4717 float: left;
4718 height: 26px;
4718 height: 26px;
4719 width:100px;
4719 width:100px;
4720 font-size: 10px;
4720 font-size: 10px;
4721 font-weight: 400;
4721 font-weight: 400;
4722 vertical-align: middle;
4722 vertical-align: middle;
4723 padding: 0px 2px 2px 2px;
4723 padding: 0px 2px 2px 2px;
4724 background-color:#eeeeee;
4724 background-color:#eeeeee;
4725 border-bottom: 1px solid #CCCCCC;
4725 border-bottom: 1px solid #CCCCCC;
4726 }
4726 }
4727
4727
4728 div.diffblock .children {
4728 div.diffblock .children {
4729 float: right;
4729 float: right;
4730 height: 26px;
4730 height: 26px;
4731 width:100px;
4731 width:100px;
4732 font-size: 10px;
4732 font-size: 10px;
4733 font-weight: 400;
4733 font-weight: 400;
4734 vertical-align: middle;
4734 vertical-align: middle;
4735 text-align: right;
4735 text-align: right;
4736 padding: 0px 2px 2px 2px;
4736 padding: 0px 2px 2px 2px;
4737 background-color:#eeeeee;
4737 background-color:#eeeeee;
4738 border-bottom: 1px solid #CCCCCC;
4738 border-bottom: 1px solid #CCCCCC;
4739 }
4739 }
4740
4740
4741 div.diffblock .code-body{
4741 div.diffblock .code-body{
4742 background: #FFFFFF;
4742 background: #FFFFFF;
4743 }
4743 }
4744 div.diffblock pre.raw{
4744 div.diffblock pre.raw{
4745 background: #FFFFFF;
4745 background: #FFFFFF;
4746 color:#000000;
4746 color:#000000;
4747 }
4747 }
4748 table.code-difftable{
4748 table.code-difftable{
4749 border-collapse: collapse;
4749 border-collapse: collapse;
4750 width: 99%;
4750 width: 99%;
4751 }
4751 }
4752 table.code-difftable td {
4752 table.code-difftable td {
4753 padding: 0 !important;
4753 padding: 0 !important;
4754 background: none !important;
4754 background: none !important;
4755 border:0 !important;
4755 border:0 !important;
4756 vertical-align: none !important;
4756 vertical-align: none !important;
4757 }
4757 }
4758 table.code-difftable .context{
4758 table.code-difftable .context{
4759 background:none repeat scroll 0 0 #DDE7EF;
4759 background:none repeat scroll 0 0 #DDE7EF;
4760 }
4760 }
4761 table.code-difftable .add{
4761 table.code-difftable .add{
4762 background:none repeat scroll 0 0 #DDFFDD;
4762 background:none repeat scroll 0 0 #DDFFDD;
4763 }
4763 }
4764 table.code-difftable .add ins{
4764 table.code-difftable .add ins{
4765 background:none repeat scroll 0 0 #AAFFAA;
4765 background:none repeat scroll 0 0 #AAFFAA;
4766 text-decoration:none;
4766 text-decoration:none;
4767 }
4767 }
4768 table.code-difftable .del{
4768 table.code-difftable .del{
4769 background:none repeat scroll 0 0 #FFDDDD;
4769 background:none repeat scroll 0 0 #FFDDDD;
4770 }
4770 }
4771 table.code-difftable .del del{
4771 table.code-difftable .del del{
4772 background:none repeat scroll 0 0 #FFAAAA;
4772 background:none repeat scroll 0 0 #FFAAAA;
4773 text-decoration:none;
4773 text-decoration:none;
4774 }
4774 }
4775
4775
4776 /** LINE NUMBERS **/
4776 /** LINE NUMBERS **/
4777 table.code-difftable .lineno{
4777 table.code-difftable .lineno{
4778
4778
4779 padding-left:2px;
4779 padding-left:2px;
4780 padding-right:2px;
4780 padding-right:2px;
4781 text-align:right;
4781 text-align:right;
4782 width:32px;
4782 width:32px;
4783 -moz-user-select:none;
4783 -moz-user-select:none;
4784 -webkit-user-select: none;
4784 -webkit-user-select: none;
4785 border-right: 1px solid #CCC !important;
4785 border-right: 1px solid #CCC !important;
4786 border-left: 0px solid #CCC !important;
4786 border-left: 0px solid #CCC !important;
4787 border-top: 0px solid #CCC !important;
4787 border-top: 0px solid #CCC !important;
4788 border-bottom: none !important;
4788 border-bottom: none !important;
4789 vertical-align: middle !important;
4789 vertical-align: middle !important;
4790
4790
4791 }
4791 }
4792 table.code-difftable .lineno.new {
4792 table.code-difftable .lineno.new {
4793 }
4793 }
4794 table.code-difftable .lineno.old {
4794 table.code-difftable .lineno.old {
4795 }
4795 }
4796 table.code-difftable .lineno a{
4796 table.code-difftable .lineno a{
4797 color:#747474 !important;
4797 color:#747474 !important;
4798 font:11px "Bitstream Vera Sans Mono",Monaco,"Courier New",Courier,monospace !important;
4798 font:11px "Bitstream Vera Sans Mono",Monaco,"Courier New",Courier,monospace !important;
4799 letter-spacing:-1px;
4799 letter-spacing:-1px;
4800 text-align:right;
4800 text-align:right;
4801 padding-right: 2px;
4801 padding-right: 2px;
4802 cursor: pointer;
4802 cursor: pointer;
4803 display: block;
4803 display: block;
4804 width: 32px;
4804 width: 32px;
4805 }
4805 }
4806
4806
4807 table.code-difftable .lineno-inline{
4807 table.code-difftable .lineno-inline{
4808 background:none repeat scroll 0 0 #FFF !important;
4808 background:none repeat scroll 0 0 #FFF !important;
4809 padding-left:2px;
4809 padding-left:2px;
4810 padding-right:2px;
4810 padding-right:2px;
4811 text-align:right;
4811 text-align:right;
4812 width:30px;
4812 width:30px;
4813 -moz-user-select:none;
4813 -moz-user-select:none;
4814 -webkit-user-select: none;
4814 -webkit-user-select: none;
4815 }
4815 }
4816
4816
4817 /** CODE **/
4817 /** CODE **/
4818 table.code-difftable .code {
4818 table.code-difftable .code {
4819 display: block;
4819 display: block;
4820 width: 100%;
4820 width: 100%;
4821 }
4821 }
4822 table.code-difftable .code td{
4822 table.code-difftable .code td{
4823 margin:0;
4823 margin:0;
4824 padding:0;
4824 padding:0;
4825 }
4825 }
4826 table.code-difftable .code pre{
4826 table.code-difftable .code pre{
4827 margin:0;
4827 margin:0;
4828 padding:0;
4828 padding:0;
4829 height: 17px;
4829 height: 17px;
4830 line-height: 17px;
4830 line-height: 17px;
4831 }
4831 }
4832
4832
4833
4833
4834 .diffblock.margined.comm .line .code:hover{
4834 .diffblock.margined.comm .line .code:hover{
4835 background-color:#FFFFCC !important;
4835 background-color:#FFFFCC !important;
4836 cursor: pointer !important;
4836 cursor: pointer !important;
4837 background-image:url("../images/icons/comment_add.png") !important;
4837 background-image:url("../images/icons/comment_add.png") !important;
4838 background-repeat:no-repeat !important;
4838 background-repeat:no-repeat !important;
4839 background-position: right !important;
4839 background-position: right !important;
4840 background-position: 0% 50% !important;
4840 background-position: 0% 50% !important;
4841 }
4841 }
4842 .diffblock.margined.comm .line .code.no-comment:hover{
4842 .diffblock.margined.comm .line .code.no-comment:hover{
4843 background-image: none !important;
4843 background-image: none !important;
4844 cursor: auto !important;
4844 cursor: auto !important;
4845 background-color: inherit !important;
4845 background-color: inherit !important;
4846
4846
4847 }
4847 }
@@ -1,215 +1,215 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 ${_('Permissions administration')} - ${c.rhodecode_name}
5 ${_('Permissions 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 ${_('Permissions')}
11 ${_('Permissions')}
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 box-left">
19 <div class="box box-left">
20 <!-- box / title -->
20 <!-- box / title -->
21 <div class="title">
21 <div class="title">
22 ${self.breadcrumbs()}
22 ${self.breadcrumbs()}
23 </div>
23 </div>
24 <h3>${_('Default permissions')}</h3>
24 <h3>${_('Default permissions')}</h3>
25 ${h.form(url('permission', id='default'),method='put')}
25 ${h.form(url('permission', id='default'),method='put')}
26 <div class="form">
26 <div class="form">
27 <!-- fields -->
27 <!-- fields -->
28 <div class="fields">
28 <div class="fields">
29 <div class="field">
29 <div class="field">
30 <div class="label label-checkbox">
30 <div class="label label-checkbox">
31 <label for="anonymous">${_('Anonymous access')}:</label>
31 <label for="anonymous">${_('Anonymous access')}:</label>
32 </div>
32 </div>
33 <div class="checkboxes">
33 <div class="checkboxes">
34 <div class="checkbox">
34 <div class="checkbox">
35 ${h.checkbox('anonymous',True)}
35 ${h.checkbox('anonymous',True)}
36 </div>
36 </div>
37 </div>
37 </div>
38 </div>
38 </div>
39 <div class="field">
39 <div class="field">
40 <div class="label">
40 <div class="label">
41 <label for="default_repo_perm">${_('Repository')}:</label>
41 <label for="default_repo_perm">${_('Repository')}:</label>
42 </div>
42 </div>
43 <div class="select">
43 <div class="select">
44 ${h.select('default_repo_perm','',c.repo_perms_choices)}
44 ${h.select('default_repo_perm','',c.repo_perms_choices)}
45
45
46 ${h.checkbox('overwrite_default_repo','true')}
46 ${h.checkbox('overwrite_default_repo','true')}
47 <label for="overwrite_default_repo">
47 <label for="overwrite_default_repo">
48 <span class="tooltip"
48 <span class="tooltip"
49 title="${h.tooltip(_('All default permissions on each repository will be reset to choosen permission, note that all custom default permission on repositories will be lost'))}">
49 title="${h.tooltip(_('All default permissions on each repository will be reset to choosen permission, note that all custom default permission on repositories will be lost'))}">
50 ${_('overwrite existing settings')}</span> </label>
50 ${_('overwrite existing settings')}</span> </label>
51 </div>
51 </div>
52 </div>
52 </div>
53 <div class="field">
53 <div class="field">
54 <div class="label">
54 <div class="label">
55 <label for="default_group_perm">${_('Repository group')}:</label>
55 <label for="default_group_perm">${_('Repository group')}:</label>
56 </div>
56 </div>
57 <div class="select">
57 <div class="select">
58 ${h.select('default_group_perm','',c.group_perms_choices)}
58 ${h.select('default_group_perm','',c.group_perms_choices)}
59 ${h.checkbox('overwrite_default_group','true')}
59 ${h.checkbox('overwrite_default_group','true')}
60 <label for="overwrite_default_group">
60 <label for="overwrite_default_group">
61 <span class="tooltip"
61 <span class="tooltip"
62 title="${h.tooltip(_('All default permissions on each repository group will be reset to choosen permission, note that all custom default permission on repositories group will be lost'))}">
62 title="${h.tooltip(_('All default permissions on each repository group will be reset to choosen permission, note that all custom default permission on repositories group will be lost'))}">
63 ${_('overwrite existing settings')}</span> </label>
63 ${_('overwrite existing settings')}</span> </label>
64
64
65 </div>
65 </div>
66 </div>
66 </div>
67 <div class="field">
67 <div class="field">
68 <div class="label">
68 <div class="label">
69 <label for="default_register">${_('Registration')}:</label>
69 <label for="default_register">${_('Registration')}:</label>
70 </div>
70 </div>
71 <div class="select">
71 <div class="select">
72 ${h.select('default_register','',c.register_choices)}
72 ${h.select('default_register','',c.register_choices)}
73 </div>
73 </div>
74 </div>
74 </div>
75 <div class="field">
75 <div class="field">
76 <div class="label">
76 <div class="label">
77 <label for="default_create">${_('Repository creation')}:</label>
77 <label for="default_create">${_('Repository creation')}:</label>
78 </div>
78 </div>
79 <div class="select">
79 <div class="select">
80 ${h.select('default_create','',c.create_choices)}
80 ${h.select('default_create','',c.create_choices)}
81 </div>
81 </div>
82 </div>
82 </div>
83 <div class="field">
83 <div class="field">
84 <div class="label">
84 <div class="label">
85 <label for="default_fork">${_('Repository forking')}:</label>
85 <label for="default_fork">${_('Repository forking')}:</label>
86 </div>
86 </div>
87 <div class="select">
87 <div class="select">
88 ${h.select('default_fork','',c.fork_choices)}
88 ${h.select('default_fork','',c.fork_choices)}
89 </div>
89 </div>
90 </div>
90 </div>
91 <div class="buttons">
91 <div class="buttons">
92 ${h.submit('save',_('Save'),class_="ui-btn large")}
92 ${h.submit('save',_('Save'),class_="ui-btn large")}
93 ${h.reset('reset',_('Reset'),class_="ui-btn large")}
93 ${h.reset('reset',_('Reset'),class_="ui-btn large")}
94 </div>
94 </div>
95 </div>
95 </div>
96 </div>
96 </div>
97 ${h.end_form()}
97 ${h.end_form()}
98 </div>
98 </div>
99
99
100 <div style="min-height:780px" class="box box-right">
100 <div style="min-height:780px" class="box box-right">
101 <!-- box / title -->
101 <!-- box / title -->
102 <div class="title">
102 <div class="title">
103 <h5>${_('Default User Permissions')}</h5>
103 <h5>${_('Default User Permissions')}</h5>
104 </div>
104 </div>
105
105
106 ## permissions overview
106 ## permissions overview
107 <div id="perms" class="table">
107 <div id="perms" class="table">
108 %for section in sorted(c.perm_user.permissions.keys()):
108 %for section in sorted(c.perm_user.permissions.keys()):
109 <div class="perms_section_head">${section.replace("_"," ").capitalize()}</div>
109 <div class="perms_section_head">${section.replace("_"," ").capitalize()}</div>
110 %if not c.perm_user.permissions[section]:
110 %if not c.perm_user.permissions[section]:
111 <span class="empty_data">${_('Nothing here yet')}</span>
111 <span class="empty_data">${_('Nothing here yet')}</span>
112 %else:
112 %else:
113 <div id='tbl_list_wrap_${section}' class="yui-skin-sam">
113 <div id='tbl_list_wrap_${section}' class="yui-skin-sam">
114 <table id="tbl_list_${section}">
114 <table id="tbl_list_${section}">
115 <thead>
115 <thead>
116 <tr>
116 <tr>
117 <th class="left">${_('Name')}</th>
117 <th class="left">${_('Name')}</th>
118 <th class="left">${_('Permission')}</th>
118 <th class="left">${_('Permission')}</th>
119 <th class="left">${_('Edit Permission')}</th>
119 <th class="left">${_('Edit Permission')}</th>
120 </thead>
120 </thead>
121 <tbody>
121 <tbody>
122 %for k in c.perm_user.permissions[section]:
122 %for k in c.perm_user.permissions[section]:
123 <%
123 <%
124 if section != 'global':
124 if section != 'global':
125 section_perm = c.perm_user.permissions[section].get(k)
125 section_perm = c.perm_user.permissions[section].get(k)
126 _perm = section_perm.split('.')[-1]
126 _perm = section_perm.split('.')[-1]
127 else:
127 else:
128 _perm = section_perm = None
128 _perm = section_perm = None
129 %>
129 %>
130 <tr>
130 <tr>
131 <td>
131 <td>
132 %if section == 'repositories':
132 %if section == 'repositories':
133 <a href="${h.url('summary_home',repo_name=k)}">${k}</a>
133 <a href="${h.url('summary_home',repo_name=k)}">${k}</a>
134 %elif section == 'repositories_groups':
134 %elif section == 'repositories_groups':
135 <a href="${h.url('repos_group_home',group_name=k)}">${k}</a>
135 <a href="${h.url('repos_group_home',group_name=k)}">${k}</a>
136 %else:
136 %else:
137 ${h.get_permission_name(k)}
137 ${h.get_permission_name(k)}
138 %endif
138 %endif
139 </td>
139 </td>
140 <td>
140 <td>
141 %if section == 'global':
141 %if section == 'global':
142 ${h.bool2icon(k.split('.')[-1] != 'none')}
142 ${h.bool2icon(k.split('.')[-1] != 'none')}
143 %else:
143 %else:
144 <span class="perm_tag ${_perm}">${section_perm}</span>
144 <span class="perm_tag ${_perm}">${section_perm}</span>
145 %endif
145 %endif
146 </td>
146 </td>
147 <td>
147 <td>
148 %if section == 'repositories':
148 %if section == 'repositories':
149 <a href="${h.url('edit_repo',repo_name=k,anchor='permissions_manage')}">${_('edit')}</a>
149 <a href="${h.url('edit_repo',repo_name=k,anchor='permissions_manage')}">${_('edit')}</a>
150 %elif section == 'repositories_groups':
150 %elif section == 'repositories_groups':
151 <a href="${h.url('edit_repos_group',id=k,anchor='permissions_manage')}">${_('edit')}</a>
151 <a href="${h.url('edit_repos_group',group_name=k,anchor='permissions_manage')}">${_('edit')}</a>
152 %else:
152 %else:
153 --
153 --
154 %endif
154 %endif
155 </td>
155 </td>
156 </tr>
156 </tr>
157 %endfor
157 %endfor
158 </tbody>
158 </tbody>
159 </table>
159 </table>
160 </div>
160 </div>
161 %endif
161 %endif
162 %endfor
162 %endfor
163 </div>
163 </div>
164 </div>
164 </div>
165 <div class="box box-left" style="clear:left">
165 <div class="box box-left" style="clear:left">
166 <!-- box / title -->
166 <!-- box / title -->
167 <div class="title">
167 <div class="title">
168 <h5>${_('Allowed IP addresses')}</h5>
168 <h5>${_('Allowed IP addresses')}</h5>
169 </div>
169 </div>
170
170
171 <div class="ips_wrap">
171 <div class="ips_wrap">
172 <table class="noborder">
172 <table class="noborder">
173 %if c.user_ip_map:
173 %if c.user_ip_map:
174 %for ip in c.user_ip_map:
174 %for ip in c.user_ip_map:
175 <tr>
175 <tr>
176 <td><div class="ip">${ip.ip_addr}</div></td>
176 <td><div class="ip">${ip.ip_addr}</div></td>
177 <td><div class="ip">${h.ip_range(ip.ip_addr)}</div></td>
177 <td><div class="ip">${h.ip_range(ip.ip_addr)}</div></td>
178 <td>
178 <td>
179 ${h.form(url('user_ips_delete', id=c.user.user_id),method='delete')}
179 ${h.form(url('user_ips_delete', id=c.user.user_id),method='delete')}
180 ${h.hidden('del_ip',ip.ip_id)}
180 ${h.hidden('del_ip',ip.ip_id)}
181 ${h.hidden('default_user', 'True')}
181 ${h.hidden('default_user', 'True')}
182 ${h.submit('remove_',_('delete'),id="remove_ip_%s" % ip.ip_id,
182 ${h.submit('remove_',_('delete'),id="remove_ip_%s" % ip.ip_id,
183 class_="delete_icon action_button", onclick="return confirm('"+_('Confirm to delete this ip: %s') % ip.ip_addr+"');")}
183 class_="delete_icon action_button", onclick="return confirm('"+_('Confirm to delete this ip: %s') % ip.ip_addr+"');")}
184 ${h.end_form()}
184 ${h.end_form()}
185 </td>
185 </td>
186 </tr>
186 </tr>
187 %endfor
187 %endfor
188 %else:
188 %else:
189 <tr><td><div class="ip">${_('All IP addresses are allowed')}</div></td></tr>
189 <tr><td><div class="ip">${_('All IP addresses are allowed')}</div></td></tr>
190 %endif
190 %endif
191 </table>
191 </table>
192 </div>
192 </div>
193
193
194 ${h.form(url('user_ips', id=c.user.user_id),method='put')}
194 ${h.form(url('user_ips', id=c.user.user_id),method='put')}
195 <div class="form">
195 <div class="form">
196 <!-- fields -->
196 <!-- fields -->
197 <div class="fields">
197 <div class="fields">
198 <div class="field">
198 <div class="field">
199 <div class="label">
199 <div class="label">
200 <label for="new_ip">${_('New ip address')}:</label>
200 <label for="new_ip">${_('New ip address')}:</label>
201 </div>
201 </div>
202 <div class="input">
202 <div class="input">
203 ${h.hidden('default_user', 'True')}
203 ${h.hidden('default_user', 'True')}
204 ${h.text('new_ip', class_='medium')}
204 ${h.text('new_ip', class_='medium')}
205 </div>
205 </div>
206 </div>
206 </div>
207 <div class="buttons">
207 <div class="buttons">
208 ${h.submit('save',_('Add'),class_="ui-btn large")}
208 ${h.submit('save',_('Add'),class_="ui-btn large")}
209 ${h.reset('reset',_('Reset'),class_="ui-btn large")}
209 ${h.reset('reset',_('Reset'),class_="ui-btn large")}
210 </div>
210 </div>
211 </div>
211 </div>
212 </div>
212 </div>
213 ${h.end_form()}
213 ${h.end_form()}
214 </div>
214 </div>
215 </%def>
215 </%def>
@@ -1,86 +1,86 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 ${_('Edit repos group')} ${c.repos_group.name} - ${c.rhodecode_name}
5 ${_('Edit repos group')} ${c.repos_group.name} - ${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(_('Repos groups'),h.url('repos_groups'))}
10 ${h.link_to(_('Repos groups'),h.url('repos_groups'))}
11 &raquo;
11 &raquo;
12 ${_('edit repos group')} "${c.repos_group.name}"
12 ${_('edit repos group')} "${c.repos_group.name}"
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 <ul class="links">
24 <ul class="links">
25 <li>
25 <li>
26 <span>${h.link_to(_(u'Add new child group'),h.url('new_repos_group', parent_group=c.repos_group.group_id))}</span>
26 <span>${h.link_to(_(u'Add new child group'),h.url('new_repos_group', parent_group=c.repos_group.group_id))}</span>
27 </li>
27 </li>
28 </ul>
28 </ul>
29 </div>
29 </div>
30 <!-- end box / title -->
30 <!-- end box / title -->
31 ${h.form(url('repos_group',id=c.repos_group.group_id),method='put')}
31 ${h.form(url('repos_group',group_name=c.repos_group.group_name),method='put')}
32 <div class="form">
32 <div class="form">
33 <!-- fields -->
33 <!-- fields -->
34 <div class="fields">
34 <div class="fields">
35 <div class="field">
35 <div class="field">
36 <div class="label">
36 <div class="label">
37 <label for="group_name">${_('Group name')}:</label>
37 <label for="group_name">${_('Group name')}:</label>
38 </div>
38 </div>
39 <div class="input">
39 <div class="input">
40 ${h.text('group_name',class_='medium')}
40 ${h.text('group_name',class_='medium')}
41 </div>
41 </div>
42 </div>
42 </div>
43
43
44 <div class="field">
44 <div class="field">
45 <div class="label label-textarea">
45 <div class="label label-textarea">
46 <label for="group_description">${_('Description')}:</label>
46 <label for="group_description">${_('Description')}:</label>
47 </div>
47 </div>
48 <div class="textarea text-area editor">
48 <div class="textarea text-area editor">
49 ${h.textarea('group_description',cols=23,rows=5,class_="medium")}
49 ${h.textarea('group_description',cols=23,rows=5,class_="medium")}
50 </div>
50 </div>
51 </div>
51 </div>
52
52
53 <div class="field">
53 <div class="field">
54 <div class="label">
54 <div class="label">
55 <label for="group_parent_id">${_('Group parent')}:</label>
55 <label for="group_parent_id">${_('Group parent')}:</label>
56 </div>
56 </div>
57 <div class="input">
57 <div class="input">
58 ${h.select('group_parent_id','',c.repo_groups,class_="medium")}
58 ${h.select('group_parent_id','',c.repo_groups,class_="medium")}
59 </div>
59 </div>
60 </div>
60 </div>
61 <div class="field">
61 <div class="field">
62 <div class="label">
62 <div class="label">
63 <label for="input">${_('Permissions')}:</label>
63 <label for="input">${_('Permissions')}:</label>
64 </div>
64 </div>
65 <div class="input">
65 <div class="input">
66 <%include file="repos_group_edit_perms.html"/>
66 <%include file="repos_group_edit_perms.html"/>
67 </div>
67 </div>
68 </div>
68 </div>
69 <div class="field">
69 <div class="field">
70 <div class="label label-checkbox">
70 <div class="label label-checkbox">
71 <label for="enable_locking">${_('Enable locking')}:</label>
71 <label for="enable_locking">${_('Enable locking')}:</label>
72 </div>
72 </div>
73 <div class="checkboxes">
73 <div class="checkboxes">
74 ${h.checkbox('enable_locking',value="True")}
74 ${h.checkbox('enable_locking',value="True")}
75 <span class="help-block">${_('Enable lock-by-pulling on group. This option will be applied to all other groups and repositories inside')}</span>
75 <span class="help-block">${_('Enable lock-by-pulling on group. This option will be applied to all other groups and repositories inside')}</span>
76 </div>
76 </div>
77 </div>
77 </div>
78 <div class="buttons">
78 <div class="buttons">
79 ${h.submit('save',_('Save'),class_="ui-btn large")}
79 ${h.submit('save',_('Save'),class_="ui-btn large")}
80 ${h.reset('reset',_('Reset'),class_="ui-btn large")}
80 ${h.reset('reset',_('Reset'),class_="ui-btn large")}
81 </div>
81 </div>
82 </div>
82 </div>
83 </div>
83 </div>
84 ${h.end_form()}
84 ${h.end_form()}
85 </div>
85 </div>
86 </%def>
86 </%def>
@@ -1,74 +1,74 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 ${_('Repositories 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'))} &raquo; ${_('Repositories')}
10 ${h.link_to(_('Admin'),h.url('admin_home'))} &raquo; ${_('Repositories')}
11 </%def>
11 </%def>
12 <%def name="page_nav()">
12 <%def name="page_nav()">
13 ${self.menu('admin')}
13 ${self.menu('admin')}
14 </%def>
14 </%def>
15 <%def name="main()">
15 <%def name="main()">
16 <div class="box">
16 <div class="box">
17 <!-- box / title -->
17 <!-- box / title -->
18 <div class="title">
18 <div class="title">
19 ${self.breadcrumbs()}
19 ${self.breadcrumbs()}
20 <ul class="links">
20 <ul class="links">
21 <li>
21 <li>
22 <span>${h.link_to(_(u'Add new group'),h.url('new_repos_group'))}</span>
22 <span>${h.link_to(_(u'Add new group'),h.url('new_repos_group'))}</span>
23 </li>
23 </li>
24 </ul>
24 </ul>
25 </div>
25 </div>
26 <!-- end box / title -->
26 <!-- end box / title -->
27 <div class="table">
27 <div class="table">
28 % if c.groups:
28 % if c.groups:
29 <table class="table_disp">
29 <table class="table_disp">
30
30
31 <thead>
31 <thead>
32 <tr>
32 <tr>
33 <th class="left"><a href="#">${_('Group name')}</a></th>
33 <th class="left"><a href="#">${_('Group name')}</a></th>
34 <th class="left"><a href="#">${_('Description')}</a></th>
34 <th class="left"><a href="#">${_('Description')}</a></th>
35 <th class="left"><a href="#">${_('Number of toplevel repositories')}</a></th>
35 <th class="left"><a href="#">${_('Number of toplevel repositories')}</a></th>
36 <th class="left" colspan="2">${_('action')}</th>
36 <th class="left" colspan="2">${_('action')}</th>
37 </tr>
37 </tr>
38 </thead>
38 </thead>
39
39
40 ## REPO GROUPS
40 ## REPO GROUPS
41
41
42 % for gr in c.groups:
42 % for gr in c.groups:
43 <% gr_cn = gr.repositories.count() %>
43 <% gr_cn = gr.repositories.count() %>
44 <tr>
44 <tr>
45 <td>
45 <td>
46 <div style="white-space: nowrap">
46 <div style="white-space: nowrap">
47 <img class="icon" alt="${_('Repositories group')}" src="${h.url('/images/icons/database_link.png')}"/>
47 <img class="icon" alt="${_('Repositories group')}" src="${h.url('/images/icons/database_link.png')}"/>
48 ${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))}
48 ${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))}
49 </div>
49 </div>
50 </td>
50 </td>
51 <td>${gr.group_description}</td>
51 <td>${gr.group_description}</td>
52 <td><b>${gr_cn}</b></td>
52 <td><b>${gr_cn}</b></td>
53 <td>
53 <td>
54 <a href="${h.url('edit_repos_group',id=gr.group_id)}" title="${_('edit')}">
54 <a href="${h.url('edit_repos_group',group_name=gr.group_name)}" title="${_('edit')}">
55 ${h.submit('edit_%s' % gr.group_name,_('edit'),class_="edit_icon action_button")}
55 ${h.submit('edit_%s' % gr.group_name,_('edit'),class_="edit_icon action_button")}
56 </a>
56 </a>
57 </td>
57 </td>
58 <td>
58 <td>
59 ${h.form(url('repos_group', id=gr.group_id),method='delete')}
59 ${h.form(url('repos_group', group_name=gr.group_name),method='delete')}
60 ${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)+"');")}
60 ${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)+"');")}
61 ${h.end_form()}
61 ${h.end_form()}
62 </td>
62 </td>
63 </tr>
63 </tr>
64 % endfor
64 % endfor
65
65
66 </table>
66 </table>
67 % else:
67 % else:
68 ${_('There are no repositories groups yet')}
68 ${_('There are no repositories groups yet')}
69 % endif
69 % endif
70
70
71 </div>
71 </div>
72 </div>
72 </div>
73
73
74 </%def>
74 </%def>
@@ -1,340 +1,340 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 ${_('Edit user')} ${c.user.username} - ${c.rhodecode_name}
5 ${_('Edit user')} ${c.user.username} - ${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 ${h.link_to(_('Users'),h.url('users'))}
11 ${h.link_to(_('Users'),h.url('users'))}
12 &raquo;
12 &raquo;
13 ${_('edit')} "${c.user.username}"
13 ${_('edit')} "${c.user.username}"
14 </%def>
14 </%def>
15
15
16 <%def name="page_nav()">
16 <%def name="page_nav()">
17 ${self.menu('admin')}
17 ${self.menu('admin')}
18 </%def>
18 </%def>
19
19
20 <%def name="main()">
20 <%def name="main()">
21 <div class="box box-left">
21 <div class="box box-left">
22 <!-- box / title -->
22 <!-- box / title -->
23 <div class="title">
23 <div class="title">
24 ${self.breadcrumbs()}
24 ${self.breadcrumbs()}
25 </div>
25 </div>
26 <!-- end box / title -->
26 <!-- end box / title -->
27 ${h.form(url('update_user', id=c.user.user_id),method='put')}
27 ${h.form(url('update_user', id=c.user.user_id),method='put')}
28 <div class="form">
28 <div class="form">
29 <div class="field">
29 <div class="field">
30 <div class="gravatar_box">
30 <div class="gravatar_box">
31 <div class="gravatar"><img alt="gravatar" src="${h.gravatar_url(c.user.email)}"/></div>
31 <div class="gravatar"><img alt="gravatar" src="${h.gravatar_url(c.user.email)}"/></div>
32 <p>
32 <p>
33 %if c.use_gravatar:
33 %if c.use_gravatar:
34 <strong>${_('Change your avatar at')} <a href="http://gravatar.com">gravatar.com</a></strong>
34 <strong>${_('Change your avatar at')} <a href="http://gravatar.com">gravatar.com</a></strong>
35 <br/>${_('Using')} ${c.user.email}
35 <br/>${_('Using')} ${c.user.email}
36 %else:
36 %else:
37 <br/>${c.user.email}
37 <br/>${c.user.email}
38 %endif
38 %endif
39 </div>
39 </div>
40 </div>
40 </div>
41 <div class="field">
41 <div class="field">
42 <div class="label">
42 <div class="label">
43 <label>${_('API key')}</label> ${c.user.api_key}
43 <label>${_('API key')}</label> ${c.user.api_key}
44 </div>
44 </div>
45 </div>
45 </div>
46 <div class="field">
46 <div class="field">
47 <div class="label">
47 <div class="label">
48 <label>${_('Your IP')}</label> ${c.perm_user.ip_addr or "?"}
48 <label>${_('Your IP')}</label> ${c.perm_user.ip_addr or "?"}
49 </div>
49 </div>
50 </div>
50 </div>
51 <div class="fields">
51 <div class="fields">
52 <div class="field">
52 <div class="field">
53 <div class="label">
53 <div class="label">
54 <label for="username">${_('Username')}:</label>
54 <label for="username">${_('Username')}:</label>
55 </div>
55 </div>
56 <div class="input">
56 <div class="input">
57 %if c.ldap_dn:
57 %if c.ldap_dn:
58 ${h.text('username',class_='medium disabled', readonly="readonly")}
58 ${h.text('username',class_='medium disabled', readonly="readonly")}
59 %else:
59 %else:
60 ${h.text('username',class_='medium')}
60 ${h.text('username',class_='medium')}
61 %endif:
61 %endif:
62 </div>
62 </div>
63 </div>
63 </div>
64
64
65 <div class="field">
65 <div class="field">
66 <div class="label">
66 <div class="label">
67 <label for="ldap_dn">${_('LDAP DN')}:</label>
67 <label for="ldap_dn">${_('LDAP DN')}:</label>
68 </div>
68 </div>
69 <div class="input">
69 <div class="input">
70 ${h.text('ldap_dn',class_='medium disabled',readonly="readonly")}
70 ${h.text('ldap_dn',class_='medium disabled',readonly="readonly")}
71 </div>
71 </div>
72 </div>
72 </div>
73
73
74 <div class="field">
74 <div class="field">
75 <div class="label">
75 <div class="label">
76 <label for="new_password">${_('New password')}:</label>
76 <label for="new_password">${_('New password')}:</label>
77 </div>
77 </div>
78 <div class="input">
78 <div class="input">
79 ${h.password('new_password',class_='medium',autocomplete="off")}
79 ${h.password('new_password',class_='medium',autocomplete="off")}
80 </div>
80 </div>
81 </div>
81 </div>
82
82
83 <div class="field">
83 <div class="field">
84 <div class="label">
84 <div class="label">
85 <label for="password_confirmation">${_('New password confirmation')}:</label>
85 <label for="password_confirmation">${_('New password confirmation')}:</label>
86 </div>
86 </div>
87 <div class="input">
87 <div class="input">
88 ${h.password('password_confirmation',class_="medium",autocomplete="off")}
88 ${h.password('password_confirmation',class_="medium",autocomplete="off")}
89 </div>
89 </div>
90 </div>
90 </div>
91
91
92 <div class="field">
92 <div class="field">
93 <div class="label">
93 <div class="label">
94 <label for="firstname">${_('First Name')}:</label>
94 <label for="firstname">${_('First Name')}:</label>
95 </div>
95 </div>
96 <div class="input">
96 <div class="input">
97 ${h.text('firstname',class_='medium')}
97 ${h.text('firstname',class_='medium')}
98 </div>
98 </div>
99 </div>
99 </div>
100
100
101 <div class="field">
101 <div class="field">
102 <div class="label">
102 <div class="label">
103 <label for="lastname">${_('Last Name')}:</label>
103 <label for="lastname">${_('Last Name')}:</label>
104 </div>
104 </div>
105 <div class="input">
105 <div class="input">
106 ${h.text('lastname',class_='medium')}
106 ${h.text('lastname',class_='medium')}
107 </div>
107 </div>
108 </div>
108 </div>
109
109
110 <div class="field">
110 <div class="field">
111 <div class="label">
111 <div class="label">
112 <label for="email">${_('Email')}:</label>
112 <label for="email">${_('Email')}:</label>
113 </div>
113 </div>
114 <div class="input">
114 <div class="input">
115 ${h.text('email',class_='medium')}
115 ${h.text('email',class_='medium')}
116 </div>
116 </div>
117 </div>
117 </div>
118
118
119 <div class="field">
119 <div class="field">
120 <div class="label label-checkbox">
120 <div class="label label-checkbox">
121 <label for="active">${_('Active')}:</label>
121 <label for="active">${_('Active')}:</label>
122 </div>
122 </div>
123 <div class="checkboxes">
123 <div class="checkboxes">
124 ${h.checkbox('active',value=True)}
124 ${h.checkbox('active',value=True)}
125 </div>
125 </div>
126 </div>
126 </div>
127
127
128 <div class="field">
128 <div class="field">
129 <div class="label label-checkbox">
129 <div class="label label-checkbox">
130 <label for="admin">${_('Admin')}:</label>
130 <label for="admin">${_('Admin')}:</label>
131 </div>
131 </div>
132 <div class="checkboxes">
132 <div class="checkboxes">
133 ${h.checkbox('admin',value=True)}
133 ${h.checkbox('admin',value=True)}
134 </div>
134 </div>
135 </div>
135 </div>
136 <div class="buttons">
136 <div class="buttons">
137 ${h.submit('save',_('Save'),class_="ui-btn large")}
137 ${h.submit('save',_('Save'),class_="ui-btn large")}
138 ${h.reset('reset',_('Reset'),class_="ui-btn large")}
138 ${h.reset('reset',_('Reset'),class_="ui-btn large")}
139 </div>
139 </div>
140 </div>
140 </div>
141 </div>
141 </div>
142 ${h.end_form()}
142 ${h.end_form()}
143 </div>
143 </div>
144 <div style="min-height:780px" class="box box-right">
144 <div style="min-height:780px" class="box box-right">
145 <!-- box / title -->
145 <!-- box / title -->
146 <div class="title">
146 <div class="title">
147 <h5>${_('Permissions')}</h5>
147 <h5>${_('Permissions')}</h5>
148 </div>
148 </div>
149 ${h.form(url('user_perm', id=c.user.user_id),method='put')}
149 ${h.form(url('user_perm', id=c.user.user_id),method='put')}
150 <div class="form">
150 <div class="form">
151 <!-- fields -->
151 <!-- fields -->
152 <div class="fields">
152 <div class="fields">
153 <div class="field">
153 <div class="field">
154 <div class="label label-checkbox">
154 <div class="label label-checkbox">
155 <label for="inherit_permissions">${_('Inherit default permissions')}:</label>
155 <label for="inherit_permissions">${_('Inherit default permissions')}:</label>
156 </div>
156 </div>
157 <div class="checkboxes">
157 <div class="checkboxes">
158 ${h.checkbox('inherit_default_permissions',value=True)}
158 ${h.checkbox('inherit_default_permissions',value=True)}
159 </div>
159 </div>
160 <span class="help-block">${h.literal(_('Select to inherit permissions from %s settings. '
160 <span class="help-block">${h.literal(_('Select to inherit permissions from %s settings. '
161 'With this selected below options does not have any action') % h.link_to('default', url('edit_permission', id='default')))}</span>
161 'With this selected below options does not have any action') % h.link_to('default', url('edit_permission', id='default')))}</span>
162 </div>
162 </div>
163 <div id="inherit_overlay" style="${'opacity:0.3' if c.user.inherit_default_permissions else ''}" >
163 <div id="inherit_overlay" style="${'opacity:0.3' if c.user.inherit_default_permissions else ''}" >
164 <div class="field">
164 <div class="field">
165 <div class="label label-checkbox">
165 <div class="label label-checkbox">
166 <label for="create_repo_perm">${_('Create repositories')}:</label>
166 <label for="create_repo_perm">${_('Create repositories')}:</label>
167 </div>
167 </div>
168 <div class="checkboxes">
168 <div class="checkboxes">
169 ${h.checkbox('create_repo_perm',value=True)}
169 ${h.checkbox('create_repo_perm',value=True)}
170 </div>
170 </div>
171 </div>
171 </div>
172 <div class="field">
172 <div class="field">
173 <div class="label label-checkbox">
173 <div class="label label-checkbox">
174 <label for="fork_repo_perm">${_('Fork repositories')}:</label>
174 <label for="fork_repo_perm">${_('Fork repositories')}:</label>
175 </div>
175 </div>
176 <div class="checkboxes">
176 <div class="checkboxes">
177 ${h.checkbox('fork_repo_perm',value=True)}
177 ${h.checkbox('fork_repo_perm',value=True)}
178 </div>
178 </div>
179 </div>
179 </div>
180 </div>
180 </div>
181 <div class="buttons">
181 <div class="buttons">
182 ${h.submit('save',_('Save'),class_="ui-btn large")}
182 ${h.submit('save',_('Save'),class_="ui-btn large")}
183 ${h.reset('reset',_('Reset'),class_="ui-btn large")}
183 ${h.reset('reset',_('Reset'),class_="ui-btn large")}
184 </div>
184 </div>
185 </div>
185 </div>
186 </div>
186 </div>
187 ${h.end_form()}
187 ${h.end_form()}
188
188
189 ## permissions overview
189 ## permissions overview
190 <div id="perms" class="table">
190 <div id="perms" class="table">
191 %for section in sorted(c.perm_user.permissions.keys()):
191 %for section in sorted(c.perm_user.permissions.keys()):
192 <div class="perms_section_head">${section.replace("_"," ").capitalize()}</div>
192 <div class="perms_section_head">${section.replace("_"," ").capitalize()}</div>
193 %if not c.perm_user.permissions[section]:
193 %if not c.perm_user.permissions[section]:
194 <span class="empty_data">${_('Nothing here yet')}</span>
194 <span class="empty_data">${_('Nothing here yet')}</span>
195 %else:
195 %else:
196 <div id='tbl_list_wrap_${section}' class="yui-skin-sam">
196 <div id='tbl_list_wrap_${section}' class="yui-skin-sam">
197 <table id="tbl_list_${section}">
197 <table id="tbl_list_${section}">
198 <thead>
198 <thead>
199 <tr>
199 <tr>
200 <th class="left">${_('Name')}</th>
200 <th class="left">${_('Name')}</th>
201 <th class="left">${_('Permission')}</th>
201 <th class="left">${_('Permission')}</th>
202 <th class="left">${_('Edit Permission')}</th>
202 <th class="left">${_('Edit Permission')}</th>
203 </thead>
203 </thead>
204 <tbody>
204 <tbody>
205 %for k in c.perm_user.permissions[section]:
205 %for k in c.perm_user.permissions[section]:
206 <%
206 <%
207 if section != 'global':
207 if section != 'global':
208 section_perm = c.perm_user.permissions[section].get(k)
208 section_perm = c.perm_user.permissions[section].get(k)
209 _perm = section_perm.split('.')[-1]
209 _perm = section_perm.split('.')[-1]
210 else:
210 else:
211 _perm = section_perm = None
211 _perm = section_perm = None
212 %>
212 %>
213 <tr>
213 <tr>
214 <td>
214 <td>
215 %if section == 'repositories':
215 %if section == 'repositories':
216 <a href="${h.url('summary_home',repo_name=k)}">${k}</a>
216 <a href="${h.url('summary_home',repo_name=k)}">${k}</a>
217 %elif section == 'repositories_groups':
217 %elif section == 'repositories_groups':
218 <a href="${h.url('repos_group_home',group_name=k)}">${k}</a>
218 <a href="${h.url('repos_group_home',group_name=k)}">${k}</a>
219 %else:
219 %else:
220 ${h.get_permission_name(k)}
220 ${h.get_permission_name(k)}
221 %endif
221 %endif
222 </td>
222 </td>
223 <td>
223 <td>
224 %if section == 'global':
224 %if section == 'global':
225 ${h.bool2icon(k.split('.')[-1] != 'none')}
225 ${h.bool2icon(k.split('.')[-1] != 'none')}
226 %else:
226 %else:
227 <span class="perm_tag ${_perm}">${section_perm}</span>
227 <span class="perm_tag ${_perm}">${section_perm}</span>
228 %endif
228 %endif
229 </td>
229 </td>
230 <td>
230 <td>
231 %if section == 'repositories':
231 %if section == 'repositories':
232 <a href="${h.url('edit_repo',repo_name=k,anchor='permissions_manage')}">${_('edit')}</a>
232 <a href="${h.url('edit_repo',repo_name=k,anchor='permissions_manage')}">${_('edit')}</a>
233 %elif section == 'repositories_groups':
233 %elif section == 'repositories_groups':
234 <a href="${h.url('edit_repos_group',id=k,anchor='permissions_manage')}">${_('edit')}</a>
234 <a href="${h.url('edit_repos_group',group_name=k,anchor='permissions_manage')}">${_('edit')}</a>
235 %else:
235 %else:
236 --
236 --
237 %endif
237 %endif
238 </td>
238 </td>
239 </tr>
239 </tr>
240 %endfor
240 %endfor
241 </tbody>
241 </tbody>
242 </table>
242 </table>
243 </div>
243 </div>
244 %endif
244 %endif
245 %endfor
245 %endfor
246 </div>
246 </div>
247 </div>
247 </div>
248 <div class="box box-left" style="clear:left">
248 <div class="box box-left" style="clear:left">
249 <!-- box / title -->
249 <!-- box / title -->
250 <div class="title">
250 <div class="title">
251 <h5>${_('Email addresses')}</h5>
251 <h5>${_('Email addresses')}</h5>
252 </div>
252 </div>
253
253
254 <div class="emails_wrap">
254 <div class="emails_wrap">
255 <table class="noborder">
255 <table class="noborder">
256 %for em in c.user_email_map:
256 %for em in c.user_email_map:
257 <tr>
257 <tr>
258 <td><div class="gravatar"><img alt="gravatar" src="${h.gravatar_url(em.user.email,16)}"/> </div></td>
258 <td><div class="gravatar"><img alt="gravatar" src="${h.gravatar_url(em.user.email,16)}"/> </div></td>
259 <td><div class="email">${em.email}</div></td>
259 <td><div class="email">${em.email}</div></td>
260 <td>
260 <td>
261 ${h.form(url('user_emails_delete', id=c.user.user_id),method='delete')}
261 ${h.form(url('user_emails_delete', id=c.user.user_id),method='delete')}
262 ${h.hidden('del_email',em.email_id)}
262 ${h.hidden('del_email',em.email_id)}
263 ${h.submit('remove_',_('delete'),id="remove_email_%s" % em.email_id,
263 ${h.submit('remove_',_('delete'),id="remove_email_%s" % em.email_id,
264 class_="delete_icon action_button", onclick="return confirm('"+_('Confirm to delete this email: %s') % em.email+"');")}
264 class_="delete_icon action_button", onclick="return confirm('"+_('Confirm to delete this email: %s') % em.email+"');")}
265 ${h.end_form()}
265 ${h.end_form()}
266 </td>
266 </td>
267 </tr>
267 </tr>
268 %endfor
268 %endfor
269 </table>
269 </table>
270 </div>
270 </div>
271
271
272 ${h.form(url('user_emails', id=c.user.user_id),method='put')}
272 ${h.form(url('user_emails', id=c.user.user_id),method='put')}
273 <div class="form">
273 <div class="form">
274 <!-- fields -->
274 <!-- fields -->
275 <div class="fields">
275 <div class="fields">
276 <div class="field">
276 <div class="field">
277 <div class="label">
277 <div class="label">
278 <label for="new_email">${_('New email address')}:</label>
278 <label for="new_email">${_('New email address')}:</label>
279 </div>
279 </div>
280 <div class="input">
280 <div class="input">
281 ${h.text('new_email', class_='medium')}
281 ${h.text('new_email', class_='medium')}
282 </div>
282 </div>
283 </div>
283 </div>
284 <div class="buttons">
284 <div class="buttons">
285 ${h.submit('save',_('Add'),class_="ui-btn large")}
285 ${h.submit('save',_('Add'),class_="ui-btn large")}
286 ${h.reset('reset',_('Reset'),class_="ui-btn large")}
286 ${h.reset('reset',_('Reset'),class_="ui-btn large")}
287 </div>
287 </div>
288 </div>
288 </div>
289 </div>
289 </div>
290 ${h.end_form()}
290 ${h.end_form()}
291 </div>
291 </div>
292 <div class="box box-left" style="clear:left">
292 <div class="box box-left" style="clear:left">
293 <!-- box / title -->
293 <!-- box / title -->
294 <div class="title">
294 <div class="title">
295 <h5>${_('Allowed IP addresses')}</h5>
295 <h5>${_('Allowed IP addresses')}</h5>
296 </div>
296 </div>
297
297
298 <div class="ips_wrap">
298 <div class="ips_wrap">
299 <table class="noborder">
299 <table class="noborder">
300 %if c.user_ip_map:
300 %if c.user_ip_map:
301 %for ip in c.user_ip_map:
301 %for ip in c.user_ip_map:
302 <tr>
302 <tr>
303 <td><div class="ip">${ip.ip_addr}</div></td>
303 <td><div class="ip">${ip.ip_addr}</div></td>
304 <td><div class="ip">${h.ip_range(ip.ip_addr)}</div></td>
304 <td><div class="ip">${h.ip_range(ip.ip_addr)}</div></td>
305 <td>
305 <td>
306 ${h.form(url('user_ips_delete', id=c.user.user_id),method='delete')}
306 ${h.form(url('user_ips_delete', id=c.user.user_id),method='delete')}
307 ${h.hidden('del_ip',ip.ip_id)}
307 ${h.hidden('del_ip',ip.ip_id)}
308 ${h.submit('remove_',_('delete'),id="remove_ip_%s" % ip.ip_id,
308 ${h.submit('remove_',_('delete'),id="remove_ip_%s" % ip.ip_id,
309 class_="delete_icon action_button", onclick="return confirm('"+_('Confirm to delete this ip: %s') % ip.ip_addr+"');")}
309 class_="delete_icon action_button", onclick="return confirm('"+_('Confirm to delete this ip: %s') % ip.ip_addr+"');")}
310 ${h.end_form()}
310 ${h.end_form()}
311 </td>
311 </td>
312 </tr>
312 </tr>
313 %endfor
313 %endfor
314 %else:
314 %else:
315 <tr><td><div class="ip">${_('All IP addresses are allowed')}</div></td></tr>
315 <tr><td><div class="ip">${_('All IP addresses are allowed')}</div></td></tr>
316 %endif
316 %endif
317 </table>
317 </table>
318 </div>
318 </div>
319
319
320 ${h.form(url('user_ips', id=c.user.user_id),method='put')}
320 ${h.form(url('user_ips', id=c.user.user_id),method='put')}
321 <div class="form">
321 <div class="form">
322 <!-- fields -->
322 <!-- fields -->
323 <div class="fields">
323 <div class="fields">
324 <div class="field">
324 <div class="field">
325 <div class="label">
325 <div class="label">
326 <label for="new_ip">${_('New ip address')}:</label>
326 <label for="new_ip">${_('New ip address')}:</label>
327 </div>
327 </div>
328 <div class="input">
328 <div class="input">
329 ${h.text('new_ip', class_='medium')}
329 ${h.text('new_ip', class_='medium')}
330 </div>
330 </div>
331 </div>
331 </div>
332 <div class="buttons">
332 <div class="buttons">
333 ${h.submit('save',_('Add'),class_="ui-btn large")}
333 ${h.submit('save',_('Add'),class_="ui-btn large")}
334 ${h.reset('reset',_('Reset'),class_="ui-btn large")}
334 ${h.reset('reset',_('Reset'),class_="ui-btn large")}
335 </div>
335 </div>
336 </div>
336 </div>
337 </div>
337 </div>
338 ${h.end_form()}
338 ${h.end_form()}
339 </div>
339 </div>
340 </%def>
340 </%def>
@@ -1,228 +1,228 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 ${_('Edit users group')} ${c.users_group.users_group_name} - ${c.rhodecode_name}
5 ${_('Edit users group')} ${c.users_group.users_group_name} - ${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 ${h.link_to(_('UsersGroups'),h.url('users_groups'))}
11 ${h.link_to(_('UsersGroups'),h.url('users_groups'))}
12 &raquo;
12 &raquo;
13 ${_('edit')} "${c.users_group.users_group_name}"
13 ${_('edit')} "${c.users_group.users_group_name}"
14 </%def>
14 </%def>
15
15
16 <%def name="page_nav()">
16 <%def name="page_nav()">
17 ${self.menu('admin')}
17 ${self.menu('admin')}
18 </%def>
18 </%def>
19
19
20 <%def name="main()">
20 <%def name="main()">
21 <div class="box box-left">
21 <div class="box box-left">
22 <!-- box / title -->
22 <!-- box / title -->
23 <div class="title">
23 <div class="title">
24 ${self.breadcrumbs()}
24 ${self.breadcrumbs()}
25 </div>
25 </div>
26 <!-- end box / title -->
26 <!-- end box / title -->
27 ${h.form(url('users_group', id=c.users_group.users_group_id),method='put', id='edit_users_group')}
27 ${h.form(url('users_group', id=c.users_group.users_group_id),method='put', id='edit_users_group')}
28 <div class="form">
28 <div class="form">
29 <!-- fields -->
29 <!-- fields -->
30 <div class="fields">
30 <div class="fields">
31 <div class="field">
31 <div class="field">
32 <div class="label">
32 <div class="label">
33 <label for="users_group_name">${_('Group name')}:</label>
33 <label for="users_group_name">${_('Group name')}:</label>
34 </div>
34 </div>
35 <div class="input">
35 <div class="input">
36 ${h.text('users_group_name',class_='small')}
36 ${h.text('users_group_name',class_='small')}
37 </div>
37 </div>
38 </div>
38 </div>
39
39
40 <div class="field">
40 <div class="field">
41 <div class="label label-checkbox">
41 <div class="label label-checkbox">
42 <label for="users_group_active">${_('Active')}:</label>
42 <label for="users_group_active">${_('Active')}:</label>
43 </div>
43 </div>
44 <div class="checkboxes">
44 <div class="checkboxes">
45 ${h.checkbox('users_group_active',value=True)}
45 ${h.checkbox('users_group_active',value=True)}
46 </div>
46 </div>
47 </div>
47 </div>
48 <div class="field">
48 <div class="field">
49 <div class="label">
49 <div class="label">
50 <label for="users_group_active">${_('Members')}:</label>
50 <label for="users_group_active">${_('Members')}:</label>
51 </div>
51 </div>
52 <div class="select">
52 <div class="select">
53 <table>
53 <table>
54 <tr>
54 <tr>
55 <td>
55 <td>
56 <div>
56 <div>
57 <div style="float:left">
57 <div style="float:left">
58 <div class="text" style="padding: 0px 0px 6px;">${_('Choosen group members')}</div>
58 <div class="text" style="padding: 0px 0px 6px;">${_('Choosen group members')}</div>
59 ${h.select('users_group_members',[x[0] for x in c.group_members],c.group_members,multiple=True,size=8,style="min-width:210px")}
59 ${h.select('users_group_members',[x[0] for x in c.group_members],c.group_members,multiple=True,size=8,style="min-width:210px")}
60 <div id="remove_all_elements" style="cursor:pointer;text-align:center">
60 <div id="remove_all_elements" style="cursor:pointer;text-align:center">
61 ${_('Remove all elements')}
61 ${_('Remove all elements')}
62 <img alt="remove" style="vertical-align:text-bottom" src="${h.url('/images/icons/arrow_right.png')}"/>
62 <img alt="remove" style="vertical-align:text-bottom" src="${h.url('/images/icons/arrow_right.png')}"/>
63 </div>
63 </div>
64 </div>
64 </div>
65 <div style="float:left;width:20px;padding-top:50px">
65 <div style="float:left;width:20px;padding-top:50px">
66 <img alt="add" id="add_element"
66 <img alt="add" id="add_element"
67 style="padding:2px;cursor:pointer"
67 style="padding:2px;cursor:pointer"
68 src="${h.url('/images/icons/arrow_left.png')}"/>
68 src="${h.url('/images/icons/arrow_left.png')}"/>
69 <br />
69 <br />
70 <img alt="remove" id="remove_element"
70 <img alt="remove" id="remove_element"
71 style="padding:2px;cursor:pointer"
71 style="padding:2px;cursor:pointer"
72 src="${h.url('/images/icons/arrow_right.png')}"/>
72 src="${h.url('/images/icons/arrow_right.png')}"/>
73 </div>
73 </div>
74 <div style="float:left">
74 <div style="float:left">
75 <div class="text" style="padding: 0px 0px 6px;">${_('Available members')}</div>
75 <div class="text" style="padding: 0px 0px 6px;">${_('Available members')}</div>
76 ${h.select('available_members',[],c.available_members,multiple=True,size=8,style="min-width:210px")}
76 ${h.select('available_members',[],c.available_members,multiple=True,size=8,style="min-width:210px")}
77 <div id="add_all_elements" style="cursor:pointer;text-align:center">
77 <div id="add_all_elements" style="cursor:pointer;text-align:center">
78 <img alt="add" style="vertical-align:text-bottom" src="${h.url('/images/icons/arrow_left.png')}"/>
78 <img alt="add" style="vertical-align:text-bottom" src="${h.url('/images/icons/arrow_left.png')}"/>
79 ${_('Add all elements')}
79 ${_('Add all elements')}
80 </div>
80 </div>
81 </div>
81 </div>
82 </div>
82 </div>
83 </td>
83 </td>
84 </tr>
84 </tr>
85 </table>
85 </table>
86 </div>
86 </div>
87
87
88 </div>
88 </div>
89 <div class="buttons">
89 <div class="buttons">
90 ${h.submit('save',_('save'),class_="ui-btn large")}
90 ${h.submit('save',_('save'),class_="ui-btn large")}
91 </div>
91 </div>
92 </div>
92 </div>
93 </div>
93 </div>
94 ${h.end_form()}
94 ${h.end_form()}
95 </div>
95 </div>
96
96
97 <div class="box box-right">
97 <div class="box box-right">
98 <!-- box / title -->
98 <!-- box / title -->
99 <div class="title">
99 <div class="title">
100 <h5>${_('Permissions')}</h5>
100 <h5>${_('Permissions')}</h5>
101 </div>
101 </div>
102 ${h.form(url('users_group_perm', id=c.users_group.users_group_id), method='put')}
102 ${h.form(url('users_group_perm', id=c.users_group.users_group_id), method='put')}
103 <div class="form">
103 <div class="form">
104 <!-- fields -->
104 <!-- fields -->
105 <div class="fields">
105 <div class="fields">
106 <div class="field">
106 <div class="field">
107 <div class="label label-checkbox">
107 <div class="label label-checkbox">
108 <label for="inherit_permissions">${_('Inherit default permissions')}:</label>
108 <label for="inherit_permissions">${_('Inherit default permissions')}:</label>
109 </div>
109 </div>
110 <div class="checkboxes">
110 <div class="checkboxes">
111 ${h.checkbox('inherit_default_permissions',value=True)}
111 ${h.checkbox('inherit_default_permissions',value=True)}
112 </div>
112 </div>
113 <span class="help-block">${h.literal(_('Select to inherit permissions from %s settings. '
113 <span class="help-block">${h.literal(_('Select to inherit permissions from %s settings. '
114 'With this selected below options does not have any action') % h.link_to('default', url('edit_permission', id='default')))}</span>
114 'With this selected below options does not have any action') % h.link_to('default', url('edit_permission', id='default')))}</span>
115 </div>
115 </div>
116 <div id="inherit_overlay" style="${'opacity:0.3' if c.users_group.inherit_default_permissions else ''}" >
116 <div id="inherit_overlay" style="${'opacity:0.3' if c.users_group.inherit_default_permissions else ''}" >
117 <div class="field">
117 <div class="field">
118 <div class="label label-checkbox">
118 <div class="label label-checkbox">
119 <label for="create_repo_perm">${_('Create repositories')}:</label>
119 <label for="create_repo_perm">${_('Create repositories')}:</label>
120 </div>
120 </div>
121 <div class="checkboxes">
121 <div class="checkboxes">
122 ${h.checkbox('create_repo_perm',value=True)}
122 ${h.checkbox('create_repo_perm',value=True)}
123 </div>
123 </div>
124 </div>
124 </div>
125 <div class="field">
125 <div class="field">
126 <div class="label label-checkbox">
126 <div class="label label-checkbox">
127 <label for="fork_repo_perm">${_('Fork repositories')}:</label>
127 <label for="fork_repo_perm">${_('Fork repositories')}:</label>
128 </div>
128 </div>
129 <div class="checkboxes">
129 <div class="checkboxes">
130 ${h.checkbox('fork_repo_perm',value=True)}
130 ${h.checkbox('fork_repo_perm',value=True)}
131 </div>
131 </div>
132 </div>
132 </div>
133 </div>
133 </div>
134 <div class="buttons">
134 <div class="buttons">
135 ${h.submit('save',_('Save'),class_="ui-btn large")}
135 ${h.submit('save',_('Save'),class_="ui-btn large")}
136 ${h.reset('reset',_('Reset'),class_="ui-btn large")}
136 ${h.reset('reset',_('Reset'),class_="ui-btn large")}
137 </div>
137 </div>
138 </div>
138 </div>
139 </div>
139 </div>
140 ${h.end_form()}
140 ${h.end_form()}
141 </div>
141 </div>
142
142
143 <div class="box box-right">
143 <div class="box box-right">
144 <!-- box / title -->
144 <!-- box / title -->
145 <div class="title">
145 <div class="title">
146 <h5>${_('Group members')}</h5>
146 <h5>${_('Group members')}</h5>
147 </div>
147 </div>
148
148
149 <div class="group_members_wrap">
149 <div class="group_members_wrap">
150 % if c.group_members_obj:
150 % if c.group_members_obj:
151 <ul class="group_members">
151 <ul class="group_members">
152 %for user in c.group_members_obj:
152 %for user in c.group_members_obj:
153 <li>
153 <li>
154 <div class="group_member">
154 <div class="group_member">
155 <div class="gravatar"><img alt="gravatar" src="${h.gravatar_url(user.email,24)}"/> </div>
155 <div class="gravatar"><img alt="gravatar" src="${h.gravatar_url(user.email,24)}"/> </div>
156 <div>${h.link_to(user.username, h.url('edit_user',id=user.user_id))}</div>
156 <div>${h.link_to(user.username, h.url('edit_user',id=user.user_id))}</div>
157 <div>${user.full_name}</div>
157 <div>${user.full_name}</div>
158 </div>
158 </div>
159 </li>
159 </li>
160 %endfor
160 %endfor
161 </ul>
161 </ul>
162 %else:
162 %else:
163 <span class="empty_data">${_('No members yet')}</span>
163 <span class="empty_data">${_('No members yet')}</span>
164 %endif
164 %endif
165 </div>
165 </div>
166 </div>
166 </div>
167
167
168 <div class="box box-left">
168 <div class="box box-left">
169 <!-- box / title -->
169 <!-- box / title -->
170 <div class="title">
170 <div class="title">
171 <h5>${_('Permissions defined for this group')}</h5>
171 <h5>${_('Permissions defined for this group')}</h5>
172 </div>
172 </div>
173 ## permissions overview
173 ## permissions overview
174 <div id="perms" class="table">
174 <div id="perms" class="table">
175 %for section in sorted(c.users_group.permissions.keys()):
175 %for section in sorted(c.users_group.permissions.keys()):
176 <div class="perms_section_head">${section.replace("_"," ").capitalize()}</div>
176 <div class="perms_section_head">${section.replace("_"," ").capitalize()}</div>
177 %if not c.users_group.permissions:
177 %if not c.users_group.permissions:
178 <span class="empty_data">${_('No permissions set yet')}</span>
178 <span class="empty_data">${_('No permissions set yet')}</span>
179 %else:
179 %else:
180 <div id='tbl_list_wrap_${section}' class="yui-skin-sam">
180 <div id='tbl_list_wrap_${section}' class="yui-skin-sam">
181 <table id="tbl_list_repository">
181 <table id="tbl_list_repository">
182 <thead>
182 <thead>
183 <tr>
183 <tr>
184 <th class="left">${_('Name')}</th>
184 <th class="left">${_('Name')}</th>
185 <th class="left">${_('Permission')}</th>
185 <th class="left">${_('Permission')}</th>
186 <th class="left">${_('Edit Permission')}</th>
186 <th class="left">${_('Edit Permission')}</th>
187 </thead>
187 </thead>
188 <tbody>
188 <tbody>
189 %for k in c.users_group.permissions[section]:
189 %for k in c.users_group.permissions[section]:
190 <%
190 <%
191 section_perm = c.users_group.permissions[section].get(k)
191 section_perm = c.users_group.permissions[section].get(k)
192 _perm = section_perm.split('.')[-1]
192 _perm = section_perm.split('.')[-1]
193 %>
193 %>
194 <tr>
194 <tr>
195 <td>
195 <td>
196 %if section == 'repositories':
196 %if section == 'repositories':
197 <a href="${h.url('summary_home',repo_name=k)}">${k}</a>
197 <a href="${h.url('summary_home',repo_name=k)}">${k}</a>
198 %elif section == 'repositories_groups':
198 %elif section == 'repositories_groups':
199 <a href="${h.url('repos_group_home',group_name=k)}">${k}</a>
199 <a href="${h.url('repos_group_home',group_name=k)}">${k}</a>
200 %endif
200 %endif
201 </td>
201 </td>
202 <td>
202 <td>
203 <span class="perm_tag ${_perm}">${section_perm}</span>
203 <span class="perm_tag ${_perm}">${section_perm}</span>
204 </td>
204 </td>
205 <td>
205 <td>
206 %if section == 'repositories':
206 %if section == 'repositories':
207 <a href="${h.url('edit_repo',repo_name=k,anchor='permissions_manage')}">${_('edit')}</a>
207 <a href="${h.url('edit_repo',repo_name=k,anchor='permissions_manage')}">${_('edit')}</a>
208 %elif section == 'repositories_groups':
208 %elif section == 'repositories_groups':
209 <a href="${h.url('edit_repos_group',id=k,anchor='permissions_manage')}">${_('edit')}</a>
209 <a href="${h.url('edit_repos_group',group_name=k,anchor='permissions_manage')}">${_('edit')}</a>
210 %else:
210 %else:
211 --
211 --
212 %endif
212 %endif
213 </td>
213 </td>
214 </tr>
214 </tr>
215 %endfor
215 %endfor
216 </tbody>
216 </tbody>
217 </table>
217 </table>
218 </div>
218 </div>
219 %endif
219 %endif
220 %endfor
220 %endfor
221 </div>
221 </div>
222 </div>
222 </div>
223
223
224
224
225 <script type="text/javascript">
225 <script type="text/javascript">
226 MultiSelectWidget('users_group_members','available_members','edit_users_group');
226 MultiSelectWidget('users_group_members','available_members','edit_users_group');
227 </script>
227 </script>
228 </%def>
228 </%def>
@@ -1,337 +1,342 b''
1 <%page args="parent" />
1 <%page args="parent" />
2 <div class="box">
2 <div class="box">
3 <!-- box / title -->
3 <!-- box / title -->
4 <div class="title">
4 <div class="title">
5 <h5>
5 <h5>
6 <input class="q_filter_box" id="q_filter" size="15" type="text" name="filter" value="${_('quick filter...')}"/> ${parent.breadcrumbs()} <span id="repo_count">0</span> ${_('repositories')}
6 <input class="q_filter_box" id="q_filter" size="15" type="text" name="filter" value="${_('quick filter...')}"/> ${parent.breadcrumbs()} <span id="repo_count">0</span> ${_('repositories')}
7 </h5>
7 </h5>
8 %if c.rhodecode_user.username != 'default':
8 %if c.rhodecode_user.username != 'default':
9 <ul class="links">
9 %if h.HasPermissionAny('hg.admin','hg.create.repository')():
10 %if h.HasPermissionAny('hg.admin','hg.create.repository')():
10 <ul class="links">
11 <li>
11 <li>
12 %if c.group:
12 %if c.group:
13 <span>${h.link_to(_('Add repository'),h.url('admin_settings_create_repository',parent_group=c.group.group_id))}</span>
13 <span>${h.link_to(_('Add repository'),h.url('admin_settings_create_repository',parent_group=c.group.group_id))}</span>
14 %else:
14 %else:
15 <span>${h.link_to(_('Add repository'),h.url('admin_settings_create_repository'))}</span>
15 <span>${h.link_to(_('Add repository'),h.url('admin_settings_create_repository'))}</span>
16 %endif
16 %endif
17 </li>
17 </li>
18 </ul>
19 %endif
18 %endif
19 %if c.group and h.HasReposGroupPermissionAny('group.admin')(c.group.group_name):
20 <li>
21 <span>${h.link_to(_('Edit group'),h.url('edit_repos_group',group_name=c.group.group_name), title=_('You have admin right to this group, and can edit it'))}</span>
22 </li>
23 %endif
24 </ul>
20 %endif
25 %endif
21 </div>
26 </div>
22 <!-- end box / title -->
27 <!-- end box / title -->
23 <div class="table">
28 <div class="table">
24 % if c.groups:
29 % if c.groups:
25 <div id='groups_list_wrap' class="yui-skin-sam">
30 <div id='groups_list_wrap' class="yui-skin-sam">
26 <table id="groups_list">
31 <table id="groups_list">
27 <thead>
32 <thead>
28 <tr>
33 <tr>
29 <th class="left"><a href="#">${_('Group name')}</a></th>
34 <th class="left"><a href="#">${_('Group name')}</a></th>
30 <th class="left"><a href="#">${_('Description')}</a></th>
35 <th class="left"><a href="#">${_('Description')}</a></th>
31 ##<th class="left"><a href="#">${_('Number of repositories')}</a></th>
36 ##<th class="left"><a href="#">${_('Number of repositories')}</a></th>
32 </tr>
37 </tr>
33 </thead>
38 </thead>
34
39
35 ## REPO GROUPS
40 ## REPO GROUPS
36 % for gr in c.groups:
41 % for gr in c.groups:
37 <tr>
42 <tr>
38 <td>
43 <td>
39 <div style="white-space: nowrap">
44 <div style="white-space: nowrap">
40 <img class="icon" alt="${_('Repositories group')}" src="${h.url('/images/icons/database_link.png')}"/>
45 <img class="icon" alt="${_('Repositories group')}" src="${h.url('/images/icons/database_link.png')}"/>
41 ${h.link_to(gr.name,url('repos_group_home',group_name=gr.group_name))}
46 ${h.link_to(gr.name,url('repos_group_home',group_name=gr.group_name))}
42 </div>
47 </div>
43 </td>
48 </td>
44 %if c.visual.stylify_metatags:
49 %if c.visual.stylify_metatags:
45 <td>${h.urlify_text(h.desc_stylize(gr.group_description))}</td>
50 <td>${h.urlify_text(h.desc_stylize(gr.group_description))}</td>
46 %else:
51 %else:
47 <td>${gr.group_description}</td>
52 <td>${gr.group_description}</td>
48 %endif
53 %endif
49 ## this is commented out since for multi nested repos can be HEAVY!
54 ## this is commented out since for multi nested repos can be HEAVY!
50 ## in number of executed queries during traversing uncomment at will
55 ## in number of executed queries during traversing uncomment at will
51 ##<td><b>${gr.repositories_recursive_count}</b></td>
56 ##<td><b>${gr.repositories_recursive_count}</b></td>
52 </tr>
57 </tr>
53 % endfor
58 % endfor
54 </table>
59 </table>
55 </div>
60 </div>
56 <div id="group-user-paginator" style="padding: 0px 0px 0px 0px"></div>
61 <div id="group-user-paginator" style="padding: 0px 0px 0px 0px"></div>
57 <div style="height: 20px"></div>
62 <div style="height: 20px"></div>
58 % endif
63 % endif
59 <div id="welcome" style="display:none;text-align:center">
64 <div id="welcome" style="display:none;text-align:center">
60 <h1><a href="${h.url('home')}">${c.rhodecode_name} ${c.rhodecode_version}</a></h1>
65 <h1><a href="${h.url('home')}">${c.rhodecode_name} ${c.rhodecode_version}</a></h1>
61 </div>
66 </div>
62 <%cnt=0%>
67 <%cnt=0%>
63 <%namespace name="dt" file="/data_table/_dt_elements.html"/>
68 <%namespace name="dt" file="/data_table/_dt_elements.html"/>
64 % if c.visual.lightweight_dashboard is False:
69 % if c.visual.lightweight_dashboard is False:
65 ## old full detailed version
70 ## old full detailed version
66 <div id='repos_list_wrap' class="yui-skin-sam">
71 <div id='repos_list_wrap' class="yui-skin-sam">
67 <table id="repos_list">
72 <table id="repos_list">
68 <thead>
73 <thead>
69 <tr>
74 <tr>
70 <th class="left"></th>
75 <th class="left"></th>
71 <th class="left">${_('Name')}</th>
76 <th class="left">${_('Name')}</th>
72 <th class="left">${_('Description')}</th>
77 <th class="left">${_('Description')}</th>
73 <th class="left">${_('Last change')}</th>
78 <th class="left">${_('Last change')}</th>
74 <th class="left">${_('Tip')}</th>
79 <th class="left">${_('Tip')}</th>
75 <th class="left">${_('Owner')}</th>
80 <th class="left">${_('Owner')}</th>
76 <th class="left">${_('RSS')}</th>
81 <th class="left">${_('RSS')}</th>
77 <th class="left">${_('Atom')}</th>
82 <th class="left">${_('Atom')}</th>
78 </tr>
83 </tr>
79 </thead>
84 </thead>
80 <tbody>
85 <tbody>
81 %for cnt,repo in enumerate(c.repos_list):
86 %for cnt,repo in enumerate(c.repos_list):
82 <tr class="parity${(cnt+1)%2}">
87 <tr class="parity${(cnt+1)%2}">
83 ##QUICK MENU
88 ##QUICK MENU
84 <td class="quick_repo_menu">
89 <td class="quick_repo_menu">
85 ${dt.quick_menu(repo['name'])}
90 ${dt.quick_menu(repo['name'])}
86 </td>
91 </td>
87 ##REPO NAME AND ICONS
92 ##REPO NAME AND ICONS
88 <td class="reponame">
93 <td class="reponame">
89 ${dt.repo_name(repo['name'],repo['dbrepo']['repo_type'],repo['dbrepo']['private'],h.AttributeDict(repo['dbrepo_fork']),pageargs.get('short_repo_names'))}
94 ${dt.repo_name(repo['name'],repo['dbrepo']['repo_type'],repo['dbrepo']['private'],h.AttributeDict(repo['dbrepo_fork']),pageargs.get('short_repo_names'))}
90 </td>
95 </td>
91 ##DESCRIPTION
96 ##DESCRIPTION
92 <td><span class="tooltip" title="${h.tooltip(repo['description'])}">
97 <td><span class="tooltip" title="${h.tooltip(repo['description'])}">
93 %if c.visual.stylify_metatags:
98 %if c.visual.stylify_metatags:
94 ${h.urlify_text(h.desc_stylize(h.truncate(repo['description'],60)))}</span>
99 ${h.urlify_text(h.desc_stylize(h.truncate(repo['description'],60)))}</span>
95 %else:
100 %else:
96 ${h.truncate(repo['description'],60)}</span>
101 ${h.truncate(repo['description'],60)}</span>
97 %endif
102 %endif
98 </td>
103 </td>
99 ##LAST CHANGE DATE
104 ##LAST CHANGE DATE
100 <td>
105 <td>
101 ${dt.last_change(repo['last_change'])}
106 ${dt.last_change(repo['last_change'])}
102 </td>
107 </td>
103 ##LAST REVISION
108 ##LAST REVISION
104 <td>
109 <td>
105 ${dt.revision(repo['name'],repo['rev'],repo['tip'],repo['author'],repo['last_msg'])}
110 ${dt.revision(repo['name'],repo['rev'],repo['tip'],repo['author'],repo['last_msg'])}
106 </td>
111 </td>
107 ##
112 ##
108 <td title="${repo['contact']}">${h.person(repo['contact'])}</td>
113 <td title="${repo['contact']}">${h.person(repo['contact'])}</td>
109 <td>
114 <td>
110 ${dt.rss(repo['name'])}
115 ${dt.rss(repo['name'])}
111 </td>
116 </td>
112 <td>
117 <td>
113 ${dt.atom(repo['name'])}
118 ${dt.atom(repo['name'])}
114 </td>
119 </td>
115 </tr>
120 </tr>
116 %endfor
121 %endfor
117 </tbody>
122 </tbody>
118 </table>
123 </table>
119 </div>
124 </div>
120 % else:
125 % else:
121 ## lightweight version
126 ## lightweight version
122 <div class="yui-skin-sam" id="repos_list_wrap"></div>
127 <div class="yui-skin-sam" id="repos_list_wrap"></div>
123 <div id="user-paginator" style="padding: 0px 0px 0px 0px"></div>
128 <div id="user-paginator" style="padding: 0px 0px 0px 0px"></div>
124 % endif
129 % endif
125 </div>
130 </div>
126 </div>
131 </div>
127 % if c.visual.lightweight_dashboard is False:
132 % if c.visual.lightweight_dashboard is False:
128 <script>
133 <script>
129 YUD.get('repo_count').innerHTML = ${cnt+1 if cnt else 0};
134 YUD.get('repo_count').innerHTML = ${cnt+1 if cnt else 0};
130
135
131 // groups table sorting
136 // groups table sorting
132 var myColumnDefs = [
137 var myColumnDefs = [
133 {key:"name",label:"${_('Group name')}",sortable:true,
138 {key:"name",label:"${_('Group name')}",sortable:true,
134 sortOptions: { sortFunction: groupNameSort }},
139 sortOptions: { sortFunction: groupNameSort }},
135 {key:"desc",label:"${_('Description')}",sortable:true},
140 {key:"desc",label:"${_('Description')}",sortable:true},
136 ];
141 ];
137
142
138 var myDataSource = new YAHOO.util.DataSource(YUD.get("groups_list"));
143 var myDataSource = new YAHOO.util.DataSource(YUD.get("groups_list"));
139
144
140 myDataSource.responseType = YAHOO.util.DataSource.TYPE_HTMLTABLE;
145 myDataSource.responseType = YAHOO.util.DataSource.TYPE_HTMLTABLE;
141 myDataSource.responseSchema = {
146 myDataSource.responseSchema = {
142 fields: [
147 fields: [
143 {key:"name"},
148 {key:"name"},
144 {key:"desc"},
149 {key:"desc"},
145 ]
150 ]
146 };
151 };
147
152
148 var myDataTable = new YAHOO.widget.DataTable("groups_list_wrap", myColumnDefs, myDataSource,{
153 var myDataTable = new YAHOO.widget.DataTable("groups_list_wrap", myColumnDefs, myDataSource,{
149 sortedBy:{key:"name",dir:"asc"},
154 sortedBy:{key:"name",dir:"asc"},
150 paginator: new YAHOO.widget.Paginator({
155 paginator: new YAHOO.widget.Paginator({
151 rowsPerPage: 50,
156 rowsPerPage: 50,
152 alwaysVisible: false,
157 alwaysVisible: false,
153 template : "{PreviousPageLink} {FirstPageLink} {PageLinks} {LastPageLink} {NextPageLink}",
158 template : "{PreviousPageLink} {FirstPageLink} {PageLinks} {LastPageLink} {NextPageLink}",
154 pageLinks: 5,
159 pageLinks: 5,
155 containerClass: 'pagination-wh',
160 containerClass: 'pagination-wh',
156 currentPageClass: 'pager_curpage',
161 currentPageClass: 'pager_curpage',
157 pageLinkClass: 'pager_link',
162 pageLinkClass: 'pager_link',
158 nextPageLinkLabel: '&gt;',
163 nextPageLinkLabel: '&gt;',
159 previousPageLinkLabel: '&lt;',
164 previousPageLinkLabel: '&lt;',
160 firstPageLinkLabel: '&lt;&lt;',
165 firstPageLinkLabel: '&lt;&lt;',
161 lastPageLinkLabel: '&gt;&gt;',
166 lastPageLinkLabel: '&gt;&gt;',
162 containers:['group-user-paginator']
167 containers:['group-user-paginator']
163 }),
168 }),
164 MSG_SORTASC:"${_('Click to sort ascending')}",
169 MSG_SORTASC:"${_('Click to sort ascending')}",
165 MSG_SORTDESC:"${_('Click to sort descending')}"
170 MSG_SORTDESC:"${_('Click to sort descending')}"
166 });
171 });
167
172
168 // main table sorting
173 // main table sorting
169 var myColumnDefs = [
174 var myColumnDefs = [
170 {key:"menu",label:"",sortable:false,className:"quick_repo_menu hidden"},
175 {key:"menu",label:"",sortable:false,className:"quick_repo_menu hidden"},
171 {key:"name",label:"${_('Name')}",sortable:true,
176 {key:"name",label:"${_('Name')}",sortable:true,
172 sortOptions: { sortFunction: nameSort }},
177 sortOptions: { sortFunction: nameSort }},
173 {key:"desc",label:"${_('Description')}",sortable:true},
178 {key:"desc",label:"${_('Description')}",sortable:true},
174 {key:"last_change",label:"${_('Last Change')}",sortable:true,
179 {key:"last_change",label:"${_('Last Change')}",sortable:true,
175 sortOptions: { sortFunction: ageSort }},
180 sortOptions: { sortFunction: ageSort }},
176 {key:"tip",label:"${_('Tip')}",sortable:true,
181 {key:"tip",label:"${_('Tip')}",sortable:true,
177 sortOptions: { sortFunction: revisionSort }},
182 sortOptions: { sortFunction: revisionSort }},
178 {key:"owner",label:"${_('Owner')}",sortable:true},
183 {key:"owner",label:"${_('Owner')}",sortable:true},
179 {key:"rss",label:"",sortable:false},
184 {key:"rss",label:"",sortable:false},
180 {key:"atom",label:"",sortable:false},
185 {key:"atom",label:"",sortable:false},
181 ];
186 ];
182
187
183 var myDataSource = new YAHOO.util.DataSource(YUD.get("repos_list"));
188 var myDataSource = new YAHOO.util.DataSource(YUD.get("repos_list"));
184
189
185 myDataSource.responseType = YAHOO.util.DataSource.TYPE_HTMLTABLE;
190 myDataSource.responseType = YAHOO.util.DataSource.TYPE_HTMLTABLE;
186
191
187 myDataSource.responseSchema = {
192 myDataSource.responseSchema = {
188 fields: [
193 fields: [
189 {key:"menu"},
194 {key:"menu"},
190 //{key:"raw_name"},
195 //{key:"raw_name"},
191 {key:"name"},
196 {key:"name"},
192 {key:"desc"},
197 {key:"desc"},
193 {key:"last_change"},
198 {key:"last_change"},
194 {key:"tip"},
199 {key:"tip"},
195 {key:"owner"},
200 {key:"owner"},
196 {key:"rss"},
201 {key:"rss"},
197 {key:"atom"},
202 {key:"atom"},
198 ]
203 ]
199 };
204 };
200
205
201 var myDataTable = new YAHOO.widget.DataTable("repos_list_wrap", myColumnDefs, myDataSource,
206 var myDataTable = new YAHOO.widget.DataTable("repos_list_wrap", myColumnDefs, myDataSource,
202 {
207 {
203 sortedBy:{key:"name",dir:"asc"},
208 sortedBy:{key:"name",dir:"asc"},
204 MSG_SORTASC:"${_('Click to sort ascending')}",
209 MSG_SORTASC:"${_('Click to sort ascending')}",
205 MSG_SORTDESC:"${_('Click to sort descending')}",
210 MSG_SORTDESC:"${_('Click to sort descending')}",
206 MSG_EMPTY:"${_('No records found.')}",
211 MSG_EMPTY:"${_('No records found.')}",
207 MSG_ERROR:"${_('Data error.')}",
212 MSG_ERROR:"${_('Data error.')}",
208 MSG_LOADING:"${_('Loading...')}",
213 MSG_LOADING:"${_('Loading...')}",
209 }
214 }
210 );
215 );
211 myDataTable.subscribe('postRenderEvent',function(oArgs) {
216 myDataTable.subscribe('postRenderEvent',function(oArgs) {
212 tooltip_activate();
217 tooltip_activate();
213 quick_repo_menu();
218 quick_repo_menu();
214 var func = function(node){
219 var func = function(node){
215 return node.parentNode.parentNode.parentNode.parentNode;
220 return node.parentNode.parentNode.parentNode.parentNode;
216 }
221 }
217 q_filter('q_filter',YUQ('div.table tr td a.repo_name'),func);
222 q_filter('q_filter',YUQ('div.table tr td a.repo_name'),func);
218 });
223 });
219
224
220 </script>
225 </script>
221 % else:
226 % else:
222 <script>
227 <script>
223 var data = ${c.data|n};
228 var data = ${c.data|n};
224 var myDataSource = new YAHOO.util.DataSource(data);
229 var myDataSource = new YAHOO.util.DataSource(data);
225 myDataSource.responseType = YAHOO.util.DataSource.TYPE_JSON;
230 myDataSource.responseType = YAHOO.util.DataSource.TYPE_JSON;
226
231
227 myDataSource.responseSchema = {
232 myDataSource.responseSchema = {
228 resultsList: "records",
233 resultsList: "records",
229 fields: [
234 fields: [
230 {key:"menu"},
235 {key:"menu"},
231 {key:"raw_name"},
236 {key:"raw_name"},
232 {key:"name"},
237 {key:"name"},
233 {key:"desc"},
238 {key:"desc"},
234 {key:"last_change"},
239 {key:"last_change"},
235 {key:"last_changeset"},
240 {key:"last_changeset"},
236 {key:"owner"},
241 {key:"owner"},
237 {key:"rss"},
242 {key:"rss"},
238 {key:"atom"},
243 {key:"atom"},
239 ]
244 ]
240 };
245 };
241 myDataSource.doBeforeCallback = function(req,raw,res,cb) {
246 myDataSource.doBeforeCallback = function(req,raw,res,cb) {
242 // This is the filter function
247 // This is the filter function
243 var data = res.results || [],
248 var data = res.results || [],
244 filtered = [],
249 filtered = [],
245 i,l;
250 i,l;
246
251
247 if (req) {
252 if (req) {
248 req = req.toLowerCase();
253 req = req.toLowerCase();
249 for (i = 0; i<data.length; i++) {
254 for (i = 0; i<data.length; i++) {
250 var pos = data[i].raw_name.toLowerCase().indexOf(req)
255 var pos = data[i].raw_name.toLowerCase().indexOf(req)
251 if (pos != -1) {
256 if (pos != -1) {
252 filtered.push(data[i]);
257 filtered.push(data[i]);
253 }
258 }
254 }
259 }
255 res.results = filtered;
260 res.results = filtered;
256 }
261 }
257 YUD.get('repo_count').innerHTML = res.results.length;
262 YUD.get('repo_count').innerHTML = res.results.length;
258 return res;
263 return res;
259 }
264 }
260
265
261 // main table sorting
266 // main table sorting
262 var myColumnDefs = [
267 var myColumnDefs = [
263 {key:"menu",label:"",sortable:false,className:"quick_repo_menu hidden"},
268 {key:"menu",label:"",sortable:false,className:"quick_repo_menu hidden"},
264 {key:"name",label:"${_('Name')}",sortable:true,
269 {key:"name",label:"${_('Name')}",sortable:true,
265 sortOptions: { sortFunction: nameSort }},
270 sortOptions: { sortFunction: nameSort }},
266 {key:"desc",label:"${_('Description')}",sortable:true},
271 {key:"desc",label:"${_('Description')}",sortable:true},
267 {key:"last_change",label:"${_('Last Change')}",sortable:true,
272 {key:"last_change",label:"${_('Last Change')}",sortable:true,
268 sortOptions: { sortFunction: ageSort }},
273 sortOptions: { sortFunction: ageSort }},
269 {key:"last_changeset",label:"${_('Tip')}",sortable:true,
274 {key:"last_changeset",label:"${_('Tip')}",sortable:true,
270 sortOptions: { sortFunction: revisionSort }},
275 sortOptions: { sortFunction: revisionSort }},
271 {key:"owner",label:"${_('Owner')}",sortable:true},
276 {key:"owner",label:"${_('Owner')}",sortable:true},
272 {key:"rss",label:"",sortable:false},
277 {key:"rss",label:"",sortable:false},
273 {key:"atom",label:"",sortable:false},
278 {key:"atom",label:"",sortable:false},
274 ];
279 ];
275
280
276 var myDataTable = new YAHOO.widget.DataTable("repos_list_wrap", myColumnDefs, myDataSource,{
281 var myDataTable = new YAHOO.widget.DataTable("repos_list_wrap", myColumnDefs, myDataSource,{
277 sortedBy:{key:"name",dir:"asc"},
282 sortedBy:{key:"name",dir:"asc"},
278 paginator: new YAHOO.widget.Paginator({
283 paginator: new YAHOO.widget.Paginator({
279 rowsPerPage: ${c.visual.lightweight_dashboard_items},
284 rowsPerPage: ${c.visual.lightweight_dashboard_items},
280 alwaysVisible: false,
285 alwaysVisible: false,
281 template : "{PreviousPageLink} {FirstPageLink} {PageLinks} {LastPageLink} {NextPageLink}",
286 template : "{PreviousPageLink} {FirstPageLink} {PageLinks} {LastPageLink} {NextPageLink}",
282 pageLinks: 5,
287 pageLinks: 5,
283 containerClass: 'pagination-wh',
288 containerClass: 'pagination-wh',
284 currentPageClass: 'pager_curpage',
289 currentPageClass: 'pager_curpage',
285 pageLinkClass: 'pager_link',
290 pageLinkClass: 'pager_link',
286 nextPageLinkLabel: '&gt;',
291 nextPageLinkLabel: '&gt;',
287 previousPageLinkLabel: '&lt;',
292 previousPageLinkLabel: '&lt;',
288 firstPageLinkLabel: '&lt;&lt;',
293 firstPageLinkLabel: '&lt;&lt;',
289 lastPageLinkLabel: '&gt;&gt;',
294 lastPageLinkLabel: '&gt;&gt;',
290 containers:['user-paginator']
295 containers:['user-paginator']
291 }),
296 }),
292
297
293 MSG_SORTASC:"${_('Click to sort ascending')}",
298 MSG_SORTASC:"${_('Click to sort ascending')}",
294 MSG_SORTDESC:"${_('Click to sort descending')}",
299 MSG_SORTDESC:"${_('Click to sort descending')}",
295 MSG_EMPTY:"${_('No records found.')}",
300 MSG_EMPTY:"${_('No records found.')}",
296 MSG_ERROR:"${_('Data error.')}",
301 MSG_ERROR:"${_('Data error.')}",
297 MSG_LOADING:"${_('Loading...')}",
302 MSG_LOADING:"${_('Loading...')}",
298 }
303 }
299 );
304 );
300 myDataTable.subscribe('postRenderEvent',function(oArgs) {
305 myDataTable.subscribe('postRenderEvent',function(oArgs) {
301 tooltip_activate();
306 tooltip_activate();
302 quick_repo_menu();
307 quick_repo_menu();
303 });
308 });
304
309
305 var filterTimeout = null;
310 var filterTimeout = null;
306
311
307 updateFilter = function () {
312 updateFilter = function () {
308 // Reset timeout
313 // Reset timeout
309 filterTimeout = null;
314 filterTimeout = null;
310
315
311 // Reset sort
316 // Reset sort
312 var state = myDataTable.getState();
317 var state = myDataTable.getState();
313 state.sortedBy = {key:'name', dir:YAHOO.widget.DataTable.CLASS_ASC};
318 state.sortedBy = {key:'name', dir:YAHOO.widget.DataTable.CLASS_ASC};
314
319
315 // Get filtered data
320 // Get filtered data
316 myDataSource.sendRequest(YUD.get('q_filter').value,{
321 myDataSource.sendRequest(YUD.get('q_filter').value,{
317 success : myDataTable.onDataReturnInitializeTable,
322 success : myDataTable.onDataReturnInitializeTable,
318 failure : myDataTable.onDataReturnInitializeTable,
323 failure : myDataTable.onDataReturnInitializeTable,
319 scope : myDataTable,
324 scope : myDataTable,
320 argument: state
325 argument: state
321 });
326 });
322
327
323 };
328 };
324 YUE.on('q_filter','click',function(){
329 YUE.on('q_filter','click',function(){
325 if(!YUD.hasClass('q_filter', 'loaded')){
330 if(!YUD.hasClass('q_filter', 'loaded')){
326 YUD.get('q_filter').value = '';
331 YUD.get('q_filter').value = '';
327 //TODO: load here full list later to do search within groups
332 //TODO: load here full list later to do search within groups
328 YUD.addClass('q_filter', 'loaded');
333 YUD.addClass('q_filter', 'loaded');
329 }
334 }
330 });
335 });
331
336
332 YUE.on('q_filter','keyup',function (e) {
337 YUE.on('q_filter','keyup',function (e) {
333 clearTimeout(filterTimeout);
338 clearTimeout(filterTimeout);
334 filterTimeout = setTimeout(updateFilter,600);
339 filterTimeout = setTimeout(updateFilter,600);
335 });
340 });
336 </script>
341 </script>
337 % endif
342 % endif
@@ -1,325 +1,326 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 import os
3 import os
4 from rhodecode.lib import vcs
4 from rhodecode.lib import vcs
5
5
6 from rhodecode.model.db import Repository, RepoGroup
6 from rhodecode.model.db import Repository, RepoGroup
7 from rhodecode.tests import *
7 from rhodecode.tests import *
8 from rhodecode.model.repos_group import ReposGroupModel
8 from rhodecode.model.repos_group import ReposGroupModel
9 from rhodecode.model.repo import RepoModel
9 from rhodecode.model.repo import RepoModel
10
10
11
11
12 class TestAdminReposController(TestController):
12 class TestAdminReposController(TestController):
13
13
14 def __make_repo(self):
14 def __make_repo(self):
15 pass
15 pass
16
16
17 def test_index(self):
17 def test_index(self):
18 self.log_user()
18 self.log_user()
19 response = self.app.get(url('repos'))
19 response = self.app.get(url('repos'))
20 # Test response...
20 # Test response...
21
21
22 def test_index_as_xml(self):
22 def test_index_as_xml(self):
23 response = self.app.get(url('formatted_repos', format='xml'))
23 response = self.app.get(url('formatted_repos', format='xml'))
24
24
25 def test_create_hg(self):
25 def test_create_hg(self):
26 self.log_user()
26 self.log_user()
27 repo_name = NEW_HG_REPO
27 repo_name = NEW_HG_REPO
28 description = 'description for newly created repo'
28 description = 'description for newly created repo'
29 response = self.app.post(url('repos'),
29 response = self.app.post(url('repos'),
30 _get_repo_create_params(repo_private=False,
30 _get_repo_create_params(repo_private=False,
31 repo_name=repo_name,
31 repo_name=repo_name,
32 repo_description=description))
32 repo_description=description))
33 self.checkSessionFlash(response,
33 self.checkSessionFlash(response,
34 'created repository %s' % (repo_name))
34 'created repository %s' % (repo_name))
35
35
36 #test if the repo was created in the database
36 #test if the repo was created in the database
37 new_repo = self.Session().query(Repository)\
37 new_repo = self.Session().query(Repository)\
38 .filter(Repository.repo_name == repo_name).one()
38 .filter(Repository.repo_name == repo_name).one()
39
39
40 self.assertEqual(new_repo.repo_name, repo_name)
40 self.assertEqual(new_repo.repo_name, repo_name)
41 self.assertEqual(new_repo.description, description)
41 self.assertEqual(new_repo.description, description)
42
42
43 #test if repository is visible in the list ?
43 #test if repository is visible in the list ?
44 response = response.follow()
44 response = response.follow()
45
45
46 response.mustcontain(repo_name)
46 response.mustcontain(repo_name)
47
47
48 #test if repository was created on filesystem
48 #test if repository was created on filesystem
49 try:
49 try:
50 vcs.get_repo(os.path.join(TESTS_TMP_PATH, repo_name))
50 vcs.get_repo(os.path.join(TESTS_TMP_PATH, repo_name))
51 except:
51 except:
52 self.fail('no repo %s in filesystem' % repo_name)
52 self.fail('no repo %s in filesystem' % repo_name)
53
53
54 def test_create_hg_non_ascii(self):
54 def test_create_hg_non_ascii(self):
55 self.log_user()
55 self.log_user()
56 non_ascii = "Δ…Δ™Ε‚"
56 non_ascii = "Δ…Δ™Ε‚"
57 repo_name = "%s%s" % (NEW_HG_REPO, non_ascii)
57 repo_name = "%s%s" % (NEW_HG_REPO, non_ascii)
58 repo_name_unicode = repo_name.decode('utf8')
58 repo_name_unicode = repo_name.decode('utf8')
59 description = 'description for newly created repo' + non_ascii
59 description = 'description for newly created repo' + non_ascii
60 description_unicode = description.decode('utf8')
60 description_unicode = description.decode('utf8')
61 private = False
61 private = False
62 response = self.app.post(url('repos'),
62 response = self.app.post(url('repos'),
63 _get_repo_create_params(repo_private=False,
63 _get_repo_create_params(repo_private=False,
64 repo_name=repo_name,
64 repo_name=repo_name,
65 repo_description=description))
65 repo_description=description))
66 self.checkSessionFlash(response,
66 self.checkSessionFlash(response,
67 'created repository %s' % (repo_name_unicode))
67 'created repository %s' % (repo_name_unicode))
68
68
69 #test if the repo was created in the database
69 #test if the repo was created in the database
70 new_repo = self.Session().query(Repository)\
70 new_repo = self.Session().query(Repository)\
71 .filter(Repository.repo_name == repo_name_unicode).one()
71 .filter(Repository.repo_name == repo_name_unicode).one()
72
72
73 self.assertEqual(new_repo.repo_name, repo_name_unicode)
73 self.assertEqual(new_repo.repo_name, repo_name_unicode)
74 self.assertEqual(new_repo.description, description_unicode)
74 self.assertEqual(new_repo.description, description_unicode)
75
75
76 #test if repository is visible in the list ?
76 #test if repository is visible in the list ?
77 response = response.follow()
77 response = response.follow()
78
78
79 response.mustcontain(repo_name)
79 response.mustcontain(repo_name)
80
80
81 #test if repository was created on filesystem
81 #test if repository was created on filesystem
82 try:
82 try:
83 vcs.get_repo(os.path.join(TESTS_TMP_PATH, repo_name))
83 vcs.get_repo(os.path.join(TESTS_TMP_PATH, repo_name))
84 except:
84 except:
85 self.fail('no repo %s in filesystem' % repo_name)
85 self.fail('no repo %s in filesystem' % repo_name)
86
86
87 def test_create_hg_in_group(self):
87 def test_create_hg_in_group(self):
88 self.log_user()
88 self.log_user()
89
89
90 ## create GROUP
90 ## create GROUP
91 group_name = 'sometest'
91 group_name = 'sometest'
92 gr = ReposGroupModel().create(group_name=group_name,
92 gr = ReposGroupModel().create(group_name=group_name,
93 group_description='test',)
93 group_description='test',
94 owner=TEST_USER_ADMIN_LOGIN)
94 self.Session().commit()
95 self.Session().commit()
95
96
96 repo_name = 'ingroup'
97 repo_name = 'ingroup'
97 repo_name_full = RepoGroup.url_sep().join([group_name, repo_name])
98 repo_name_full = RepoGroup.url_sep().join([group_name, repo_name])
98 description = 'description for newly created repo'
99 description = 'description for newly created repo'
99 response = self.app.post(url('repos'),
100 response = self.app.post(url('repos'),
100 _get_repo_create_params(repo_private=False,
101 _get_repo_create_params(repo_private=False,
101 repo_name=repo_name,
102 repo_name=repo_name,
102 repo_description=description,
103 repo_description=description,
103 repo_group=gr.group_id,))
104 repo_group=gr.group_id,))
104
105
105 self.checkSessionFlash(response,
106 self.checkSessionFlash(response,
106 'created repository %s' % (repo_name))
107 'created repository %s' % (repo_name))
107
108
108 #test if the repo was created in the database
109 #test if the repo was created in the database
109 new_repo = self.Session().query(Repository)\
110 new_repo = self.Session().query(Repository)\
110 .filter(Repository.repo_name == repo_name_full).one()
111 .filter(Repository.repo_name == repo_name_full).one()
111
112
112 self.assertEqual(new_repo.repo_name, repo_name_full)
113 self.assertEqual(new_repo.repo_name, repo_name_full)
113 self.assertEqual(new_repo.description, description)
114 self.assertEqual(new_repo.description, description)
114
115
115 #test if repository is visible in the list ?
116 #test if repository is visible in the list ?
116 response = response.follow()
117 response = response.follow()
117
118
118 response.mustcontain(repo_name_full)
119 response.mustcontain(repo_name_full)
119
120
120 #test if repository was created on filesystem
121 #test if repository was created on filesystem
121 try:
122 try:
122 vcs.get_repo(os.path.join(TESTS_TMP_PATH, repo_name_full))
123 vcs.get_repo(os.path.join(TESTS_TMP_PATH, repo_name_full))
123 except:
124 except:
124 ReposGroupModel().delete(group_name)
125 ReposGroupModel().delete(group_name)
125 self.Session().commit()
126 self.Session().commit()
126 self.fail('no repo %s in filesystem' % repo_name)
127 self.fail('no repo %s in filesystem' % repo_name)
127
128
128 RepoModel().delete(repo_name_full)
129 RepoModel().delete(repo_name_full)
129 ReposGroupModel().delete(group_name)
130 ReposGroupModel().delete(group_name)
130 self.Session().commit()
131 self.Session().commit()
131
132
132 def test_create_git(self):
133 def test_create_git(self):
133 self.log_user()
134 self.log_user()
134 repo_name = NEW_GIT_REPO
135 repo_name = NEW_GIT_REPO
135 description = 'description for newly created repo'
136 description = 'description for newly created repo'
136
137
137 response = self.app.post(url('repos'),
138 response = self.app.post(url('repos'),
138 _get_repo_create_params(repo_private=False,
139 _get_repo_create_params(repo_private=False,
139 repo_type='git',
140 repo_type='git',
140 repo_name=repo_name,
141 repo_name=repo_name,
141 repo_description=description))
142 repo_description=description))
142 self.checkSessionFlash(response,
143 self.checkSessionFlash(response,
143 'created repository %s' % (repo_name))
144 'created repository %s' % (repo_name))
144
145
145 #test if the repo was created in the database
146 #test if the repo was created in the database
146 new_repo = self.Session().query(Repository)\
147 new_repo = self.Session().query(Repository)\
147 .filter(Repository.repo_name == repo_name).one()
148 .filter(Repository.repo_name == repo_name).one()
148
149
149 self.assertEqual(new_repo.repo_name, repo_name)
150 self.assertEqual(new_repo.repo_name, repo_name)
150 self.assertEqual(new_repo.description, description)
151 self.assertEqual(new_repo.description, description)
151
152
152 #test if repository is visible in the list ?
153 #test if repository is visible in the list ?
153 response = response.follow()
154 response = response.follow()
154
155
155 response.mustcontain(repo_name)
156 response.mustcontain(repo_name)
156
157
157 #test if repository was created on filesystem
158 #test if repository was created on filesystem
158 try:
159 try:
159 vcs.get_repo(os.path.join(TESTS_TMP_PATH, repo_name))
160 vcs.get_repo(os.path.join(TESTS_TMP_PATH, repo_name))
160 except:
161 except:
161 self.fail('no repo %s in filesystem' % repo_name)
162 self.fail('no repo %s in filesystem' % repo_name)
162
163
163 def test_create_git_non_ascii(self):
164 def test_create_git_non_ascii(self):
164 self.log_user()
165 self.log_user()
165 non_ascii = "Δ…Δ™Ε‚"
166 non_ascii = "Δ…Δ™Ε‚"
166 repo_name = "%s%s" % (NEW_GIT_REPO, non_ascii)
167 repo_name = "%s%s" % (NEW_GIT_REPO, non_ascii)
167 repo_name_unicode = repo_name.decode('utf8')
168 repo_name_unicode = repo_name.decode('utf8')
168 description = 'description for newly created repo' + non_ascii
169 description = 'description for newly created repo' + non_ascii
169 description_unicode = description.decode('utf8')
170 description_unicode = description.decode('utf8')
170 private = False
171 private = False
171 response = self.app.post(url('repos'),
172 response = self.app.post(url('repos'),
172 _get_repo_create_params(repo_private=False,
173 _get_repo_create_params(repo_private=False,
173 repo_type='git',
174 repo_type='git',
174 repo_name=repo_name,
175 repo_name=repo_name,
175 repo_description=description))
176 repo_description=description))
176
177
177 self.checkSessionFlash(response,
178 self.checkSessionFlash(response,
178 'created repository %s' % (repo_name_unicode))
179 'created repository %s' % (repo_name_unicode))
179
180
180 #test if the repo was created in the database
181 #test if the repo was created in the database
181 new_repo = self.Session().query(Repository)\
182 new_repo = self.Session().query(Repository)\
182 .filter(Repository.repo_name == repo_name_unicode).one()
183 .filter(Repository.repo_name == repo_name_unicode).one()
183
184
184 self.assertEqual(new_repo.repo_name, repo_name_unicode)
185 self.assertEqual(new_repo.repo_name, repo_name_unicode)
185 self.assertEqual(new_repo.description, description_unicode)
186 self.assertEqual(new_repo.description, description_unicode)
186
187
187 #test if repository is visible in the list ?
188 #test if repository is visible in the list ?
188 response = response.follow()
189 response = response.follow()
189
190
190 response.mustcontain(repo_name)
191 response.mustcontain(repo_name)
191
192
192 #test if repository was created on filesystem
193 #test if repository was created on filesystem
193 try:
194 try:
194 vcs.get_repo(os.path.join(TESTS_TMP_PATH, repo_name))
195 vcs.get_repo(os.path.join(TESTS_TMP_PATH, repo_name))
195 except:
196 except:
196 self.fail('no repo %s in filesystem' % repo_name)
197 self.fail('no repo %s in filesystem' % repo_name)
197
198
198 def test_new(self):
199 def test_new(self):
199 self.log_user()
200 self.log_user()
200 response = self.app.get(url('new_repo'))
201 response = self.app.get(url('new_repo'))
201
202
202 def test_new_as_xml(self):
203 def test_new_as_xml(self):
203 response = self.app.get(url('formatted_new_repo', format='xml'))
204 response = self.app.get(url('formatted_new_repo', format='xml'))
204
205
205 def test_update(self):
206 def test_update(self):
206 response = self.app.put(url('repo', repo_name=HG_REPO))
207 response = self.app.put(url('repo', repo_name=HG_REPO))
207
208
208 def test_update_browser_fakeout(self):
209 def test_update_browser_fakeout(self):
209 response = self.app.post(url('repo', repo_name=HG_REPO),
210 response = self.app.post(url('repo', repo_name=HG_REPO),
210 params=dict(_method='put'))
211 params=dict(_method='put'))
211
212
212 def test_delete_hg(self):
213 def test_delete_hg(self):
213 self.log_user()
214 self.log_user()
214 repo_name = 'vcs_test_new_to_delete'
215 repo_name = 'vcs_test_new_to_delete'
215 description = 'description for newly created repo'
216 description = 'description for newly created repo'
216 response = self.app.post(url('repos'),
217 response = self.app.post(url('repos'),
217 _get_repo_create_params(repo_private=False,
218 _get_repo_create_params(repo_private=False,
218 repo_type='hg',
219 repo_type='hg',
219 repo_name=repo_name,
220 repo_name=repo_name,
220 repo_description=description))
221 repo_description=description))
221
222
222 self.checkSessionFlash(response,
223 self.checkSessionFlash(response,
223 'created repository %s' % (repo_name))
224 'created repository %s' % (repo_name))
224
225
225 #test if the repo was created in the database
226 #test if the repo was created in the database
226 new_repo = self.Session().query(Repository)\
227 new_repo = self.Session().query(Repository)\
227 .filter(Repository.repo_name == repo_name).one()
228 .filter(Repository.repo_name == repo_name).one()
228
229
229 self.assertEqual(new_repo.repo_name, repo_name)
230 self.assertEqual(new_repo.repo_name, repo_name)
230 self.assertEqual(new_repo.description, description)
231 self.assertEqual(new_repo.description, description)
231
232
232 #test if repository is visible in the list ?
233 #test if repository is visible in the list ?
233 response = response.follow()
234 response = response.follow()
234
235
235 response.mustcontain(repo_name)
236 response.mustcontain(repo_name)
236
237
237 #test if repository was created on filesystem
238 #test if repository was created on filesystem
238 try:
239 try:
239 vcs.get_repo(os.path.join(TESTS_TMP_PATH, repo_name))
240 vcs.get_repo(os.path.join(TESTS_TMP_PATH, repo_name))
240 except:
241 except:
241 self.fail('no repo %s in filesystem' % repo_name)
242 self.fail('no repo %s in filesystem' % repo_name)
242
243
243 response = self.app.delete(url('repo', repo_name=repo_name))
244 response = self.app.delete(url('repo', repo_name=repo_name))
244
245
245 self.assertTrue('''deleted repository %s''' % (repo_name) in
246 self.assertTrue('''deleted repository %s''' % (repo_name) in
246 response.session['flash'][0])
247 response.session['flash'][0])
247
248
248 response.follow()
249 response.follow()
249
250
250 #check if repo was deleted from db
251 #check if repo was deleted from db
251 deleted_repo = self.Session().query(Repository)\
252 deleted_repo = self.Session().query(Repository)\
252 .filter(Repository.repo_name == repo_name).scalar()
253 .filter(Repository.repo_name == repo_name).scalar()
253
254
254 self.assertEqual(deleted_repo, None)
255 self.assertEqual(deleted_repo, None)
255
256
256 self.assertEqual(os.path.isdir(os.path.join(TESTS_TMP_PATH, repo_name)),
257 self.assertEqual(os.path.isdir(os.path.join(TESTS_TMP_PATH, repo_name)),
257 False)
258 False)
258
259
259 def test_delete_git(self):
260 def test_delete_git(self):
260 self.log_user()
261 self.log_user()
261 repo_name = 'vcs_test_new_to_delete'
262 repo_name = 'vcs_test_new_to_delete'
262 description = 'description for newly created repo'
263 description = 'description for newly created repo'
263 private = False
264 private = False
264 response = self.app.post(url('repos'),
265 response = self.app.post(url('repos'),
265 _get_repo_create_params(repo_private=False,
266 _get_repo_create_params(repo_private=False,
266 repo_type='git',
267 repo_type='git',
267 repo_name=repo_name,
268 repo_name=repo_name,
268 repo_description=description))
269 repo_description=description))
269
270
270 self.checkSessionFlash(response,
271 self.checkSessionFlash(response,
271 'created repository %s' % (repo_name))
272 'created repository %s' % (repo_name))
272
273
273 #test if the repo was created in the database
274 #test if the repo was created in the database
274 new_repo = self.Session().query(Repository)\
275 new_repo = self.Session().query(Repository)\
275 .filter(Repository.repo_name == repo_name).one()
276 .filter(Repository.repo_name == repo_name).one()
276
277
277 self.assertEqual(new_repo.repo_name, repo_name)
278 self.assertEqual(new_repo.repo_name, repo_name)
278 self.assertEqual(new_repo.description, description)
279 self.assertEqual(new_repo.description, description)
279
280
280 #test if repository is visible in the list ?
281 #test if repository is visible in the list ?
281 response = response.follow()
282 response = response.follow()
282
283
283 response.mustcontain(repo_name)
284 response.mustcontain(repo_name)
284
285
285 #test if repository was created on filesystem
286 #test if repository was created on filesystem
286 try:
287 try:
287 vcs.get_repo(os.path.join(TESTS_TMP_PATH, repo_name))
288 vcs.get_repo(os.path.join(TESTS_TMP_PATH, repo_name))
288 except:
289 except:
289 self.fail('no repo %s in filesystem' % repo_name)
290 self.fail('no repo %s in filesystem' % repo_name)
290
291
291 response = self.app.delete(url('repo', repo_name=repo_name))
292 response = self.app.delete(url('repo', repo_name=repo_name))
292
293
293 self.assertTrue('''deleted repository %s''' % (repo_name) in
294 self.assertTrue('''deleted repository %s''' % (repo_name) in
294 response.session['flash'][0])
295 response.session['flash'][0])
295
296
296 response.follow()
297 response.follow()
297
298
298 #check if repo was deleted from db
299 #check if repo was deleted from db
299 deleted_repo = self.Session().query(Repository)\
300 deleted_repo = self.Session().query(Repository)\
300 .filter(Repository.repo_name == repo_name).scalar()
301 .filter(Repository.repo_name == repo_name).scalar()
301
302
302 self.assertEqual(deleted_repo, None)
303 self.assertEqual(deleted_repo, None)
303
304
304 self.assertEqual(os.path.isdir(os.path.join(TESTS_TMP_PATH, repo_name)),
305 self.assertEqual(os.path.isdir(os.path.join(TESTS_TMP_PATH, repo_name)),
305 False)
306 False)
306
307
307 def test_delete_repo_with_group(self):
308 def test_delete_repo_with_group(self):
308 #TODO:
309 #TODO:
309 pass
310 pass
310
311
311 def test_delete_browser_fakeout(self):
312 def test_delete_browser_fakeout(self):
312 response = self.app.post(url('repo', repo_name=HG_REPO),
313 response = self.app.post(url('repo', repo_name=HG_REPO),
313 params=dict(_method='delete'))
314 params=dict(_method='delete'))
314
315
315 def test_show_hg(self):
316 def test_show_hg(self):
316 self.log_user()
317 self.log_user()
317 response = self.app.get(url('repo', repo_name=HG_REPO))
318 response = self.app.get(url('repo', repo_name=HG_REPO))
318
319
319 def test_show_git(self):
320 def test_show_git(self):
320 self.log_user()
321 self.log_user()
321 response = self.app.get(url('repo', repo_name=GIT_REPO))
322 response = self.app.get(url('repo', repo_name=GIT_REPO))
322
323
323
324
324 def test_edit(self):
325 def test_edit(self):
325 response = self.app.get(url('edit_repo', repo_name=HG_REPO))
326 response = self.app.get(url('edit_repo', repo_name=HG_REPO))
@@ -1,43 +1,51 b''
1 from rhodecode.tests import *
1 from rhodecode.tests import *
2
2
3
3 class TestReposGroupsController(TestController):
4 class TestReposGroupsController(TestController):
4
5
5 def test_index(self):
6 def test_index(self):
7 self.log_user()
6 response = self.app.get(url('repos_groups'))
8 response = self.app.get(url('repos_groups'))
7 # Test response...
9 response.mustcontain('There are no repositories groups yet')
8
10
9 def test_index_as_xml(self):
11 # def test_index_as_xml(self):
10 response = self.app.get(url('formatted_repos_groups', format='xml'))
12 # response = self.app.get(url('formatted_repos_groups', format='xml'))
11
13 #
12 def test_create(self):
14 # def test_create(self):
13 response = self.app.post(url('repos_groups'))
15 # response = self.app.post(url('repos_groups'))
14
16
15 def test_new(self):
17 def test_new(self):
18 self.log_user()
16 response = self.app.get(url('new_repos_group'))
19 response = self.app.get(url('new_repos_group'))
17
20
18 def test_new_as_xml(self):
21 def test_new_by_regular_user(self):
19 response = self.app.get(url('formatted_new_repos_group', format='xml'))
22 self.log_user(TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)
20
23 response = self.app.get(url('new_repos_group'), status=403)
21 def test_update(self):
24 #
22 response = self.app.put(url('repos_group', id=1))
25 # def test_new_as_xml(self):
23
26 # response = self.app.get(url('formatted_new_repos_group', format='xml'))
24 def test_update_browser_fakeout(self):
27 #
25 response = self.app.post(url('repos_group', id=1), params=dict(_method='put'))
28 # def test_update(self):
26
29 # response = self.app.put(url('repos_group', group_name=1))
27 def test_delete(self):
30 #
28 response = self.app.delete(url('repos_group', id=1))
31 # def test_update_browser_fakeout(self):
29
32 # response = self.app.post(url('repos_group', group_name=1), params=dict(_method='put'))
30 def test_delete_browser_fakeout(self):
33 #
31 response = self.app.post(url('repos_group', id=1), params=dict(_method='delete'))
34 # def test_delete(self):
32
35 # self.log_user()
33 def test_show(self):
36 # response = self.app.delete(url('repos_group', group_name=1))
34 response = self.app.get(url('repos_group', id=1))
37 #
35
38 # def test_delete_browser_fakeout(self):
36 def test_show_as_xml(self):
39 # response = self.app.post(url('repos_group', group_name=1), params=dict(_method='delete'))
37 response = self.app.get(url('formatted_repos_group', id=1, format='xml'))
40 #
38
41 # def test_show(self):
39 def test_edit(self):
42 # response = self.app.get(url('repos_group', group_name=1))
40 response = self.app.get(url('edit_repos_group', id=1))
43 #
41
44 # def test_show_as_xml(self):
42 def test_edit_as_xml(self):
45 # response = self.app.get(url('formatted_repos_group', group_name=1, format='xml'))
43 response = self.app.get(url('formatted_edit_repos_group', id=1, format='xml'))
46 #
47 # def test_edit(self):
48 # response = self.app.get(url('edit_repos_group', group_name=1))
49 #
50 # def test_edit_as_xml(self):
51 # response = self.app.get(url('formatted_edit_repos_group', group_name=1, format='xml'))
@@ -1,120 +1,120 b''
1 import os
1 import os
2 import unittest
2 import unittest
3 import functools
3 import functools
4 from rhodecode.tests import *
4 from rhodecode.tests import *
5
5
6
6
7 from rhodecode.model.repos_group import ReposGroupModel
7 from rhodecode.model.repos_group import ReposGroupModel
8 from rhodecode.model.repo import RepoModel
8 from rhodecode.model.repo import RepoModel
9 from rhodecode.model.db import RepoGroup, Repository, User
9 from rhodecode.model.db import RepoGroup, Repository, User
10 from rhodecode.model.user import UserModel
10 from rhodecode.model.user import UserModel
11
11
12 from rhodecode.lib.auth import AuthUser
12 from rhodecode.lib.auth import AuthUser
13 from rhodecode.model.meta import Session
13 from rhodecode.model.meta import Session
14
14
15
15
16 def _make_group(path, desc='desc', parent_id=None,
16 def _make_group(path, desc='desc', parent_id=None,
17 skip_if_exists=False):
17 skip_if_exists=False):
18
18
19 gr = RepoGroup.get_by_group_name(path)
19 gr = RepoGroup.get_by_group_name(path)
20 if gr and skip_if_exists:
20 if gr and skip_if_exists:
21 return gr
21 return gr
22 if isinstance(parent_id, RepoGroup):
22 if isinstance(parent_id, RepoGroup):
23 parent_id = parent_id.group_id
23 parent_id = parent_id.group_id
24 gr = ReposGroupModel().create(path, desc, parent_id)
24 gr = ReposGroupModel().create(path, desc, TEST_USER_ADMIN_LOGIN, parent_id)
25 return gr
25 return gr
26
26
27
27
28 def _make_repo(name, repos_group=None, repo_type='hg', private=False):
28 def _make_repo(name, repos_group=None, repo_type='hg', private=False):
29 return RepoModel().create_repo(name, repo_type, 'desc',
29 return RepoModel().create_repo(name, repo_type, 'desc',
30 TEST_USER_ADMIN_LOGIN,
30 TEST_USER_ADMIN_LOGIN,
31 repos_group=repos_group,
31 repos_group=repos_group,
32 private=private)
32 private=private)
33
33
34
34
35 def _destroy_project_tree(test_u1_id):
35 def _destroy_project_tree(test_u1_id):
36 Session.remove()
36 Session.remove()
37 repos_group = RepoGroup.get_by_group_name(group_name='g0')
37 repos_group = RepoGroup.get_by_group_name(group_name='g0')
38 for el in reversed(repos_group.recursive_groups_and_repos()):
38 for el in reversed(repos_group.recursive_groups_and_repos()):
39 if isinstance(el, Repository):
39 if isinstance(el, Repository):
40 RepoModel().delete(el)
40 RepoModel().delete(el)
41 elif isinstance(el, RepoGroup):
41 elif isinstance(el, RepoGroup):
42 ReposGroupModel().delete(el, force_delete=True)
42 ReposGroupModel().delete(el, force_delete=True)
43
43
44 u = User.get(test_u1_id)
44 u = User.get(test_u1_id)
45 Session().delete(u)
45 Session().delete(u)
46 Session().commit()
46 Session().commit()
47
47
48
48
49 def _create_project_tree():
49 def _create_project_tree():
50 """
50 """
51 Creates a tree of groups and repositories to test permissions
51 Creates a tree of groups and repositories to test permissions
52
52
53 structure
53 structure
54 [g0] - group `g0` with 3 subgroups
54 [g0] - group `g0` with 3 subgroups
55 |
55 |
56 |__[g0_1] group g0_1 with 2 groups 0 repos
56 |__[g0_1] group g0_1 with 2 groups 0 repos
57 | |
57 | |
58 | |__[g0_1_1] group g0_1_1 with 1 group 2 repos
58 | |__[g0_1_1] group g0_1_1 with 1 group 2 repos
59 | | |__<g0/g0_1/g0_1_1/g0_1_1_r1>
59 | | |__<g0/g0_1/g0_1_1/g0_1_1_r1>
60 | | |__<g0/g0_1/g0_1_1/g0_1_1_r2>
60 | | |__<g0/g0_1/g0_1_1/g0_1_1_r2>
61 | |__<g0/g0_1/g0_1_r1>
61 | |__<g0/g0_1/g0_1_r1>
62 |
62 |
63 |__[g0_2] 2 repos
63 |__[g0_2] 2 repos
64 | |
64 | |
65 | |__<g0/g0_2/g0_2_r1>
65 | |__<g0/g0_2/g0_2_r1>
66 | |__<g0/g0_2/g0_2_r2>
66 | |__<g0/g0_2/g0_2_r2>
67 |
67 |
68 |__[g0_3] 1 repo
68 |__[g0_3] 1 repo
69 |
69 |
70 |_<g0/g0_3/g0_3_r1>
70 |_<g0/g0_3/g0_3_r1>
71 |_<g0/g0_3/g0_3_r2_private>
71 |_<g0/g0_3/g0_3_r2_private>
72
72
73 """
73 """
74 test_u1 = UserModel().create_or_update(
74 test_u1 = UserModel().create_or_update(
75 username=u'test_u1', password=u'qweqwe',
75 username=u'test_u1', password=u'qweqwe',
76 email=u'test_u1@rhodecode.org', firstname=u'test_u1', lastname=u'test_u1'
76 email=u'test_u1@rhodecode.org', firstname=u'test_u1', lastname=u'test_u1'
77 )
77 )
78 g0 = _make_group('g0')
78 g0 = _make_group('g0')
79 g0_1 = _make_group('g0_1', parent_id=g0)
79 g0_1 = _make_group('g0_1', parent_id=g0)
80 g0_1_1 = _make_group('g0_1_1', parent_id=g0_1)
80 g0_1_1 = _make_group('g0_1_1', parent_id=g0_1)
81 g0_1_1_r1 = _make_repo('g0/g0_1/g0_1_1/g0_1_1_r1', repos_group=g0_1_1)
81 g0_1_1_r1 = _make_repo('g0/g0_1/g0_1_1/g0_1_1_r1', repos_group=g0_1_1)
82 g0_1_1_r2 = _make_repo('g0/g0_1/g0_1_1/g0_1_1_r2', repos_group=g0_1_1)
82 g0_1_1_r2 = _make_repo('g0/g0_1/g0_1_1/g0_1_1_r2', repos_group=g0_1_1)
83 g0_1_r1 = _make_repo('g0/g0_1/g0_1_r1', repos_group=g0_1)
83 g0_1_r1 = _make_repo('g0/g0_1/g0_1_r1', repos_group=g0_1)
84 g0_2 = _make_group('g0_2', parent_id=g0)
84 g0_2 = _make_group('g0_2', parent_id=g0)
85 g0_2_r1 = _make_repo('g0/g0_2/g0_2_r1', repos_group=g0_2)
85 g0_2_r1 = _make_repo('g0/g0_2/g0_2_r1', repos_group=g0_2)
86 g0_2_r2 = _make_repo('g0/g0_2/g0_2_r2', repos_group=g0_2)
86 g0_2_r2 = _make_repo('g0/g0_2/g0_2_r2', repos_group=g0_2)
87 g0_3 = _make_group('g0_3', parent_id=g0)
87 g0_3 = _make_group('g0_3', parent_id=g0)
88 g0_3_r1 = _make_repo('g0/g0_3/g0_3_r1', repos_group=g0_3)
88 g0_3_r1 = _make_repo('g0/g0_3/g0_3_r1', repos_group=g0_3)
89 g0_3_r2_private = _make_repo('g0/g0_3/g0_3_r1_private', repos_group=g0_3,
89 g0_3_r2_private = _make_repo('g0/g0_3/g0_3_r1_private', repos_group=g0_3,
90 private=True)
90 private=True)
91 return test_u1
91 return test_u1
92
92
93
93
94 def expected_count(group_name, objects=False):
94 def expected_count(group_name, objects=False):
95 repos_group = RepoGroup.get_by_group_name(group_name=group_name)
95 repos_group = RepoGroup.get_by_group_name(group_name=group_name)
96 objs = repos_group.recursive_groups_and_repos()
96 objs = repos_group.recursive_groups_and_repos()
97 if objects:
97 if objects:
98 return objs
98 return objs
99 return len(objs)
99 return len(objs)
100
100
101
101
102 def _check_expected_count(items, repo_items, expected):
102 def _check_expected_count(items, repo_items, expected):
103 should_be = len(items + repo_items)
103 should_be = len(items + repo_items)
104 there_are = len(expected)
104 there_are = len(expected)
105 assert should_be == there_are, ('%s != %s' % ((items + repo_items), expected))
105 assert should_be == there_are, ('%s != %s' % ((items + repo_items), expected))
106
106
107
107
108 def check_tree_perms(obj_name, repo_perm, prefix, expected_perm):
108 def check_tree_perms(obj_name, repo_perm, prefix, expected_perm):
109 assert repo_perm == expected_perm, ('obj:`%s` got perm:`%s` should:`%s`'
109 assert repo_perm == expected_perm, ('obj:`%s` got perm:`%s` should:`%s`'
110 % (obj_name, repo_perm, expected_perm))
110 % (obj_name, repo_perm, expected_perm))
111
111
112
112
113 def _get_perms(filter_='', recursive=True, key=None, test_u1_id=None):
113 def _get_perms(filter_='', recursive=True, key=None, test_u1_id=None):
114 test_u1 = AuthUser(user_id=test_u1_id)
114 test_u1 = AuthUser(user_id=test_u1_id)
115 for k, v in test_u1.permissions[key].items():
115 for k, v in test_u1.permissions[key].items():
116 if recursive and k.startswith(filter_):
116 if recursive and k.startswith(filter_):
117 yield k, v
117 yield k, v
118 elif not recursive:
118 elif not recursive:
119 if k == filter_:
119 if k == filter_:
120 yield k, v
120 yield k, v
@@ -1,165 +1,165 b''
1 import os
1 import os
2 import unittest
2 import unittest
3 from rhodecode.tests import *
3 from rhodecode.tests import *
4
4
5 from rhodecode.model.repos_group import ReposGroupModel
5 from rhodecode.model.repos_group import ReposGroupModel
6 from rhodecode.model.repo import RepoModel
6 from rhodecode.model.repo import RepoModel
7 from rhodecode.model.db import RepoGroup, User
7 from rhodecode.model.db import RepoGroup, User
8 from rhodecode.model.meta import Session
8 from rhodecode.model.meta import Session
9 from sqlalchemy.exc import IntegrityError
9 from sqlalchemy.exc import IntegrityError
10
10
11
11
12 def _make_group(path, desc='desc', parent_id=None,
12 def _make_group(path, desc='desc', parent_id=None,
13 skip_if_exists=False):
13 skip_if_exists=False):
14
14
15 gr = RepoGroup.get_by_group_name(path)
15 gr = RepoGroup.get_by_group_name(path)
16 if gr and skip_if_exists:
16 if gr and skip_if_exists:
17 return gr
17 return gr
18 if isinstance(parent_id, RepoGroup):
18 if isinstance(parent_id, RepoGroup):
19 parent_id = parent_id.group_id
19 parent_id = parent_id.group_id
20 gr = ReposGroupModel().create(path, desc, parent_id)
20 gr = ReposGroupModel().create(path, desc, TEST_USER_ADMIN_LOGIN, parent_id)
21 return gr
21 return gr
22
22
23
23
24 class TestReposGroups(unittest.TestCase):
24 class TestReposGroups(unittest.TestCase):
25
25
26 def setUp(self):
26 def setUp(self):
27 self.g1 = _make_group('test1', skip_if_exists=True)
27 self.g1 = _make_group('test1', skip_if_exists=True)
28 Session().commit()
28 Session().commit()
29 self.g2 = _make_group('test2', skip_if_exists=True)
29 self.g2 = _make_group('test2', skip_if_exists=True)
30 Session().commit()
30 Session().commit()
31 self.g3 = _make_group('test3', skip_if_exists=True)
31 self.g3 = _make_group('test3', skip_if_exists=True)
32 Session().commit()
32 Session().commit()
33
33
34 def tearDown(self):
34 def tearDown(self):
35 print 'out'
35 print 'out'
36
36
37 def __check_path(self, *path):
37 def __check_path(self, *path):
38 """
38 """
39 Checks the path for existance !
39 Checks the path for existance !
40 """
40 """
41 path = [TESTS_TMP_PATH] + list(path)
41 path = [TESTS_TMP_PATH] + list(path)
42 path = os.path.join(*path)
42 path = os.path.join(*path)
43 return os.path.isdir(path)
43 return os.path.isdir(path)
44
44
45 def _check_folders(self):
45 def _check_folders(self):
46 print os.listdir(TESTS_TMP_PATH)
46 print os.listdir(TESTS_TMP_PATH)
47
47
48 def __delete_group(self, id_):
48 def __delete_group(self, id_):
49 ReposGroupModel().delete(id_)
49 ReposGroupModel().delete(id_)
50
50
51 def __update_group(self, id_, path, desc='desc', parent_id=None):
51 def __update_group(self, id_, path, desc='desc', parent_id=None):
52 form_data = dict(
52 form_data = dict(
53 group_name=path,
53 group_name=path,
54 group_description=desc,
54 group_description=desc,
55 group_parent_id=parent_id,
55 group_parent_id=parent_id,
56 perms_updates=[],
56 perms_updates=[],
57 perms_new=[],
57 perms_new=[],
58 enable_locking=False,
58 enable_locking=False,
59 recursive=False
59 recursive=False
60 )
60 )
61 gr = ReposGroupModel().update(id_, form_data)
61 gr = ReposGroupModel().update(id_, form_data)
62 return gr
62 return gr
63
63
64 def test_create_group(self):
64 def test_create_group(self):
65 g = _make_group('newGroup')
65 g = _make_group('newGroup')
66 self.assertEqual(g.full_path, 'newGroup')
66 self.assertEqual(g.full_path, 'newGroup')
67
67
68 self.assertTrue(self.__check_path('newGroup'))
68 self.assertTrue(self.__check_path('newGroup'))
69
69
70 def test_create_same_name_group(self):
70 def test_create_same_name_group(self):
71 self.assertRaises(IntegrityError, lambda: _make_group('newGroup'))
71 self.assertRaises(IntegrityError, lambda: _make_group('newGroup'))
72 Session().rollback()
72 Session().rollback()
73
73
74 def test_same_subgroup(self):
74 def test_same_subgroup(self):
75 sg1 = _make_group('sub1', parent_id=self.g1.group_id)
75 sg1 = _make_group('sub1', parent_id=self.g1.group_id)
76 self.assertEqual(sg1.parent_group, self.g1)
76 self.assertEqual(sg1.parent_group, self.g1)
77 self.assertEqual(sg1.full_path, 'test1/sub1')
77 self.assertEqual(sg1.full_path, 'test1/sub1')
78 self.assertTrue(self.__check_path('test1', 'sub1'))
78 self.assertTrue(self.__check_path('test1', 'sub1'))
79
79
80 ssg1 = _make_group('subsub1', parent_id=sg1.group_id)
80 ssg1 = _make_group('subsub1', parent_id=sg1.group_id)
81 self.assertEqual(ssg1.parent_group, sg1)
81 self.assertEqual(ssg1.parent_group, sg1)
82 self.assertEqual(ssg1.full_path, 'test1/sub1/subsub1')
82 self.assertEqual(ssg1.full_path, 'test1/sub1/subsub1')
83 self.assertTrue(self.__check_path('test1', 'sub1', 'subsub1'))
83 self.assertTrue(self.__check_path('test1', 'sub1', 'subsub1'))
84
84
85 def test_remove_group(self):
85 def test_remove_group(self):
86 sg1 = _make_group('deleteme')
86 sg1 = _make_group('deleteme')
87 self.__delete_group(sg1.group_id)
87 self.__delete_group(sg1.group_id)
88
88
89 self.assertEqual(RepoGroup.get(sg1.group_id), None)
89 self.assertEqual(RepoGroup.get(sg1.group_id), None)
90 self.assertFalse(self.__check_path('deteteme'))
90 self.assertFalse(self.__check_path('deteteme'))
91
91
92 sg1 = _make_group('deleteme', parent_id=self.g1.group_id)
92 sg1 = _make_group('deleteme', parent_id=self.g1.group_id)
93 self.__delete_group(sg1.group_id)
93 self.__delete_group(sg1.group_id)
94
94
95 self.assertEqual(RepoGroup.get(sg1.group_id), None)
95 self.assertEqual(RepoGroup.get(sg1.group_id), None)
96 self.assertFalse(self.__check_path('test1', 'deteteme'))
96 self.assertFalse(self.__check_path('test1', 'deteteme'))
97
97
98 def test_rename_single_group(self):
98 def test_rename_single_group(self):
99 sg1 = _make_group('initial')
99 sg1 = _make_group('initial')
100
100
101 new_sg1 = self.__update_group(sg1.group_id, 'after')
101 new_sg1 = self.__update_group(sg1.group_id, 'after')
102 self.assertTrue(self.__check_path('after'))
102 self.assertTrue(self.__check_path('after'))
103 self.assertEqual(RepoGroup.get_by_group_name('initial'), None)
103 self.assertEqual(RepoGroup.get_by_group_name('initial'), None)
104
104
105 def test_update_group_parent(self):
105 def test_update_group_parent(self):
106
106
107 sg1 = _make_group('initial', parent_id=self.g1.group_id)
107 sg1 = _make_group('initial', parent_id=self.g1.group_id)
108
108
109 new_sg1 = self.__update_group(sg1.group_id, 'after', parent_id=self.g1.group_id)
109 new_sg1 = self.__update_group(sg1.group_id, 'after', parent_id=self.g1.group_id)
110 self.assertTrue(self.__check_path('test1', 'after'))
110 self.assertTrue(self.__check_path('test1', 'after'))
111 self.assertEqual(RepoGroup.get_by_group_name('test1/initial'), None)
111 self.assertEqual(RepoGroup.get_by_group_name('test1/initial'), None)
112
112
113 new_sg1 = self.__update_group(sg1.group_id, 'after', parent_id=self.g3.group_id)
113 new_sg1 = self.__update_group(sg1.group_id, 'after', parent_id=self.g3.group_id)
114 self.assertTrue(self.__check_path('test3', 'after'))
114 self.assertTrue(self.__check_path('test3', 'after'))
115 self.assertEqual(RepoGroup.get_by_group_name('test3/initial'), None)
115 self.assertEqual(RepoGroup.get_by_group_name('test3/initial'), None)
116
116
117 new_sg1 = self.__update_group(sg1.group_id, 'hello')
117 new_sg1 = self.__update_group(sg1.group_id, 'hello')
118 self.assertTrue(self.__check_path('hello'))
118 self.assertTrue(self.__check_path('hello'))
119
119
120 self.assertEqual(RepoGroup.get_by_group_name('hello'), new_sg1)
120 self.assertEqual(RepoGroup.get_by_group_name('hello'), new_sg1)
121
121
122 def test_subgrouping_with_repo(self):
122 def test_subgrouping_with_repo(self):
123
123
124 g1 = _make_group('g1')
124 g1 = _make_group('g1')
125 g2 = _make_group('g2')
125 g2 = _make_group('g2')
126
126
127 # create new repo
127 # create new repo
128 form_data = _get_repo_create_params(repo_name='john')
128 form_data = _get_repo_create_params(repo_name='john')
129 cur_user = User.get_by_username(TEST_USER_ADMIN_LOGIN)
129 cur_user = User.get_by_username(TEST_USER_ADMIN_LOGIN)
130 r = RepoModel().create(form_data, cur_user)
130 r = RepoModel().create(form_data, cur_user)
131
131
132 self.assertEqual(r.repo_name, 'john')
132 self.assertEqual(r.repo_name, 'john')
133
133
134 # put repo into group
134 # put repo into group
135 form_data = form_data
135 form_data = form_data
136 form_data['repo_group'] = g1.group_id
136 form_data['repo_group'] = g1.group_id
137 form_data['perms_new'] = []
137 form_data['perms_new'] = []
138 form_data['perms_updates'] = []
138 form_data['perms_updates'] = []
139 RepoModel().update(r.repo_name, **form_data)
139 RepoModel().update(r.repo_name, **form_data)
140 self.assertEqual(r.repo_name, 'g1/john')
140 self.assertEqual(r.repo_name, 'g1/john')
141
141
142 self.__update_group(g1.group_id, 'g1', parent_id=g2.group_id)
142 self.__update_group(g1.group_id, 'g1', parent_id=g2.group_id)
143 self.assertTrue(self.__check_path('g2', 'g1'))
143 self.assertTrue(self.__check_path('g2', 'g1'))
144
144
145 # test repo
145 # test repo
146 self.assertEqual(r.repo_name, RepoGroup.url_sep().join(['g2', 'g1',
146 self.assertEqual(r.repo_name, RepoGroup.url_sep().join(['g2', 'g1',
147 r.just_name]))
147 r.just_name]))
148
148
149 def test_move_to_root(self):
149 def test_move_to_root(self):
150 g1 = _make_group('t11')
150 g1 = _make_group('t11')
151 Session().commit()
151 Session().commit()
152 g2 = _make_group('t22', parent_id=g1.group_id)
152 g2 = _make_group('t22', parent_id=g1.group_id)
153 Session().commit()
153 Session().commit()
154
154
155 self.assertEqual(g2.full_path, 't11/t22')
155 self.assertEqual(g2.full_path, 't11/t22')
156 self.assertTrue(self.__check_path('t11', 't22'))
156 self.assertTrue(self.__check_path('t11', 't22'))
157
157
158 g2 = self.__update_group(g2.group_id, 'g22', parent_id=None)
158 g2 = self.__update_group(g2.group_id, 'g22', parent_id=None)
159 Session().commit()
159 Session().commit()
160
160
161 self.assertEqual(g2.group_name, 'g22')
161 self.assertEqual(g2.group_name, 'g22')
162 # we moved out group from t1 to '' so it's full path should be 'g2'
162 # we moved out group from t1 to '' so it's full path should be 'g2'
163 self.assertEqual(g2.full_path, 'g22')
163 self.assertEqual(g2.full_path, 'g22')
164 self.assertFalse(self.__check_path('t11', 't22'))
164 self.assertFalse(self.__check_path('t11', 't22'))
165 self.assertTrue(self.__check_path('g22'))
165 self.assertTrue(self.__check_path('g22'))
@@ -1,247 +1,249 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 import unittest
2 import unittest
3 import formencode
3 import formencode
4
4
5 from rhodecode.tests import *
5 from rhodecode.tests import *
6
6
7 from rhodecode.model import validators as v
7 from rhodecode.model import validators as v
8 from rhodecode.model.users_group import UsersGroupModel
8 from rhodecode.model.users_group import UsersGroupModel
9
9
10 from rhodecode.model.meta import Session
10 from rhodecode.model.meta import Session
11 from rhodecode.model.repos_group import ReposGroupModel
11 from rhodecode.model.repos_group import ReposGroupModel
12 from rhodecode.config.routing import ADMIN_PREFIX
12 from rhodecode.config.routing import ADMIN_PREFIX
13 from rhodecode.model.db import ChangesetStatus, Repository
13 from rhodecode.model.db import ChangesetStatus, Repository
14 from rhodecode.model.changeset_status import ChangesetStatusModel
14 from rhodecode.model.changeset_status import ChangesetStatusModel
15 from rhodecode.model.comment import ChangesetCommentsModel
15 from rhodecode.model.comment import ChangesetCommentsModel
16
16
17
17
18 class TestReposGroups(unittest.TestCase):
18 class TestReposGroups(unittest.TestCase):
19
19
20 def setUp(self):
20 def setUp(self):
21 pass
21 pass
22
22
23 def tearDown(self):
23 def tearDown(self):
24 pass
24 pass
25
25
26 def test_Message_extractor(self):
26 def test_Message_extractor(self):
27 validator = v.ValidUsername()
27 validator = v.ValidUsername()
28 self.assertRaises(formencode.Invalid, validator.to_python, 'default')
28 self.assertRaises(formencode.Invalid, validator.to_python, 'default')
29
29
30 class StateObj(object):
30 class StateObj(object):
31 pass
31 pass
32
32
33 self.assertRaises(formencode.Invalid,
33 self.assertRaises(formencode.Invalid,
34 validator.to_python, 'default', StateObj)
34 validator.to_python, 'default', StateObj)
35
35
36 def test_ValidUsername(self):
36 def test_ValidUsername(self):
37 validator = v.ValidUsername()
37 validator = v.ValidUsername()
38
38
39 self.assertRaises(formencode.Invalid, validator.to_python, 'default')
39 self.assertRaises(formencode.Invalid, validator.to_python, 'default')
40 self.assertRaises(formencode.Invalid, validator.to_python, 'new_user')
40 self.assertRaises(formencode.Invalid, validator.to_python, 'new_user')
41 self.assertRaises(formencode.Invalid, validator.to_python, '.,')
41 self.assertRaises(formencode.Invalid, validator.to_python, '.,')
42 self.assertRaises(formencode.Invalid, validator.to_python,
42 self.assertRaises(formencode.Invalid, validator.to_python,
43 TEST_USER_ADMIN_LOGIN)
43 TEST_USER_ADMIN_LOGIN)
44 self.assertEqual('test', validator.to_python('test'))
44 self.assertEqual('test', validator.to_python('test'))
45
45
46 validator = v.ValidUsername(edit=True, old_data={'user_id': 1})
46 validator = v.ValidUsername(edit=True, old_data={'user_id': 1})
47
47
48 def test_ValidRepoUser(self):
48 def test_ValidRepoUser(self):
49 validator = v.ValidRepoUser()
49 validator = v.ValidRepoUser()
50 self.assertRaises(formencode.Invalid, validator.to_python, 'nouser')
50 self.assertRaises(formencode.Invalid, validator.to_python, 'nouser')
51 self.assertEqual(TEST_USER_ADMIN_LOGIN,
51 self.assertEqual(TEST_USER_ADMIN_LOGIN,
52 validator.to_python(TEST_USER_ADMIN_LOGIN))
52 validator.to_python(TEST_USER_ADMIN_LOGIN))
53
53
54 def test_ValidUsersGroup(self):
54 def test_ValidUsersGroup(self):
55 validator = v.ValidUsersGroup()
55 validator = v.ValidUsersGroup()
56 self.assertRaises(formencode.Invalid, validator.to_python, 'default')
56 self.assertRaises(formencode.Invalid, validator.to_python, 'default')
57 self.assertRaises(formencode.Invalid, validator.to_python, '.,')
57 self.assertRaises(formencode.Invalid, validator.to_python, '.,')
58
58
59 gr = UsersGroupModel().create('test')
59 gr = UsersGroupModel().create('test')
60 gr2 = UsersGroupModel().create('tes2')
60 gr2 = UsersGroupModel().create('tes2')
61 Session.commit()
61 Session.commit()
62 self.assertRaises(formencode.Invalid, validator.to_python, 'test')
62 self.assertRaises(formencode.Invalid, validator.to_python, 'test')
63 assert gr.users_group_id != None
63 assert gr.users_group_id != None
64 validator = v.ValidUsersGroup(edit=True,
64 validator = v.ValidUsersGroup(edit=True,
65 old_data={'users_group_id':
65 old_data={'users_group_id':
66 gr2.users_group_id})
66 gr2.users_group_id})
67
67
68 self.assertRaises(formencode.Invalid, validator.to_python, 'test')
68 self.assertRaises(formencode.Invalid, validator.to_python, 'test')
69 self.assertRaises(formencode.Invalid, validator.to_python, 'TesT')
69 self.assertRaises(formencode.Invalid, validator.to_python, 'TesT')
70 self.assertRaises(formencode.Invalid, validator.to_python, 'TEST')
70 self.assertRaises(formencode.Invalid, validator.to_python, 'TEST')
71 UsersGroupModel().delete(gr)
71 UsersGroupModel().delete(gr)
72 UsersGroupModel().delete(gr2)
72 UsersGroupModel().delete(gr2)
73 Session.commit()
73 Session.commit()
74
74
75 def test_ValidReposGroup(self):
75 def test_ValidReposGroup(self):
76 validator = v.ValidReposGroup()
76 validator = v.ValidReposGroup()
77 model = ReposGroupModel()
77 model = ReposGroupModel()
78 self.assertRaises(formencode.Invalid, validator.to_python,
78 self.assertRaises(formencode.Invalid, validator.to_python,
79 {'group_name': HG_REPO, })
79 {'group_name': HG_REPO, })
80 gr = model.create(group_name='test_gr', group_description='desc',
80 gr = model.create(group_name='test_gr', group_description='desc',
81 parent=None,
81 parent=None,
82 just_db=True)
82 just_db=True,
83 owner=TEST_USER_ADMIN_LOGIN)
83 self.assertRaises(formencode.Invalid,
84 self.assertRaises(formencode.Invalid,
84 validator.to_python, {'group_name': gr.group_name, })
85 validator.to_python, {'group_name': gr.group_name, })
85
86
86 validator = v.ValidReposGroup(edit=True,
87 validator = v.ValidReposGroup(edit=True,
87 old_data={'group_id': gr.group_id})
88 old_data={'group_id': gr.group_id})
88 self.assertRaises(formencode.Invalid,
89 self.assertRaises(formencode.Invalid,
89 validator.to_python, {
90 validator.to_python, {
90 'group_name': gr.group_name + 'n',
91 'group_name': gr.group_name + 'n',
91 'group_parent_id': gr.group_id
92 'group_parent_id': gr.group_id
92 })
93 })
93 model.delete(gr)
94 model.delete(gr)
94
95
95 def test_ValidPassword(self):
96 def test_ValidPassword(self):
96 validator = v.ValidPassword()
97 validator = v.ValidPassword()
97 self.assertEqual('lol', validator.to_python('lol'))
98 self.assertEqual('lol', validator.to_python('lol'))
98 self.assertEqual(None, validator.to_python(None))
99 self.assertEqual(None, validator.to_python(None))
99 self.assertRaises(formencode.Invalid, validator.to_python, 'Δ…Δ‡ΕΌΕΊ')
100 self.assertRaises(formencode.Invalid, validator.to_python, 'Δ…Δ‡ΕΌΕΊ')
100
101
101 def test_ValidPasswordsMatch(self):
102 def test_ValidPasswordsMatch(self):
102 validator = v.ValidPasswordsMatch()
103 validator = v.ValidPasswordsMatch()
103 self.assertRaises(formencode.Invalid,
104 self.assertRaises(formencode.Invalid,
104 validator.to_python, {'password': 'pass',
105 validator.to_python, {'password': 'pass',
105 'password_confirmation': 'pass2'})
106 'password_confirmation': 'pass2'})
106
107
107 self.assertRaises(formencode.Invalid,
108 self.assertRaises(formencode.Invalid,
108 validator.to_python, {'new_password': 'pass',
109 validator.to_python, {'new_password': 'pass',
109 'password_confirmation': 'pass2'})
110 'password_confirmation': 'pass2'})
110
111
111 self.assertEqual({'new_password': 'pass',
112 self.assertEqual({'new_password': 'pass',
112 'password_confirmation': 'pass'},
113 'password_confirmation': 'pass'},
113 validator.to_python({'new_password': 'pass',
114 validator.to_python({'new_password': 'pass',
114 'password_confirmation': 'pass'}))
115 'password_confirmation': 'pass'}))
115
116
116 self.assertEqual({'password': 'pass',
117 self.assertEqual({'password': 'pass',
117 'password_confirmation': 'pass'},
118 'password_confirmation': 'pass'},
118 validator.to_python({'password': 'pass',
119 validator.to_python({'password': 'pass',
119 'password_confirmation': 'pass'}))
120 'password_confirmation': 'pass'}))
120
121
121 def test_ValidAuth(self):
122 def test_ValidAuth(self):
122 validator = v.ValidAuth()
123 validator = v.ValidAuth()
123 valid_creds = {
124 valid_creds = {
124 'username': TEST_USER_REGULAR2_LOGIN,
125 'username': TEST_USER_REGULAR2_LOGIN,
125 'password': TEST_USER_REGULAR2_PASS,
126 'password': TEST_USER_REGULAR2_PASS,
126 }
127 }
127 invalid_creds = {
128 invalid_creds = {
128 'username': 'err',
129 'username': 'err',
129 'password': 'err',
130 'password': 'err',
130 }
131 }
131 self.assertEqual(valid_creds, validator.to_python(valid_creds))
132 self.assertEqual(valid_creds, validator.to_python(valid_creds))
132 self.assertRaises(formencode.Invalid,
133 self.assertRaises(formencode.Invalid,
133 validator.to_python, invalid_creds)
134 validator.to_python, invalid_creds)
134
135
135 def test_ValidAuthToken(self):
136 def test_ValidAuthToken(self):
136 validator = v.ValidAuthToken()
137 validator = v.ValidAuthToken()
137 # this is untestable without a threadlocal
138 # this is untestable without a threadlocal
138 # self.assertRaises(formencode.Invalid,
139 # self.assertRaises(formencode.Invalid,
139 # validator.to_python, 'BadToken')
140 # validator.to_python, 'BadToken')
140 validator
141 validator
141
142
142 def test_ValidRepoName(self):
143 def test_ValidRepoName(self):
143 validator = v.ValidRepoName()
144 validator = v.ValidRepoName()
144
145
145 self.assertRaises(formencode.Invalid,
146 self.assertRaises(formencode.Invalid,
146 validator.to_python, {'repo_name': ''})
147 validator.to_python, {'repo_name': ''})
147
148
148 self.assertRaises(formencode.Invalid,
149 self.assertRaises(formencode.Invalid,
149 validator.to_python, {'repo_name': HG_REPO})
150 validator.to_python, {'repo_name': HG_REPO})
150
151
151 gr = ReposGroupModel().create(group_name='group_test',
152 gr = ReposGroupModel().create(group_name='group_test',
152 group_description='desc',
153 group_description='desc',
153 parent=None,)
154 parent=None,
155 owner=TEST_USER_ADMIN_LOGIN)
154 self.assertRaises(formencode.Invalid,
156 self.assertRaises(formencode.Invalid,
155 validator.to_python, {'repo_name': gr.group_name})
157 validator.to_python, {'repo_name': gr.group_name})
156
158
157 #TODO: write an error case for that ie. create a repo withinh a group
159 #TODO: write an error case for that ie. create a repo withinh a group
158 # self.assertRaises(formencode.Invalid,
160 # self.assertRaises(formencode.Invalid,
159 # validator.to_python, {'repo_name': 'some',
161 # validator.to_python, {'repo_name': 'some',
160 # 'repo_group': gr.group_id})
162 # 'repo_group': gr.group_id})
161
163
162 def test_ValidForkName(self):
164 def test_ValidForkName(self):
163 # this uses ValidRepoName validator
165 # this uses ValidRepoName validator
164 assert True
166 assert True
165
167
166 @parameterized.expand([
168 @parameterized.expand([
167 ('test', 'test'), ('lolz!', 'lolz'), (' aavv', 'aavv'),
169 ('test', 'test'), ('lolz!', 'lolz'), (' aavv', 'aavv'),
168 ('ala ma kota', 'ala-ma-kota'), ('@nooo', 'nooo'),
170 ('ala ma kota', 'ala-ma-kota'), ('@nooo', 'nooo'),
169 ('$!haha lolz !', 'haha-lolz'), ('$$$$$', ''), ('{}OK!', 'OK'),
171 ('$!haha lolz !', 'haha-lolz'), ('$$$$$', ''), ('{}OK!', 'OK'),
170 ('/]re po', 're-po')])
172 ('/]re po', 're-po')])
171 def test_SlugifyName(self, name, expected):
173 def test_SlugifyName(self, name, expected):
172 validator = v.SlugifyName()
174 validator = v.SlugifyName()
173 self.assertEqual(expected, validator.to_python(name))
175 self.assertEqual(expected, validator.to_python(name))
174
176
175 def test_ValidCloneUri(self):
177 def test_ValidCloneUri(self):
176 #TODO: write this one
178 #TODO: write this one
177 pass
179 pass
178
180
179 def test_ValidForkType(self):
181 def test_ValidForkType(self):
180 validator = v.ValidForkType(old_data={'repo_type': 'hg'})
182 validator = v.ValidForkType(old_data={'repo_type': 'hg'})
181 self.assertEqual('hg', validator.to_python('hg'))
183 self.assertEqual('hg', validator.to_python('hg'))
182 self.assertRaises(formencode.Invalid, validator.to_python, 'git')
184 self.assertRaises(formencode.Invalid, validator.to_python, 'git')
183
185
184 def test_ValidPerms(self):
186 def test_ValidPerms(self):
185 #TODO: write this one
187 #TODO: write this one
186 pass
188 pass
187
189
188 def test_ValidSettings(self):
190 def test_ValidSettings(self):
189 validator = v.ValidSettings()
191 validator = v.ValidSettings()
190 self.assertEqual({'pass': 'pass'},
192 self.assertEqual({'pass': 'pass'},
191 validator.to_python(value={'user': 'test',
193 validator.to_python(value={'user': 'test',
192 'pass': 'pass'}))
194 'pass': 'pass'}))
193
195
194 self.assertEqual({'user2': 'test', 'pass': 'pass'},
196 self.assertEqual({'user2': 'test', 'pass': 'pass'},
195 validator.to_python(value={'user2': 'test',
197 validator.to_python(value={'user2': 'test',
196 'pass': 'pass'}))
198 'pass': 'pass'}))
197
199
198 def test_ValidPath(self):
200 def test_ValidPath(self):
199 validator = v.ValidPath()
201 validator = v.ValidPath()
200 self.assertEqual(TESTS_TMP_PATH,
202 self.assertEqual(TESTS_TMP_PATH,
201 validator.to_python(TESTS_TMP_PATH))
203 validator.to_python(TESTS_TMP_PATH))
202 self.assertRaises(formencode.Invalid, validator.to_python,
204 self.assertRaises(formencode.Invalid, validator.to_python,
203 '/no_such_dir')
205 '/no_such_dir')
204
206
205 def test_UniqSystemEmail(self):
207 def test_UniqSystemEmail(self):
206 validator = v.UniqSystemEmail(old_data={})
208 validator = v.UniqSystemEmail(old_data={})
207
209
208 self.assertEqual('mail@python.org',
210 self.assertEqual('mail@python.org',
209 validator.to_python('MaiL@Python.org'))
211 validator.to_python('MaiL@Python.org'))
210
212
211 email = TEST_USER_REGULAR2_EMAIL
213 email = TEST_USER_REGULAR2_EMAIL
212 self.assertRaises(formencode.Invalid, validator.to_python, email)
214 self.assertRaises(formencode.Invalid, validator.to_python, email)
213
215
214 def test_ValidSystemEmail(self):
216 def test_ValidSystemEmail(self):
215 validator = v.ValidSystemEmail()
217 validator = v.ValidSystemEmail()
216 email = TEST_USER_REGULAR2_EMAIL
218 email = TEST_USER_REGULAR2_EMAIL
217
219
218 self.assertEqual(email, validator.to_python(email))
220 self.assertEqual(email, validator.to_python(email))
219 self.assertRaises(formencode.Invalid, validator.to_python, 'err')
221 self.assertRaises(formencode.Invalid, validator.to_python, 'err')
220
222
221 def test_LdapLibValidator(self):
223 def test_LdapLibValidator(self):
222 validator = v.LdapLibValidator()
224 validator = v.LdapLibValidator()
223 self.assertRaises(v.LdapImportError, validator.to_python, 'err')
225 self.assertRaises(v.LdapImportError, validator.to_python, 'err')
224
226
225 def test_AttrLoginValidator(self):
227 def test_AttrLoginValidator(self):
226 validator = v.AttrLoginValidator()
228 validator = v.AttrLoginValidator()
227 self.assertRaises(formencode.Invalid, validator.to_python, 123)
229 self.assertRaises(formencode.Invalid, validator.to_python, 123)
228
230
229 def test_NotReviewedRevisions(self):
231 def test_NotReviewedRevisions(self):
230 repo_id = Repository.get_by_repo_name(HG_REPO).repo_id
232 repo_id = Repository.get_by_repo_name(HG_REPO).repo_id
231 validator = v.NotReviewedRevisions(repo_id)
233 validator = v.NotReviewedRevisions(repo_id)
232 rev = '0' * 40
234 rev = '0' * 40
233 # add status for a rev, that should throw an error because it is already
235 # add status for a rev, that should throw an error because it is already
234 # reviewed
236 # reviewed
235 new_status = ChangesetStatus()
237 new_status = ChangesetStatus()
236 new_status.author = ChangesetStatusModel()._get_user(TEST_USER_ADMIN_LOGIN)
238 new_status.author = ChangesetStatusModel()._get_user(TEST_USER_ADMIN_LOGIN)
237 new_status.repo = ChangesetStatusModel()._get_repo(HG_REPO)
239 new_status.repo = ChangesetStatusModel()._get_repo(HG_REPO)
238 new_status.status = ChangesetStatus.STATUS_APPROVED
240 new_status.status = ChangesetStatus.STATUS_APPROVED
239 new_status.comment = None
241 new_status.comment = None
240 new_status.revision = rev
242 new_status.revision = rev
241 Session().add(new_status)
243 Session().add(new_status)
242 Session().commit()
244 Session().commit()
243 try:
245 try:
244 self.assertRaises(formencode.Invalid, validator.to_python, [rev])
246 self.assertRaises(formencode.Invalid, validator.to_python, [rev])
245 finally:
247 finally:
246 Session().delete(new_status)
248 Session().delete(new_status)
247 Session().commit()
249 Session().commit()
General Comments 0
You need to be logged in to leave comments. Login now