##// END OF EJS Templates
- added commenting to pull requests...
marcink -
r2443:fd0a8224 codereview
parent child Browse files
Show More
@@ -1,552 +1,558 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 try:
35 try:
36 by_id = repo_name.split('_')
36 by_id = repo_name.split('_')
37 if len(by_id) == 2 and by_id[1].isdigit():
37 if len(by_id) == 2 and by_id[1].isdigit():
38 repo_name = Repository.get(by_id[1]).repo_name
38 repo_name = Repository.get(by_id[1]).repo_name
39 match_dict['repo_name'] = repo_name
39 match_dict['repo_name'] = repo_name
40 except:
40 except:
41 pass
41 pass
42
42
43 return is_valid_repo(repo_name, config['base_path'])
43 return is_valid_repo(repo_name, config['base_path'])
44
44
45 def check_group(environ, match_dict):
45 def check_group(environ, match_dict):
46 """
46 """
47 check for valid repositories group for proper 404 handling
47 check for valid repositories group for proper 404 handling
48
48
49 :param environ:
49 :param environ:
50 :param match_dict:
50 :param match_dict:
51 """
51 """
52 repos_group_name = match_dict.get('group_name')
52 repos_group_name = match_dict.get('group_name')
53
53
54 return is_valid_repos_group(repos_group_name, config['base_path'])
54 return is_valid_repos_group(repos_group_name, config['base_path'])
55
55
56 def check_int(environ, match_dict):
56 def check_int(environ, match_dict):
57 return match_dict.get('id').isdigit()
57 return match_dict.get('id').isdigit()
58
58
59 # The ErrorController route (handles 404/500 error pages); it should
59 # The ErrorController route (handles 404/500 error pages); it should
60 # likely stay at the top, ensuring it can always be resolved
60 # likely stay at the top, ensuring it can always be resolved
61 rmap.connect('/error/{action}', controller='error')
61 rmap.connect('/error/{action}', controller='error')
62 rmap.connect('/error/{action}/{id}', controller='error')
62 rmap.connect('/error/{action}/{id}', controller='error')
63
63
64 #==========================================================================
64 #==========================================================================
65 # CUSTOM ROUTES HERE
65 # CUSTOM ROUTES HERE
66 #==========================================================================
66 #==========================================================================
67
67
68 #MAIN PAGE
68 #MAIN PAGE
69 rmap.connect('home', '/', controller='home', action='index')
69 rmap.connect('home', '/', controller='home', action='index')
70 rmap.connect('repo_switcher', '/repos', controller='home',
70 rmap.connect('repo_switcher', '/repos', controller='home',
71 action='repo_switcher')
71 action='repo_switcher')
72 rmap.connect('branch_tag_switcher', '/branches-tags/{repo_name:.*}',
72 rmap.connect('branch_tag_switcher', '/branches-tags/{repo_name:.*}',
73 controller='home', action='branch_tag_switcher')
73 controller='home', action='branch_tag_switcher')
74 rmap.connect('bugtracker',
74 rmap.connect('bugtracker',
75 "http://bitbucket.org/marcinkuzminski/rhodecode/issues",
75 "http://bitbucket.org/marcinkuzminski/rhodecode/issues",
76 _static=True)
76 _static=True)
77 rmap.connect('rst_help',
77 rmap.connect('rst_help',
78 "http://docutils.sourceforge.net/docs/user/rst/quickref.html",
78 "http://docutils.sourceforge.net/docs/user/rst/quickref.html",
79 _static=True)
79 _static=True)
80 rmap.connect('rhodecode_official', "http://rhodecode.org", _static=True)
80 rmap.connect('rhodecode_official', "http://rhodecode.org", _static=True)
81
81
82 #ADMIN REPOSITORY REST ROUTES
82 #ADMIN REPOSITORY REST ROUTES
83 with rmap.submapper(path_prefix=ADMIN_PREFIX,
83 with rmap.submapper(path_prefix=ADMIN_PREFIX,
84 controller='admin/repos') as m:
84 controller='admin/repos') as m:
85 m.connect("repos", "/repos",
85 m.connect("repos", "/repos",
86 action="create", conditions=dict(method=["POST"]))
86 action="create", conditions=dict(method=["POST"]))
87 m.connect("repos", "/repos",
87 m.connect("repos", "/repos",
88 action="index", conditions=dict(method=["GET"]))
88 action="index", conditions=dict(method=["GET"]))
89 m.connect("formatted_repos", "/repos.{format}",
89 m.connect("formatted_repos", "/repos.{format}",
90 action="index",
90 action="index",
91 conditions=dict(method=["GET"]))
91 conditions=dict(method=["GET"]))
92 m.connect("new_repo", "/repos/new",
92 m.connect("new_repo", "/repos/new",
93 action="new", conditions=dict(method=["GET"]))
93 action="new", conditions=dict(method=["GET"]))
94 m.connect("formatted_new_repo", "/repos/new.{format}",
94 m.connect("formatted_new_repo", "/repos/new.{format}",
95 action="new", conditions=dict(method=["GET"]))
95 action="new", conditions=dict(method=["GET"]))
96 m.connect("/repos/{repo_name:.*}",
96 m.connect("/repos/{repo_name:.*}",
97 action="update", conditions=dict(method=["PUT"],
97 action="update", conditions=dict(method=["PUT"],
98 function=check_repo))
98 function=check_repo))
99 m.connect("/repos/{repo_name:.*}",
99 m.connect("/repos/{repo_name:.*}",
100 action="delete", conditions=dict(method=["DELETE"],
100 action="delete", conditions=dict(method=["DELETE"],
101 function=check_repo))
101 function=check_repo))
102 m.connect("edit_repo", "/repos/{repo_name:.*}/edit",
102 m.connect("edit_repo", "/repos/{repo_name:.*}/edit",
103 action="edit", conditions=dict(method=["GET"],
103 action="edit", conditions=dict(method=["GET"],
104 function=check_repo))
104 function=check_repo))
105 m.connect("formatted_edit_repo", "/repos/{repo_name:.*}.{format}/edit",
105 m.connect("formatted_edit_repo", "/repos/{repo_name:.*}.{format}/edit",
106 action="edit", conditions=dict(method=["GET"],
106 action="edit", conditions=dict(method=["GET"],
107 function=check_repo))
107 function=check_repo))
108 m.connect("repo", "/repos/{repo_name:.*}",
108 m.connect("repo", "/repos/{repo_name:.*}",
109 action="show", conditions=dict(method=["GET"],
109 action="show", conditions=dict(method=["GET"],
110 function=check_repo))
110 function=check_repo))
111 m.connect("formatted_repo", "/repos/{repo_name:.*}.{format}",
111 m.connect("formatted_repo", "/repos/{repo_name:.*}.{format}",
112 action="show", conditions=dict(method=["GET"],
112 action="show", conditions=dict(method=["GET"],
113 function=check_repo))
113 function=check_repo))
114 #ajax delete repo perm user
114 #ajax delete repo perm user
115 m.connect('delete_repo_user', "/repos_delete_user/{repo_name:.*}",
115 m.connect('delete_repo_user', "/repos_delete_user/{repo_name:.*}",
116 action="delete_perm_user",
116 action="delete_perm_user",
117 conditions=dict(method=["DELETE"], function=check_repo))
117 conditions=dict(method=["DELETE"], function=check_repo))
118
118
119 #ajax delete repo perm users_group
119 #ajax delete repo perm users_group
120 m.connect('delete_repo_users_group',
120 m.connect('delete_repo_users_group',
121 "/repos_delete_users_group/{repo_name:.*}",
121 "/repos_delete_users_group/{repo_name:.*}",
122 action="delete_perm_users_group",
122 action="delete_perm_users_group",
123 conditions=dict(method=["DELETE"], function=check_repo))
123 conditions=dict(method=["DELETE"], function=check_repo))
124
124
125 #settings actions
125 #settings actions
126 m.connect('repo_stats', "/repos_stats/{repo_name:.*}",
126 m.connect('repo_stats', "/repos_stats/{repo_name:.*}",
127 action="repo_stats", conditions=dict(method=["DELETE"],
127 action="repo_stats", conditions=dict(method=["DELETE"],
128 function=check_repo))
128 function=check_repo))
129 m.connect('repo_cache', "/repos_cache/{repo_name:.*}",
129 m.connect('repo_cache', "/repos_cache/{repo_name:.*}",
130 action="repo_cache", conditions=dict(method=["DELETE"],
130 action="repo_cache", conditions=dict(method=["DELETE"],
131 function=check_repo))
131 function=check_repo))
132 m.connect('repo_public_journal', "/repos_public_journal/{repo_name:.*}",
132 m.connect('repo_public_journal', "/repos_public_journal/{repo_name:.*}",
133 action="repo_public_journal", conditions=dict(method=["PUT"],
133 action="repo_public_journal", conditions=dict(method=["PUT"],
134 function=check_repo))
134 function=check_repo))
135 m.connect('repo_pull', "/repo_pull/{repo_name:.*}",
135 m.connect('repo_pull', "/repo_pull/{repo_name:.*}",
136 action="repo_pull", conditions=dict(method=["PUT"],
136 action="repo_pull", conditions=dict(method=["PUT"],
137 function=check_repo))
137 function=check_repo))
138 m.connect('repo_as_fork', "/repo_as_fork/{repo_name:.*}",
138 m.connect('repo_as_fork', "/repo_as_fork/{repo_name:.*}",
139 action="repo_as_fork", conditions=dict(method=["PUT"],
139 action="repo_as_fork", conditions=dict(method=["PUT"],
140 function=check_repo))
140 function=check_repo))
141
141
142 with rmap.submapper(path_prefix=ADMIN_PREFIX,
142 with rmap.submapper(path_prefix=ADMIN_PREFIX,
143 controller='admin/repos_groups') as m:
143 controller='admin/repos_groups') as m:
144 m.connect("repos_groups", "/repos_groups",
144 m.connect("repos_groups", "/repos_groups",
145 action="create", conditions=dict(method=["POST"]))
145 action="create", conditions=dict(method=["POST"]))
146 m.connect("repos_groups", "/repos_groups",
146 m.connect("repos_groups", "/repos_groups",
147 action="index", conditions=dict(method=["GET"]))
147 action="index", conditions=dict(method=["GET"]))
148 m.connect("formatted_repos_groups", "/repos_groups.{format}",
148 m.connect("formatted_repos_groups", "/repos_groups.{format}",
149 action="index", conditions=dict(method=["GET"]))
149 action="index", conditions=dict(method=["GET"]))
150 m.connect("new_repos_group", "/repos_groups/new",
150 m.connect("new_repos_group", "/repos_groups/new",
151 action="new", conditions=dict(method=["GET"]))
151 action="new", conditions=dict(method=["GET"]))
152 m.connect("formatted_new_repos_group", "/repos_groups/new.{format}",
152 m.connect("formatted_new_repos_group", "/repos_groups/new.{format}",
153 action="new", conditions=dict(method=["GET"]))
153 action="new", conditions=dict(method=["GET"]))
154 m.connect("update_repos_group", "/repos_groups/{id}",
154 m.connect("update_repos_group", "/repos_groups/{id}",
155 action="update", conditions=dict(method=["PUT"],
155 action="update", conditions=dict(method=["PUT"],
156 function=check_int))
156 function=check_int))
157 m.connect("delete_repos_group", "/repos_groups/{id}",
157 m.connect("delete_repos_group", "/repos_groups/{id}",
158 action="delete", conditions=dict(method=["DELETE"],
158 action="delete", conditions=dict(method=["DELETE"],
159 function=check_int))
159 function=check_int))
160 m.connect("edit_repos_group", "/repos_groups/{id}/edit",
160 m.connect("edit_repos_group", "/repos_groups/{id}/edit",
161 action="edit", conditions=dict(method=["GET"],
161 action="edit", conditions=dict(method=["GET"],
162 function=check_int))
162 function=check_int))
163 m.connect("formatted_edit_repos_group",
163 m.connect("formatted_edit_repos_group",
164 "/repos_groups/{id}.{format}/edit",
164 "/repos_groups/{id}.{format}/edit",
165 action="edit", conditions=dict(method=["GET"],
165 action="edit", conditions=dict(method=["GET"],
166 function=check_int))
166 function=check_int))
167 m.connect("repos_group", "/repos_groups/{id}",
167 m.connect("repos_group", "/repos_groups/{id}",
168 action="show", conditions=dict(method=["GET"],
168 action="show", conditions=dict(method=["GET"],
169 function=check_int))
169 function=check_int))
170 m.connect("formatted_repos_group", "/repos_groups/{id}.{format}",
170 m.connect("formatted_repos_group", "/repos_groups/{id}.{format}",
171 action="show", conditions=dict(method=["GET"],
171 action="show", conditions=dict(method=["GET"],
172 function=check_int))
172 function=check_int))
173 # ajax delete repos group perm user
173 # ajax delete repos group perm user
174 m.connect('delete_repos_group_user_perm',
174 m.connect('delete_repos_group_user_perm',
175 "/delete_repos_group_user_perm/{group_name:.*}",
175 "/delete_repos_group_user_perm/{group_name:.*}",
176 action="delete_repos_group_user_perm",
176 action="delete_repos_group_user_perm",
177 conditions=dict(method=["DELETE"], function=check_group))
177 conditions=dict(method=["DELETE"], function=check_group))
178
178
179 # ajax delete repos group perm users_group
179 # ajax delete repos group perm users_group
180 m.connect('delete_repos_group_users_group_perm',
180 m.connect('delete_repos_group_users_group_perm',
181 "/delete_repos_group_users_group_perm/{group_name:.*}",
181 "/delete_repos_group_users_group_perm/{group_name:.*}",
182 action="delete_repos_group_users_group_perm",
182 action="delete_repos_group_users_group_perm",
183 conditions=dict(method=["DELETE"], function=check_group))
183 conditions=dict(method=["DELETE"], function=check_group))
184
184
185 #ADMIN USER REST ROUTES
185 #ADMIN USER REST ROUTES
186 with rmap.submapper(path_prefix=ADMIN_PREFIX,
186 with rmap.submapper(path_prefix=ADMIN_PREFIX,
187 controller='admin/users') as m:
187 controller='admin/users') as m:
188 m.connect("users", "/users",
188 m.connect("users", "/users",
189 action="create", conditions=dict(method=["POST"]))
189 action="create", conditions=dict(method=["POST"]))
190 m.connect("users", "/users",
190 m.connect("users", "/users",
191 action="index", conditions=dict(method=["GET"]))
191 action="index", conditions=dict(method=["GET"]))
192 m.connect("formatted_users", "/users.{format}",
192 m.connect("formatted_users", "/users.{format}",
193 action="index", conditions=dict(method=["GET"]))
193 action="index", conditions=dict(method=["GET"]))
194 m.connect("new_user", "/users/new",
194 m.connect("new_user", "/users/new",
195 action="new", conditions=dict(method=["GET"]))
195 action="new", conditions=dict(method=["GET"]))
196 m.connect("formatted_new_user", "/users/new.{format}",
196 m.connect("formatted_new_user", "/users/new.{format}",
197 action="new", conditions=dict(method=["GET"]))
197 action="new", conditions=dict(method=["GET"]))
198 m.connect("update_user", "/users/{id}",
198 m.connect("update_user", "/users/{id}",
199 action="update", conditions=dict(method=["PUT"]))
199 action="update", conditions=dict(method=["PUT"]))
200 m.connect("delete_user", "/users/{id}",
200 m.connect("delete_user", "/users/{id}",
201 action="delete", conditions=dict(method=["DELETE"]))
201 action="delete", conditions=dict(method=["DELETE"]))
202 m.connect("edit_user", "/users/{id}/edit",
202 m.connect("edit_user", "/users/{id}/edit",
203 action="edit", conditions=dict(method=["GET"]))
203 action="edit", conditions=dict(method=["GET"]))
204 m.connect("formatted_edit_user",
204 m.connect("formatted_edit_user",
205 "/users/{id}.{format}/edit",
205 "/users/{id}.{format}/edit",
206 action="edit", conditions=dict(method=["GET"]))
206 action="edit", conditions=dict(method=["GET"]))
207 m.connect("user", "/users/{id}",
207 m.connect("user", "/users/{id}",
208 action="show", conditions=dict(method=["GET"]))
208 action="show", conditions=dict(method=["GET"]))
209 m.connect("formatted_user", "/users/{id}.{format}",
209 m.connect("formatted_user", "/users/{id}.{format}",
210 action="show", conditions=dict(method=["GET"]))
210 action="show", conditions=dict(method=["GET"]))
211
211
212 #EXTRAS USER ROUTES
212 #EXTRAS USER ROUTES
213 m.connect("user_perm", "/users_perm/{id}",
213 m.connect("user_perm", "/users_perm/{id}",
214 action="update_perm", conditions=dict(method=["PUT"]))
214 action="update_perm", conditions=dict(method=["PUT"]))
215 m.connect("user_emails", "/users_emails/{id}",
215 m.connect("user_emails", "/users_emails/{id}",
216 action="add_email", conditions=dict(method=["PUT"]))
216 action="add_email", conditions=dict(method=["PUT"]))
217 m.connect("user_emails_delete", "/users_emails/{id}",
217 m.connect("user_emails_delete", "/users_emails/{id}",
218 action="delete_email", conditions=dict(method=["DELETE"]))
218 action="delete_email", conditions=dict(method=["DELETE"]))
219
219
220 #ADMIN USERS GROUPS REST ROUTES
220 #ADMIN USERS GROUPS REST ROUTES
221 with rmap.submapper(path_prefix=ADMIN_PREFIX,
221 with rmap.submapper(path_prefix=ADMIN_PREFIX,
222 controller='admin/users_groups') as m:
222 controller='admin/users_groups') as m:
223 m.connect("users_groups", "/users_groups",
223 m.connect("users_groups", "/users_groups",
224 action="create", conditions=dict(method=["POST"]))
224 action="create", conditions=dict(method=["POST"]))
225 m.connect("users_groups", "/users_groups",
225 m.connect("users_groups", "/users_groups",
226 action="index", conditions=dict(method=["GET"]))
226 action="index", conditions=dict(method=["GET"]))
227 m.connect("formatted_users_groups", "/users_groups.{format}",
227 m.connect("formatted_users_groups", "/users_groups.{format}",
228 action="index", conditions=dict(method=["GET"]))
228 action="index", conditions=dict(method=["GET"]))
229 m.connect("new_users_group", "/users_groups/new",
229 m.connect("new_users_group", "/users_groups/new",
230 action="new", conditions=dict(method=["GET"]))
230 action="new", conditions=dict(method=["GET"]))
231 m.connect("formatted_new_users_group", "/users_groups/new.{format}",
231 m.connect("formatted_new_users_group", "/users_groups/new.{format}",
232 action="new", conditions=dict(method=["GET"]))
232 action="new", conditions=dict(method=["GET"]))
233 m.connect("update_users_group", "/users_groups/{id}",
233 m.connect("update_users_group", "/users_groups/{id}",
234 action="update", conditions=dict(method=["PUT"]))
234 action="update", conditions=dict(method=["PUT"]))
235 m.connect("delete_users_group", "/users_groups/{id}",
235 m.connect("delete_users_group", "/users_groups/{id}",
236 action="delete", conditions=dict(method=["DELETE"]))
236 action="delete", conditions=dict(method=["DELETE"]))
237 m.connect("edit_users_group", "/users_groups/{id}/edit",
237 m.connect("edit_users_group", "/users_groups/{id}/edit",
238 action="edit", conditions=dict(method=["GET"]))
238 action="edit", conditions=dict(method=["GET"]))
239 m.connect("formatted_edit_users_group",
239 m.connect("formatted_edit_users_group",
240 "/users_groups/{id}.{format}/edit",
240 "/users_groups/{id}.{format}/edit",
241 action="edit", conditions=dict(method=["GET"]))
241 action="edit", conditions=dict(method=["GET"]))
242 m.connect("users_group", "/users_groups/{id}",
242 m.connect("users_group", "/users_groups/{id}",
243 action="show", conditions=dict(method=["GET"]))
243 action="show", conditions=dict(method=["GET"]))
244 m.connect("formatted_users_group", "/users_groups/{id}.{format}",
244 m.connect("formatted_users_group", "/users_groups/{id}.{format}",
245 action="show", conditions=dict(method=["GET"]))
245 action="show", conditions=dict(method=["GET"]))
246
246
247 #EXTRAS USER ROUTES
247 #EXTRAS USER ROUTES
248 m.connect("users_group_perm", "/users_groups_perm/{id}",
248 m.connect("users_group_perm", "/users_groups_perm/{id}",
249 action="update_perm", conditions=dict(method=["PUT"]))
249 action="update_perm", conditions=dict(method=["PUT"]))
250
250
251 #ADMIN GROUP REST ROUTES
251 #ADMIN GROUP REST ROUTES
252 rmap.resource('group', 'groups',
252 rmap.resource('group', 'groups',
253 controller='admin/groups', path_prefix=ADMIN_PREFIX)
253 controller='admin/groups', path_prefix=ADMIN_PREFIX)
254
254
255 #ADMIN PERMISSIONS REST ROUTES
255 #ADMIN PERMISSIONS REST ROUTES
256 rmap.resource('permission', 'permissions',
256 rmap.resource('permission', 'permissions',
257 controller='admin/permissions', path_prefix=ADMIN_PREFIX)
257 controller='admin/permissions', path_prefix=ADMIN_PREFIX)
258
258
259 ##ADMIN LDAP SETTINGS
259 ##ADMIN LDAP SETTINGS
260 rmap.connect('ldap_settings', '%s/ldap' % ADMIN_PREFIX,
260 rmap.connect('ldap_settings', '%s/ldap' % ADMIN_PREFIX,
261 controller='admin/ldap_settings', action='ldap_settings',
261 controller='admin/ldap_settings', action='ldap_settings',
262 conditions=dict(method=["POST"]))
262 conditions=dict(method=["POST"]))
263
263
264 rmap.connect('ldap_home', '%s/ldap' % ADMIN_PREFIX,
264 rmap.connect('ldap_home', '%s/ldap' % ADMIN_PREFIX,
265 controller='admin/ldap_settings')
265 controller='admin/ldap_settings')
266
266
267 #ADMIN SETTINGS REST ROUTES
267 #ADMIN SETTINGS REST ROUTES
268 with rmap.submapper(path_prefix=ADMIN_PREFIX,
268 with rmap.submapper(path_prefix=ADMIN_PREFIX,
269 controller='admin/settings') as m:
269 controller='admin/settings') as m:
270 m.connect("admin_settings", "/settings",
270 m.connect("admin_settings", "/settings",
271 action="create", conditions=dict(method=["POST"]))
271 action="create", conditions=dict(method=["POST"]))
272 m.connect("admin_settings", "/settings",
272 m.connect("admin_settings", "/settings",
273 action="index", conditions=dict(method=["GET"]))
273 action="index", conditions=dict(method=["GET"]))
274 m.connect("formatted_admin_settings", "/settings.{format}",
274 m.connect("formatted_admin_settings", "/settings.{format}",
275 action="index", conditions=dict(method=["GET"]))
275 action="index", conditions=dict(method=["GET"]))
276 m.connect("admin_new_setting", "/settings/new",
276 m.connect("admin_new_setting", "/settings/new",
277 action="new", conditions=dict(method=["GET"]))
277 action="new", conditions=dict(method=["GET"]))
278 m.connect("formatted_admin_new_setting", "/settings/new.{format}",
278 m.connect("formatted_admin_new_setting", "/settings/new.{format}",
279 action="new", conditions=dict(method=["GET"]))
279 action="new", conditions=dict(method=["GET"]))
280 m.connect("/settings/{setting_id}",
280 m.connect("/settings/{setting_id}",
281 action="update", conditions=dict(method=["PUT"]))
281 action="update", conditions=dict(method=["PUT"]))
282 m.connect("/settings/{setting_id}",
282 m.connect("/settings/{setting_id}",
283 action="delete", conditions=dict(method=["DELETE"]))
283 action="delete", conditions=dict(method=["DELETE"]))
284 m.connect("admin_edit_setting", "/settings/{setting_id}/edit",
284 m.connect("admin_edit_setting", "/settings/{setting_id}/edit",
285 action="edit", conditions=dict(method=["GET"]))
285 action="edit", conditions=dict(method=["GET"]))
286 m.connect("formatted_admin_edit_setting",
286 m.connect("formatted_admin_edit_setting",
287 "/settings/{setting_id}.{format}/edit",
287 "/settings/{setting_id}.{format}/edit",
288 action="edit", conditions=dict(method=["GET"]))
288 action="edit", conditions=dict(method=["GET"]))
289 m.connect("admin_setting", "/settings/{setting_id}",
289 m.connect("admin_setting", "/settings/{setting_id}",
290 action="show", conditions=dict(method=["GET"]))
290 action="show", conditions=dict(method=["GET"]))
291 m.connect("formatted_admin_setting", "/settings/{setting_id}.{format}",
291 m.connect("formatted_admin_setting", "/settings/{setting_id}.{format}",
292 action="show", conditions=dict(method=["GET"]))
292 action="show", conditions=dict(method=["GET"]))
293 m.connect("admin_settings_my_account", "/my_account",
293 m.connect("admin_settings_my_account", "/my_account",
294 action="my_account", conditions=dict(method=["GET"]))
294 action="my_account", conditions=dict(method=["GET"]))
295 m.connect("admin_settings_my_account_update", "/my_account_update",
295 m.connect("admin_settings_my_account_update", "/my_account_update",
296 action="my_account_update", conditions=dict(method=["PUT"]))
296 action="my_account_update", conditions=dict(method=["PUT"]))
297 m.connect("admin_settings_create_repository", "/create_repository",
297 m.connect("admin_settings_create_repository", "/create_repository",
298 action="create_repository", conditions=dict(method=["GET"]))
298 action="create_repository", conditions=dict(method=["GET"]))
299
299
300 #NOTIFICATION REST ROUTES
300 #NOTIFICATION REST ROUTES
301 with rmap.submapper(path_prefix=ADMIN_PREFIX,
301 with rmap.submapper(path_prefix=ADMIN_PREFIX,
302 controller='admin/notifications') as m:
302 controller='admin/notifications') as m:
303 m.connect("notifications", "/notifications",
303 m.connect("notifications", "/notifications",
304 action="create", conditions=dict(method=["POST"]))
304 action="create", conditions=dict(method=["POST"]))
305 m.connect("notifications", "/notifications",
305 m.connect("notifications", "/notifications",
306 action="index", conditions=dict(method=["GET"]))
306 action="index", conditions=dict(method=["GET"]))
307 m.connect("notifications_mark_all_read", "/notifications/mark_all_read",
307 m.connect("notifications_mark_all_read", "/notifications/mark_all_read",
308 action="mark_all_read", conditions=dict(method=["GET"]))
308 action="mark_all_read", conditions=dict(method=["GET"]))
309 m.connect("formatted_notifications", "/notifications.{format}",
309 m.connect("formatted_notifications", "/notifications.{format}",
310 action="index", conditions=dict(method=["GET"]))
310 action="index", conditions=dict(method=["GET"]))
311 m.connect("new_notification", "/notifications/new",
311 m.connect("new_notification", "/notifications/new",
312 action="new", conditions=dict(method=["GET"]))
312 action="new", conditions=dict(method=["GET"]))
313 m.connect("formatted_new_notification", "/notifications/new.{format}",
313 m.connect("formatted_new_notification", "/notifications/new.{format}",
314 action="new", conditions=dict(method=["GET"]))
314 action="new", conditions=dict(method=["GET"]))
315 m.connect("/notification/{notification_id}",
315 m.connect("/notification/{notification_id}",
316 action="update", conditions=dict(method=["PUT"]))
316 action="update", conditions=dict(method=["PUT"]))
317 m.connect("/notification/{notification_id}",
317 m.connect("/notification/{notification_id}",
318 action="delete", conditions=dict(method=["DELETE"]))
318 action="delete", conditions=dict(method=["DELETE"]))
319 m.connect("edit_notification", "/notification/{notification_id}/edit",
319 m.connect("edit_notification", "/notification/{notification_id}/edit",
320 action="edit", conditions=dict(method=["GET"]))
320 action="edit", conditions=dict(method=["GET"]))
321 m.connect("formatted_edit_notification",
321 m.connect("formatted_edit_notification",
322 "/notification/{notification_id}.{format}/edit",
322 "/notification/{notification_id}.{format}/edit",
323 action="edit", conditions=dict(method=["GET"]))
323 action="edit", conditions=dict(method=["GET"]))
324 m.connect("notification", "/notification/{notification_id}",
324 m.connect("notification", "/notification/{notification_id}",
325 action="show", conditions=dict(method=["GET"]))
325 action="show", conditions=dict(method=["GET"]))
326 m.connect("formatted_notification", "/notifications/{notification_id}.{format}",
326 m.connect("formatted_notification", "/notifications/{notification_id}.{format}",
327 action="show", conditions=dict(method=["GET"]))
327 action="show", conditions=dict(method=["GET"]))
328
328
329 #ADMIN MAIN PAGES
329 #ADMIN MAIN PAGES
330 with rmap.submapper(path_prefix=ADMIN_PREFIX,
330 with rmap.submapper(path_prefix=ADMIN_PREFIX,
331 controller='admin/admin') as m:
331 controller='admin/admin') as m:
332 m.connect('admin_home', '', action='index')
332 m.connect('admin_home', '', action='index')
333 m.connect('admin_add_repo', '/add_repo/{new_repo:[a-z0-9\. _-]*}',
333 m.connect('admin_add_repo', '/add_repo/{new_repo:[a-z0-9\. _-]*}',
334 action='add_repo')
334 action='add_repo')
335
335
336 #==========================================================================
336 #==========================================================================
337 # API V2
337 # API V2
338 #==========================================================================
338 #==========================================================================
339 with rmap.submapper(path_prefix=ADMIN_PREFIX,
339 with rmap.submapper(path_prefix=ADMIN_PREFIX,
340 controller='api/api') as m:
340 controller='api/api') as m:
341 m.connect('api', '/api')
341 m.connect('api', '/api')
342
342
343 #USER JOURNAL
343 #USER JOURNAL
344 rmap.connect('journal', '%s/journal' % ADMIN_PREFIX,
344 rmap.connect('journal', '%s/journal' % ADMIN_PREFIX,
345 controller='journal', action='index')
345 controller='journal', action='index')
346 rmap.connect('journal_rss', '%s/journal/rss' % ADMIN_PREFIX,
346 rmap.connect('journal_rss', '%s/journal/rss' % ADMIN_PREFIX,
347 controller='journal', action='journal_rss')
347 controller='journal', action='journal_rss')
348 rmap.connect('journal_atom', '%s/journal/atom' % ADMIN_PREFIX,
348 rmap.connect('journal_atom', '%s/journal/atom' % ADMIN_PREFIX,
349 controller='journal', action='journal_atom')
349 controller='journal', action='journal_atom')
350
350
351 rmap.connect('public_journal', '%s/public_journal' % ADMIN_PREFIX,
351 rmap.connect('public_journal', '%s/public_journal' % ADMIN_PREFIX,
352 controller='journal', action="public_journal")
352 controller='journal', action="public_journal")
353
353
354 rmap.connect('public_journal_rss', '%s/public_journal/rss' % ADMIN_PREFIX,
354 rmap.connect('public_journal_rss', '%s/public_journal/rss' % ADMIN_PREFIX,
355 controller='journal', action="public_journal_rss")
355 controller='journal', action="public_journal_rss")
356
356
357 rmap.connect('public_journal_rss_old', '%s/public_journal_rss' % ADMIN_PREFIX,
357 rmap.connect('public_journal_rss_old', '%s/public_journal_rss' % ADMIN_PREFIX,
358 controller='journal', action="public_journal_rss")
358 controller='journal', action="public_journal_rss")
359
359
360 rmap.connect('public_journal_atom',
360 rmap.connect('public_journal_atom',
361 '%s/public_journal/atom' % ADMIN_PREFIX, controller='journal',
361 '%s/public_journal/atom' % ADMIN_PREFIX, controller='journal',
362 action="public_journal_atom")
362 action="public_journal_atom")
363
363
364 rmap.connect('public_journal_atom_old',
364 rmap.connect('public_journal_atom_old',
365 '%s/public_journal_atom' % ADMIN_PREFIX, controller='journal',
365 '%s/public_journal_atom' % ADMIN_PREFIX, controller='journal',
366 action="public_journal_atom")
366 action="public_journal_atom")
367
367
368 rmap.connect('toggle_following', '%s/toggle_following' % ADMIN_PREFIX,
368 rmap.connect('toggle_following', '%s/toggle_following' % ADMIN_PREFIX,
369 controller='journal', action='toggle_following',
369 controller='journal', action='toggle_following',
370 conditions=dict(method=["POST"]))
370 conditions=dict(method=["POST"]))
371
371
372 #SEARCH
372 #SEARCH
373 rmap.connect('search', '%s/search' % ADMIN_PREFIX, controller='search',)
373 rmap.connect('search', '%s/search' % ADMIN_PREFIX, controller='search',)
374 rmap.connect('search_repo', '%s/search/{search_repo:.*}' % ADMIN_PREFIX,
374 rmap.connect('search_repo', '%s/search/{search_repo:.*}' % ADMIN_PREFIX,
375 controller='search')
375 controller='search')
376
376
377 #LOGIN/LOGOUT/REGISTER/SIGN IN
377 #LOGIN/LOGOUT/REGISTER/SIGN IN
378 rmap.connect('login_home', '%s/login' % ADMIN_PREFIX, controller='login')
378 rmap.connect('login_home', '%s/login' % ADMIN_PREFIX, controller='login')
379 rmap.connect('logout_home', '%s/logout' % ADMIN_PREFIX, controller='login',
379 rmap.connect('logout_home', '%s/logout' % ADMIN_PREFIX, controller='login',
380 action='logout')
380 action='logout')
381
381
382 rmap.connect('register', '%s/register' % ADMIN_PREFIX, controller='login',
382 rmap.connect('register', '%s/register' % ADMIN_PREFIX, controller='login',
383 action='register')
383 action='register')
384
384
385 rmap.connect('reset_password', '%s/password_reset' % ADMIN_PREFIX,
385 rmap.connect('reset_password', '%s/password_reset' % ADMIN_PREFIX,
386 controller='login', action='password_reset')
386 controller='login', action='password_reset')
387
387
388 rmap.connect('reset_password_confirmation',
388 rmap.connect('reset_password_confirmation',
389 '%s/password_reset_confirmation' % ADMIN_PREFIX,
389 '%s/password_reset_confirmation' % ADMIN_PREFIX,
390 controller='login', action='password_reset_confirmation')
390 controller='login', action='password_reset_confirmation')
391
391
392 #FEEDS
392 #FEEDS
393 rmap.connect('rss_feed_home', '/{repo_name:.*}/feed/rss',
393 rmap.connect('rss_feed_home', '/{repo_name:.*}/feed/rss',
394 controller='feed', action='rss',
394 controller='feed', action='rss',
395 conditions=dict(function=check_repo))
395 conditions=dict(function=check_repo))
396
396
397 rmap.connect('atom_feed_home', '/{repo_name:.*}/feed/atom',
397 rmap.connect('atom_feed_home', '/{repo_name:.*}/feed/atom',
398 controller='feed', action='atom',
398 controller='feed', action='atom',
399 conditions=dict(function=check_repo))
399 conditions=dict(function=check_repo))
400
400
401 #==========================================================================
401 #==========================================================================
402 # REPOSITORY ROUTES
402 # REPOSITORY ROUTES
403 #==========================================================================
403 #==========================================================================
404 rmap.connect('summary_home', '/{repo_name:.*}',
404 rmap.connect('summary_home', '/{repo_name:.*}',
405 controller='summary',
405 controller='summary',
406 conditions=dict(function=check_repo))
406 conditions=dict(function=check_repo))
407
407
408 rmap.connect('repos_group_home', '/{group_name:.*}',
408 rmap.connect('repos_group_home', '/{group_name:.*}',
409 controller='admin/repos_groups', action="show_by_name",
409 controller='admin/repos_groups', action="show_by_name",
410 conditions=dict(function=check_group))
410 conditions=dict(function=check_group))
411
411
412 rmap.connect('changeset_home', '/{repo_name:.*}/changeset/{revision}',
412 rmap.connect('changeset_home', '/{repo_name:.*}/changeset/{revision}',
413 controller='changeset', revision='tip',
413 controller='changeset', revision='tip',
414 conditions=dict(function=check_repo))
414 conditions=dict(function=check_repo))
415
415
416 rmap.connect('changeset_comment',
416 rmap.connect('changeset_comment',
417 '/{repo_name:.*}/changeset/{revision}/comment',
417 '/{repo_name:.*}/changeset/{revision}/comment',
418 controller='changeset', revision='tip', action='comment',
418 controller='changeset', revision='tip', action='comment',
419 conditions=dict(function=check_repo))
419 conditions=dict(function=check_repo))
420
420
421 rmap.connect('changeset_comment_delete',
421 rmap.connect('changeset_comment_delete',
422 '/{repo_name:.*}/changeset/comment/{comment_id}/delete',
422 '/{repo_name:.*}/changeset/comment/{comment_id}/delete',
423 controller='changeset', action='delete_comment',
423 controller='changeset', action='delete_comment',
424 conditions=dict(function=check_repo, method=["DELETE"]))
424 conditions=dict(function=check_repo, method=["DELETE"]))
425
425
426 rmap.connect('raw_changeset_home',
426 rmap.connect('raw_changeset_home',
427 '/{repo_name:.*}/raw-changeset/{revision}',
427 '/{repo_name:.*}/raw-changeset/{revision}',
428 controller='changeset', action='raw_changeset',
428 controller='changeset', action='raw_changeset',
429 revision='tip', conditions=dict(function=check_repo))
429 revision='tip', conditions=dict(function=check_repo))
430
430
431 rmap.connect('compare_url',
431 rmap.connect('compare_url',
432 '/{repo_name:.*}/compare/{org_ref_type}@{org_ref}...{other_ref_type}@{other_ref}',
432 '/{repo_name:.*}/compare/{org_ref_type}@{org_ref}...{other_ref_type}@{other_ref}',
433 controller='compare', action='index',
433 controller='compare', action='index',
434 conditions=dict(function=check_repo),
434 conditions=dict(function=check_repo),
435 requirements=dict(org_ref_type='(branch|book|tag)',
435 requirements=dict(org_ref_type='(branch|book|tag)',
436 other_ref_type='(branch|book|tag)'))
436 other_ref_type='(branch|book|tag)'))
437
437
438 rmap.connect('pullrequest_home',
438 rmap.connect('pullrequest_home',
439 '/{repo_name:.*}/pull-request/new', controller='pullrequests',
439 '/{repo_name:.*}/pull-request/new', controller='pullrequests',
440 action='index', conditions=dict(function=check_repo,
440 action='index', conditions=dict(function=check_repo,
441 method=["GET"]))
441 method=["GET"]))
442
442
443 rmap.connect('pullrequest',
443 rmap.connect('pullrequest',
444 '/{repo_name:.*}/pull-request/new', controller='pullrequests',
444 '/{repo_name:.*}/pull-request/new', controller='pullrequests',
445 action='create', conditions=dict(function=check_repo,
445 action='create', conditions=dict(function=check_repo,
446 method=["POST"]))
446 method=["POST"]))
447
447
448 rmap.connect('pullrequest_show',
448 rmap.connect('pullrequest_show',
449 '/{repo_name:.*}/pull-request/{pull_request_id}',
449 '/{repo_name:.*}/pull-request/{pull_request_id}',
450 controller='pullrequests',
450 controller='pullrequests',
451 action='show', conditions=dict(function=check_repo,
451 action='show', conditions=dict(function=check_repo,
452 method=["GET"]))
452 method=["GET"]))
453
453
454 rmap.connect('pullrequest_show_all',
454 rmap.connect('pullrequest_show_all',
455 '/{repo_name:.*}/pull-request',
455 '/{repo_name:.*}/pull-request',
456 controller='pullrequests',
456 controller='pullrequests',
457 action='show_all', conditions=dict(function=check_repo,
457 action='show_all', conditions=dict(function=check_repo,
458 method=["GET"]))
458 method=["GET"]))
459
459
460 rmap.connect('pullrequest_comment',
461 '/{repo_name:.*}/pull-request-comment/{pull_request_id}',
462 controller='pullrequests',
463 action='comment', conditions=dict(function=check_repo,
464 method=["POST"]))
465
460 rmap.connect('summary_home', '/{repo_name:.*}/summary',
466 rmap.connect('summary_home', '/{repo_name:.*}/summary',
461 controller='summary', conditions=dict(function=check_repo))
467 controller='summary', conditions=dict(function=check_repo))
462
468
463 rmap.connect('shortlog_home', '/{repo_name:.*}/shortlog',
469 rmap.connect('shortlog_home', '/{repo_name:.*}/shortlog',
464 controller='shortlog', conditions=dict(function=check_repo))
470 controller='shortlog', conditions=dict(function=check_repo))
465
471
466 rmap.connect('branches_home', '/{repo_name:.*}/branches',
472 rmap.connect('branches_home', '/{repo_name:.*}/branches',
467 controller='branches', conditions=dict(function=check_repo))
473 controller='branches', conditions=dict(function=check_repo))
468
474
469 rmap.connect('tags_home', '/{repo_name:.*}/tags',
475 rmap.connect('tags_home', '/{repo_name:.*}/tags',
470 controller='tags', conditions=dict(function=check_repo))
476 controller='tags', conditions=dict(function=check_repo))
471
477
472 rmap.connect('bookmarks_home', '/{repo_name:.*}/bookmarks',
478 rmap.connect('bookmarks_home', '/{repo_name:.*}/bookmarks',
473 controller='bookmarks', conditions=dict(function=check_repo))
479 controller='bookmarks', conditions=dict(function=check_repo))
474
480
475 rmap.connect('changelog_home', '/{repo_name:.*}/changelog',
481 rmap.connect('changelog_home', '/{repo_name:.*}/changelog',
476 controller='changelog', conditions=dict(function=check_repo))
482 controller='changelog', conditions=dict(function=check_repo))
477
483
478 rmap.connect('changelog_details', '/{repo_name:.*}/changelog_details/{cs}',
484 rmap.connect('changelog_details', '/{repo_name:.*}/changelog_details/{cs}',
479 controller='changelog', action='changelog_details',
485 controller='changelog', action='changelog_details',
480 conditions=dict(function=check_repo))
486 conditions=dict(function=check_repo))
481
487
482 rmap.connect('files_home', '/{repo_name:.*}/files/{revision}/{f_path:.*}',
488 rmap.connect('files_home', '/{repo_name:.*}/files/{revision}/{f_path:.*}',
483 controller='files', revision='tip', f_path='',
489 controller='files', revision='tip', f_path='',
484 conditions=dict(function=check_repo))
490 conditions=dict(function=check_repo))
485
491
486 rmap.connect('files_diff_home', '/{repo_name:.*}/diff/{f_path:.*}',
492 rmap.connect('files_diff_home', '/{repo_name:.*}/diff/{f_path:.*}',
487 controller='files', action='diff', revision='tip', f_path='',
493 controller='files', action='diff', revision='tip', f_path='',
488 conditions=dict(function=check_repo))
494 conditions=dict(function=check_repo))
489
495
490 rmap.connect('files_rawfile_home',
496 rmap.connect('files_rawfile_home',
491 '/{repo_name:.*}/rawfile/{revision}/{f_path:.*}',
497 '/{repo_name:.*}/rawfile/{revision}/{f_path:.*}',
492 controller='files', action='rawfile', revision='tip',
498 controller='files', action='rawfile', revision='tip',
493 f_path='', conditions=dict(function=check_repo))
499 f_path='', conditions=dict(function=check_repo))
494
500
495 rmap.connect('files_raw_home',
501 rmap.connect('files_raw_home',
496 '/{repo_name:.*}/raw/{revision}/{f_path:.*}',
502 '/{repo_name:.*}/raw/{revision}/{f_path:.*}',
497 controller='files', action='raw', revision='tip', f_path='',
503 controller='files', action='raw', revision='tip', f_path='',
498 conditions=dict(function=check_repo))
504 conditions=dict(function=check_repo))
499
505
500 rmap.connect('files_annotate_home',
506 rmap.connect('files_annotate_home',
501 '/{repo_name:.*}/annotate/{revision}/{f_path:.*}',
507 '/{repo_name:.*}/annotate/{revision}/{f_path:.*}',
502 controller='files', action='index', revision='tip',
508 controller='files', action='index', revision='tip',
503 f_path='', annotate=True, conditions=dict(function=check_repo))
509 f_path='', annotate=True, conditions=dict(function=check_repo))
504
510
505 rmap.connect('files_edit_home',
511 rmap.connect('files_edit_home',
506 '/{repo_name:.*}/edit/{revision}/{f_path:.*}',
512 '/{repo_name:.*}/edit/{revision}/{f_path:.*}',
507 controller='files', action='edit', revision='tip',
513 controller='files', action='edit', revision='tip',
508 f_path='', conditions=dict(function=check_repo))
514 f_path='', conditions=dict(function=check_repo))
509
515
510 rmap.connect('files_add_home',
516 rmap.connect('files_add_home',
511 '/{repo_name:.*}/add/{revision}/{f_path:.*}',
517 '/{repo_name:.*}/add/{revision}/{f_path:.*}',
512 controller='files', action='add', revision='tip',
518 controller='files', action='add', revision='tip',
513 f_path='', conditions=dict(function=check_repo))
519 f_path='', conditions=dict(function=check_repo))
514
520
515 rmap.connect('files_archive_home', '/{repo_name:.*}/archive/{fname}',
521 rmap.connect('files_archive_home', '/{repo_name:.*}/archive/{fname}',
516 controller='files', action='archivefile',
522 controller='files', action='archivefile',
517 conditions=dict(function=check_repo))
523 conditions=dict(function=check_repo))
518
524
519 rmap.connect('files_nodelist_home',
525 rmap.connect('files_nodelist_home',
520 '/{repo_name:.*}/nodelist/{revision}/{f_path:.*}',
526 '/{repo_name:.*}/nodelist/{revision}/{f_path:.*}',
521 controller='files', action='nodelist',
527 controller='files', action='nodelist',
522 conditions=dict(function=check_repo))
528 conditions=dict(function=check_repo))
523
529
524 rmap.connect('repo_settings_delete', '/{repo_name:.*}/settings',
530 rmap.connect('repo_settings_delete', '/{repo_name:.*}/settings',
525 controller='settings', action="delete",
531 controller='settings', action="delete",
526 conditions=dict(method=["DELETE"], function=check_repo))
532 conditions=dict(method=["DELETE"], function=check_repo))
527
533
528 rmap.connect('repo_settings_update', '/{repo_name:.*}/settings',
534 rmap.connect('repo_settings_update', '/{repo_name:.*}/settings',
529 controller='settings', action="update",
535 controller='settings', action="update",
530 conditions=dict(method=["PUT"], function=check_repo))
536 conditions=dict(method=["PUT"], function=check_repo))
531
537
532 rmap.connect('repo_settings_home', '/{repo_name:.*}/settings',
538 rmap.connect('repo_settings_home', '/{repo_name:.*}/settings',
533 controller='settings', action='index',
539 controller='settings', action='index',
534 conditions=dict(function=check_repo))
540 conditions=dict(function=check_repo))
535
541
536 rmap.connect('repo_fork_create_home', '/{repo_name:.*}/fork',
542 rmap.connect('repo_fork_create_home', '/{repo_name:.*}/fork',
537 controller='forks', action='fork_create',
543 controller='forks', action='fork_create',
538 conditions=dict(function=check_repo, method=["POST"]))
544 conditions=dict(function=check_repo, method=["POST"]))
539
545
540 rmap.connect('repo_fork_home', '/{repo_name:.*}/fork',
546 rmap.connect('repo_fork_home', '/{repo_name:.*}/fork',
541 controller='forks', action='fork',
547 controller='forks', action='fork',
542 conditions=dict(function=check_repo))
548 conditions=dict(function=check_repo))
543
549
544 rmap.connect('repo_forks_home', '/{repo_name:.*}/forks',
550 rmap.connect('repo_forks_home', '/{repo_name:.*}/forks',
545 controller='forks', action='forks',
551 controller='forks', action='forks',
546 conditions=dict(function=check_repo))
552 conditions=dict(function=check_repo))
547
553
548 rmap.connect('repo_followers_home', '/{repo_name:.*}/followers',
554 rmap.connect('repo_followers_home', '/{repo_name:.*}/followers',
549 controller='followers', action='followers',
555 controller='followers', action='followers',
550 conditions=dict(function=check_repo))
556 conditions=dict(function=check_repo))
551
557
552 return rmap
558 return rmap
@@ -1,428 +1,428 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.controllers.changeset
3 rhodecode.controllers.changeset
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 changeset controller for pylons showoing changes beetween
6 changeset controller for pylons showoing changes beetween
7 revisions
7 revisions
8
8
9 :created_on: Apr 25, 2010
9 :created_on: Apr 25, 2010
10 :author: marcink
10 :author: marcink
11 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
12 :license: GPLv3, see COPYING for more details.
12 :license: GPLv3, see COPYING for more details.
13 """
13 """
14 # This program is free software: you can redistribute it and/or modify
14 # This program is free software: you can redistribute it and/or modify
15 # it under the terms of the GNU General Public License as published by
15 # it under the terms of the GNU General Public License as published by
16 # the Free Software Foundation, either version 3 of the License, or
16 # the Free Software Foundation, either version 3 of the License, or
17 # (at your option) any later version.
17 # (at your option) any later version.
18 #
18 #
19 # This program is distributed in the hope that it will be useful,
19 # This program is distributed in the hope that it will be useful,
20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 # GNU General Public License for more details.
22 # GNU General Public License for more details.
23 #
23 #
24 # You should have received a copy of the GNU General Public License
24 # You should have received a copy of the GNU General Public License
25 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 # along with this program. If not, see <http://www.gnu.org/licenses/>.
26 import logging
26 import logging
27 import traceback
27 import traceback
28 from collections import defaultdict
28 from collections import defaultdict
29 from webob.exc import HTTPForbidden
29 from webob.exc import HTTPForbidden
30
30
31 from pylons import tmpl_context as c, url, request, response
31 from pylons import tmpl_context as c, url, request, response
32 from pylons.i18n.translation import _
32 from pylons.i18n.translation import _
33 from pylons.controllers.util import redirect
33 from pylons.controllers.util import redirect
34 from pylons.decorators import jsonify
34 from pylons.decorators import jsonify
35
35
36 from rhodecode.lib.vcs.exceptions import RepositoryError, ChangesetError, \
36 from rhodecode.lib.vcs.exceptions import RepositoryError, ChangesetError, \
37 ChangesetDoesNotExistError
37 ChangesetDoesNotExistError
38 from rhodecode.lib.vcs.nodes import FileNode
38 from rhodecode.lib.vcs.nodes import FileNode
39
39
40 import rhodecode.lib.helpers as h
40 import rhodecode.lib.helpers as h
41 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
41 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
42 from rhodecode.lib.base import BaseRepoController, render
42 from rhodecode.lib.base import BaseRepoController, render
43 from rhodecode.lib.utils import EmptyChangeset, action_logger
43 from rhodecode.lib.utils import EmptyChangeset, action_logger
44 from rhodecode.lib.compat import OrderedDict
44 from rhodecode.lib.compat import OrderedDict
45 from rhodecode.lib import diffs
45 from rhodecode.lib import diffs
46 from rhodecode.model.db import ChangesetComment, ChangesetStatus
46 from rhodecode.model.db import ChangesetComment, ChangesetStatus
47 from rhodecode.model.comment import ChangesetCommentsModel
47 from rhodecode.model.comment import ChangesetCommentsModel
48 from rhodecode.model.changeset_status import ChangesetStatusModel
48 from rhodecode.model.changeset_status import ChangesetStatusModel
49 from rhodecode.model.meta import Session
49 from rhodecode.model.meta import Session
50 from rhodecode.lib.diffs import wrapped_diff
50 from rhodecode.lib.diffs import wrapped_diff
51 from rhodecode.model.repo import RepoModel
51 from rhodecode.model.repo import RepoModel
52
52
53 log = logging.getLogger(__name__)
53 log = logging.getLogger(__name__)
54
54
55
55
56 def _update_with_GET(params, GET):
56 def _update_with_GET(params, GET):
57 for k in ['diff1', 'diff2', 'diff']:
57 for k in ['diff1', 'diff2', 'diff']:
58 params[k] += GET.getall(k)
58 params[k] += GET.getall(k)
59
59
60
60
61 def anchor_url(revision, path, GET):
61 def anchor_url(revision, path, GET):
62 fid = h.FID(revision, path)
62 fid = h.FID(revision, path)
63 return h.url.current(anchor=fid, **dict(GET))
63 return h.url.current(anchor=fid, **dict(GET))
64
64
65
65
66 def get_ignore_ws(fid, GET):
66 def get_ignore_ws(fid, GET):
67 ig_ws_global = GET.get('ignorews')
67 ig_ws_global = GET.get('ignorews')
68 ig_ws = filter(lambda k: k.startswith('WS'), GET.getall(fid))
68 ig_ws = filter(lambda k: k.startswith('WS'), GET.getall(fid))
69 if ig_ws:
69 if ig_ws:
70 try:
70 try:
71 return int(ig_ws[0].split(':')[-1])
71 return int(ig_ws[0].split(':')[-1])
72 except:
72 except:
73 pass
73 pass
74 return ig_ws_global
74 return ig_ws_global
75
75
76
76
77 def _ignorews_url(GET, fileid=None):
77 def _ignorews_url(GET, fileid=None):
78 fileid = str(fileid) if fileid else None
78 fileid = str(fileid) if fileid else None
79 params = defaultdict(list)
79 params = defaultdict(list)
80 _update_with_GET(params, GET)
80 _update_with_GET(params, GET)
81 lbl = _('show white space')
81 lbl = _('show white space')
82 ig_ws = get_ignore_ws(fileid, GET)
82 ig_ws = get_ignore_ws(fileid, GET)
83 ln_ctx = get_line_ctx(fileid, GET)
83 ln_ctx = get_line_ctx(fileid, GET)
84 # global option
84 # global option
85 if fileid is None:
85 if fileid is None:
86 if ig_ws is None:
86 if ig_ws is None:
87 params['ignorews'] += [1]
87 params['ignorews'] += [1]
88 lbl = _('ignore white space')
88 lbl = _('ignore white space')
89 ctx_key = 'context'
89 ctx_key = 'context'
90 ctx_val = ln_ctx
90 ctx_val = ln_ctx
91 # per file options
91 # per file options
92 else:
92 else:
93 if ig_ws is None:
93 if ig_ws is None:
94 params[fileid] += ['WS:1']
94 params[fileid] += ['WS:1']
95 lbl = _('ignore white space')
95 lbl = _('ignore white space')
96
96
97 ctx_key = fileid
97 ctx_key = fileid
98 ctx_val = 'C:%s' % ln_ctx
98 ctx_val = 'C:%s' % ln_ctx
99 # if we have passed in ln_ctx pass it along to our params
99 # if we have passed in ln_ctx pass it along to our params
100 if ln_ctx:
100 if ln_ctx:
101 params[ctx_key] += [ctx_val]
101 params[ctx_key] += [ctx_val]
102
102
103 params['anchor'] = fileid
103 params['anchor'] = fileid
104 img = h.image(h.url('/images/icons/text_strikethrough.png'), lbl, class_='icon')
104 img = h.image(h.url('/images/icons/text_strikethrough.png'), lbl, class_='icon')
105 return h.link_to(img, h.url.current(**params), title=lbl, class_='tooltip')
105 return h.link_to(img, h.url.current(**params), title=lbl, class_='tooltip')
106
106
107
107
108 def get_line_ctx(fid, GET):
108 def get_line_ctx(fid, GET):
109 ln_ctx_global = GET.get('context')
109 ln_ctx_global = GET.get('context')
110 ln_ctx = filter(lambda k: k.startswith('C'), GET.getall(fid))
110 ln_ctx = filter(lambda k: k.startswith('C'), GET.getall(fid))
111
111
112 if ln_ctx:
112 if ln_ctx:
113 retval = ln_ctx[0].split(':')[-1]
113 retval = ln_ctx[0].split(':')[-1]
114 else:
114 else:
115 retval = ln_ctx_global
115 retval = ln_ctx_global
116
116
117 try:
117 try:
118 return int(retval)
118 return int(retval)
119 except:
119 except:
120 return
120 return
121
121
122
122
123 def _context_url(GET, fileid=None):
123 def _context_url(GET, fileid=None):
124 """
124 """
125 Generates url for context lines
125 Generates url for context lines
126
126
127 :param fileid:
127 :param fileid:
128 """
128 """
129
129
130 fileid = str(fileid) if fileid else None
130 fileid = str(fileid) if fileid else None
131 ig_ws = get_ignore_ws(fileid, GET)
131 ig_ws = get_ignore_ws(fileid, GET)
132 ln_ctx = (get_line_ctx(fileid, GET) or 3) * 2
132 ln_ctx = (get_line_ctx(fileid, GET) or 3) * 2
133
133
134 params = defaultdict(list)
134 params = defaultdict(list)
135 _update_with_GET(params, GET)
135 _update_with_GET(params, GET)
136
136
137 # global option
137 # global option
138 if fileid is None:
138 if fileid is None:
139 if ln_ctx > 0:
139 if ln_ctx > 0:
140 params['context'] += [ln_ctx]
140 params['context'] += [ln_ctx]
141
141
142 if ig_ws:
142 if ig_ws:
143 ig_ws_key = 'ignorews'
143 ig_ws_key = 'ignorews'
144 ig_ws_val = 1
144 ig_ws_val = 1
145
145
146 # per file option
146 # per file option
147 else:
147 else:
148 params[fileid] += ['C:%s' % ln_ctx]
148 params[fileid] += ['C:%s' % ln_ctx]
149 ig_ws_key = fileid
149 ig_ws_key = fileid
150 ig_ws_val = 'WS:%s' % 1
150 ig_ws_val = 'WS:%s' % 1
151
151
152 if ig_ws:
152 if ig_ws:
153 params[ig_ws_key] += [ig_ws_val]
153 params[ig_ws_key] += [ig_ws_val]
154
154
155 lbl = _('%s line context') % ln_ctx
155 lbl = _('%s line context') % ln_ctx
156
156
157 params['anchor'] = fileid
157 params['anchor'] = fileid
158 img = h.image(h.url('/images/icons/table_add.png'), lbl, class_='icon')
158 img = h.image(h.url('/images/icons/table_add.png'), lbl, class_='icon')
159 return h.link_to(img, h.url.current(**params), title=lbl, class_='tooltip')
159 return h.link_to(img, h.url.current(**params), title=lbl, class_='tooltip')
160
160
161
161
162 class ChangesetController(BaseRepoController):
162 class ChangesetController(BaseRepoController):
163
163
164 @LoginRequired()
164 @LoginRequired()
165 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
165 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
166 'repository.admin')
166 'repository.admin')
167 def __before__(self):
167 def __before__(self):
168 super(ChangesetController, self).__before__()
168 super(ChangesetController, self).__before__()
169 c.affected_files_cut_off = 60
169 c.affected_files_cut_off = 60
170 repo_model = RepoModel()
170 repo_model = RepoModel()
171 c.users_array = repo_model.get_users_js()
171 c.users_array = repo_model.get_users_js()
172 c.users_groups_array = repo_model.get_users_groups_js()
172 c.users_groups_array = repo_model.get_users_groups_js()
173
173
174 def index(self, revision):
174 def index(self, revision):
175
175
176 c.anchor_url = anchor_url
176 c.anchor_url = anchor_url
177 c.ignorews_url = _ignorews_url
177 c.ignorews_url = _ignorews_url
178 c.context_url = _context_url
178 c.context_url = _context_url
179 limit_off = request.GET.get('fulldiff')
179 limit_off = request.GET.get('fulldiff')
180 #get ranges of revisions if preset
180 #get ranges of revisions if preset
181 rev_range = revision.split('...')[:2]
181 rev_range = revision.split('...')[:2]
182 enable_comments = True
182 enable_comments = True
183 try:
183 try:
184 if len(rev_range) == 2:
184 if len(rev_range) == 2:
185 enable_comments = False
185 enable_comments = False
186 rev_start = rev_range[0]
186 rev_start = rev_range[0]
187 rev_end = rev_range[1]
187 rev_end = rev_range[1]
188 rev_ranges = c.rhodecode_repo.get_changesets(start=rev_start,
188 rev_ranges = c.rhodecode_repo.get_changesets(start=rev_start,
189 end=rev_end)
189 end=rev_end)
190 else:
190 else:
191 rev_ranges = [c.rhodecode_repo.get_changeset(revision)]
191 rev_ranges = [c.rhodecode_repo.get_changeset(revision)]
192
192
193 c.cs_ranges = list(rev_ranges)
193 c.cs_ranges = list(rev_ranges)
194 if not c.cs_ranges:
194 if not c.cs_ranges:
195 raise RepositoryError('Changeset range returned empty result')
195 raise RepositoryError('Changeset range returned empty result')
196
196
197 except (RepositoryError, ChangesetDoesNotExistError, Exception), e:
197 except (RepositoryError, ChangesetDoesNotExistError, Exception), e:
198 log.error(traceback.format_exc())
198 log.error(traceback.format_exc())
199 h.flash(str(e), category='warning')
199 h.flash(str(e), category='warning')
200 return redirect(url('home'))
200 return redirect(url('home'))
201
201
202 c.changes = OrderedDict()
202 c.changes = OrderedDict()
203
203
204 c.lines_added = 0 # count of lines added
204 c.lines_added = 0 # count of lines added
205 c.lines_deleted = 0 # count of lines removes
205 c.lines_deleted = 0 # count of lines removes
206
206
207 cumulative_diff = 0
207 cumulative_diff = 0
208 c.cut_off = False # defines if cut off limit is reached
208 c.cut_off = False # defines if cut off limit is reached
209 c.changeset_statuses = ChangesetStatus.STATUSES
209 c.changeset_statuses = ChangesetStatus.STATUSES
210 c.comments = []
210 c.comments = []
211 c.statuses = []
211 c.statuses = []
212 c.inline_comments = []
212 c.inline_comments = []
213 c.inline_cnt = 0
213 c.inline_cnt = 0
214 # Iterate over ranges (default changeset view is always one changeset)
214 # Iterate over ranges (default changeset view is always one changeset)
215 for changeset in c.cs_ranges:
215 for changeset in c.cs_ranges:
216
216
217 c.statuses.extend([ChangesetStatusModel()\
217 c.statuses.extend([ChangesetStatusModel()\
218 .get_status(c.rhodecode_db_repo.repo_id,
218 .get_status(c.rhodecode_db_repo.repo_id,
219 changeset.raw_id)])
219 changeset.raw_id)])
220
220
221 c.comments.extend(ChangesetCommentsModel()\
221 c.comments.extend(ChangesetCommentsModel()\
222 .get_comments(c.rhodecode_db_repo.repo_id,
222 .get_comments(c.rhodecode_db_repo.repo_id,
223 revision=changeset.raw_id))
223 revision=changeset.raw_id))
224 inlines = ChangesetCommentsModel()\
224 inlines = ChangesetCommentsModel()\
225 .get_inline_comments(c.rhodecode_db_repo.repo_id,
225 .get_inline_comments(c.rhodecode_db_repo.repo_id,
226 revision=changeset.raw_id)
226 revision=changeset.raw_id)
227 c.inline_comments.extend(inlines)
227 c.inline_comments.extend(inlines)
228 c.changes[changeset.raw_id] = []
228 c.changes[changeset.raw_id] = []
229 try:
229 try:
230 changeset_parent = changeset.parents[0]
230 changeset_parent = changeset.parents[0]
231 except IndexError:
231 except IndexError:
232 changeset_parent = None
232 changeset_parent = None
233
233
234 #==================================================================
234 #==================================================================
235 # ADDED FILES
235 # ADDED FILES
236 #==================================================================
236 #==================================================================
237 for node in changeset.added:
237 for node in changeset.added:
238 fid = h.FID(revision, node.path)
238 fid = h.FID(revision, node.path)
239 line_context_lcl = get_line_ctx(fid, request.GET)
239 line_context_lcl = get_line_ctx(fid, request.GET)
240 ign_whitespace_lcl = get_ignore_ws(fid, request.GET)
240 ign_whitespace_lcl = get_ignore_ws(fid, request.GET)
241 lim = self.cut_off_limit
241 lim = self.cut_off_limit
242 if cumulative_diff > self.cut_off_limit:
242 if cumulative_diff > self.cut_off_limit:
243 lim = -1 if limit_off is None else None
243 lim = -1 if limit_off is None else None
244 size, cs1, cs2, diff, st = wrapped_diff(
244 size, cs1, cs2, diff, st = wrapped_diff(
245 filenode_old=None,
245 filenode_old=None,
246 filenode_new=node,
246 filenode_new=node,
247 cut_off_limit=lim,
247 cut_off_limit=lim,
248 ignore_whitespace=ign_whitespace_lcl,
248 ignore_whitespace=ign_whitespace_lcl,
249 line_context=line_context_lcl,
249 line_context=line_context_lcl,
250 enable_comments=enable_comments
250 enable_comments=enable_comments
251 )
251 )
252 cumulative_diff += size
252 cumulative_diff += size
253 c.lines_added += st[0]
253 c.lines_added += st[0]
254 c.lines_deleted += st[1]
254 c.lines_deleted += st[1]
255 c.changes[changeset.raw_id].append(
255 c.changes[changeset.raw_id].append(
256 ('added', node, diff, cs1, cs2, st)
256 ('added', node, diff, cs1, cs2, st)
257 )
257 )
258
258
259 #==================================================================
259 #==================================================================
260 # CHANGED FILES
260 # CHANGED FILES
261 #==================================================================
261 #==================================================================
262 for node in changeset.changed:
262 for node in changeset.changed:
263 try:
263 try:
264 filenode_old = changeset_parent.get_node(node.path)
264 filenode_old = changeset_parent.get_node(node.path)
265 except ChangesetError:
265 except ChangesetError:
266 log.warning('Unable to fetch parent node for diff')
266 log.warning('Unable to fetch parent node for diff')
267 filenode_old = FileNode(node.path, '', EmptyChangeset())
267 filenode_old = FileNode(node.path, '', EmptyChangeset())
268
268
269 fid = h.FID(revision, node.path)
269 fid = h.FID(revision, node.path)
270 line_context_lcl = get_line_ctx(fid, request.GET)
270 line_context_lcl = get_line_ctx(fid, request.GET)
271 ign_whitespace_lcl = get_ignore_ws(fid, request.GET)
271 ign_whitespace_lcl = get_ignore_ws(fid, request.GET)
272 lim = self.cut_off_limit
272 lim = self.cut_off_limit
273 if cumulative_diff > self.cut_off_limit:
273 if cumulative_diff > self.cut_off_limit:
274 lim = -1 if limit_off is None else None
274 lim = -1 if limit_off is None else None
275 size, cs1, cs2, diff, st = wrapped_diff(
275 size, cs1, cs2, diff, st = wrapped_diff(
276 filenode_old=filenode_old,
276 filenode_old=filenode_old,
277 filenode_new=node,
277 filenode_new=node,
278 cut_off_limit=lim,
278 cut_off_limit=lim,
279 ignore_whitespace=ign_whitespace_lcl,
279 ignore_whitespace=ign_whitespace_lcl,
280 line_context=line_context_lcl,
280 line_context=line_context_lcl,
281 enable_comments=enable_comments
281 enable_comments=enable_comments
282 )
282 )
283 cumulative_diff += size
283 cumulative_diff += size
284 c.lines_added += st[0]
284 c.lines_added += st[0]
285 c.lines_deleted += st[1]
285 c.lines_deleted += st[1]
286 c.changes[changeset.raw_id].append(
286 c.changes[changeset.raw_id].append(
287 ('changed', node, diff, cs1, cs2, st)
287 ('changed', node, diff, cs1, cs2, st)
288 )
288 )
289 #==================================================================
289 #==================================================================
290 # REMOVED FILES
290 # REMOVED FILES
291 #==================================================================
291 #==================================================================
292 for node in changeset.removed:
292 for node in changeset.removed:
293 c.changes[changeset.raw_id].append(
293 c.changes[changeset.raw_id].append(
294 ('removed', node, None, None, None, (0, 0))
294 ('removed', node, None, None, None, (0, 0))
295 )
295 )
296
296
297 # count inline comments
297 # count inline comments
298 for __, lines in c.inline_comments:
298 for __, lines in c.inline_comments:
299 for comments in lines.values():
299 for comments in lines.values():
300 c.inline_cnt += len(comments)
300 c.inline_cnt += len(comments)
301
301
302 if len(c.cs_ranges) == 1:
302 if len(c.cs_ranges) == 1:
303 c.changeset = c.cs_ranges[0]
303 c.changeset = c.cs_ranges[0]
304 c.changes = c.changes[c.changeset.raw_id]
304 c.changes = c.changes[c.changeset.raw_id]
305
305
306 return render('changeset/changeset.html')
306 return render('changeset/changeset.html')
307 else:
307 else:
308 return render('changeset/changeset_range.html')
308 return render('changeset/changeset_range.html')
309
309
310 def raw_changeset(self, revision):
310 def raw_changeset(self, revision):
311
311
312 method = request.GET.get('diff', 'show')
312 method = request.GET.get('diff', 'show')
313 ignore_whitespace = request.GET.get('ignorews') == '1'
313 ignore_whitespace = request.GET.get('ignorews') == '1'
314 line_context = request.GET.get('context', 3)
314 line_context = request.GET.get('context', 3)
315 try:
315 try:
316 c.scm_type = c.rhodecode_repo.alias
316 c.scm_type = c.rhodecode_repo.alias
317 c.changeset = c.rhodecode_repo.get_changeset(revision)
317 c.changeset = c.rhodecode_repo.get_changeset(revision)
318 except RepositoryError:
318 except RepositoryError:
319 log.error(traceback.format_exc())
319 log.error(traceback.format_exc())
320 return redirect(url('home'))
320 return redirect(url('home'))
321 else:
321 else:
322 try:
322 try:
323 c.changeset_parent = c.changeset.parents[0]
323 c.changeset_parent = c.changeset.parents[0]
324 except IndexError:
324 except IndexError:
325 c.changeset_parent = None
325 c.changeset_parent = None
326 c.changes = []
326 c.changes = []
327
327
328 for node in c.changeset.added:
328 for node in c.changeset.added:
329 filenode_old = FileNode(node.path, '')
329 filenode_old = FileNode(node.path, '')
330 if filenode_old.is_binary or node.is_binary:
330 if filenode_old.is_binary or node.is_binary:
331 diff = _('binary file') + '\n'
331 diff = _('binary file') + '\n'
332 else:
332 else:
333 f_gitdiff = diffs.get_gitdiff(filenode_old, node,
333 f_gitdiff = diffs.get_gitdiff(filenode_old, node,
334 ignore_whitespace=ignore_whitespace,
334 ignore_whitespace=ignore_whitespace,
335 context=line_context)
335 context=line_context)
336 diff = diffs.DiffProcessor(f_gitdiff,
336 diff = diffs.DiffProcessor(f_gitdiff,
337 format='gitdiff').raw_diff()
337 format='gitdiff').raw_diff()
338
338
339 cs1 = None
339 cs1 = None
340 cs2 = node.changeset.raw_id
340 cs2 = node.changeset.raw_id
341 c.changes.append(('added', node, diff, cs1, cs2))
341 c.changes.append(('added', node, diff, cs1, cs2))
342
342
343 for node in c.changeset.changed:
343 for node in c.changeset.changed:
344 filenode_old = c.changeset_parent.get_node(node.path)
344 filenode_old = c.changeset_parent.get_node(node.path)
345 if filenode_old.is_binary or node.is_binary:
345 if filenode_old.is_binary or node.is_binary:
346 diff = _('binary file')
346 diff = _('binary file')
347 else:
347 else:
348 f_gitdiff = diffs.get_gitdiff(filenode_old, node,
348 f_gitdiff = diffs.get_gitdiff(filenode_old, node,
349 ignore_whitespace=ignore_whitespace,
349 ignore_whitespace=ignore_whitespace,
350 context=line_context)
350 context=line_context)
351 diff = diffs.DiffProcessor(f_gitdiff,
351 diff = diffs.DiffProcessor(f_gitdiff,
352 format='gitdiff').raw_diff()
352 format='gitdiff').raw_diff()
353
353
354 cs1 = filenode_old.changeset.raw_id
354 cs1 = filenode_old.changeset.raw_id
355 cs2 = node.changeset.raw_id
355 cs2 = node.changeset.raw_id
356 c.changes.append(('changed', node, diff, cs1, cs2))
356 c.changes.append(('changed', node, diff, cs1, cs2))
357
357
358 response.content_type = 'text/plain'
358 response.content_type = 'text/plain'
359
359
360 if method == 'download':
360 if method == 'download':
361 response.content_disposition = 'attachment; filename=%s.patch' \
361 response.content_disposition = 'attachment; filename=%s.patch' \
362 % revision
362 % revision
363
363
364 c.parent_tmpl = ''.join(['# Parent %s\n' % x.raw_id
364 c.parent_tmpl = ''.join(['# Parent %s\n' % x.raw_id
365 for x in c.changeset.parents])
365 for x in c.changeset.parents])
366
366
367 c.diffs = ''
367 c.diffs = ''
368 for x in c.changes:
368 for x in c.changes:
369 c.diffs += x[2]
369 c.diffs += x[2]
370
370
371 return render('changeset/raw_changeset.html')
371 return render('changeset/raw_changeset.html')
372
372
373 @jsonify
373 @jsonify
374 def comment(self, repo_name, revision):
374 def comment(self, repo_name, revision):
375 status = request.POST.get('changeset_status')
375 status = request.POST.get('changeset_status')
376 change_status = request.POST.get('change_changeset_status')
376 change_status = request.POST.get('change_changeset_status')
377
377
378 comm = ChangesetCommentsModel().create(
378 comm = ChangesetCommentsModel().create(
379 text=request.POST.get('text'),
379 text=request.POST.get('text'),
380 repo_id=c.rhodecode_db_repo.repo_id,
380 repo_id=c.rhodecode_db_repo.repo_id,
381 user_id=c.rhodecode_user.user_id,
381 user_id=c.rhodecode_user.user_id,
382 revision=revision,
382 revision=revision,
383 f_path=request.POST.get('f_path'),
383 f_path=request.POST.get('f_path'),
384 line_no=request.POST.get('line'),
384 line_no=request.POST.get('line'),
385 status_change=(ChangesetStatus.get_status_lbl(status)
385 status_change=(ChangesetStatus.get_status_lbl(status)
386 if status and change_status else None)
386 if status and change_status else None)
387 )
387 )
388
388
389 # get status if set !
389 # get status if set !
390 if status and change_status:
390 if status and change_status:
391 ChangesetStatusModel().set_status(
391 ChangesetStatusModel().set_status(
392 c.rhodecode_db_repo.repo_id,
392 c.rhodecode_db_repo.repo_id,
393 revision,
394 status,
393 status,
395 c.rhodecode_user.user_id,
394 c.rhodecode_user.user_id,
396 comm,
395 comm,
396 revision=revision,
397 )
397 )
398 action_logger(self.rhodecode_user,
398 action_logger(self.rhodecode_user,
399 'user_commented_revision:%s' % revision,
399 'user_commented_revision:%s' % revision,
400 c.rhodecode_db_repo, self.ip_addr, self.sa)
400 c.rhodecode_db_repo, self.ip_addr, self.sa)
401
401
402 Session.commit()
402 Session.commit()
403
403
404 if not request.environ.get('HTTP_X_PARTIAL_XHR'):
404 if not request.environ.get('HTTP_X_PARTIAL_XHR'):
405 return redirect(h.url('changeset_home', repo_name=repo_name,
405 return redirect(h.url('changeset_home', repo_name=repo_name,
406 revision=revision))
406 revision=revision))
407
407
408 data = {
408 data = {
409 'target_id': h.safeid(h.safe_unicode(request.POST.get('f_path'))),
409 'target_id': h.safeid(h.safe_unicode(request.POST.get('f_path'))),
410 }
410 }
411 if comm:
411 if comm:
412 c.co = comm
412 c.co = comm
413 data.update(comm.get_dict())
413 data.update(comm.get_dict())
414 data.update({'rendered_text':
414 data.update({'rendered_text':
415 render('changeset/changeset_comment_block.html')})
415 render('changeset/changeset_comment_block.html')})
416
416
417 return data
417 return data
418
418
419 @jsonify
419 @jsonify
420 def delete_comment(self, repo_name, comment_id):
420 def delete_comment(self, repo_name, comment_id):
421 co = ChangesetComment.get(comment_id)
421 co = ChangesetComment.get(comment_id)
422 owner = lambda: co.author.user_id == c.rhodecode_user.user_id
422 owner = lambda: co.author.user_id == c.rhodecode_user.user_id
423 if h.HasPermissionAny('hg.admin', 'repository.admin')() or owner:
423 if h.HasPermissionAny('hg.admin', 'repository.admin')() or owner:
424 ChangesetCommentsModel().delete(comment=co)
424 ChangesetCommentsModel().delete(comment=co)
425 Session.commit()
425 Session.commit()
426 return True
426 return True
427 else:
427 else:
428 raise HTTPForbidden()
428 raise HTTPForbidden()
@@ -1,216 +1,264 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.controllers.pullrequests
3 rhodecode.controllers.pullrequests
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 pull requests controller for rhodecode for initializing pull requests
6 pull requests controller for rhodecode for initializing pull requests
7
7
8 :created_on: May 7, 2012
8 :created_on: May 7, 2012
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 import logging
25 import logging
26 import traceback
26 import traceback
27 import binascii
28
27
29 from webob.exc import HTTPNotFound
28 from webob.exc import HTTPNotFound
30
29
31 from pylons import request, response, session, tmpl_context as c, url
30 from pylons import request, response, session, tmpl_context as c, url
32 from pylons.controllers.util import abort, redirect
31 from pylons.controllers.util import abort, redirect
33 from pylons.i18n.translation import _
32 from pylons.i18n.translation import _
33 from pylons.decorators import jsonify
34
34
35 from rhodecode.lib.base import BaseRepoController, render
35 from rhodecode.lib.base import BaseRepoController, render
36 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
36 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
37 from rhodecode.lib import helpers as h
37 from rhodecode.lib import helpers as h
38 from rhodecode.lib import diffs
38 from rhodecode.lib import diffs
39 from rhodecode.model.db import User, PullRequest, Repository, ChangesetStatus
39 from rhodecode.lib.utils import action_logger
40 from rhodecode.model.db import User, PullRequest, ChangesetStatus
40 from rhodecode.model.pull_request import PullRequestModel
41 from rhodecode.model.pull_request import PullRequestModel
41 from rhodecode.model.meta import Session
42 from rhodecode.model.meta import Session
42 from rhodecode.model.repo import RepoModel
43 from rhodecode.model.repo import RepoModel
43 from rhodecode.model.comment import ChangesetCommentsModel
44 from rhodecode.model.comment import ChangesetCommentsModel
44 from rhodecode.model.changeset_status import ChangesetStatusModel
45 from rhodecode.model.changeset_status import ChangesetStatusModel
45
46
46 log = logging.getLogger(__name__)
47 log = logging.getLogger(__name__)
47
48
48
49
49 class PullrequestsController(BaseRepoController):
50 class PullrequestsController(BaseRepoController):
50
51
51 @LoginRequired()
52 @LoginRequired()
52 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
53 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
53 'repository.admin')
54 'repository.admin')
54 def __before__(self):
55 def __before__(self):
55 super(PullrequestsController, self).__before__()
56 super(PullrequestsController, self).__before__()
56
57
57 def _get_repo_refs(self, repo):
58 def _get_repo_refs(self, repo):
58 hist_l = []
59 hist_l = []
59
60
60 branches_group = ([('branch:%s:%s' % (k, v), k) for
61 branches_group = ([('branch:%s:%s' % (k, v), k) for
61 k, v in repo.branches.iteritems()], _("Branches"))
62 k, v in repo.branches.iteritems()], _("Branches"))
62 bookmarks_group = ([('book:%s:%s' % (k, v), k) for
63 bookmarks_group = ([('book:%s:%s' % (k, v), k) for
63 k, v in repo.bookmarks.iteritems()], _("Bookmarks"))
64 k, v in repo.bookmarks.iteritems()], _("Bookmarks"))
64 tags_group = ([('tag:%s:%s' % (k, v), k) for
65 tags_group = ([('tag:%s:%s' % (k, v), k) for
65 k, v in repo.tags.iteritems()], _("Tags"))
66 k, v in repo.tags.iteritems()], _("Tags"))
66
67
67 hist_l.append(bookmarks_group)
68 hist_l.append(bookmarks_group)
68 hist_l.append(branches_group)
69 hist_l.append(branches_group)
69 hist_l.append(tags_group)
70 hist_l.append(tags_group)
70
71
71 return hist_l
72 return hist_l
72
73
73 def show_all(self, repo_name):
74 def show_all(self, repo_name):
74 c.pull_requests = PullRequestModel().get_all(repo_name)
75 c.pull_requests = PullRequestModel().get_all(repo_name)
75 c.repo_name = repo_name
76 c.repo_name = repo_name
76 return render('/pullrequests/pullrequest_show_all.html')
77 return render('/pullrequests/pullrequest_show_all.html')
77
78
78 def index(self):
79 def index(self):
79 org_repo = c.rhodecode_db_repo
80 org_repo = c.rhodecode_db_repo
80 c.org_refs = self._get_repo_refs(c.rhodecode_repo)
81 c.org_refs = self._get_repo_refs(c.rhodecode_repo)
81 c.org_repos = []
82 c.org_repos = []
82 c.other_repos = []
83 c.other_repos = []
83 c.org_repos.append((org_repo.repo_name, '%s/%s' % (
84 c.org_repos.append((org_repo.repo_name, '%s/%s' % (
84 org_repo.user.username, c.repo_name))
85 org_repo.user.username, c.repo_name))
85 )
86 )
86
87
87 c.other_refs = c.org_refs
88 c.other_refs = c.org_refs
88 c.other_repos.extend(c.org_repos)
89 c.other_repos.extend(c.org_repos)
89 c.default_pull_request = org_repo.repo_name
90 c.default_pull_request = org_repo.repo_name
90 #gather forks and add to this list
91 #gather forks and add to this list
91 for fork in org_repo.forks:
92 for fork in org_repo.forks:
92 c.other_repos.append((fork.repo_name, '%s/%s' % (
93 c.other_repos.append((fork.repo_name, '%s/%s' % (
93 fork.user.username, fork.repo_name))
94 fork.user.username, fork.repo_name))
94 )
95 )
95 #add parents of this fork also
96 #add parents of this fork also
96 if org_repo.parent:
97 if org_repo.parent:
97 c.default_pull_request = org_repo.parent.repo_name
98 c.default_pull_request = org_repo.parent.repo_name
98 c.other_repos.append((org_repo.parent.repo_name, '%s/%s' % (
99 c.other_repos.append((org_repo.parent.repo_name, '%s/%s' % (
99 org_repo.parent.user.username,
100 org_repo.parent.user.username,
100 org_repo.parent.repo_name))
101 org_repo.parent.repo_name))
101 )
102 )
102
103
103 #TODO: maybe the owner should be default ?
104 #TODO: maybe the owner should be default ?
104 c.review_members = []
105 c.review_members = []
105 c.available_members = []
106 c.available_members = []
106 for u in User.query().filter(User.username != 'default').all():
107 for u in User.query().filter(User.username != 'default').all():
107 uname = u.username
108 uname = u.username
108 if org_repo.user == u:
109 if org_repo.user == u:
109 uname = _('%s (owner)' % u.username)
110 uname = _('%s (owner)' % u.username)
110 # auto add owner to pull-request recipients
111 # auto add owner to pull-request recipients
111 c.review_members.append([u.user_id, uname])
112 c.review_members.append([u.user_id, uname])
112 c.available_members.append([u.user_id, uname])
113 c.available_members.append([u.user_id, uname])
113 return render('/pullrequests/pullrequest.html')
114 return render('/pullrequests/pullrequest.html')
114
115
115 def create(self, repo_name):
116 def create(self, repo_name):
116 req_p = request.POST
117 req_p = request.POST
117 org_repo = req_p['org_repo']
118 org_repo = req_p['org_repo']
118 org_ref = req_p['org_ref']
119 org_ref = req_p['org_ref']
119 other_repo = req_p['other_repo']
120 other_repo = req_p['other_repo']
120 other_ref = req_p['other_ref']
121 other_ref = req_p['other_ref']
121 revisions = req_p.getall('revisions')
122 revisions = req_p.getall('revisions')
122 reviewers = req_p.getall('review_members')
123 reviewers = req_p.getall('review_members')
123 #TODO: wrap this into a FORM !!!
124 #TODO: wrap this into a FORM !!!
124
125
125 title = req_p['pullrequest_title']
126 title = req_p['pullrequest_title']
126 description = req_p['pullrequest_desc']
127 description = req_p['pullrequest_desc']
127
128
128 try:
129 try:
129 model = PullRequestModel()
130 model = PullRequestModel()
130 model.create(self.rhodecode_user.user_id, org_repo,
131 model.create(self.rhodecode_user.user_id, org_repo,
131 org_ref, other_repo, other_ref, revisions,
132 org_ref, other_repo, other_ref, revisions,
132 reviewers, title, description)
133 reviewers, title, description)
133 Session.commit()
134 Session.commit()
134 h.flash(_('Pull request send'), category='success')
135 h.flash(_('Pull request send'), category='success')
135 except Exception:
136 except Exception:
136 raise
137 raise
137 h.flash(_('Error occured during sending pull request'),
138 h.flash(_('Error occured during sending pull request'),
138 category='error')
139 category='error')
139 log.error(traceback.format_exc())
140 log.error(traceback.format_exc())
140
141
141 return redirect(url('changelog_home', repo_name=repo_name))
142 return redirect(url('changelog_home', repo_name=repo_name))
142
143
143 def _load_compare_data(self, pull_request):
144 def _load_compare_data(self, pull_request):
144 """
145 """
145 Load context data needed for generating compare diff
146 Load context data needed for generating compare diff
146
147
147 :param pull_request:
148 :param pull_request:
148 :type pull_request:
149 :type pull_request:
149 """
150 """
150
151
151 org_repo = pull_request.org_repo
152 org_repo = pull_request.org_repo
152 org_ref_type, org_ref_, org_ref = pull_request.org_ref.split(':')
153 org_ref_type, org_ref_, org_ref = pull_request.org_ref.split(':')
153 other_repo = pull_request.other_repo
154 other_repo = pull_request.other_repo
154 other_ref_type, other_ref, other_ref_ = pull_request.other_ref.split(':')
155 other_ref_type, other_ref, other_ref_ = pull_request.other_ref.split(':')
155
156
156 org_ref = (org_ref_type, org_ref)
157 org_ref = (org_ref_type, org_ref)
157 other_ref = (other_ref_type, other_ref)
158 other_ref = (other_ref_type, other_ref)
158
159
159 c.org_repo = org_repo
160 c.org_repo = org_repo
160 c.other_repo = other_repo
161 c.other_repo = other_repo
161
162
162 c.cs_ranges, discovery_data = PullRequestModel().get_compare_data(
163 c.cs_ranges, discovery_data = PullRequestModel().get_compare_data(
163 org_repo, org_ref, other_repo, other_ref
164 org_repo, org_ref, other_repo, other_ref
164 )
165 )
165
166
166 c.statuses = c.rhodecode_db_repo.statuses([x.raw_id for x in
167 c.statuses = c.rhodecode_db_repo.statuses([x.raw_id for x in
167 c.cs_ranges])
168 c.cs_ranges])
168 # defines that we need hidden inputs with changesets
169 # defines that we need hidden inputs with changesets
169 c.as_form = request.GET.get('as_form', False)
170 c.as_form = request.GET.get('as_form', False)
170
171
171 c.org_ref = org_ref[1]
172 c.org_ref = org_ref[1]
172 c.other_ref = other_ref[1]
173 c.other_ref = other_ref[1]
173 # diff needs to have swapped org with other to generate proper diff
174 # diff needs to have swapped org with other to generate proper diff
174 _diff = diffs.differ(other_repo, other_ref, org_repo, org_ref,
175 _diff = diffs.differ(other_repo, other_ref, org_repo, org_ref,
175 discovery_data)
176 discovery_data)
176 diff_processor = diffs.DiffProcessor(_diff, format='gitdiff')
177 diff_processor = diffs.DiffProcessor(_diff, format='gitdiff')
177 _parsed = diff_processor.prepare()
178 _parsed = diff_processor.prepare()
178
179
179 c.files = []
180 c.files = []
180 c.changes = {}
181 c.changes = {}
181
182
182 for f in _parsed:
183 for f in _parsed:
183 fid = h.FID('', f['filename'])
184 fid = h.FID('', f['filename'])
184 c.files.append([fid, f['operation'], f['filename'], f['stats']])
185 c.files.append([fid, f['operation'], f['filename'], f['stats']])
185 diff = diff_processor.as_html(enable_comments=False, diff_lines=[f])
186 diff = diff_processor.as_html(enable_comments=False, diff_lines=[f])
186 c.changes[fid] = [f['operation'], f['filename'], diff]
187 c.changes[fid] = [f['operation'], f['filename'], diff]
187
188
188 def show(self, repo_name, pull_request_id):
189 def show(self, repo_name, pull_request_id):
189 repo_model = RepoModel()
190 repo_model = RepoModel()
190 c.users_array = repo_model.get_users_js()
191 c.users_array = repo_model.get_users_js()
191 c.users_groups_array = repo_model.get_users_groups_js()
192 c.users_groups_array = repo_model.get_users_groups_js()
192 c.pull_request = PullRequest.get(pull_request_id)
193 c.pull_request = PullRequest.get(pull_request_id)
193
194
194 # load compare data into template context
195 # load compare data into template context
195 self._load_compare_data(c.pull_request)
196 self._load_compare_data(c.pull_request)
196
197
197 # inline comments
198 # inline comments
198 c.inline_cnt = 0
199 c.inline_cnt = 0
199 c.inline_comments = ChangesetCommentsModel()\
200 c.inline_comments = ChangesetCommentsModel()\
200 .get_inline_comments(c.rhodecode_db_repo.repo_id,
201 .get_inline_comments(c.rhodecode_db_repo.repo_id,
201 pull_request=pull_request_id)
202 pull_request=pull_request_id)
202 # count inline comments
203 # count inline comments
203 for __, lines in c.inline_comments:
204 for __, lines in c.inline_comments:
204 for comments in lines.values():
205 for comments in lines.values():
205 c.inline_cnt += len(comments)
206 c.inline_cnt += len(comments)
206 # comments
207 # comments
207 c.comments = ChangesetCommentsModel()\
208 c.comments = ChangesetCommentsModel()\
208 .get_comments(c.rhodecode_db_repo.repo_id,
209 .get_comments(c.rhodecode_db_repo.repo_id,
209 pull_request=pull_request_id)
210 pull_request=pull_request_id)
210
211
211 # changeset(pull-request) statuse
212 # changeset(pull-request) status
212 c.current_changeset_status = ChangesetStatusModel()\
213 c.current_changeset_status = ChangesetStatusModel()\
213 .get_status(c.rhodecode_db_repo.repo_id,
214 .get_status(c.pull_request.org_repo,
214 pull_request=pull_request_id)
215 pull_request=c.pull_request)
215 c.changeset_statuses = ChangesetStatus.STATUSES
216 c.changeset_statuses = ChangesetStatus.STATUSES
216 return render('/pullrequests/pullrequest_show.html')
217 return render('/pullrequests/pullrequest_show.html')
218
219 @jsonify
220 def comment(self, repo_name, pull_request_id):
221
222 status = request.POST.get('changeset_status')
223 change_status = request.POST.get('change_changeset_status')
224
225 comm = ChangesetCommentsModel().create(
226 text=request.POST.get('text'),
227 repo_id=c.rhodecode_db_repo.repo_id,
228 user_id=c.rhodecode_user.user_id,
229 pull_request=pull_request_id,
230 f_path=request.POST.get('f_path'),
231 line_no=request.POST.get('line'),
232 status_change=(ChangesetStatus.get_status_lbl(status)
233 if status and change_status else None)
234 )
235
236 # get status if set !
237 if status and change_status:
238 ChangesetStatusModel().set_status(
239 c.rhodecode_db_repo.repo_id,
240 status,
241 c.rhodecode_user.user_id,
242 comm,
243 pull_request=pull_request_id
244 )
245 action_logger(self.rhodecode_user,
246 'user_commented_pull_request:%s' % pull_request_id,
247 c.rhodecode_db_repo, self.ip_addr, self.sa)
248
249 Session.commit()
250
251 if not request.environ.get('HTTP_X_PARTIAL_XHR'):
252 return redirect(h.url('pullrequest_show', repo_name=repo_name,
253 pull_request_id=pull_request_id))
254
255 data = {
256 'target_id': h.safeid(h.safe_unicode(request.POST.get('f_path'))),
257 }
258 if comm:
259 c.co = comm
260 data.update(comm.get_dict())
261 data.update({'rendered_text':
262 render('changeset/changeset_comment_block.html')})
263
264 return data No newline at end of file
@@ -1,108 +1,139 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.model.changeset_status
3 rhodecode.model.changeset_status
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6
6
7 :created_on: Apr 30, 2012
7 :created_on: Apr 30, 2012
8 :author: marcink
8 :author: marcink
9 :copyright: (C) 2011-2012 Marcin Kuzminski <marcin@python-works.com>
9 :copyright: (C) 2011-2012 Marcin Kuzminski <marcin@python-works.com>
10 :license: GPLv3, see COPYING for more details.
10 :license: GPLv3, see COPYING for more details.
11 """
11 """
12 # This program is free software: you can redistribute it and/or modify
12 # This program is free software: you can redistribute it and/or modify
13 # it under the terms of the GNU General Public License as published by
13 # it under the terms of the GNU General Public License as published by
14 # the Free Software Foundation, either version 3 of the License, or
14 # the Free Software Foundation, either version 3 of the License, or
15 # (at your option) any later version.
15 # (at your option) any later version.
16 #
16 #
17 # This program is distributed in the hope that it will be useful,
17 # This program is distributed in the hope that it will be useful,
18 # but WITHOUT ANY WARRANTY; without even the implied warranty of
18 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # GNU General Public License for more details.
20 # GNU General Public License for more details.
21 #
21 #
22 # You should have received a copy of the GNU General Public License
22 # You should have received a copy of the GNU General Public License
23 # along with this program. If not, see <http://www.gnu.org/licenses/>.
23 # along with this program. If not, see <http://www.gnu.org/licenses/>.
24
24
25
25
26 import logging
26 import logging
27
27
28 from rhodecode.model import BaseModel
28 from rhodecode.model import BaseModel
29 from rhodecode.model.db import ChangesetStatus, PullRequest
29 from rhodecode.model.db import ChangesetStatus, PullRequest
30
30
31 log = logging.getLogger(__name__)
31 log = logging.getLogger(__name__)
32
32
33
33
34 class ChangesetStatusModel(BaseModel):
34 class ChangesetStatusModel(BaseModel):
35
35
36 def __get_changeset_status(self, changeset_status):
36 def __get_changeset_status(self, changeset_status):
37 return self._get_instance(ChangesetStatus, changeset_status)
37 return self._get_instance(ChangesetStatus, changeset_status)
38
38
39 def __get_pull_request(self, pull_request):
39 def __get_pull_request(self, pull_request):
40 return self._get_instance(PullRequest, pull_request)
40 return self._get_instance(PullRequest, pull_request)
41
41
42 def get_status(self, repo, revision=None, pull_request=None):
42 def get_status(self, repo, revision=None, pull_request=None):
43 """
43 """
44 Returns latest status of changeset for given revision or for given
44 Returns latest status of changeset for given revision or for given
45 pull request. Statuses are versioned inside a table itself and
45 pull request. Statuses are versioned inside a table itself and
46 version == 0 is always the current one
46 version == 0 is always the current one
47
47
48 :param repo:
48 :param repo:
49 :type repo:
49 :type repo:
50 :param revision: 40char hash or None
50 :param revision: 40char hash or None
51 :type revision: str
51 :type revision: str
52 :param pull_request: pull_request reference
52 :param pull_request: pull_request reference
53 :type:
53 :type:
54 """
54 """
55 repo = self._get_repo(repo)
55 repo = self._get_repo(repo)
56
56
57 q = ChangesetStatus.query()\
57 q = ChangesetStatus.query()\
58 .filter(ChangesetStatus.repo == repo)\
58 .filter(ChangesetStatus.repo == repo)\
59 .filter(ChangesetStatus.version == 0)
59 .filter(ChangesetStatus.version == 0)
60
60
61 if revision:
61 if revision:
62 q = q.filter(ChangesetStatus.revision == revision)
62 q = q.filter(ChangesetStatus.revision == revision)
63 elif pull_request:
63 elif pull_request:
64 pull_request = self.__get_pull_request(pull_request)
64 pull_request = self.__get_pull_request(pull_request)
65 q = q.filter(ChangesetStatus.pull_request == pull_request)
65 q = q.filter(ChangesetStatus.pull_request == pull_request)
66 else:
66 else:
67 raise Exception('Please specify revision or pull_request')
67 raise Exception('Please specify revision or pull_request')
68
68
69 status = q.scalar()
69 # need to use first here since there can be multiple statuses
70 # returned from pull_request
71 status = q.first()
70 status = status.status if status else status
72 status = status.status if status else status
71 st = status or ChangesetStatus.DEFAULT
73 st = status or ChangesetStatus.DEFAULT
72 return str(st)
74 return str(st)
73
75
74 def set_status(self, repo, revision, status, user, comment):
76 def set_status(self, repo, status, user, comment, revision=None,
77 pull_request=None):
75 """
78 """
76 Creates new status for changeset or updates the old ones bumping their
79 Creates new status for changeset or updates the old ones bumping their
77 version, leaving the current status at
80 version, leaving the current status at
78
81
79 :param repo:
82 :param repo:
80 :type repo:
83 :type repo:
81 :param revision:
84 :param revision:
82 :type revision:
85 :type revision:
83 :param status:
86 :param status:
84 :type status:
87 :type status:
85 :param user:
88 :param user:
86 :type user:
89 :type user:
87 :param comment:
90 :param comment:
88 :type comment:
91 :type comment:
89 """
92 """
90 repo = self._get_repo(repo)
93 repo = self._get_repo(repo)
91
94
92 cur_statuses = ChangesetStatus.query()\
95 q = ChangesetStatus.query()
93 .filter(ChangesetStatus.repo == repo)\
96
94 .filter(ChangesetStatus.revision == revision)\
97 if revision:
95 .all()
98 q = q.filter(ChangesetStatus.repo == repo)
99 q = q.filter(ChangesetStatus.revision == revision)
100 elif pull_request:
101 pull_request = self.__get_pull_request(pull_request)
102 q = q.filter(ChangesetStatus.repo == pull_request.org_repo)
103 q = q.filter(ChangesetStatus.pull_request == pull_request)
104 cur_statuses = q.all()
105
96 if cur_statuses:
106 if cur_statuses:
97 for st in cur_statuses:
107 for st in cur_statuses:
98 st.version += 1
108 st.version += 1
99 self.sa.add(st)
109 self.sa.add(st)
100 new_status = ChangesetStatus()
110
101 new_status.author = self._get_user(user)
111 def _create_status(user, repo, status, comment, revision, pull_request):
102 new_status.repo = self._get_repo(repo)
112 new_status = ChangesetStatus()
103 new_status.status = status
113 new_status.author = self._get_user(user)
104 new_status.revision = revision
114 new_status.repo = self._get_repo(repo)
105 new_status.comment = comment
115 new_status.status = status
106 self.sa.add(new_status)
116 new_status.comment = comment
107 return new_status
117 new_status.revision = revision
118 new_status.pull_request = pull_request
119 return new_status
108
120
121 if revision:
122 new_status = _create_status(user=user, repo=repo, status=status,
123 comment=comment, revision=revision,
124 pull_request=None)
125 self.sa.add(new_status)
126 return new_status
127 elif pull_request:
128 #pull request can have more than one revision associated to it
129 #we need to create new version for each one
130 new_statuses = []
131 repo = pull_request.org_repo
132 for rev in pull_request.revisions:
133 new_status = _create_status(user=user, repo=repo,
134 status=status, comment=comment,
135 revision=rev,
136 pull_request=pull_request)
137 new_statuses.append(new_status)
138 self.sa.add(new_status)
139 return new_statuses
@@ -1,188 +1,211 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.model.comment
3 rhodecode.model.comment
4 ~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 comments model for RhodeCode
6 comments model for RhodeCode
7
7
8 :created_on: Nov 11, 2011
8 :created_on: Nov 11, 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 logging
26 import logging
27 import traceback
27 import traceback
28
28
29 from pylons.i18n.translation import _
29 from pylons.i18n.translation import _
30 from sqlalchemy.util.compat import defaultdict
30 from sqlalchemy.util.compat import defaultdict
31
31
32 from rhodecode.lib.utils2 import extract_mentioned_users, safe_unicode
32 from rhodecode.lib.utils2 import extract_mentioned_users, safe_unicode
33 from rhodecode.lib import helpers as h
33 from rhodecode.lib import helpers as h
34 from rhodecode.model import BaseModel
34 from rhodecode.model import BaseModel
35 from rhodecode.model.db import ChangesetComment, User, Repository, \
35 from rhodecode.model.db import ChangesetComment, User, Repository, \
36 Notification, PullRequest
36 Notification, PullRequest
37 from rhodecode.model.notification import NotificationModel
37 from rhodecode.model.notification import NotificationModel
38
38
39 log = logging.getLogger(__name__)
39 log = logging.getLogger(__name__)
40
40
41
41
42 class ChangesetCommentsModel(BaseModel):
42 class ChangesetCommentsModel(BaseModel):
43
43
44 def __get_changeset_comment(self, changeset_comment):
44 def __get_changeset_comment(self, changeset_comment):
45 return self._get_instance(ChangesetComment, changeset_comment)
45 return self._get_instance(ChangesetComment, changeset_comment)
46
46
47 def __get_pull_request(self, pull_request):
47 def __get_pull_request(self, pull_request):
48 return self._get_instance(PullRequest, pull_request)
48 return self._get_instance(PullRequest, pull_request)
49
49
50 def _extract_mentions(self, s):
50 def _extract_mentions(self, s):
51 user_objects = []
51 user_objects = []
52 for username in extract_mentioned_users(s):
52 for username in extract_mentioned_users(s):
53 user_obj = User.get_by_username(username, case_insensitive=True)
53 user_obj = User.get_by_username(username, case_insensitive=True)
54 if user_obj:
54 if user_obj:
55 user_objects.append(user_obj)
55 user_objects.append(user_obj)
56 return user_objects
56 return user_objects
57
57
58 def create(self, text, repo_id, user_id, revision, f_path=None,
58 def create(self, text, repo_id, user_id, revision=None, pull_request=None,
59 line_no=None, status_change=None):
59 f_path=None, line_no=None, status_change=None):
60 """
60 """
61 Creates new comment for changeset. IF status_change is not none
61 Creates new comment for changeset or pull request.
62 this comment is associated with a status change of changeset
62 IF status_change is not none this comment is associated with a
63 status change of changeset or changesets associated with pull request
63
64
64 :param text:
65 :param text:
65 :param repo_id:
66 :param repo_id:
66 :param user_id:
67 :param user_id:
67 :param revision:
68 :param revision:
69 :param pull_request:
68 :param f_path:
70 :param f_path:
69 :param line_no:
71 :param line_no:
70 :param status_change:
72 :param status_change:
71 """
73 """
74 if not text:
75 return
72
76
73 if text:
77 repo = Repository.get(repo_id)
74 repo = Repository.get(repo_id)
78 comment = ChangesetComment()
79 comment.repo = repo
80 comment.user_id = user_id
81 comment.text = text
82 comment.f_path = f_path
83 comment.line_no = line_no
84
85 if revision:
75 cs = repo.scm_instance.get_changeset(revision)
86 cs = repo.scm_instance.get_changeset(revision)
76 desc = "%s - %s" % (cs.short_id, h.shorter(cs.message, 256))
87 desc = "%s - %s" % (cs.short_id, h.shorter(cs.message, 256))
77 author_email = cs.author_email
88 author_email = cs.author_email
78 comment = ChangesetComment()
79 comment.repo = repo
80 comment.user_id = user_id
81 comment.revision = revision
89 comment.revision = revision
82 comment.text = text
90 elif pull_request:
83 comment.f_path = f_path
91 pull_request = self.__get_pull_request(pull_request)
84 comment.line_no = line_no
92 comment.pull_request = pull_request
93 desc = ''
94 else:
95 raise Exception('Please specify revision or pull_request_id')
85
96
86 self.sa.add(comment)
97 self.sa.add(comment)
87 self.sa.flush()
98 self.sa.flush()
88 # make notification
99
89 line = ''
100 # make notification
101 line = ''
102 body = text
103
104 #changeset
105 if revision:
90 if line_no:
106 if line_no:
91 line = _('on line %s') % line_no
107 line = _('on line %s') % line_no
92 subj = safe_unicode(
108 subj = safe_unicode(
93 h.link_to('Re commit: %(commit_desc)s %(line)s' % \
109 h.link_to('Re commit: %(commit_desc)s %(line)s' % \
94 {'commit_desc': desc, 'line': line},
110 {'commit_desc': desc, 'line': line},
95 h.url('changeset_home', repo_name=repo.repo_name,
111 h.url('changeset_home', repo_name=repo.repo_name,
96 revision=revision,
112 revision=revision,
97 anchor='comment-%s' % comment.comment_id,
113 anchor='comment-%s' % comment.comment_id,
98 qualified=True,
114 qualified=True,
99 )
115 )
100 )
116 )
101 )
117 )
102
118 notification_type = Notification.TYPE_CHANGESET_COMMENT
103 body = text
104
105 # get the current participants of this changeset
119 # get the current participants of this changeset
106 recipients = ChangesetComment.get_users(revision=revision)
120 recipients = ChangesetComment.get_users(revision=revision)
107
108 # add changeset author if it's in rhodecode system
121 # add changeset author if it's in rhodecode system
109 recipients += [User.get_by_email(author_email)]
122 recipients += [User.get_by_email(author_email)]
123 #pull request
124 elif pull_request:
125 #TODO: make this something usefull
126 subj = 'commented on pull request something...'
127 notification_type = Notification.TYPE_PULL_REQUEST_COMMENT
128 # get the current participants of this pull request
129 recipients = ChangesetComment.get_users(pull_request_id=
130 pull_request.pull_request_id)
131 # add pull request author
132 recipients += [pull_request.author]
110
133
111 # create notification objects, and emails
134 # create notification objects, and emails
135 NotificationModel().create(
136 created_by=user_id, subject=subj, body=body,
137 recipients=recipients, type_=notification_type,
138 email_kwargs={'status_change': status_change}
139 )
140
141 mention_recipients = set(self._extract_mentions(body))\
142 .difference(recipients)
143 if mention_recipients:
144 subj = _('[Mention]') + ' ' + subj
112 NotificationModel().create(
145 NotificationModel().create(
113 created_by=user_id, subject=subj, body=body,
146 created_by=user_id, subject=subj, body=body,
114 recipients=recipients, type_=Notification.TYPE_CHANGESET_COMMENT,
147 recipients=mention_recipients,
115 email_kwargs={'status_change': status_change}
148 type_=notification_type,
149 email_kwargs={'status_change': status_change}
116 )
150 )
117
151
118 mention_recipients = set(self._extract_mentions(body))\
152 return comment
119 .difference(recipients)
120 if mention_recipients:
121 subj = _('[Mention]') + ' ' + subj
122 NotificationModel().create(
123 created_by=user_id, subject=subj, body=body,
124 recipients=mention_recipients,
125 type_=Notification.TYPE_CHANGESET_COMMENT,
126 email_kwargs={'status_change': status_change}
127 )
128
129 return comment
130
153
131 def delete(self, comment):
154 def delete(self, comment):
132 """
155 """
133 Deletes given comment
156 Deletes given comment
134
157
135 :param comment_id:
158 :param comment_id:
136 """
159 """
137 comment = self.__get_changeset_comment(comment)
160 comment = self.__get_changeset_comment(comment)
138 self.sa.delete(comment)
161 self.sa.delete(comment)
139
162
140 return comment
163 return comment
141
164
142 def get_comments(self, repo_id, revision=None, pull_request=None):
165 def get_comments(self, repo_id, revision=None, pull_request=None):
143 """
166 """
144 Get's main comments based on revision or pull_request_id
167 Get's main comments based on revision or pull_request_id
145
168
146 :param repo_id:
169 :param repo_id:
147 :type repo_id:
170 :type repo_id:
148 :param revision:
171 :param revision:
149 :type revision:
172 :type revision:
150 :param pull_request:
173 :param pull_request:
151 :type pull_request:
174 :type pull_request:
152 """
175 """
153
176
154 q = ChangesetComment.query()\
177 q = ChangesetComment.query()\
155 .filter(ChangesetComment.repo_id == repo_id)\
178 .filter(ChangesetComment.repo_id == repo_id)\
156 .filter(ChangesetComment.line_no == None)\
179 .filter(ChangesetComment.line_no == None)\
157 .filter(ChangesetComment.f_path == None)
180 .filter(ChangesetComment.f_path == None)
158 if revision:
181 if revision:
159 q = q.filter(ChangesetComment.revision == revision)
182 q = q.filter(ChangesetComment.revision == revision)
160 elif pull_request:
183 elif pull_request:
161 pull_request = self.__get_pull_request(pull_request)
184 pull_request = self.__get_pull_request(pull_request)
162 q = q.filter(ChangesetComment.pull_request == pull_request)
185 q = q.filter(ChangesetComment.pull_request == pull_request)
163 else:
186 else:
164 raise Exception('Please specify revision or pull_request')
187 raise Exception('Please specify revision or pull_request')
165 return q.all()
188 return q.all()
166
189
167 def get_inline_comments(self, repo_id, revision=None, pull_request=None):
190 def get_inline_comments(self, repo_id, revision=None, pull_request=None):
168 q = self.sa.query(ChangesetComment)\
191 q = self.sa.query(ChangesetComment)\
169 .filter(ChangesetComment.repo_id == repo_id)\
192 .filter(ChangesetComment.repo_id == repo_id)\
170 .filter(ChangesetComment.line_no != None)\
193 .filter(ChangesetComment.line_no != None)\
171 .filter(ChangesetComment.f_path != None)\
194 .filter(ChangesetComment.f_path != None)\
172 .order_by(ChangesetComment.comment_id.asc())\
195 .order_by(ChangesetComment.comment_id.asc())\
173
196
174 if revision:
197 if revision:
175 q = q.filter(ChangesetComment.revision == revision)
198 q = q.filter(ChangesetComment.revision == revision)
176 elif pull_request:
199 elif pull_request:
177 pull_request = self.__get_pull_request(pull_request)
200 pull_request = self.__get_pull_request(pull_request)
178 q = q.filter(ChangesetComment.pull_request == pull_request)
201 q = q.filter(ChangesetComment.pull_request == pull_request)
179 else:
202 else:
180 raise Exception('Please specify revision or pull_request_id')
203 raise Exception('Please specify revision or pull_request_id')
181
204
182 comments = q.all()
205 comments = q.all()
183
206
184 paths = defaultdict(lambda: defaultdict(list))
207 paths = defaultdict(lambda: defaultdict(list))
185
208
186 for co in comments:
209 for co in comments:
187 paths[co.f_path][co.line_no].append(co)
210 paths[co.f_path][co.line_no].append(co)
188 return paths.items()
211 return paths.items()
@@ -1,1532 +1,1542 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 from collections import defaultdict
31 from collections import defaultdict
32
32
33 from sqlalchemy import *
33 from sqlalchemy import *
34 from sqlalchemy.ext.hybrid import hybrid_property
34 from sqlalchemy.ext.hybrid import hybrid_property
35 from sqlalchemy.orm import relationship, joinedload, class_mapper, validates
35 from sqlalchemy.orm import relationship, joinedload, class_mapper, validates
36 from sqlalchemy.exc import DatabaseError
36 from sqlalchemy.exc import DatabaseError
37 from beaker.cache import cache_region, region_invalidate
37 from beaker.cache import cache_region, region_invalidate
38
38
39 from pylons.i18n.translation import lazy_ugettext as _
39 from pylons.i18n.translation import lazy_ugettext as _
40
40
41 from rhodecode.lib.vcs import get_backend
41 from rhodecode.lib.vcs import get_backend
42 from rhodecode.lib.vcs.utils.helpers import get_scm
42 from rhodecode.lib.vcs.utils.helpers import get_scm
43 from rhodecode.lib.vcs.exceptions import VCSError
43 from rhodecode.lib.vcs.exceptions import VCSError
44 from rhodecode.lib.vcs.utils.lazy import LazyProperty
44 from rhodecode.lib.vcs.utils.lazy import LazyProperty
45
45
46 from rhodecode.lib.utils2 import str2bool, safe_str, get_changeset_safe, \
46 from rhodecode.lib.utils2 import str2bool, safe_str, get_changeset_safe, \
47 safe_unicode
47 safe_unicode
48 from rhodecode.lib.compat import json
48 from rhodecode.lib.compat import json
49 from rhodecode.lib.caching_query import FromCache
49 from rhodecode.lib.caching_query import FromCache
50
50
51 from rhodecode.model.meta import Base, Session
51 from rhodecode.model.meta import Base, Session
52
52
53
53
54 URL_SEP = '/'
54 URL_SEP = '/'
55 log = logging.getLogger(__name__)
55 log = logging.getLogger(__name__)
56
56
57 #==============================================================================
57 #==============================================================================
58 # BASE CLASSES
58 # BASE CLASSES
59 #==============================================================================
59 #==============================================================================
60
60
61 _hash_key = lambda k: hashlib.md5(safe_str(k)).hexdigest()
61 _hash_key = lambda k: hashlib.md5(safe_str(k)).hexdigest()
62
62
63
63
64 class ModelSerializer(json.JSONEncoder):
64 class ModelSerializer(json.JSONEncoder):
65 """
65 """
66 Simple Serializer for JSON,
66 Simple Serializer for JSON,
67
67
68 usage::
68 usage::
69
69
70 to make object customized for serialization implement a __json__
70 to make object customized for serialization implement a __json__
71 method that will return a dict for serialization into json
71 method that will return a dict for serialization into json
72
72
73 example::
73 example::
74
74
75 class Task(object):
75 class Task(object):
76
76
77 def __init__(self, name, value):
77 def __init__(self, name, value):
78 self.name = name
78 self.name = name
79 self.value = value
79 self.value = value
80
80
81 def __json__(self):
81 def __json__(self):
82 return dict(name=self.name,
82 return dict(name=self.name,
83 value=self.value)
83 value=self.value)
84
84
85 """
85 """
86
86
87 def default(self, obj):
87 def default(self, obj):
88
88
89 if hasattr(obj, '__json__'):
89 if hasattr(obj, '__json__'):
90 return obj.__json__()
90 return obj.__json__()
91 else:
91 else:
92 return json.JSONEncoder.default(self, obj)
92 return json.JSONEncoder.default(self, obj)
93
93
94
94
95 class BaseModel(object):
95 class BaseModel(object):
96 """
96 """
97 Base Model for all classess
97 Base Model for all classess
98 """
98 """
99
99
100 @classmethod
100 @classmethod
101 def _get_keys(cls):
101 def _get_keys(cls):
102 """return column names for this model """
102 """return column names for this model """
103 return class_mapper(cls).c.keys()
103 return class_mapper(cls).c.keys()
104
104
105 def get_dict(self):
105 def get_dict(self):
106 """
106 """
107 return dict with keys and values corresponding
107 return dict with keys and values corresponding
108 to this model data """
108 to this model data """
109
109
110 d = {}
110 d = {}
111 for k in self._get_keys():
111 for k in self._get_keys():
112 d[k] = getattr(self, k)
112 d[k] = getattr(self, k)
113
113
114 # also use __json__() if present to get additional fields
114 # also use __json__() if present to get additional fields
115 for k, val in getattr(self, '__json__', lambda: {})().iteritems():
115 for k, val in getattr(self, '__json__', lambda: {})().iteritems():
116 d[k] = val
116 d[k] = val
117 return d
117 return d
118
118
119 def get_appstruct(self):
119 def get_appstruct(self):
120 """return list with keys and values tupples corresponding
120 """return list with keys and values tupples corresponding
121 to this model data """
121 to this model data """
122
122
123 l = []
123 l = []
124 for k in self._get_keys():
124 for k in self._get_keys():
125 l.append((k, getattr(self, k),))
125 l.append((k, getattr(self, k),))
126 return l
126 return l
127
127
128 def populate_obj(self, populate_dict):
128 def populate_obj(self, populate_dict):
129 """populate model with data from given populate_dict"""
129 """populate model with data from given populate_dict"""
130
130
131 for k in self._get_keys():
131 for k in self._get_keys():
132 if k in populate_dict:
132 if k in populate_dict:
133 setattr(self, k, populate_dict[k])
133 setattr(self, k, populate_dict[k])
134
134
135 @classmethod
135 @classmethod
136 def query(cls):
136 def query(cls):
137 return Session.query(cls)
137 return Session.query(cls)
138
138
139 @classmethod
139 @classmethod
140 def get(cls, id_):
140 def get(cls, id_):
141 if id_:
141 if id_:
142 return cls.query().get(id_)
142 return cls.query().get(id_)
143
143
144 @classmethod
144 @classmethod
145 def getAll(cls):
145 def getAll(cls):
146 return cls.query().all()
146 return cls.query().all()
147
147
148 @classmethod
148 @classmethod
149 def delete(cls, id_):
149 def delete(cls, id_):
150 obj = cls.query().get(id_)
150 obj = cls.query().get(id_)
151 Session.delete(obj)
151 Session.delete(obj)
152
152
153 def __repr__(self):
153 def __repr__(self):
154 if hasattr(self, '__unicode__'):
154 if hasattr(self, '__unicode__'):
155 # python repr needs to return str
155 # python repr needs to return str
156 return safe_str(self.__unicode__())
156 return safe_str(self.__unicode__())
157 return '<DB:%s>' % (self.__class__.__name__)
157 return '<DB:%s>' % (self.__class__.__name__)
158
158
159
159
160 class RhodeCodeSetting(Base, BaseModel):
160 class RhodeCodeSetting(Base, BaseModel):
161 __tablename__ = 'rhodecode_settings'
161 __tablename__ = 'rhodecode_settings'
162 __table_args__ = (
162 __table_args__ = (
163 UniqueConstraint('app_settings_name'),
163 UniqueConstraint('app_settings_name'),
164 {'extend_existing': True, 'mysql_engine': 'InnoDB',
164 {'extend_existing': True, 'mysql_engine': 'InnoDB',
165 'mysql_charset': 'utf8'}
165 'mysql_charset': 'utf8'}
166 )
166 )
167 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
167 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
168 app_settings_name = Column("app_settings_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
168 app_settings_name = Column("app_settings_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
169 _app_settings_value = Column("app_settings_value", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
169 _app_settings_value = Column("app_settings_value", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
170
170
171 def __init__(self, k='', v=''):
171 def __init__(self, k='', v=''):
172 self.app_settings_name = k
172 self.app_settings_name = k
173 self.app_settings_value = v
173 self.app_settings_value = v
174
174
175 @validates('_app_settings_value')
175 @validates('_app_settings_value')
176 def validate_settings_value(self, key, val):
176 def validate_settings_value(self, key, val):
177 assert type(val) == unicode
177 assert type(val) == unicode
178 return val
178 return val
179
179
180 @hybrid_property
180 @hybrid_property
181 def app_settings_value(self):
181 def app_settings_value(self):
182 v = self._app_settings_value
182 v = self._app_settings_value
183 if self.app_settings_name == 'ldap_active':
183 if self.app_settings_name == 'ldap_active':
184 v = str2bool(v)
184 v = str2bool(v)
185 return v
185 return v
186
186
187 @app_settings_value.setter
187 @app_settings_value.setter
188 def app_settings_value(self, val):
188 def app_settings_value(self, val):
189 """
189 """
190 Setter that will always make sure we use unicode in app_settings_value
190 Setter that will always make sure we use unicode in app_settings_value
191
191
192 :param val:
192 :param val:
193 """
193 """
194 self._app_settings_value = safe_unicode(val)
194 self._app_settings_value = safe_unicode(val)
195
195
196 def __unicode__(self):
196 def __unicode__(self):
197 return u"<%s('%s:%s')>" % (
197 return u"<%s('%s:%s')>" % (
198 self.__class__.__name__,
198 self.__class__.__name__,
199 self.app_settings_name, self.app_settings_value
199 self.app_settings_name, self.app_settings_value
200 )
200 )
201
201
202 @classmethod
202 @classmethod
203 def get_by_name(cls, ldap_key):
203 def get_by_name(cls, ldap_key):
204 return cls.query()\
204 return cls.query()\
205 .filter(cls.app_settings_name == ldap_key).scalar()
205 .filter(cls.app_settings_name == ldap_key).scalar()
206
206
207 @classmethod
207 @classmethod
208 def get_app_settings(cls, cache=False):
208 def get_app_settings(cls, cache=False):
209
209
210 ret = cls.query()
210 ret = cls.query()
211
211
212 if cache:
212 if cache:
213 ret = ret.options(FromCache("sql_cache_short", "get_hg_settings"))
213 ret = ret.options(FromCache("sql_cache_short", "get_hg_settings"))
214
214
215 if not ret:
215 if not ret:
216 raise Exception('Could not get application settings !')
216 raise Exception('Could not get application settings !')
217 settings = {}
217 settings = {}
218 for each in ret:
218 for each in ret:
219 settings['rhodecode_' + each.app_settings_name] = \
219 settings['rhodecode_' + each.app_settings_name] = \
220 each.app_settings_value
220 each.app_settings_value
221
221
222 return settings
222 return settings
223
223
224 @classmethod
224 @classmethod
225 def get_ldap_settings(cls, cache=False):
225 def get_ldap_settings(cls, cache=False):
226 ret = cls.query()\
226 ret = cls.query()\
227 .filter(cls.app_settings_name.startswith('ldap_')).all()
227 .filter(cls.app_settings_name.startswith('ldap_')).all()
228 fd = {}
228 fd = {}
229 for row in ret:
229 for row in ret:
230 fd.update({row.app_settings_name: row.app_settings_value})
230 fd.update({row.app_settings_name: row.app_settings_value})
231
231
232 return fd
232 return fd
233
233
234
234
235 class RhodeCodeUi(Base, BaseModel):
235 class RhodeCodeUi(Base, BaseModel):
236 __tablename__ = 'rhodecode_ui'
236 __tablename__ = 'rhodecode_ui'
237 __table_args__ = (
237 __table_args__ = (
238 UniqueConstraint('ui_key'),
238 UniqueConstraint('ui_key'),
239 {'extend_existing': True, 'mysql_engine': 'InnoDB',
239 {'extend_existing': True, 'mysql_engine': 'InnoDB',
240 'mysql_charset': 'utf8'}
240 'mysql_charset': 'utf8'}
241 )
241 )
242
242
243 HOOK_UPDATE = 'changegroup.update'
243 HOOK_UPDATE = 'changegroup.update'
244 HOOK_REPO_SIZE = 'changegroup.repo_size'
244 HOOK_REPO_SIZE = 'changegroup.repo_size'
245 HOOK_PUSH = 'changegroup.push_logger'
245 HOOK_PUSH = 'changegroup.push_logger'
246 HOOK_PULL = 'preoutgoing.pull_logger'
246 HOOK_PULL = 'preoutgoing.pull_logger'
247
247
248 ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
248 ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
249 ui_section = Column("ui_section", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
249 ui_section = Column("ui_section", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
250 ui_key = Column("ui_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
250 ui_key = Column("ui_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
251 ui_value = Column("ui_value", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
251 ui_value = Column("ui_value", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
252 ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True)
252 ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True)
253
253
254 @classmethod
254 @classmethod
255 def get_by_key(cls, key):
255 def get_by_key(cls, key):
256 return cls.query().filter(cls.ui_key == key)
256 return cls.query().filter(cls.ui_key == key)
257
257
258 @classmethod
258 @classmethod
259 def get_builtin_hooks(cls):
259 def get_builtin_hooks(cls):
260 q = cls.query()
260 q = cls.query()
261 q = q.filter(cls.ui_key.in_([cls.HOOK_UPDATE,
261 q = q.filter(cls.ui_key.in_([cls.HOOK_UPDATE,
262 cls.HOOK_REPO_SIZE,
262 cls.HOOK_REPO_SIZE,
263 cls.HOOK_PUSH, cls.HOOK_PULL]))
263 cls.HOOK_PUSH, cls.HOOK_PULL]))
264 return q.all()
264 return q.all()
265
265
266 @classmethod
266 @classmethod
267 def get_custom_hooks(cls):
267 def get_custom_hooks(cls):
268 q = cls.query()
268 q = cls.query()
269 q = q.filter(~cls.ui_key.in_([cls.HOOK_UPDATE,
269 q = q.filter(~cls.ui_key.in_([cls.HOOK_UPDATE,
270 cls.HOOK_REPO_SIZE,
270 cls.HOOK_REPO_SIZE,
271 cls.HOOK_PUSH, cls.HOOK_PULL]))
271 cls.HOOK_PUSH, cls.HOOK_PULL]))
272 q = q.filter(cls.ui_section == 'hooks')
272 q = q.filter(cls.ui_section == 'hooks')
273 return q.all()
273 return q.all()
274
274
275 @classmethod
275 @classmethod
276 def get_repos_location(cls):
276 def get_repos_location(cls):
277 return cls.get_by_key('/').one().ui_value
277 return cls.get_by_key('/').one().ui_value
278
278
279 @classmethod
279 @classmethod
280 def create_or_update_hook(cls, key, val):
280 def create_or_update_hook(cls, key, val):
281 new_ui = cls.get_by_key(key).scalar() or cls()
281 new_ui = cls.get_by_key(key).scalar() or cls()
282 new_ui.ui_section = 'hooks'
282 new_ui.ui_section = 'hooks'
283 new_ui.ui_active = True
283 new_ui.ui_active = True
284 new_ui.ui_key = key
284 new_ui.ui_key = key
285 new_ui.ui_value = val
285 new_ui.ui_value = val
286
286
287 Session.add(new_ui)
287 Session.add(new_ui)
288
288
289
289
290 class User(Base, BaseModel):
290 class User(Base, BaseModel):
291 __tablename__ = 'users'
291 __tablename__ = 'users'
292 __table_args__ = (
292 __table_args__ = (
293 UniqueConstraint('username'), UniqueConstraint('email'),
293 UniqueConstraint('username'), UniqueConstraint('email'),
294 {'extend_existing': True, 'mysql_engine': 'InnoDB',
294 {'extend_existing': True, 'mysql_engine': 'InnoDB',
295 'mysql_charset': 'utf8'}
295 'mysql_charset': 'utf8'}
296 )
296 )
297 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
297 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
298 username = Column("username", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
298 username = Column("username", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
299 password = Column("password", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
299 password = Column("password", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
300 active = Column("active", Boolean(), nullable=True, unique=None, default=None)
300 active = Column("active", Boolean(), nullable=True, unique=None, default=None)
301 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
301 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
302 name = Column("name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
302 name = Column("name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
303 lastname = Column("lastname", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
303 lastname = Column("lastname", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
304 _email = Column("email", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
304 _email = Column("email", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
305 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
305 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
306 ldap_dn = Column("ldap_dn", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
306 ldap_dn = Column("ldap_dn", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
307 api_key = Column("api_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
307 api_key = Column("api_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
308
308
309 user_log = relationship('UserLog', cascade='all')
309 user_log = relationship('UserLog', cascade='all')
310 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
310 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
311
311
312 repositories = relationship('Repository')
312 repositories = relationship('Repository')
313 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
313 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
314 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
314 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
315 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
315 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
316
316
317 group_member = relationship('UsersGroupMember', cascade='all')
317 group_member = relationship('UsersGroupMember', cascade='all')
318
318
319 notifications = relationship('UserNotification', cascade='all')
319 notifications = relationship('UserNotification', cascade='all')
320 # notifications assigned to this user
320 # notifications assigned to this user
321 user_created_notifications = relationship('Notification', cascade='all')
321 user_created_notifications = relationship('Notification', cascade='all')
322 # comments created by this user
322 # comments created by this user
323 user_comments = relationship('ChangesetComment', cascade='all')
323 user_comments = relationship('ChangesetComment', cascade='all')
324
324
325 @hybrid_property
325 @hybrid_property
326 def email(self):
326 def email(self):
327 return self._email
327 return self._email
328
328
329 @email.setter
329 @email.setter
330 def email(self, val):
330 def email(self, val):
331 self._email = val.lower() if val else None
331 self._email = val.lower() if val else None
332
332
333 @property
333 @property
334 def full_name(self):
334 def full_name(self):
335 return '%s %s' % (self.name, self.lastname)
335 return '%s %s' % (self.name, self.lastname)
336
336
337 @property
337 @property
338 def full_name_or_username(self):
338 def full_name_or_username(self):
339 return ('%s %s' % (self.name, self.lastname)
339 return ('%s %s' % (self.name, self.lastname)
340 if (self.name and self.lastname) else self.username)
340 if (self.name and self.lastname) else self.username)
341
341
342 @property
342 @property
343 def full_contact(self):
343 def full_contact(self):
344 return '%s %s <%s>' % (self.name, self.lastname, self.email)
344 return '%s %s <%s>' % (self.name, self.lastname, self.email)
345
345
346 @property
346 @property
347 def short_contact(self):
347 def short_contact(self):
348 return '%s %s' % (self.name, self.lastname)
348 return '%s %s' % (self.name, self.lastname)
349
349
350 @property
350 @property
351 def is_admin(self):
351 def is_admin(self):
352 return self.admin
352 return self.admin
353
353
354 def __unicode__(self):
354 def __unicode__(self):
355 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
355 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
356 self.user_id, self.username)
356 self.user_id, self.username)
357
357
358 @classmethod
358 @classmethod
359 def get_by_username(cls, username, case_insensitive=False, cache=False):
359 def get_by_username(cls, username, case_insensitive=False, cache=False):
360 if case_insensitive:
360 if case_insensitive:
361 q = cls.query().filter(cls.username.ilike(username))
361 q = cls.query().filter(cls.username.ilike(username))
362 else:
362 else:
363 q = cls.query().filter(cls.username == username)
363 q = cls.query().filter(cls.username == username)
364
364
365 if cache:
365 if cache:
366 q = q.options(FromCache(
366 q = q.options(FromCache(
367 "sql_cache_short",
367 "sql_cache_short",
368 "get_user_%s" % _hash_key(username)
368 "get_user_%s" % _hash_key(username)
369 )
369 )
370 )
370 )
371 return q.scalar()
371 return q.scalar()
372
372
373 @classmethod
373 @classmethod
374 def get_by_api_key(cls, api_key, cache=False):
374 def get_by_api_key(cls, api_key, cache=False):
375 q = cls.query().filter(cls.api_key == api_key)
375 q = cls.query().filter(cls.api_key == api_key)
376
376
377 if cache:
377 if cache:
378 q = q.options(FromCache("sql_cache_short",
378 q = q.options(FromCache("sql_cache_short",
379 "get_api_key_%s" % api_key))
379 "get_api_key_%s" % api_key))
380 return q.scalar()
380 return q.scalar()
381
381
382 @classmethod
382 @classmethod
383 def get_by_email(cls, email, case_insensitive=False, cache=False):
383 def get_by_email(cls, email, case_insensitive=False, cache=False):
384 if case_insensitive:
384 if case_insensitive:
385 q = cls.query().filter(cls.email.ilike(email))
385 q = cls.query().filter(cls.email.ilike(email))
386 else:
386 else:
387 q = cls.query().filter(cls.email == email)
387 q = cls.query().filter(cls.email == email)
388
388
389 if cache:
389 if cache:
390 q = q.options(FromCache("sql_cache_short",
390 q = q.options(FromCache("sql_cache_short",
391 "get_email_key_%s" % email))
391 "get_email_key_%s" % email))
392
392
393 ret = q.scalar()
393 ret = q.scalar()
394 if ret is None:
394 if ret is None:
395 q = UserEmailMap.query()
395 q = UserEmailMap.query()
396 # try fetching in alternate email map
396 # try fetching in alternate email map
397 if case_insensitive:
397 if case_insensitive:
398 q = q.filter(UserEmailMap.email.ilike(email))
398 q = q.filter(UserEmailMap.email.ilike(email))
399 else:
399 else:
400 q = q.filter(UserEmailMap.email == email)
400 q = q.filter(UserEmailMap.email == email)
401 q = q.options(joinedload(UserEmailMap.user))
401 q = q.options(joinedload(UserEmailMap.user))
402 if cache:
402 if cache:
403 q = q.options(FromCache("sql_cache_short",
403 q = q.options(FromCache("sql_cache_short",
404 "get_email_map_key_%s" % email))
404 "get_email_map_key_%s" % email))
405 ret = getattr(q.scalar(), 'user', None)
405 ret = getattr(q.scalar(), 'user', None)
406
406
407 return ret
407 return ret
408
408
409 def update_lastlogin(self):
409 def update_lastlogin(self):
410 """Update user lastlogin"""
410 """Update user lastlogin"""
411 self.last_login = datetime.datetime.now()
411 self.last_login = datetime.datetime.now()
412 Session.add(self)
412 Session.add(self)
413 log.debug('updated user %s lastlogin' % self.username)
413 log.debug('updated user %s lastlogin' % self.username)
414
414
415 def __json__(self):
415 def __json__(self):
416 return dict(
416 return dict(
417 user_id=self.user_id,
417 user_id=self.user_id,
418 first_name=self.name,
418 first_name=self.name,
419 last_name=self.lastname,
419 last_name=self.lastname,
420 email=self.email,
420 email=self.email,
421 full_name=self.full_name,
421 full_name=self.full_name,
422 full_name_or_username=self.full_name_or_username,
422 full_name_or_username=self.full_name_or_username,
423 short_contact=self.short_contact,
423 short_contact=self.short_contact,
424 full_contact=self.full_contact
424 full_contact=self.full_contact
425 )
425 )
426
426
427
427
428 class UserEmailMap(Base, BaseModel):
428 class UserEmailMap(Base, BaseModel):
429 __tablename__ = 'user_email_map'
429 __tablename__ = 'user_email_map'
430 __table_args__ = (
430 __table_args__ = (
431 UniqueConstraint('email'),
431 UniqueConstraint('email'),
432 {'extend_existing': True, 'mysql_engine':'InnoDB',
432 {'extend_existing': True, 'mysql_engine':'InnoDB',
433 'mysql_charset': 'utf8'}
433 'mysql_charset': 'utf8'}
434 )
434 )
435 __mapper_args__ = {}
435 __mapper_args__ = {}
436
436
437 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
437 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
438 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
438 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
439 _email = Column("email", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
439 _email = Column("email", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
440
440
441 user = relationship('User')
441 user = relationship('User')
442
442
443 @validates('_email')
443 @validates('_email')
444 def validate_email(self, key, email):
444 def validate_email(self, key, email):
445 # check if this email is not main one
445 # check if this email is not main one
446 main_email = Session.query(User).filter(User.email == email).scalar()
446 main_email = Session.query(User).filter(User.email == email).scalar()
447 if main_email is not None:
447 if main_email is not None:
448 raise AttributeError('email %s is present is user table' % email)
448 raise AttributeError('email %s is present is user table' % email)
449 return email
449 return email
450
450
451 @hybrid_property
451 @hybrid_property
452 def email(self):
452 def email(self):
453 return self._email
453 return self._email
454
454
455 @email.setter
455 @email.setter
456 def email(self, val):
456 def email(self, val):
457 self._email = val.lower() if val else None
457 self._email = val.lower() if val else None
458
458
459
459
460 class UserLog(Base, BaseModel):
460 class UserLog(Base, BaseModel):
461 __tablename__ = 'user_logs'
461 __tablename__ = 'user_logs'
462 __table_args__ = (
462 __table_args__ = (
463 {'extend_existing': True, 'mysql_engine': 'InnoDB',
463 {'extend_existing': True, 'mysql_engine': 'InnoDB',
464 'mysql_charset': 'utf8'},
464 'mysql_charset': 'utf8'},
465 )
465 )
466 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
466 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
467 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
467 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
468 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True)
468 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True)
469 repository_name = Column("repository_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
469 repository_name = Column("repository_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
470 user_ip = Column("user_ip", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
470 user_ip = Column("user_ip", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
471 action = Column("action", UnicodeText(length=1200000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
471 action = Column("action", UnicodeText(length=1200000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
472 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
472 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
473
473
474 @property
474 @property
475 def action_as_day(self):
475 def action_as_day(self):
476 return datetime.date(*self.action_date.timetuple()[:3])
476 return datetime.date(*self.action_date.timetuple()[:3])
477
477
478 user = relationship('User')
478 user = relationship('User')
479 repository = relationship('Repository', cascade='')
479 repository = relationship('Repository', cascade='')
480
480
481
481
482 class UsersGroup(Base, BaseModel):
482 class UsersGroup(Base, BaseModel):
483 __tablename__ = 'users_groups'
483 __tablename__ = 'users_groups'
484 __table_args__ = (
484 __table_args__ = (
485 {'extend_existing': True, 'mysql_engine': 'InnoDB',
485 {'extend_existing': True, 'mysql_engine': 'InnoDB',
486 'mysql_charset': 'utf8'},
486 'mysql_charset': 'utf8'},
487 )
487 )
488
488
489 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
489 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
490 users_group_name = Column("users_group_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
490 users_group_name = Column("users_group_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
491 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
491 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
492
492
493 members = relationship('UsersGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
493 members = relationship('UsersGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
494 users_group_to_perm = relationship('UsersGroupToPerm', cascade='all')
494 users_group_to_perm = relationship('UsersGroupToPerm', cascade='all')
495 users_group_repo_to_perm = relationship('UsersGroupRepoToPerm', cascade='all')
495 users_group_repo_to_perm = relationship('UsersGroupRepoToPerm', cascade='all')
496
496
497 def __unicode__(self):
497 def __unicode__(self):
498 return u'<userGroup(%s)>' % (self.users_group_name)
498 return u'<userGroup(%s)>' % (self.users_group_name)
499
499
500 @classmethod
500 @classmethod
501 def get_by_group_name(cls, group_name, cache=False,
501 def get_by_group_name(cls, group_name, cache=False,
502 case_insensitive=False):
502 case_insensitive=False):
503 if case_insensitive:
503 if case_insensitive:
504 q = cls.query().filter(cls.users_group_name.ilike(group_name))
504 q = cls.query().filter(cls.users_group_name.ilike(group_name))
505 else:
505 else:
506 q = cls.query().filter(cls.users_group_name == group_name)
506 q = cls.query().filter(cls.users_group_name == group_name)
507 if cache:
507 if cache:
508 q = q.options(FromCache(
508 q = q.options(FromCache(
509 "sql_cache_short",
509 "sql_cache_short",
510 "get_user_%s" % _hash_key(group_name)
510 "get_user_%s" % _hash_key(group_name)
511 )
511 )
512 )
512 )
513 return q.scalar()
513 return q.scalar()
514
514
515 @classmethod
515 @classmethod
516 def get(cls, users_group_id, cache=False):
516 def get(cls, users_group_id, cache=False):
517 users_group = cls.query()
517 users_group = cls.query()
518 if cache:
518 if cache:
519 users_group = users_group.options(FromCache("sql_cache_short",
519 users_group = users_group.options(FromCache("sql_cache_short",
520 "get_users_group_%s" % users_group_id))
520 "get_users_group_%s" % users_group_id))
521 return users_group.get(users_group_id)
521 return users_group.get(users_group_id)
522
522
523
523
524 class UsersGroupMember(Base, BaseModel):
524 class UsersGroupMember(Base, BaseModel):
525 __tablename__ = 'users_groups_members'
525 __tablename__ = 'users_groups_members'
526 __table_args__ = (
526 __table_args__ = (
527 {'extend_existing': True, 'mysql_engine': 'InnoDB',
527 {'extend_existing': True, 'mysql_engine': 'InnoDB',
528 'mysql_charset': 'utf8'},
528 'mysql_charset': 'utf8'},
529 )
529 )
530
530
531 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
531 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
532 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
532 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
533 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
533 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
534
534
535 user = relationship('User', lazy='joined')
535 user = relationship('User', lazy='joined')
536 users_group = relationship('UsersGroup')
536 users_group = relationship('UsersGroup')
537
537
538 def __init__(self, gr_id='', u_id=''):
538 def __init__(self, gr_id='', u_id=''):
539 self.users_group_id = gr_id
539 self.users_group_id = gr_id
540 self.user_id = u_id
540 self.user_id = u_id
541
541
542
542
543 class Repository(Base, BaseModel):
543 class Repository(Base, BaseModel):
544 __tablename__ = 'repositories'
544 __tablename__ = 'repositories'
545 __table_args__ = (
545 __table_args__ = (
546 UniqueConstraint('repo_name'),
546 UniqueConstraint('repo_name'),
547 {'extend_existing': True, 'mysql_engine': 'InnoDB',
547 {'extend_existing': True, 'mysql_engine': 'InnoDB',
548 'mysql_charset': 'utf8'},
548 'mysql_charset': 'utf8'},
549 )
549 )
550
550
551 repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
551 repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
552 repo_name = Column("repo_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
552 repo_name = Column("repo_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
553 clone_uri = Column("clone_uri", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
553 clone_uri = Column("clone_uri", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
554 repo_type = Column("repo_type", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default='hg')
554 repo_type = Column("repo_type", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default='hg')
555 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
555 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
556 private = Column("private", Boolean(), nullable=True, unique=None, default=None)
556 private = Column("private", Boolean(), nullable=True, unique=None, default=None)
557 enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True)
557 enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True)
558 enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True)
558 enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True)
559 description = Column("description", String(length=10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
559 description = Column("description", String(length=10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
560 created_on = Column('created_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
560 created_on = Column('created_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
561
561
562 fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None)
562 fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None)
563 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None)
563 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None)
564
564
565 user = relationship('User')
565 user = relationship('User')
566 fork = relationship('Repository', remote_side=repo_id)
566 fork = relationship('Repository', remote_side=repo_id)
567 group = relationship('RepoGroup')
567 group = relationship('RepoGroup')
568 repo_to_perm = relationship('UserRepoToPerm', cascade='all', order_by='UserRepoToPerm.repo_to_perm_id')
568 repo_to_perm = relationship('UserRepoToPerm', cascade='all', order_by='UserRepoToPerm.repo_to_perm_id')
569 users_group_to_perm = relationship('UsersGroupRepoToPerm', cascade='all')
569 users_group_to_perm = relationship('UsersGroupRepoToPerm', cascade='all')
570 stats = relationship('Statistics', cascade='all', uselist=False)
570 stats = relationship('Statistics', cascade='all', uselist=False)
571
571
572 followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id', cascade='all')
572 followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id', cascade='all')
573
573
574 logs = relationship('UserLog')
574 logs = relationship('UserLog')
575 comments = relationship('ChangesetComment')
575 comments = relationship('ChangesetComment')
576
576
577 def __unicode__(self):
577 def __unicode__(self):
578 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
578 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
579 self.repo_name)
579 self.repo_name)
580
580
581 @classmethod
581 @classmethod
582 def url_sep(cls):
582 def url_sep(cls):
583 return URL_SEP
583 return URL_SEP
584
584
585 @classmethod
585 @classmethod
586 def get_by_repo_name(cls, repo_name):
586 def get_by_repo_name(cls, repo_name):
587 q = Session.query(cls).filter(cls.repo_name == repo_name)
587 q = Session.query(cls).filter(cls.repo_name == repo_name)
588 q = q.options(joinedload(Repository.fork))\
588 q = q.options(joinedload(Repository.fork))\
589 .options(joinedload(Repository.user))\
589 .options(joinedload(Repository.user))\
590 .options(joinedload(Repository.group))
590 .options(joinedload(Repository.group))
591 return q.scalar()
591 return q.scalar()
592
592
593 @classmethod
593 @classmethod
594 def get_by_full_path(cls, repo_full_path):
594 def get_by_full_path(cls, repo_full_path):
595 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
595 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
596 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
596 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
597
597
598 @classmethod
598 @classmethod
599 def get_repo_forks(cls, repo_id):
599 def get_repo_forks(cls, repo_id):
600 return cls.query().filter(Repository.fork_id == repo_id)
600 return cls.query().filter(Repository.fork_id == repo_id)
601
601
602 @classmethod
602 @classmethod
603 def base_path(cls):
603 def base_path(cls):
604 """
604 """
605 Returns base path when all repos are stored
605 Returns base path when all repos are stored
606
606
607 :param cls:
607 :param cls:
608 """
608 """
609 q = Session.query(RhodeCodeUi)\
609 q = Session.query(RhodeCodeUi)\
610 .filter(RhodeCodeUi.ui_key == cls.url_sep())
610 .filter(RhodeCodeUi.ui_key == cls.url_sep())
611 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
611 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
612 return q.one().ui_value
612 return q.one().ui_value
613
613
614 @property
614 @property
615 def forks(self):
615 def forks(self):
616 """
616 """
617 Return forks of this repo
617 Return forks of this repo
618 """
618 """
619 return Repository.get_repo_forks(self.repo_id)
619 return Repository.get_repo_forks(self.repo_id)
620
620
621 @property
621 @property
622 def parent(self):
622 def parent(self):
623 """
623 """
624 Returns fork parent
624 Returns fork parent
625 """
625 """
626 return self.fork
626 return self.fork
627
627
628 @property
628 @property
629 def just_name(self):
629 def just_name(self):
630 return self.repo_name.split(Repository.url_sep())[-1]
630 return self.repo_name.split(Repository.url_sep())[-1]
631
631
632 @property
632 @property
633 def groups_with_parents(self):
633 def groups_with_parents(self):
634 groups = []
634 groups = []
635 if self.group is None:
635 if self.group is None:
636 return groups
636 return groups
637
637
638 cur_gr = self.group
638 cur_gr = self.group
639 groups.insert(0, cur_gr)
639 groups.insert(0, cur_gr)
640 while 1:
640 while 1:
641 gr = getattr(cur_gr, 'parent_group', None)
641 gr = getattr(cur_gr, 'parent_group', None)
642 cur_gr = cur_gr.parent_group
642 cur_gr = cur_gr.parent_group
643 if gr is None:
643 if gr is None:
644 break
644 break
645 groups.insert(0, gr)
645 groups.insert(0, gr)
646
646
647 return groups
647 return groups
648
648
649 @property
649 @property
650 def groups_and_repo(self):
650 def groups_and_repo(self):
651 return self.groups_with_parents, self.just_name
651 return self.groups_with_parents, self.just_name
652
652
653 @LazyProperty
653 @LazyProperty
654 def repo_path(self):
654 def repo_path(self):
655 """
655 """
656 Returns base full path for that repository means where it actually
656 Returns base full path for that repository means where it actually
657 exists on a filesystem
657 exists on a filesystem
658 """
658 """
659 q = Session.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key ==
659 q = Session.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key ==
660 Repository.url_sep())
660 Repository.url_sep())
661 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
661 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
662 return q.one().ui_value
662 return q.one().ui_value
663
663
664 @property
664 @property
665 def repo_full_path(self):
665 def repo_full_path(self):
666 p = [self.repo_path]
666 p = [self.repo_path]
667 # we need to split the name by / since this is how we store the
667 # we need to split the name by / since this is how we store the
668 # names in the database, but that eventually needs to be converted
668 # names in the database, but that eventually needs to be converted
669 # into a valid system path
669 # into a valid system path
670 p += self.repo_name.split(Repository.url_sep())
670 p += self.repo_name.split(Repository.url_sep())
671 return os.path.join(*p)
671 return os.path.join(*p)
672
672
673 def get_new_name(self, repo_name):
673 def get_new_name(self, repo_name):
674 """
674 """
675 returns new full repository name based on assigned group and new new
675 returns new full repository name based on assigned group and new new
676
676
677 :param group_name:
677 :param group_name:
678 """
678 """
679 path_prefix = self.group.full_path_splitted if self.group else []
679 path_prefix = self.group.full_path_splitted if self.group else []
680 return Repository.url_sep().join(path_prefix + [repo_name])
680 return Repository.url_sep().join(path_prefix + [repo_name])
681
681
682 @property
682 @property
683 def _ui(self):
683 def _ui(self):
684 """
684 """
685 Creates an db based ui object for this repository
685 Creates an db based ui object for this repository
686 """
686 """
687 from mercurial import ui
687 from mercurial import ui
688 from mercurial import config
688 from mercurial import config
689 baseui = ui.ui()
689 baseui = ui.ui()
690
690
691 #clean the baseui object
691 #clean the baseui object
692 baseui._ocfg = config.config()
692 baseui._ocfg = config.config()
693 baseui._ucfg = config.config()
693 baseui._ucfg = config.config()
694 baseui._tcfg = config.config()
694 baseui._tcfg = config.config()
695
695
696 ret = RhodeCodeUi.query()\
696 ret = RhodeCodeUi.query()\
697 .options(FromCache("sql_cache_short", "repository_repo_ui")).all()
697 .options(FromCache("sql_cache_short", "repository_repo_ui")).all()
698
698
699 hg_ui = ret
699 hg_ui = ret
700 for ui_ in hg_ui:
700 for ui_ in hg_ui:
701 if ui_.ui_active:
701 if ui_.ui_active:
702 log.debug('settings ui from db[%s]%s:%s', ui_.ui_section,
702 log.debug('settings ui from db[%s]%s:%s', ui_.ui_section,
703 ui_.ui_key, ui_.ui_value)
703 ui_.ui_key, ui_.ui_value)
704 baseui.setconfig(ui_.ui_section, ui_.ui_key, ui_.ui_value)
704 baseui.setconfig(ui_.ui_section, ui_.ui_key, ui_.ui_value)
705
705
706 return baseui
706 return baseui
707
707
708 @classmethod
708 @classmethod
709 def is_valid(cls, repo_name):
709 def is_valid(cls, repo_name):
710 """
710 """
711 returns True if given repo name is a valid filesystem repository
711 returns True if given repo name is a valid filesystem repository
712
712
713 :param cls:
713 :param cls:
714 :param repo_name:
714 :param repo_name:
715 """
715 """
716 from rhodecode.lib.utils import is_valid_repo
716 from rhodecode.lib.utils import is_valid_repo
717
717
718 return is_valid_repo(repo_name, cls.base_path())
718 return is_valid_repo(repo_name, cls.base_path())
719
719
720 #==========================================================================
720 #==========================================================================
721 # SCM PROPERTIES
721 # SCM PROPERTIES
722 #==========================================================================
722 #==========================================================================
723
723
724 def get_changeset(self, rev=None):
724 def get_changeset(self, rev=None):
725 return get_changeset_safe(self.scm_instance, rev)
725 return get_changeset_safe(self.scm_instance, rev)
726
726
727 @property
727 @property
728 def tip(self):
728 def tip(self):
729 return self.get_changeset('tip')
729 return self.get_changeset('tip')
730
730
731 @property
731 @property
732 def author(self):
732 def author(self):
733 return self.tip.author
733 return self.tip.author
734
734
735 @property
735 @property
736 def last_change(self):
736 def last_change(self):
737 return self.scm_instance.last_change
737 return self.scm_instance.last_change
738
738
739 def comments(self, revisions=None):
739 def comments(self, revisions=None):
740 """
740 """
741 Returns comments for this repository grouped by revisions
741 Returns comments for this repository grouped by revisions
742
742
743 :param revisions: filter query by revisions only
743 :param revisions: filter query by revisions only
744 """
744 """
745 cmts = ChangesetComment.query()\
745 cmts = ChangesetComment.query()\
746 .filter(ChangesetComment.repo == self)
746 .filter(ChangesetComment.repo == self)
747 if revisions:
747 if revisions:
748 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
748 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
749 grouped = defaultdict(list)
749 grouped = defaultdict(list)
750 for cmt in cmts.all():
750 for cmt in cmts.all():
751 grouped[cmt.revision].append(cmt)
751 grouped[cmt.revision].append(cmt)
752 return grouped
752 return grouped
753
753
754 def statuses(self, revisions=None):
754 def statuses(self, revisions=None):
755 """
755 """
756 Returns statuses for this repository
756 Returns statuses for this repository
757
757
758 :param revisions: list of revisions to get statuses for
758 :param revisions: list of revisions to get statuses for
759 :type revisions: list
759 :type revisions: list
760 """
760 """
761
761
762 statuses = ChangesetStatus.query()\
762 statuses = ChangesetStatus.query()\
763 .filter(ChangesetStatus.repo == self)\
763 .filter(ChangesetStatus.repo == self)\
764 .filter(ChangesetStatus.version == 0)
764 .filter(ChangesetStatus.version == 0)
765 if revisions:
765 if revisions:
766 statuses = statuses.filter(ChangesetStatus.revision.in_(revisions))
766 statuses = statuses.filter(ChangesetStatus.revision.in_(revisions))
767 grouped = {}
767 grouped = {}
768 for stat in statuses.all():
768 for stat in statuses.all():
769 grouped[stat.revision] = [str(stat.status), stat.status_lbl]
769 pr_id = pr_repo = None
770 if stat.pull_request:
771 pr_id = stat.pull_request.pull_request_id
772 pr_repo = stat.pull_request.other_repo.repo_name
773 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
774 pr_id, pr_repo]
770 return grouped
775 return grouped
771
776
772 #==========================================================================
777 #==========================================================================
773 # SCM CACHE INSTANCE
778 # SCM CACHE INSTANCE
774 #==========================================================================
779 #==========================================================================
775
780
776 @property
781 @property
777 def invalidate(self):
782 def invalidate(self):
778 return CacheInvalidation.invalidate(self.repo_name)
783 return CacheInvalidation.invalidate(self.repo_name)
779
784
780 def set_invalidate(self):
785 def set_invalidate(self):
781 """
786 """
782 set a cache for invalidation for this instance
787 set a cache for invalidation for this instance
783 """
788 """
784 CacheInvalidation.set_invalidate(self.repo_name)
789 CacheInvalidation.set_invalidate(self.repo_name)
785
790
786 @LazyProperty
791 @LazyProperty
787 def scm_instance(self):
792 def scm_instance(self):
788 return self.__get_instance()
793 return self.__get_instance()
789
794
790 def scm_instance_cached(self, cache_map=None):
795 def scm_instance_cached(self, cache_map=None):
791 @cache_region('long_term')
796 @cache_region('long_term')
792 def _c(repo_name):
797 def _c(repo_name):
793 return self.__get_instance()
798 return self.__get_instance()
794 rn = self.repo_name
799 rn = self.repo_name
795 log.debug('Getting cached instance of repo')
800 log.debug('Getting cached instance of repo')
796
801
797 if cache_map:
802 if cache_map:
798 # get using prefilled cache_map
803 # get using prefilled cache_map
799 invalidate_repo = cache_map[self.repo_name]
804 invalidate_repo = cache_map[self.repo_name]
800 if invalidate_repo:
805 if invalidate_repo:
801 invalidate_repo = (None if invalidate_repo.cache_active
806 invalidate_repo = (None if invalidate_repo.cache_active
802 else invalidate_repo)
807 else invalidate_repo)
803 else:
808 else:
804 # get from invalidate
809 # get from invalidate
805 invalidate_repo = self.invalidate
810 invalidate_repo = self.invalidate
806
811
807 if invalidate_repo is not None:
812 if invalidate_repo is not None:
808 region_invalidate(_c, None, rn)
813 region_invalidate(_c, None, rn)
809 # update our cache
814 # update our cache
810 CacheInvalidation.set_valid(invalidate_repo.cache_key)
815 CacheInvalidation.set_valid(invalidate_repo.cache_key)
811 return _c(rn)
816 return _c(rn)
812
817
813 def __get_instance(self):
818 def __get_instance(self):
814 repo_full_path = self.repo_full_path
819 repo_full_path = self.repo_full_path
815 try:
820 try:
816 alias = get_scm(repo_full_path)[0]
821 alias = get_scm(repo_full_path)[0]
817 log.debug('Creating instance of %s repository' % alias)
822 log.debug('Creating instance of %s repository' % alias)
818 backend = get_backend(alias)
823 backend = get_backend(alias)
819 except VCSError:
824 except VCSError:
820 log.error(traceback.format_exc())
825 log.error(traceback.format_exc())
821 log.error('Perhaps this repository is in db and not in '
826 log.error('Perhaps this repository is in db and not in '
822 'filesystem run rescan repositories with '
827 'filesystem run rescan repositories with '
823 '"destroy old data " option from admin panel')
828 '"destroy old data " option from admin panel')
824 return
829 return
825
830
826 if alias == 'hg':
831 if alias == 'hg':
827
832
828 repo = backend(safe_str(repo_full_path), create=False,
833 repo = backend(safe_str(repo_full_path), create=False,
829 baseui=self._ui)
834 baseui=self._ui)
830 # skip hidden web repository
835 # skip hidden web repository
831 if repo._get_hidden():
836 if repo._get_hidden():
832 return
837 return
833 else:
838 else:
834 repo = backend(repo_full_path, create=False)
839 repo = backend(repo_full_path, create=False)
835
840
836 return repo
841 return repo
837
842
838
843
839 class RepoGroup(Base, BaseModel):
844 class RepoGroup(Base, BaseModel):
840 __tablename__ = 'groups'
845 __tablename__ = 'groups'
841 __table_args__ = (
846 __table_args__ = (
842 UniqueConstraint('group_name', 'group_parent_id'),
847 UniqueConstraint('group_name', 'group_parent_id'),
843 CheckConstraint('group_id != group_parent_id'),
848 CheckConstraint('group_id != group_parent_id'),
844 {'extend_existing': True, 'mysql_engine': 'InnoDB',
849 {'extend_existing': True, 'mysql_engine': 'InnoDB',
845 'mysql_charset': 'utf8'},
850 'mysql_charset': 'utf8'},
846 )
851 )
847 __mapper_args__ = {'order_by': 'group_name'}
852 __mapper_args__ = {'order_by': 'group_name'}
848
853
849 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
854 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
850 group_name = Column("group_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
855 group_name = Column("group_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
851 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
856 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
852 group_description = Column("group_description", String(length=10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
857 group_description = Column("group_description", String(length=10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
853
858
854 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
859 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
855 users_group_to_perm = relationship('UsersGroupRepoGroupToPerm', cascade='all')
860 users_group_to_perm = relationship('UsersGroupRepoGroupToPerm', cascade='all')
856
861
857 parent_group = relationship('RepoGroup', remote_side=group_id)
862 parent_group = relationship('RepoGroup', remote_side=group_id)
858
863
859 def __init__(self, group_name='', parent_group=None):
864 def __init__(self, group_name='', parent_group=None):
860 self.group_name = group_name
865 self.group_name = group_name
861 self.parent_group = parent_group
866 self.parent_group = parent_group
862
867
863 def __unicode__(self):
868 def __unicode__(self):
864 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.group_id,
869 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.group_id,
865 self.group_name)
870 self.group_name)
866
871
867 @classmethod
872 @classmethod
868 def groups_choices(cls):
873 def groups_choices(cls):
869 from webhelpers.html import literal as _literal
874 from webhelpers.html import literal as _literal
870 repo_groups = [('', '')]
875 repo_groups = [('', '')]
871 sep = ' &raquo; '
876 sep = ' &raquo; '
872 _name = lambda k: _literal(sep.join(k))
877 _name = lambda k: _literal(sep.join(k))
873
878
874 repo_groups.extend([(x.group_id, _name(x.full_path_splitted))
879 repo_groups.extend([(x.group_id, _name(x.full_path_splitted))
875 for x in cls.query().all()])
880 for x in cls.query().all()])
876
881
877 repo_groups = sorted(repo_groups, key=lambda t: t[1].split(sep)[0])
882 repo_groups = sorted(repo_groups, key=lambda t: t[1].split(sep)[0])
878 return repo_groups
883 return repo_groups
879
884
880 @classmethod
885 @classmethod
881 def url_sep(cls):
886 def url_sep(cls):
882 return URL_SEP
887 return URL_SEP
883
888
884 @classmethod
889 @classmethod
885 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
890 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
886 if case_insensitive:
891 if case_insensitive:
887 gr = cls.query()\
892 gr = cls.query()\
888 .filter(cls.group_name.ilike(group_name))
893 .filter(cls.group_name.ilike(group_name))
889 else:
894 else:
890 gr = cls.query()\
895 gr = cls.query()\
891 .filter(cls.group_name == group_name)
896 .filter(cls.group_name == group_name)
892 if cache:
897 if cache:
893 gr = gr.options(FromCache(
898 gr = gr.options(FromCache(
894 "sql_cache_short",
899 "sql_cache_short",
895 "get_group_%s" % _hash_key(group_name)
900 "get_group_%s" % _hash_key(group_name)
896 )
901 )
897 )
902 )
898 return gr.scalar()
903 return gr.scalar()
899
904
900 @property
905 @property
901 def parents(self):
906 def parents(self):
902 parents_recursion_limit = 5
907 parents_recursion_limit = 5
903 groups = []
908 groups = []
904 if self.parent_group is None:
909 if self.parent_group is None:
905 return groups
910 return groups
906 cur_gr = self.parent_group
911 cur_gr = self.parent_group
907 groups.insert(0, cur_gr)
912 groups.insert(0, cur_gr)
908 cnt = 0
913 cnt = 0
909 while 1:
914 while 1:
910 cnt += 1
915 cnt += 1
911 gr = getattr(cur_gr, 'parent_group', None)
916 gr = getattr(cur_gr, 'parent_group', None)
912 cur_gr = cur_gr.parent_group
917 cur_gr = cur_gr.parent_group
913 if gr is None:
918 if gr is None:
914 break
919 break
915 if cnt == parents_recursion_limit:
920 if cnt == parents_recursion_limit:
916 # this will prevent accidental infinit loops
921 # this will prevent accidental infinit loops
917 log.error('group nested more than %s' %
922 log.error('group nested more than %s' %
918 parents_recursion_limit)
923 parents_recursion_limit)
919 break
924 break
920
925
921 groups.insert(0, gr)
926 groups.insert(0, gr)
922 return groups
927 return groups
923
928
924 @property
929 @property
925 def children(self):
930 def children(self):
926 return RepoGroup.query().filter(RepoGroup.parent_group == self)
931 return RepoGroup.query().filter(RepoGroup.parent_group == self)
927
932
928 @property
933 @property
929 def name(self):
934 def name(self):
930 return self.group_name.split(RepoGroup.url_sep())[-1]
935 return self.group_name.split(RepoGroup.url_sep())[-1]
931
936
932 @property
937 @property
933 def full_path(self):
938 def full_path(self):
934 return self.group_name
939 return self.group_name
935
940
936 @property
941 @property
937 def full_path_splitted(self):
942 def full_path_splitted(self):
938 return self.group_name.split(RepoGroup.url_sep())
943 return self.group_name.split(RepoGroup.url_sep())
939
944
940 @property
945 @property
941 def repositories(self):
946 def repositories(self):
942 return Repository.query()\
947 return Repository.query()\
943 .filter(Repository.group == self)\
948 .filter(Repository.group == self)\
944 .order_by(Repository.repo_name)
949 .order_by(Repository.repo_name)
945
950
946 @property
951 @property
947 def repositories_recursive_count(self):
952 def repositories_recursive_count(self):
948 cnt = self.repositories.count()
953 cnt = self.repositories.count()
949
954
950 def children_count(group):
955 def children_count(group):
951 cnt = 0
956 cnt = 0
952 for child in group.children:
957 for child in group.children:
953 cnt += child.repositories.count()
958 cnt += child.repositories.count()
954 cnt += children_count(child)
959 cnt += children_count(child)
955 return cnt
960 return cnt
956
961
957 return cnt + children_count(self)
962 return cnt + children_count(self)
958
963
959 def get_new_name(self, group_name):
964 def get_new_name(self, group_name):
960 """
965 """
961 returns new full group name based on parent and new name
966 returns new full group name based on parent and new name
962
967
963 :param group_name:
968 :param group_name:
964 """
969 """
965 path_prefix = (self.parent_group.full_path_splitted if
970 path_prefix = (self.parent_group.full_path_splitted if
966 self.parent_group else [])
971 self.parent_group else [])
967 return RepoGroup.url_sep().join(path_prefix + [group_name])
972 return RepoGroup.url_sep().join(path_prefix + [group_name])
968
973
969
974
970 class Permission(Base, BaseModel):
975 class Permission(Base, BaseModel):
971 __tablename__ = 'permissions'
976 __tablename__ = 'permissions'
972 __table_args__ = (
977 __table_args__ = (
973 {'extend_existing': True, 'mysql_engine': 'InnoDB',
978 {'extend_existing': True, 'mysql_engine': 'InnoDB',
974 'mysql_charset': 'utf8'},
979 'mysql_charset': 'utf8'},
975 )
980 )
976 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
981 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
977 permission_name = Column("permission_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
982 permission_name = Column("permission_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
978 permission_longname = Column("permission_longname", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
983 permission_longname = Column("permission_longname", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
979
984
980 def __unicode__(self):
985 def __unicode__(self):
981 return u"<%s('%s:%s')>" % (
986 return u"<%s('%s:%s')>" % (
982 self.__class__.__name__, self.permission_id, self.permission_name
987 self.__class__.__name__, self.permission_id, self.permission_name
983 )
988 )
984
989
985 @classmethod
990 @classmethod
986 def get_by_key(cls, key):
991 def get_by_key(cls, key):
987 return cls.query().filter(cls.permission_name == key).scalar()
992 return cls.query().filter(cls.permission_name == key).scalar()
988
993
989 @classmethod
994 @classmethod
990 def get_default_perms(cls, default_user_id):
995 def get_default_perms(cls, default_user_id):
991 q = Session.query(UserRepoToPerm, Repository, cls)\
996 q = Session.query(UserRepoToPerm, Repository, cls)\
992 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
997 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
993 .join((cls, UserRepoToPerm.permission_id == cls.permission_id))\
998 .join((cls, UserRepoToPerm.permission_id == cls.permission_id))\
994 .filter(UserRepoToPerm.user_id == default_user_id)
999 .filter(UserRepoToPerm.user_id == default_user_id)
995
1000
996 return q.all()
1001 return q.all()
997
1002
998 @classmethod
1003 @classmethod
999 def get_default_group_perms(cls, default_user_id):
1004 def get_default_group_perms(cls, default_user_id):
1000 q = Session.query(UserRepoGroupToPerm, RepoGroup, cls)\
1005 q = Session.query(UserRepoGroupToPerm, RepoGroup, cls)\
1001 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
1006 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
1002 .join((cls, UserRepoGroupToPerm.permission_id == cls.permission_id))\
1007 .join((cls, UserRepoGroupToPerm.permission_id == cls.permission_id))\
1003 .filter(UserRepoGroupToPerm.user_id == default_user_id)
1008 .filter(UserRepoGroupToPerm.user_id == default_user_id)
1004
1009
1005 return q.all()
1010 return q.all()
1006
1011
1007
1012
1008 class UserRepoToPerm(Base, BaseModel):
1013 class UserRepoToPerm(Base, BaseModel):
1009 __tablename__ = 'repo_to_perm'
1014 __tablename__ = 'repo_to_perm'
1010 __table_args__ = (
1015 __table_args__ = (
1011 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
1016 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
1012 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1017 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1013 'mysql_charset': 'utf8'}
1018 'mysql_charset': 'utf8'}
1014 )
1019 )
1015 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1020 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1016 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1021 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1017 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1022 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1018 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1023 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1019
1024
1020 user = relationship('User')
1025 user = relationship('User')
1021 repository = relationship('Repository')
1026 repository = relationship('Repository')
1022 permission = relationship('Permission')
1027 permission = relationship('Permission')
1023
1028
1024 @classmethod
1029 @classmethod
1025 def create(cls, user, repository, permission):
1030 def create(cls, user, repository, permission):
1026 n = cls()
1031 n = cls()
1027 n.user = user
1032 n.user = user
1028 n.repository = repository
1033 n.repository = repository
1029 n.permission = permission
1034 n.permission = permission
1030 Session.add(n)
1035 Session.add(n)
1031 return n
1036 return n
1032
1037
1033 def __unicode__(self):
1038 def __unicode__(self):
1034 return u'<user:%s => %s >' % (self.user, self.repository)
1039 return u'<user:%s => %s >' % (self.user, self.repository)
1035
1040
1036
1041
1037 class UserToPerm(Base, BaseModel):
1042 class UserToPerm(Base, BaseModel):
1038 __tablename__ = 'user_to_perm'
1043 __tablename__ = 'user_to_perm'
1039 __table_args__ = (
1044 __table_args__ = (
1040 UniqueConstraint('user_id', 'permission_id'),
1045 UniqueConstraint('user_id', 'permission_id'),
1041 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1046 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1042 'mysql_charset': 'utf8'}
1047 'mysql_charset': 'utf8'}
1043 )
1048 )
1044 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1049 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1045 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1050 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1046 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1051 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1047
1052
1048 user = relationship('User')
1053 user = relationship('User')
1049 permission = relationship('Permission', lazy='joined')
1054 permission = relationship('Permission', lazy='joined')
1050
1055
1051
1056
1052 class UsersGroupRepoToPerm(Base, BaseModel):
1057 class UsersGroupRepoToPerm(Base, BaseModel):
1053 __tablename__ = 'users_group_repo_to_perm'
1058 __tablename__ = 'users_group_repo_to_perm'
1054 __table_args__ = (
1059 __table_args__ = (
1055 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
1060 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
1056 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1061 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1057 'mysql_charset': 'utf8'}
1062 'mysql_charset': 'utf8'}
1058 )
1063 )
1059 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1064 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1060 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1065 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1061 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1066 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1062 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1067 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1063
1068
1064 users_group = relationship('UsersGroup')
1069 users_group = relationship('UsersGroup')
1065 permission = relationship('Permission')
1070 permission = relationship('Permission')
1066 repository = relationship('Repository')
1071 repository = relationship('Repository')
1067
1072
1068 @classmethod
1073 @classmethod
1069 def create(cls, users_group, repository, permission):
1074 def create(cls, users_group, repository, permission):
1070 n = cls()
1075 n = cls()
1071 n.users_group = users_group
1076 n.users_group = users_group
1072 n.repository = repository
1077 n.repository = repository
1073 n.permission = permission
1078 n.permission = permission
1074 Session.add(n)
1079 Session.add(n)
1075 return n
1080 return n
1076
1081
1077 def __unicode__(self):
1082 def __unicode__(self):
1078 return u'<userGroup:%s => %s >' % (self.users_group, self.repository)
1083 return u'<userGroup:%s => %s >' % (self.users_group, self.repository)
1079
1084
1080
1085
1081 class UsersGroupToPerm(Base, BaseModel):
1086 class UsersGroupToPerm(Base, BaseModel):
1082 __tablename__ = 'users_group_to_perm'
1087 __tablename__ = 'users_group_to_perm'
1083 __table_args__ = (
1088 __table_args__ = (
1084 UniqueConstraint('users_group_id', 'permission_id',),
1089 UniqueConstraint('users_group_id', 'permission_id',),
1085 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1090 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1086 'mysql_charset': 'utf8'}
1091 'mysql_charset': 'utf8'}
1087 )
1092 )
1088 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1093 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1089 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1094 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1090 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1095 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1091
1096
1092 users_group = relationship('UsersGroup')
1097 users_group = relationship('UsersGroup')
1093 permission = relationship('Permission')
1098 permission = relationship('Permission')
1094
1099
1095
1100
1096 class UserRepoGroupToPerm(Base, BaseModel):
1101 class UserRepoGroupToPerm(Base, BaseModel):
1097 __tablename__ = 'user_repo_group_to_perm'
1102 __tablename__ = 'user_repo_group_to_perm'
1098 __table_args__ = (
1103 __table_args__ = (
1099 UniqueConstraint('user_id', 'group_id', 'permission_id'),
1104 UniqueConstraint('user_id', 'group_id', 'permission_id'),
1100 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1105 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1101 'mysql_charset': 'utf8'}
1106 'mysql_charset': 'utf8'}
1102 )
1107 )
1103
1108
1104 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1109 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1105 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1110 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1106 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
1111 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
1107 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1112 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1108
1113
1109 user = relationship('User')
1114 user = relationship('User')
1110 group = relationship('RepoGroup')
1115 group = relationship('RepoGroup')
1111 permission = relationship('Permission')
1116 permission = relationship('Permission')
1112
1117
1113
1118
1114 class UsersGroupRepoGroupToPerm(Base, BaseModel):
1119 class UsersGroupRepoGroupToPerm(Base, BaseModel):
1115 __tablename__ = 'users_group_repo_group_to_perm'
1120 __tablename__ = 'users_group_repo_group_to_perm'
1116 __table_args__ = (
1121 __table_args__ = (
1117 UniqueConstraint('users_group_id', 'group_id'),
1122 UniqueConstraint('users_group_id', 'group_id'),
1118 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1123 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1119 'mysql_charset': 'utf8'}
1124 'mysql_charset': 'utf8'}
1120 )
1125 )
1121
1126
1122 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)
1127 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)
1123 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1128 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1124 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
1129 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
1125 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1130 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1126
1131
1127 users_group = relationship('UsersGroup')
1132 users_group = relationship('UsersGroup')
1128 permission = relationship('Permission')
1133 permission = relationship('Permission')
1129 group = relationship('RepoGroup')
1134 group = relationship('RepoGroup')
1130
1135
1131
1136
1132 class Statistics(Base, BaseModel):
1137 class Statistics(Base, BaseModel):
1133 __tablename__ = 'statistics'
1138 __tablename__ = 'statistics'
1134 __table_args__ = (
1139 __table_args__ = (
1135 UniqueConstraint('repository_id'),
1140 UniqueConstraint('repository_id'),
1136 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1141 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1137 'mysql_charset': 'utf8'}
1142 'mysql_charset': 'utf8'}
1138 )
1143 )
1139 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1144 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1140 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
1145 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
1141 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
1146 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
1142 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
1147 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
1143 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
1148 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
1144 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
1149 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
1145
1150
1146 repository = relationship('Repository', single_parent=True)
1151 repository = relationship('Repository', single_parent=True)
1147
1152
1148
1153
1149 class UserFollowing(Base, BaseModel):
1154 class UserFollowing(Base, BaseModel):
1150 __tablename__ = 'user_followings'
1155 __tablename__ = 'user_followings'
1151 __table_args__ = (
1156 __table_args__ = (
1152 UniqueConstraint('user_id', 'follows_repository_id'),
1157 UniqueConstraint('user_id', 'follows_repository_id'),
1153 UniqueConstraint('user_id', 'follows_user_id'),
1158 UniqueConstraint('user_id', 'follows_user_id'),
1154 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1159 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1155 'mysql_charset': 'utf8'}
1160 'mysql_charset': 'utf8'}
1156 )
1161 )
1157
1162
1158 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1163 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1159 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1164 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1160 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
1165 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
1161 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1166 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1162 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
1167 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
1163
1168
1164 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
1169 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
1165
1170
1166 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
1171 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
1167 follows_repository = relationship('Repository', order_by='Repository.repo_name')
1172 follows_repository = relationship('Repository', order_by='Repository.repo_name')
1168
1173
1169 @classmethod
1174 @classmethod
1170 def get_repo_followers(cls, repo_id):
1175 def get_repo_followers(cls, repo_id):
1171 return cls.query().filter(cls.follows_repo_id == repo_id)
1176 return cls.query().filter(cls.follows_repo_id == repo_id)
1172
1177
1173
1178
1174 class CacheInvalidation(Base, BaseModel):
1179 class CacheInvalidation(Base, BaseModel):
1175 __tablename__ = 'cache_invalidation'
1180 __tablename__ = 'cache_invalidation'
1176 __table_args__ = (
1181 __table_args__ = (
1177 UniqueConstraint('cache_key'),
1182 UniqueConstraint('cache_key'),
1178 Index('key_idx', 'cache_key'),
1183 Index('key_idx', 'cache_key'),
1179 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1184 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1180 'mysql_charset': 'utf8'},
1185 'mysql_charset': 'utf8'},
1181 )
1186 )
1182 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1187 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1183 cache_key = Column("cache_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1188 cache_key = Column("cache_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1184 cache_args = Column("cache_args", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1189 cache_args = Column("cache_args", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1185 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
1190 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
1186
1191
1187 def __init__(self, cache_key, cache_args=''):
1192 def __init__(self, cache_key, cache_args=''):
1188 self.cache_key = cache_key
1193 self.cache_key = cache_key
1189 self.cache_args = cache_args
1194 self.cache_args = cache_args
1190 self.cache_active = False
1195 self.cache_active = False
1191
1196
1192 def __unicode__(self):
1197 def __unicode__(self):
1193 return u"<%s('%s:%s')>" % (self.__class__.__name__,
1198 return u"<%s('%s:%s')>" % (self.__class__.__name__,
1194 self.cache_id, self.cache_key)
1199 self.cache_id, self.cache_key)
1195
1200
1196 @classmethod
1201 @classmethod
1197 def clear_cache(cls):
1202 def clear_cache(cls):
1198 cls.query().delete()
1203 cls.query().delete()
1199
1204
1200 @classmethod
1205 @classmethod
1201 def _get_key(cls, key):
1206 def _get_key(cls, key):
1202 """
1207 """
1203 Wrapper for generating a key, together with a prefix
1208 Wrapper for generating a key, together with a prefix
1204
1209
1205 :param key:
1210 :param key:
1206 """
1211 """
1207 import rhodecode
1212 import rhodecode
1208 prefix = ''
1213 prefix = ''
1209 iid = rhodecode.CONFIG.get('instance_id')
1214 iid = rhodecode.CONFIG.get('instance_id')
1210 if iid:
1215 if iid:
1211 prefix = iid
1216 prefix = iid
1212 return "%s%s" % (prefix, key), prefix, key.rstrip('_README')
1217 return "%s%s" % (prefix, key), prefix, key.rstrip('_README')
1213
1218
1214 @classmethod
1219 @classmethod
1215 def get_by_key(cls, key):
1220 def get_by_key(cls, key):
1216 return cls.query().filter(cls.cache_key == key).scalar()
1221 return cls.query().filter(cls.cache_key == key).scalar()
1217
1222
1218 @classmethod
1223 @classmethod
1219 def _get_or_create_key(cls, key, prefix, org_key):
1224 def _get_or_create_key(cls, key, prefix, org_key):
1220 inv_obj = Session.query(cls).filter(cls.cache_key == key).scalar()
1225 inv_obj = Session.query(cls).filter(cls.cache_key == key).scalar()
1221 if not inv_obj:
1226 if not inv_obj:
1222 try:
1227 try:
1223 inv_obj = CacheInvalidation(key, org_key)
1228 inv_obj = CacheInvalidation(key, org_key)
1224 Session.add(inv_obj)
1229 Session.add(inv_obj)
1225 Session.commit()
1230 Session.commit()
1226 except Exception:
1231 except Exception:
1227 log.error(traceback.format_exc())
1232 log.error(traceback.format_exc())
1228 Session.rollback()
1233 Session.rollback()
1229 return inv_obj
1234 return inv_obj
1230
1235
1231 @classmethod
1236 @classmethod
1232 def invalidate(cls, key):
1237 def invalidate(cls, key):
1233 """
1238 """
1234 Returns Invalidation object if this given key should be invalidated
1239 Returns Invalidation object if this given key should be invalidated
1235 None otherwise. `cache_active = False` means that this cache
1240 None otherwise. `cache_active = False` means that this cache
1236 state is not valid and needs to be invalidated
1241 state is not valid and needs to be invalidated
1237
1242
1238 :param key:
1243 :param key:
1239 """
1244 """
1240
1245
1241 key, _prefix, _org_key = cls._get_key(key)
1246 key, _prefix, _org_key = cls._get_key(key)
1242 inv = cls._get_or_create_key(key, _prefix, _org_key)
1247 inv = cls._get_or_create_key(key, _prefix, _org_key)
1243
1248
1244 if inv and inv.cache_active is False:
1249 if inv and inv.cache_active is False:
1245 return inv
1250 return inv
1246
1251
1247 @classmethod
1252 @classmethod
1248 def set_invalidate(cls, key):
1253 def set_invalidate(cls, key):
1249 """
1254 """
1250 Mark this Cache key for invalidation
1255 Mark this Cache key for invalidation
1251
1256
1252 :param key:
1257 :param key:
1253 """
1258 """
1254
1259
1255 key, _prefix, _org_key = cls._get_key(key)
1260 key, _prefix, _org_key = cls._get_key(key)
1256 inv_objs = Session.query(cls).filter(cls.cache_args == _org_key).all()
1261 inv_objs = Session.query(cls).filter(cls.cache_args == _org_key).all()
1257 log.debug('marking %s key[s] %s for invalidation' % (len(inv_objs),
1262 log.debug('marking %s key[s] %s for invalidation' % (len(inv_objs),
1258 _org_key))
1263 _org_key))
1259 try:
1264 try:
1260 for inv_obj in inv_objs:
1265 for inv_obj in inv_objs:
1261 if inv_obj:
1266 if inv_obj:
1262 inv_obj.cache_active = False
1267 inv_obj.cache_active = False
1263
1268
1264 Session.add(inv_obj)
1269 Session.add(inv_obj)
1265 Session.commit()
1270 Session.commit()
1266 except Exception:
1271 except Exception:
1267 log.error(traceback.format_exc())
1272 log.error(traceback.format_exc())
1268 Session.rollback()
1273 Session.rollback()
1269
1274
1270 @classmethod
1275 @classmethod
1271 def set_valid(cls, key):
1276 def set_valid(cls, key):
1272 """
1277 """
1273 Mark this cache key as active and currently cached
1278 Mark this cache key as active and currently cached
1274
1279
1275 :param key:
1280 :param key:
1276 """
1281 """
1277 inv_obj = cls.get_by_key(key)
1282 inv_obj = cls.get_by_key(key)
1278 inv_obj.cache_active = True
1283 inv_obj.cache_active = True
1279 Session.add(inv_obj)
1284 Session.add(inv_obj)
1280 Session.commit()
1285 Session.commit()
1281
1286
1282 @classmethod
1287 @classmethod
1283 def get_cache_map(cls):
1288 def get_cache_map(cls):
1284
1289
1285 class cachemapdict(dict):
1290 class cachemapdict(dict):
1286
1291
1287 def __init__(self, *args, **kwargs):
1292 def __init__(self, *args, **kwargs):
1288 fixkey = kwargs.get('fixkey')
1293 fixkey = kwargs.get('fixkey')
1289 if fixkey:
1294 if fixkey:
1290 del kwargs['fixkey']
1295 del kwargs['fixkey']
1291 self.fixkey = fixkey
1296 self.fixkey = fixkey
1292 super(cachemapdict, self).__init__(*args, **kwargs)
1297 super(cachemapdict, self).__init__(*args, **kwargs)
1293
1298
1294 def __getattr__(self, name):
1299 def __getattr__(self, name):
1295 key = name
1300 key = name
1296 if self.fixkey:
1301 if self.fixkey:
1297 key, _prefix, _org_key = cls._get_key(key)
1302 key, _prefix, _org_key = cls._get_key(key)
1298 if key in self.__dict__:
1303 if key in self.__dict__:
1299 return self.__dict__[key]
1304 return self.__dict__[key]
1300 else:
1305 else:
1301 return self[key]
1306 return self[key]
1302
1307
1303 def __getitem__(self, key):
1308 def __getitem__(self, key):
1304 if self.fixkey:
1309 if self.fixkey:
1305 key, _prefix, _org_key = cls._get_key(key)
1310 key, _prefix, _org_key = cls._get_key(key)
1306 try:
1311 try:
1307 return super(cachemapdict, self).__getitem__(key)
1312 return super(cachemapdict, self).__getitem__(key)
1308 except KeyError:
1313 except KeyError:
1309 return
1314 return
1310
1315
1311 cache_map = cachemapdict(fixkey=True)
1316 cache_map = cachemapdict(fixkey=True)
1312 for obj in cls.query().all():
1317 for obj in cls.query().all():
1313 cache_map[obj.cache_key] = cachemapdict(obj.get_dict())
1318 cache_map[obj.cache_key] = cachemapdict(obj.get_dict())
1314 return cache_map
1319 return cache_map
1315
1320
1316
1321
1317 class ChangesetComment(Base, BaseModel):
1322 class ChangesetComment(Base, BaseModel):
1318 __tablename__ = 'changeset_comments'
1323 __tablename__ = 'changeset_comments'
1319 __table_args__ = (
1324 __table_args__ = (
1320 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1325 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1321 'mysql_charset': 'utf8'},
1326 'mysql_charset': 'utf8'},
1322 )
1327 )
1323 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
1328 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
1324 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1329 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1325 revision = Column('revision', String(40), nullable=True)
1330 revision = Column('revision', String(40), nullable=True)
1326 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
1331 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
1327 line_no = Column('line_no', Unicode(10), nullable=True)
1332 line_no = Column('line_no', Unicode(10), nullable=True)
1328 f_path = Column('f_path', Unicode(1000), nullable=True)
1333 f_path = Column('f_path', Unicode(1000), nullable=True)
1329 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
1334 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
1330 text = Column('text', Unicode(25000), nullable=False)
1335 text = Column('text', Unicode(25000), nullable=False)
1331 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
1336 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
1332
1337
1333 author = relationship('User', lazy='joined')
1338 author = relationship('User', lazy='joined')
1334 repo = relationship('Repository')
1339 repo = relationship('Repository')
1335 status_change = relationship('ChangesetStatus', uselist=False)
1340 status_change = relationship('ChangesetStatus', uselist=False)
1336 pull_request = relationship('PullRequest', lazy='joined')
1341 pull_request = relationship('PullRequest', lazy='joined')
1337
1342
1338 @classmethod
1343 @classmethod
1339 def get_users(cls, revision):
1344 def get_users(cls, revision=None, pull_request_id=None):
1340 """
1345 """
1341 Returns user associated with this changesetComment. ie those
1346 Returns user associated with this ChangesetComment. ie those
1342 who actually commented
1347 who actually commented
1343
1348
1344 :param cls:
1349 :param cls:
1345 :param revision:
1350 :param revision:
1346 """
1351 """
1347 return Session.query(User)\
1352 q = Session.query(User)\
1348 .filter(cls.revision == revision)\
1353 .join(ChangesetComment.author)
1349 .join(ChangesetComment.author).all()
1354 if revision:
1355 q = q.filter(cls.revision == revision)
1356 elif pull_request_id:
1357 q = q.filter(cls.pull_request_id == pull_request_id)
1358 return q.all()
1350
1359
1351
1360
1352 class ChangesetStatus(Base, BaseModel):
1361 class ChangesetStatus(Base, BaseModel):
1353 __tablename__ = 'changeset_statuses'
1362 __tablename__ = 'changeset_statuses'
1354 __table_args__ = (
1363 __table_args__ = (
1355 UniqueConstraint('repo_id', 'revision', 'version'),
1364 UniqueConstraint('repo_id', 'revision', 'version'),
1356 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1365 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1357 'mysql_charset': 'utf8'}
1366 'mysql_charset': 'utf8'}
1358 )
1367 )
1359
1368
1360 STATUSES = [
1369 STATUSES = [
1361 ('not_reviewed', _("Not Reviewed")), # (no icon) and default
1370 ('not_reviewed', _("Not Reviewed")), # (no icon) and default
1362 ('approved', _("Approved")),
1371 ('approved', _("Approved")),
1363 ('rejected', _("Rejected")),
1372 ('rejected', _("Rejected")),
1364 ('under_review', _("Under Review")),
1373 ('under_review', _("Under Review")),
1365 ]
1374 ]
1366 DEFAULT = STATUSES[0][0]
1375 DEFAULT = STATUSES[0][0]
1367
1376
1368 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
1377 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
1369 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1378 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1370 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
1379 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
1371 revision = Column('revision', String(40), nullable=False)
1380 revision = Column('revision', String(40), nullable=False)
1372 status = Column('status', String(128), nullable=False, default=DEFAULT)
1381 status = Column('status', String(128), nullable=False, default=DEFAULT)
1373 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
1382 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
1374 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
1383 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
1375 version = Column('version', Integer(), nullable=False, default=0)
1384 version = Column('version', Integer(), nullable=False, default=0)
1376 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
1385 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
1377
1386
1378 author = relationship('User', lazy='joined')
1387 author = relationship('User', lazy='joined')
1379 repo = relationship('Repository')
1388 repo = relationship('Repository')
1380 comment = relationship('ChangesetComment', lazy='joined')
1389 comment = relationship('ChangesetComment', lazy='joined')
1381 pull_request = relationship('PullRequest', lazy='joined')
1390 pull_request = relationship('PullRequest', lazy='joined')
1382
1391
1383 @classmethod
1392 @classmethod
1384 def get_status_lbl(cls, value):
1393 def get_status_lbl(cls, value):
1385 return dict(cls.STATUSES).get(value)
1394 return dict(cls.STATUSES).get(value)
1386
1395
1387 @property
1396 @property
1388 def status_lbl(self):
1397 def status_lbl(self):
1389 return ChangesetStatus.get_status_lbl(self.status)
1398 return ChangesetStatus.get_status_lbl(self.status)
1390
1399
1391
1400
1392 class PullRequest(Base, BaseModel):
1401 class PullRequest(Base, BaseModel):
1393 __tablename__ = 'pull_requests'
1402 __tablename__ = 'pull_requests'
1394 __table_args__ = (
1403 __table_args__ = (
1395 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1404 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1396 'mysql_charset': 'utf8'},
1405 'mysql_charset': 'utf8'},
1397 )
1406 )
1398
1407
1399 pull_request_id = Column('pull_request_id', Integer(), nullable=False, primary_key=True)
1408 pull_request_id = Column('pull_request_id', Integer(), nullable=False, primary_key=True)
1400 title = Column('title', Unicode(256), nullable=True)
1409 title = Column('title', Unicode(256), nullable=True)
1401 description = Column('description', Unicode(10240), nullable=True)
1410 description = Column('description', Unicode(10240), nullable=True)
1402 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1411 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1403 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
1412 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
1404 _revisions = Column('revisions', UnicodeText(20500)) # 500 revisions max
1413 _revisions = Column('revisions', UnicodeText(20500)) # 500 revisions max
1405 org_repo_id = Column('org_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1414 org_repo_id = Column('org_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1406 org_ref = Column('org_ref', Unicode(256), nullable=False)
1415 org_ref = Column('org_ref', Unicode(256), nullable=False)
1407 other_repo_id = Column('other_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1416 other_repo_id = Column('other_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1408 other_ref = Column('other_ref', Unicode(256), nullable=False)
1417 other_ref = Column('other_ref', Unicode(256), nullable=False)
1409
1418
1410 @hybrid_property
1419 @hybrid_property
1411 def revisions(self):
1420 def revisions(self):
1412 return self._revisions.split(':')
1421 return self._revisions.split(':')
1413
1422
1414 @revisions.setter
1423 @revisions.setter
1415 def revisions(self, val):
1424 def revisions(self, val):
1416 self._revisions = ':'.join(val)
1425 self._revisions = ':'.join(val)
1417
1426
1418 author = relationship('User', lazy='joined')
1427 author = relationship('User', lazy='joined')
1419 reviewers = relationship('PullRequestReviewers')
1428 reviewers = relationship('PullRequestReviewers')
1420 org_repo = relationship('Repository', primaryjoin='PullRequest.org_repo_id==Repository.repo_id')
1429 org_repo = relationship('Repository', primaryjoin='PullRequest.org_repo_id==Repository.repo_id')
1421 other_repo = relationship('Repository', primaryjoin='PullRequest.other_repo_id==Repository.repo_id')
1430 other_repo = relationship('Repository', primaryjoin='PullRequest.other_repo_id==Repository.repo_id')
1422
1431
1423 def __json__(self):
1432 def __json__(self):
1424 return dict(
1433 return dict(
1425 revisions=self.revisions
1434 revisions=self.revisions
1426 )
1435 )
1427
1436
1428
1437
1429 class PullRequestReviewers(Base, BaseModel):
1438 class PullRequestReviewers(Base, BaseModel):
1430 __tablename__ = 'pull_request_reviewers'
1439 __tablename__ = 'pull_request_reviewers'
1431 __table_args__ = (
1440 __table_args__ = (
1432 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1441 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1433 'mysql_charset': 'utf8'},
1442 'mysql_charset': 'utf8'},
1434 )
1443 )
1435
1444
1436 def __init__(self, user=None, pull_request=None):
1445 def __init__(self, user=None, pull_request=None):
1437 self.user = user
1446 self.user = user
1438 self.pull_request = pull_request
1447 self.pull_request = pull_request
1439
1448
1440 pull_requests_reviewers_id = Column('pull_requests_reviewers_id', Integer(), nullable=False, primary_key=True)
1449 pull_requests_reviewers_id = Column('pull_requests_reviewers_id', Integer(), nullable=False, primary_key=True)
1441 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=False)
1450 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=False)
1442 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
1451 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
1443
1452
1444 user = relationship('User')
1453 user = relationship('User')
1445 pull_request = relationship('PullRequest')
1454 pull_request = relationship('PullRequest')
1446
1455
1447
1456
1448 class Notification(Base, BaseModel):
1457 class Notification(Base, BaseModel):
1449 __tablename__ = 'notifications'
1458 __tablename__ = 'notifications'
1450 __table_args__ = (
1459 __table_args__ = (
1451 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1460 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1452 'mysql_charset': 'utf8'},
1461 'mysql_charset': 'utf8'},
1453 )
1462 )
1454
1463
1455 TYPE_CHANGESET_COMMENT = u'cs_comment'
1464 TYPE_CHANGESET_COMMENT = u'cs_comment'
1456 TYPE_MESSAGE = u'message'
1465 TYPE_MESSAGE = u'message'
1457 TYPE_MENTION = u'mention'
1466 TYPE_MENTION = u'mention'
1458 TYPE_REGISTRATION = u'registration'
1467 TYPE_REGISTRATION = u'registration'
1459 TYPE_PULL_REQUEST = u'pull_request'
1468 TYPE_PULL_REQUEST = u'pull_request'
1469 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
1460
1470
1461 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
1471 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
1462 subject = Column('subject', Unicode(512), nullable=True)
1472 subject = Column('subject', Unicode(512), nullable=True)
1463 body = Column('body', Unicode(50000), nullable=True)
1473 body = Column('body', Unicode(50000), nullable=True)
1464 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
1474 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
1465 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1475 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1466 type_ = Column('type', Unicode(256))
1476 type_ = Column('type', Unicode(256))
1467
1477
1468 created_by_user = relationship('User')
1478 created_by_user = relationship('User')
1469 notifications_to_users = relationship('UserNotification', lazy='joined',
1479 notifications_to_users = relationship('UserNotification', lazy='joined',
1470 cascade="all, delete, delete-orphan")
1480 cascade="all, delete, delete-orphan")
1471
1481
1472 @property
1482 @property
1473 def recipients(self):
1483 def recipients(self):
1474 return [x.user for x in UserNotification.query()\
1484 return [x.user for x in UserNotification.query()\
1475 .filter(UserNotification.notification == self)\
1485 .filter(UserNotification.notification == self)\
1476 .order_by(UserNotification.user).all()]
1486 .order_by(UserNotification.user).all()]
1477
1487
1478 @classmethod
1488 @classmethod
1479 def create(cls, created_by, subject, body, recipients, type_=None):
1489 def create(cls, created_by, subject, body, recipients, type_=None):
1480 if type_ is None:
1490 if type_ is None:
1481 type_ = Notification.TYPE_MESSAGE
1491 type_ = Notification.TYPE_MESSAGE
1482
1492
1483 notification = cls()
1493 notification = cls()
1484 notification.created_by_user = created_by
1494 notification.created_by_user = created_by
1485 notification.subject = subject
1495 notification.subject = subject
1486 notification.body = body
1496 notification.body = body
1487 notification.type_ = type_
1497 notification.type_ = type_
1488 notification.created_on = datetime.datetime.now()
1498 notification.created_on = datetime.datetime.now()
1489
1499
1490 for u in recipients:
1500 for u in recipients:
1491 assoc = UserNotification()
1501 assoc = UserNotification()
1492 assoc.notification = notification
1502 assoc.notification = notification
1493 u.notifications.append(assoc)
1503 u.notifications.append(assoc)
1494 Session.add(notification)
1504 Session.add(notification)
1495 return notification
1505 return notification
1496
1506
1497 @property
1507 @property
1498 def description(self):
1508 def description(self):
1499 from rhodecode.model.notification import NotificationModel
1509 from rhodecode.model.notification import NotificationModel
1500 return NotificationModel().make_description(self)
1510 return NotificationModel().make_description(self)
1501
1511
1502
1512
1503 class UserNotification(Base, BaseModel):
1513 class UserNotification(Base, BaseModel):
1504 __tablename__ = 'user_to_notification'
1514 __tablename__ = 'user_to_notification'
1505 __table_args__ = (
1515 __table_args__ = (
1506 UniqueConstraint('user_id', 'notification_id'),
1516 UniqueConstraint('user_id', 'notification_id'),
1507 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1517 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1508 'mysql_charset': 'utf8'}
1518 'mysql_charset': 'utf8'}
1509 )
1519 )
1510 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
1520 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
1511 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
1521 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
1512 read = Column('read', Boolean, default=False)
1522 read = Column('read', Boolean, default=False)
1513 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
1523 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
1514
1524
1515 user = relationship('User', lazy="joined")
1525 user = relationship('User', lazy="joined")
1516 notification = relationship('Notification', lazy="joined",
1526 notification = relationship('Notification', lazy="joined",
1517 order_by=lambda: Notification.created_on.desc(),)
1527 order_by=lambda: Notification.created_on.desc(),)
1518
1528
1519 def mark_as_read(self):
1529 def mark_as_read(self):
1520 self.read = True
1530 self.read = True
1521 Session.add(self)
1531 Session.add(self)
1522
1532
1523
1533
1524 class DbMigrateVersion(Base, BaseModel):
1534 class DbMigrateVersion(Base, BaseModel):
1525 __tablename__ = 'db_migrate_version'
1535 __tablename__ = 'db_migrate_version'
1526 __table_args__ = (
1536 __table_args__ = (
1527 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1537 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1528 'mysql_charset': 'utf8'},
1538 'mysql_charset': 'utf8'},
1529 )
1539 )
1530 repository_id = Column('repository_id', String(250), primary_key=True)
1540 repository_id = Column('repository_id', String(250), primary_key=True)
1531 repository_path = Column('repository_path', Text)
1541 repository_path = Column('repository_path', Text)
1532 version = Column('version', Integer)
1542 version = Column('version', Integer)
@@ -1,256 +1,258 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.model.notification
3 rhodecode.model.notification
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 Model for notifications
6 Model for notifications
7
7
8
8
9 :created_on: Nov 20, 2011
9 :created_on: Nov 20, 2011
10 :author: marcink
10 :author: marcink
11 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
12 :license: GPLv3, see COPYING for more details.
12 :license: GPLv3, see COPYING for more details.
13 """
13 """
14 # This program is free software: you can redistribute it and/or modify
14 # This program is free software: you can redistribute it and/or modify
15 # it under the terms of the GNU General Public License as published by
15 # it under the terms of the GNU General Public License as published by
16 # the Free Software Foundation, either version 3 of the License, or
16 # the Free Software Foundation, either version 3 of the License, or
17 # (at your option) any later version.
17 # (at your option) any later version.
18 #
18 #
19 # This program is distributed in the hope that it will be useful,
19 # This program is distributed in the hope that it will be useful,
20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 # GNU General Public License for more details.
22 # GNU General Public License for more details.
23 #
23 #
24 # You should have received a copy of the GNU General Public License
24 # You should have received a copy of the GNU General Public License
25 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 # along with this program. If not, see <http://www.gnu.org/licenses/>.
26
26
27 import os
27 import os
28 import logging
28 import logging
29 import traceback
29 import traceback
30 import datetime
30 import datetime
31
31
32 from pylons.i18n.translation import _
32 from pylons.i18n.translation import _
33
33
34 import rhodecode
34 import rhodecode
35 from rhodecode.config.conf import DATETIME_FORMAT
35 from rhodecode.config.conf import DATETIME_FORMAT
36 from rhodecode.lib import helpers as h
36 from rhodecode.lib import helpers as h
37 from rhodecode.model import BaseModel
37 from rhodecode.model import BaseModel
38 from rhodecode.model.db import Notification, User, UserNotification
38 from rhodecode.model.db import Notification, User, UserNotification
39 from sqlalchemy.orm import joinedload
39 from sqlalchemy.orm import joinedload
40
40
41 log = logging.getLogger(__name__)
41 log = logging.getLogger(__name__)
42
42
43
43
44 class NotificationModel(BaseModel):
44 class NotificationModel(BaseModel):
45
45
46 def __get_notification(self, notification):
46 def __get_notification(self, notification):
47 if isinstance(notification, Notification):
47 if isinstance(notification, Notification):
48 return notification
48 return notification
49 elif isinstance(notification, (int, long)):
49 elif isinstance(notification, (int, long)):
50 return Notification.get(notification)
50 return Notification.get(notification)
51 else:
51 else:
52 if notification:
52 if notification:
53 raise Exception('notification must be int, long or Instance'
53 raise Exception('notification must be int, long or Instance'
54 ' of Notification got %s' % type(notification))
54 ' of Notification got %s' % type(notification))
55
55
56 def create(self, created_by, subject, body, recipients=None,
56 def create(self, created_by, subject, body, recipients=None,
57 type_=Notification.TYPE_MESSAGE, with_email=True,
57 type_=Notification.TYPE_MESSAGE, with_email=True,
58 email_kwargs={}):
58 email_kwargs={}):
59 """
59 """
60
60
61 Creates notification of given type
61 Creates notification of given type
62
62
63 :param created_by: int, str or User instance. User who created this
63 :param created_by: int, str or User instance. User who created this
64 notification
64 notification
65 :param subject:
65 :param subject:
66 :param body:
66 :param body:
67 :param recipients: list of int, str or User objects, when None
67 :param recipients: list of int, str or User objects, when None
68 is given send to all admins
68 is given send to all admins
69 :param type_: type of notification
69 :param type_: type of notification
70 :param with_email: send email with this notification
70 :param with_email: send email with this notification
71 :param email_kwargs: additional dict to pass as args to email template
71 :param email_kwargs: additional dict to pass as args to email template
72 """
72 """
73 from rhodecode.lib.celerylib import tasks, run_task
73 from rhodecode.lib.celerylib import tasks, run_task
74
74
75 if recipients and not getattr(recipients, '__iter__', False):
75 if recipients and not getattr(recipients, '__iter__', False):
76 raise Exception('recipients must be a list of iterable')
76 raise Exception('recipients must be a list of iterable')
77
77
78 created_by_obj = self._get_user(created_by)
78 created_by_obj = self._get_user(created_by)
79
79
80 if recipients:
80 if recipients:
81 recipients_objs = []
81 recipients_objs = []
82 for u in recipients:
82 for u in recipients:
83 obj = self._get_user(u)
83 obj = self._get_user(u)
84 if obj:
84 if obj:
85 recipients_objs.append(obj)
85 recipients_objs.append(obj)
86 recipients_objs = set(recipients_objs)
86 recipients_objs = set(recipients_objs)
87 log.debug('sending notifications %s to %s' % (
87 log.debug('sending notifications %s to %s' % (
88 type_, recipients_objs)
88 type_, recipients_objs)
89 )
89 )
90 else:
90 else:
91 # empty recipients means to all admins
91 # empty recipients means to all admins
92 recipients_objs = User.query().filter(User.admin == True).all()
92 recipients_objs = User.query().filter(User.admin == True).all()
93 log.debug('sending notifications %s to admins: %s' % (
93 log.debug('sending notifications %s to admins: %s' % (
94 type_, recipients_objs)
94 type_, recipients_objs)
95 )
95 )
96 notif = Notification.create(
96 notif = Notification.create(
97 created_by=created_by_obj, subject=subject,
97 created_by=created_by_obj, subject=subject,
98 body=body, recipients=recipients_objs, type_=type_
98 body=body, recipients=recipients_objs, type_=type_
99 )
99 )
100
100
101 if with_email is False:
101 if with_email is False:
102 return notif
102 return notif
103
103
104 #don't send email to person who created this comment
104 #don't send email to person who created this comment
105 rec_objs = set(recipients_objs).difference(set([created_by_obj]))
105 rec_objs = set(recipients_objs).difference(set([created_by_obj]))
106
106
107 # send email with notification to all other participants
107 # send email with notification to all other participants
108 for rec in rec_objs:
108 for rec in rec_objs:
109 email_subject = NotificationModel().make_description(notif, False)
109 email_subject = NotificationModel().make_description(notif, False)
110 type_ = type_
110 type_ = type_
111 email_body = body
111 email_body = body
112 ## this is passed into template
112 ## this is passed into template
113 kwargs = {'subject': subject, 'body': h.rst_w_mentions(body)}
113 kwargs = {'subject': subject, 'body': h.rst_w_mentions(body)}
114 kwargs.update(email_kwargs)
114 kwargs.update(email_kwargs)
115 email_body_html = EmailNotificationModel()\
115 email_body_html = EmailNotificationModel()\
116 .get_email_tmpl(type_, **kwargs)
116 .get_email_tmpl(type_, **kwargs)
117
117
118 run_task(tasks.send_email, rec.email, email_subject, email_body,
118 run_task(tasks.send_email, rec.email, email_subject, email_body,
119 email_body_html)
119 email_body_html)
120
120
121 return notif
121 return notif
122
122
123 def delete(self, user, notification):
123 def delete(self, user, notification):
124 # we don't want to remove actual notification just the assignment
124 # we don't want to remove actual notification just the assignment
125 try:
125 try:
126 notification = self.__get_notification(notification)
126 notification = self.__get_notification(notification)
127 user = self._get_user(user)
127 user = self._get_user(user)
128 if notification and user:
128 if notification and user:
129 obj = UserNotification.query()\
129 obj = UserNotification.query()\
130 .filter(UserNotification.user == user)\
130 .filter(UserNotification.user == user)\
131 .filter(UserNotification.notification
131 .filter(UserNotification.notification
132 == notification)\
132 == notification)\
133 .one()
133 .one()
134 self.sa.delete(obj)
134 self.sa.delete(obj)
135 return True
135 return True
136 except Exception:
136 except Exception:
137 log.error(traceback.format_exc())
137 log.error(traceback.format_exc())
138 raise
138 raise
139
139
140 def get_for_user(self, user, filter_=None):
140 def get_for_user(self, user, filter_=None):
141 """
141 """
142 Get mentions for given user, filter them if filter dict is given
142 Get mentions for given user, filter them if filter dict is given
143
143
144 :param user:
144 :param user:
145 :type user:
145 :type user:
146 :param filter:
146 :param filter:
147 """
147 """
148 user = self._get_user(user)
148 user = self._get_user(user)
149
149
150 q = UserNotification.query()\
150 q = UserNotification.query()\
151 .filter(UserNotification.user == user)\
151 .filter(UserNotification.user == user)\
152 .join((Notification, UserNotification.notification_id ==
152 .join((Notification, UserNotification.notification_id ==
153 Notification.notification_id))
153 Notification.notification_id))
154
154
155 if filter_:
155 if filter_:
156 q = q.filter(Notification.type_ == filter_.get('type'))
156 q = q.filter(Notification.type_ == filter_.get('type'))
157
157
158 return q.all()
158 return q.all()
159
159
160 def mark_all_read_for_user(self, user, filter_=None):
160 def mark_all_read_for_user(self, user, filter_=None):
161 user = self._get_user(user)
161 user = self._get_user(user)
162 q = UserNotification.query()\
162 q = UserNotification.query()\
163 .filter(UserNotification.user == user)\
163 .filter(UserNotification.user == user)\
164 .filter(UserNotification.read == False)\
164 .filter(UserNotification.read == False)\
165 .join((Notification, UserNotification.notification_id ==
165 .join((Notification, UserNotification.notification_id ==
166 Notification.notification_id))
166 Notification.notification_id))
167 if filter_:
167 if filter_:
168 q = q.filter(Notification.type_ == filter_.get('type'))
168 q = q.filter(Notification.type_ == filter_.get('type'))
169
169
170 # this is a little inefficient but sqlalchemy doesn't support
170 # this is a little inefficient but sqlalchemy doesn't support
171 # update on joined tables :(
171 # update on joined tables :(
172 for obj in q.all():
172 for obj in q.all():
173 obj.read = True
173 obj.read = True
174 self.sa.add(obj)
174 self.sa.add(obj)
175
175
176 def get_unread_cnt_for_user(self, user):
176 def get_unread_cnt_for_user(self, user):
177 user = self._get_user(user)
177 user = self._get_user(user)
178 return UserNotification.query()\
178 return UserNotification.query()\
179 .filter(UserNotification.read == False)\
179 .filter(UserNotification.read == False)\
180 .filter(UserNotification.user == user).count()
180 .filter(UserNotification.user == user).count()
181
181
182 def get_unread_for_user(self, user):
182 def get_unread_for_user(self, user):
183 user = self._get_user(user)
183 user = self._get_user(user)
184 return [x.notification for x in UserNotification.query()\
184 return [x.notification for x in UserNotification.query()\
185 .filter(UserNotification.read == False)\
185 .filter(UserNotification.read == False)\
186 .filter(UserNotification.user == user).all()]
186 .filter(UserNotification.user == user).all()]
187
187
188 def get_user_notification(self, user, notification):
188 def get_user_notification(self, user, notification):
189 user = self._get_user(user)
189 user = self._get_user(user)
190 notification = self.__get_notification(notification)
190 notification = self.__get_notification(notification)
191
191
192 return UserNotification.query()\
192 return UserNotification.query()\
193 .filter(UserNotification.notification == notification)\
193 .filter(UserNotification.notification == notification)\
194 .filter(UserNotification.user == user).scalar()
194 .filter(UserNotification.user == user).scalar()
195
195
196 def make_description(self, notification, show_age=True):
196 def make_description(self, notification, show_age=True):
197 """
197 """
198 Creates a human readable description based on properties
198 Creates a human readable description based on properties
199 of notification object
199 of notification object
200 """
200 """
201
201 #alias
202 _n = notification
202 _map = {
203 _map = {
203 notification.TYPE_CHANGESET_COMMENT: _('commented on commit'),
204 _n.TYPE_CHANGESET_COMMENT: _('commented on commit'),
204 notification.TYPE_MESSAGE: _('sent message'),
205 _n.TYPE_MESSAGE: _('sent message'),
205 notification.TYPE_MENTION: _('mentioned you'),
206 _n.TYPE_MENTION: _('mentioned you'),
206 notification.TYPE_REGISTRATION: _('registered in RhodeCode'),
207 _n.TYPE_REGISTRATION: _('registered in RhodeCode'),
207 notification.TYPE_PULL_REQUEST: _('opened new pull request')
208 _n.TYPE_PULL_REQUEST: _('opened new pull request'),
209 _n.TYPE_PULL_REQUEST_COMMENT: _('commented on pull request')
208 }
210 }
209
211
210 tmpl = "%(user)s %(action)s %(when)s"
212 tmpl = "%(user)s %(action)s %(when)s"
211 if show_age:
213 if show_age:
212 when = h.age(notification.created_on)
214 when = h.age(notification.created_on)
213 else:
215 else:
214 DTF = lambda d: datetime.datetime.strftime(d, DATETIME_FORMAT)
216 DTF = lambda d: datetime.datetime.strftime(d, DATETIME_FORMAT)
215 when = DTF(notification.created_on)
217 when = DTF(notification.created_on)
216
218
217 data = dict(
219 data = dict(
218 user=notification.created_by_user.username,
220 user=notification.created_by_user.username,
219 action=_map[notification.type_], when=when,
221 action=_map[notification.type_], when=when,
220 )
222 )
221 return tmpl % data
223 return tmpl % data
222
224
223
225
224 class EmailNotificationModel(BaseModel):
226 class EmailNotificationModel(BaseModel):
225
227
226 TYPE_CHANGESET_COMMENT = Notification.TYPE_CHANGESET_COMMENT
228 TYPE_CHANGESET_COMMENT = Notification.TYPE_CHANGESET_COMMENT
227 TYPE_PASSWORD_RESET = 'passoword_link'
229 TYPE_PASSWORD_RESET = 'passoword_link'
228 TYPE_REGISTRATION = Notification.TYPE_REGISTRATION
230 TYPE_REGISTRATION = Notification.TYPE_REGISTRATION
229 TYPE_PULL_REQUEST = Notification.TYPE_PULL_REQUEST
231 TYPE_PULL_REQUEST = Notification.TYPE_PULL_REQUEST
230 TYPE_DEFAULT = 'default'
232 TYPE_DEFAULT = 'default'
231
233
232 def __init__(self):
234 def __init__(self):
233 self._template_root = rhodecode.CONFIG['pylons.paths']['templates'][0]
235 self._template_root = rhodecode.CONFIG['pylons.paths']['templates'][0]
234 self._tmpl_lookup = rhodecode.CONFIG['pylons.app_globals'].mako_lookup
236 self._tmpl_lookup = rhodecode.CONFIG['pylons.app_globals'].mako_lookup
235
237
236 self.email_types = {
238 self.email_types = {
237 self.TYPE_CHANGESET_COMMENT: 'email_templates/changeset_comment.html',
239 self.TYPE_CHANGESET_COMMENT: 'email_templates/changeset_comment.html',
238 self.TYPE_PASSWORD_RESET: 'email_templates/password_reset.html',
240 self.TYPE_PASSWORD_RESET: 'email_templates/password_reset.html',
239 self.TYPE_REGISTRATION: 'email_templates/registration.html',
241 self.TYPE_REGISTRATION: 'email_templates/registration.html',
240 self.TYPE_DEFAULT: 'email_templates/default.html'
242 self.TYPE_DEFAULT: 'email_templates/default.html'
241 }
243 }
242
244
243 def get_email_tmpl(self, type_, **kwargs):
245 def get_email_tmpl(self, type_, **kwargs):
244 """
246 """
245 return generated template for email based on given type
247 return generated template for email based on given type
246
248
247 :param type_:
249 :param type_:
248 """
250 """
249
251
250 base = self.email_types.get(type_, self.email_types[self.TYPE_DEFAULT])
252 base = self.email_types.get(type_, self.email_types[self.TYPE_DEFAULT])
251 email_template = self._tmpl_lookup.get_template(base)
253 email_template = self._tmpl_lookup.get_template(base)
252 # translator inject
254 # translator inject
253 _kwargs = {'_': _}
255 _kwargs = {'_': _}
254 _kwargs.update(kwargs)
256 _kwargs.update(kwargs)
255 log.debug('rendering tmpl %s with kwargs %s' % (base, _kwargs))
257 log.debug('rendering tmpl %s with kwargs %s' % (base, _kwargs))
256 return email_template.render(**_kwargs)
258 return email_template.render(**_kwargs)
@@ -1,251 +1,257 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2
2
3 <%inherit file="/base/base.html"/>
3 <%inherit file="/base/base.html"/>
4
4
5 <%def name="title()">
5 <%def name="title()">
6 ${_('%s Changelog') % c.repo_name} - ${c.rhodecode_name}
6 ${_('%s Changelog') % c.repo_name} - ${c.rhodecode_name}
7 </%def>
7 </%def>
8
8
9 <%def name="breadcrumbs_links()">
9 <%def name="breadcrumbs_links()">
10 ${h.link_to(u'Home',h.url('/'))}
10 ${h.link_to(u'Home',h.url('/'))}
11 &raquo;
11 &raquo;
12 ${h.link_to(c.repo_name,h.url('summary_home',repo_name=c.repo_name))}
12 ${h.link_to(c.repo_name,h.url('summary_home',repo_name=c.repo_name))}
13 &raquo;
13 &raquo;
14 <% size = c.size if c.size <= c.total_cs else c.total_cs %>
14 <% size = c.size if c.size <= c.total_cs else c.total_cs %>
15 ${_('Changelog')} - ${ungettext('showing %d out of %d revision', 'showing %d out of %d revisions', size) % (size, c.total_cs)}
15 ${_('Changelog')} - ${ungettext('showing %d out of %d revision', 'showing %d out of %d revisions', size) % (size, c.total_cs)}
16 </%def>
16 </%def>
17
17
18 <%def name="page_nav()">
18 <%def name="page_nav()">
19 ${self.menu('changelog')}
19 ${self.menu('changelog')}
20 </%def>
20 </%def>
21
21
22 <%def name="main()">
22 <%def name="main()">
23 <div class="box">
23 <div class="box">
24 <!-- box / title -->
24 <!-- box / title -->
25 <div class="title">
25 <div class="title">
26 ${self.breadcrumbs()}
26 ${self.breadcrumbs()}
27 </div>
27 </div>
28 <div class="table">
28 <div class="table">
29 % if c.pagination:
29 % if c.pagination:
30 <div id="graph">
30 <div id="graph">
31 <div id="graph_nodes">
31 <div id="graph_nodes">
32 <canvas id="graph_canvas"></canvas>
32 <canvas id="graph_canvas"></canvas>
33 </div>
33 </div>
34 <div id="graph_content">
34 <div id="graph_content">
35 <div class="info_box" style="clear: both;padding: 10px 6px;vertical-align: right;text-align: right;">
35 <div class="info_box" style="clear: both;padding: 10px 6px;vertical-align: right;text-align: right;">
36 %if c.rhodecode_db_repo.fork:
36 %if c.rhodecode_db_repo.fork:
37 <a title="${_('compare fork with %s' % c.rhodecode_db_repo.fork.repo_name)}" href="${h.url('compare_url',repo_name=c.repo_name,org_ref_type='branch',org_ref='default',other_ref_type='branch',other_ref='default',repo=c.rhodecode_db_repo.fork.repo_name)}" class="ui-btn small">${_('Compare fork')}</a>
37 <a title="${_('compare fork with %s' % c.rhodecode_db_repo.fork.repo_name)}" href="${h.url('compare_url',repo_name=c.repo_name,org_ref_type='branch',org_ref='default',other_ref_type='branch',other_ref='default',repo=c.rhodecode_db_repo.fork.repo_name)}" class="ui-btn small">${_('Compare fork')}</a>
38 %endif
38 %endif
39 <a href="${h.url('pullrequest_home',repo_name=c.repo_name)}" class="ui-btn small">${_('Open new pull request')}</a>
39 <a href="${h.url('pullrequest_home',repo_name=c.repo_name)}" class="ui-btn small">${_('Open new pull request')}</a>
40 </div>
40 </div>
41 <div class="container_header">
41 <div class="container_header">
42 ${h.form(h.url.current(),method='get')}
42 ${h.form(h.url.current(),method='get')}
43 <div class="info_box" style="float:left">
43 <div class="info_box" style="float:left">
44 ${h.submit('set',_('Show'),class_="ui-btn")}
44 ${h.submit('set',_('Show'),class_="ui-btn")}
45 ${h.text('size',size=1,value=c.size)}
45 ${h.text('size',size=1,value=c.size)}
46 ${_('revisions')}
46 ${_('revisions')}
47 </div>
47 </div>
48 ${h.end_form()}
48 ${h.end_form()}
49 <div id="rev_range_container" style="display:none"></div>
49 <div id="rev_range_container" style="display:none"></div>
50 <div style="float:right">${h.select('branch_filter',c.branch_name,c.branch_filters)}</div>
50 <div style="float:right">${h.select('branch_filter',c.branch_name,c.branch_filters)}</div>
51 </div>
51 </div>
52
52
53 %for cnt,cs in enumerate(c.pagination):
53 %for cnt,cs in enumerate(c.pagination):
54 <div id="chg_${cnt+1}" class="container ${'tablerow%s' % (cnt%2)}">
54 <div id="chg_${cnt+1}" class="container ${'tablerow%s' % (cnt%2)}">
55 <div class="left">
55 <div class="left">
56 <div>
56 <div>
57 ${h.checkbox(cs.short_id,class_="changeset_range")}
57 ${h.checkbox(cs.short_id,class_="changeset_range")}
58 <span class="tooltip" title="${h.tooltip(h.age(cs.date))}"><a href="${h.url('changeset_home',repo_name=c.repo_name,revision=cs.raw_id)}"><span class="changeset_id">${cs.revision}:<span class="changeset_hash">${h.short_id(cs.raw_id)}</span></span></a></span>
58 <span class="tooltip" title="${h.tooltip(h.age(cs.date))}"><a href="${h.url('changeset_home',repo_name=c.repo_name,revision=cs.raw_id)}"><span class="changeset_id">${cs.revision}:<span class="changeset_hash">${h.short_id(cs.raw_id)}</span></span></a></span>
59 </div>
59 </div>
60 <div class="author">
60 <div class="author">
61 <div class="gravatar">
61 <div class="gravatar">
62 <img alt="gravatar" src="${h.gravatar_url(h.email(cs.author),16)}"/>
62 <img alt="gravatar" src="${h.gravatar_url(h.email(cs.author),16)}"/>
63 </div>
63 </div>
64 <div title="${cs.author}" class="user">${h.shorter(h.person(cs.author),22)}</div>
64 <div title="${cs.author}" class="user">${h.shorter(h.person(cs.author),22)}</div>
65 </div>
65 </div>
66 <div class="date">${h.fmt_date(cs.date)}</div>
66 <div class="date">${h.fmt_date(cs.date)}</div>
67 </div>
67 </div>
68 <div class="mid">
68 <div class="mid">
69 <div class="message">${h.urlify_commit(cs.message, c.repo_name,h.url('changeset_home',repo_name=c.repo_name,revision=cs.raw_id))}</div>
69 <div class="message">${h.urlify_commit(cs.message, c.repo_name,h.url('changeset_home',repo_name=c.repo_name,revision=cs.raw_id))}</div>
70 <div class="expand"><span class="expandtext">&darr; ${_('show more')} &darr;</span></div>
70 <div class="expand"><span class="expandtext">&darr; ${_('show more')} &darr;</span></div>
71 </div>
71 </div>
72 <div class="right">
72 <div class="right">
73 <div class="changes">
73 <div class="changes">
74 <div id="${cs.raw_id}" style="float:right;" class="changed_total tooltip" title="${h.tooltip(_('Affected number of files, click to show more details'))}">${len(cs.affected_files)}</div>
74 <div id="${cs.raw_id}" style="float:right;" class="changed_total tooltip" title="${h.tooltip(_('Affected number of files, click to show more details'))}">${len(cs.affected_files)}</div>
75 <div class="comments-container">
75 <div class="comments-container">
76 %if len(c.comments.get(cs.raw_id,[])) > 0:
76 %if len(c.comments.get(cs.raw_id,[])) > 0:
77 <div class="comments-cnt" title="${('comments')}">
77 <div class="comments-cnt" title="${('comments')}">
78 <a href="${h.url('changeset_home',repo_name=c.repo_name,revision=cs.raw_id,anchor='comment-%s' % c.comments[cs.raw_id][0].comment_id)}">
78 <a href="${h.url('changeset_home',repo_name=c.repo_name,revision=cs.raw_id,anchor='comment-%s' % c.comments[cs.raw_id][0].comment_id)}">
79 <div class="comments-cnt">${len(c.comments[cs.raw_id])}</div>
79 <div class="comments-cnt">${len(c.comments[cs.raw_id])}</div>
80 <img src="${h.url('/images/icons/comments.png')}">
80 <img src="${h.url('/images/icons/comments.png')}">
81 </a>
81 </a>
82 </div>
82 </div>
83 %endif
83 %endif
84 </div>
84 </div>
85 <div class="changeset-status-container">
85 <div class="changeset-status-container">
86 %if c.statuses.get(cs.raw_id):
86 %if c.statuses.get(cs.raw_id):
87 <div title="${_('Changeset status')}" class="changeset-status-lbl">${c.statuses.get(cs.raw_id)[1]}</div>
87 <div title="${_('Changeset status')}" class="changeset-status-lbl">${c.statuses.get(cs.raw_id)[1]}</div>
88 <div class="changeset-status-ico"><img src="${h.url('/images/icons/flag_status_%s.png' % c.statuses.get(cs.raw_id)[0])}" /></div>
88 <div class="changeset-status-ico">
89 %if c.statuses.get(cs.raw_id)[2]:
90 <a class="tooltip" title="${_('Click to open associated pull request')}" href="${h.url('pullrequest_show',repo_name=c.statuses.get(cs.raw_id)[3],pull_request_id=c.statuses.get(cs.raw_id)[2])}"><img src="${h.url('/images/icons/flag_status_%s.png' % c.statuses.get(cs.raw_id)[0])}" /></a>
91 %else:
92 <img src="${h.url('/images/icons/flag_status_%s.png' % c.statuses.get(cs.raw_id)[0])}" />
93 %endif
94 </div>
89 %endif
95 %endif
90 </div>
96 </div>
91 </div>
97 </div>
92 %if cs.parents:
98 %if cs.parents:
93 %for p_cs in reversed(cs.parents):
99 %for p_cs in reversed(cs.parents):
94 <div class="parent">${_('Parent')}
100 <div class="parent">${_('Parent')}
95 <span class="changeset_id">${p_cs.revision}:<span class="changeset_hash">${h.link_to(h.short_id(p_cs.raw_id),
101 <span class="changeset_id">${p_cs.revision}:<span class="changeset_hash">${h.link_to(h.short_id(p_cs.raw_id),
96 h.url('changeset_home',repo_name=c.repo_name,revision=p_cs.raw_id),title=p_cs.message)}</span></span>
102 h.url('changeset_home',repo_name=c.repo_name,revision=p_cs.raw_id),title=p_cs.message)}</span></span>
97 </div>
103 </div>
98 %endfor
104 %endfor
99 %else:
105 %else:
100 <div class="parent">${_('No parents')}</div>
106 <div class="parent">${_('No parents')}</div>
101 %endif
107 %endif
102
108
103 <span class="logtags">
109 <span class="logtags">
104 %if len(cs.parents)>1:
110 %if len(cs.parents)>1:
105 <span class="merge">${_('merge')}</span>
111 <span class="merge">${_('merge')}</span>
106 %endif
112 %endif
107 %if cs.branch:
113 %if cs.branch:
108 <span class="branchtag" title="${'%s %s' % (_('branch'),cs.branch)}">
114 <span class="branchtag" title="${'%s %s' % (_('branch'),cs.branch)}">
109 ${h.link_to(h.shorter(cs.branch),h.url('files_home',repo_name=c.repo_name,revision=cs.raw_id))}
115 ${h.link_to(h.shorter(cs.branch),h.url('files_home',repo_name=c.repo_name,revision=cs.raw_id))}
110 </span>
116 </span>
111 %endif
117 %endif
112 %if h.is_hg(c.rhodecode_repo):
118 %if h.is_hg(c.rhodecode_repo):
113 %for book in cs.bookmarks:
119 %for book in cs.bookmarks:
114 <span class="bookbook" title="${'%s %s' % (_('bookmark'),book)}">
120 <span class="bookbook" title="${'%s %s' % (_('bookmark'),book)}">
115 ${h.link_to(h.shorter(book),h.url('files_home',repo_name=c.repo_name,revision=cs.raw_id))}
121 ${h.link_to(h.shorter(book),h.url('files_home',repo_name=c.repo_name,revision=cs.raw_id))}
116 </span>
122 </span>
117 %endfor
123 %endfor
118 %endif
124 %endif
119 %for tag in cs.tags:
125 %for tag in cs.tags:
120 <span class="tagtag" title="${'%s %s' % (_('tag'),tag)}">
126 <span class="tagtag" title="${'%s %s' % (_('tag'),tag)}">
121 ${h.link_to(h.shorter(tag),h.url('files_home',repo_name=c.repo_name,revision=cs.raw_id))}</span>
127 ${h.link_to(h.shorter(tag),h.url('files_home',repo_name=c.repo_name,revision=cs.raw_id))}</span>
122 %endfor
128 %endfor
123 </span>
129 </span>
124 </div>
130 </div>
125 </div>
131 </div>
126
132
127 %endfor
133 %endfor
128 <div class="pagination-wh pagination-left">
134 <div class="pagination-wh pagination-left">
129 ${c.pagination.pager('$link_previous ~2~ $link_next')}
135 ${c.pagination.pager('$link_previous ~2~ $link_next')}
130 </div>
136 </div>
131 </div>
137 </div>
132 </div>
138 </div>
133
139
134 <script type="text/javascript" src="${h.url('/js/graph.js')}"></script>
140 <script type="text/javascript" src="${h.url('/js/graph.js')}"></script>
135 <script type="text/javascript">
141 <script type="text/javascript">
136 YAHOO.util.Event.onDOMReady(function(){
142 YAHOO.util.Event.onDOMReady(function(){
137
143
138 //Monitor range checkboxes and build a link to changesets
144 //Monitor range checkboxes and build a link to changesets
139 //ranges
145 //ranges
140 var checkboxes = YUD.getElementsByClassName('changeset_range');
146 var checkboxes = YUD.getElementsByClassName('changeset_range');
141 var url_tmpl = "${h.url('changeset_home',repo_name=c.repo_name,revision='__REVRANGE__')}";
147 var url_tmpl = "${h.url('changeset_home',repo_name=c.repo_name,revision='__REVRANGE__')}";
142 YUE.on(checkboxes,'click',function(e){
148 YUE.on(checkboxes,'click',function(e){
143 var checked_checkboxes = [];
149 var checked_checkboxes = [];
144 for (pos in checkboxes){
150 for (pos in checkboxes){
145 if(checkboxes[pos].checked){
151 if(checkboxes[pos].checked){
146 checked_checkboxes.push(checkboxes[pos]);
152 checked_checkboxes.push(checkboxes[pos]);
147 }
153 }
148 }
154 }
149 if(checked_checkboxes.length>1){
155 if(checked_checkboxes.length>1){
150 var rev_end = checked_checkboxes[0].name;
156 var rev_end = checked_checkboxes[0].name;
151 var rev_start = checked_checkboxes[checked_checkboxes.length-1].name;
157 var rev_start = checked_checkboxes[checked_checkboxes.length-1].name;
152
158
153 var url = url_tmpl.replace('__REVRANGE__',
159 var url = url_tmpl.replace('__REVRANGE__',
154 rev_start+'...'+rev_end);
160 rev_start+'...'+rev_end);
155
161
156 var link = "<a href="+url+">${_('Show selected changes __S -> __E')}</a>"
162 var link = "<a href="+url+">${_('Show selected changes __S -> __E')}</a>"
157 link = link.replace('__S',rev_start);
163 link = link.replace('__S',rev_start);
158 link = link.replace('__E',rev_end);
164 link = link.replace('__E',rev_end);
159 YUD.get('rev_range_container').innerHTML = link;
165 YUD.get('rev_range_container').innerHTML = link;
160 YUD.setStyle('rev_range_container','display','');
166 YUD.setStyle('rev_range_container','display','');
161 }
167 }
162 else{
168 else{
163 YUD.setStyle('rev_range_container','display','none');
169 YUD.setStyle('rev_range_container','display','none');
164
170
165 }
171 }
166 });
172 });
167
173
168 var msgs = YUQ('.message');
174 var msgs = YUQ('.message');
169 // get first element height
175 // get first element height
170 var el = YUQ('#graph_content .container')[0];
176 var el = YUQ('#graph_content .container')[0];
171 var row_h = el.clientHeight;
177 var row_h = el.clientHeight;
172 for(var i=0;i<msgs.length;i++){
178 for(var i=0;i<msgs.length;i++){
173 var m = msgs[i];
179 var m = msgs[i];
174
180
175 var h = m.clientHeight;
181 var h = m.clientHeight;
176 var pad = YUD.getStyle(m,'padding');
182 var pad = YUD.getStyle(m,'padding');
177 if(h > row_h){
183 if(h > row_h){
178 var offset = row_h - (h+12);
184 var offset = row_h - (h+12);
179 YUD.setStyle(m.nextElementSibling,'display','block');
185 YUD.setStyle(m.nextElementSibling,'display','block');
180 YUD.setStyle(m.nextElementSibling,'margin-top',offset+'px');
186 YUD.setStyle(m.nextElementSibling,'margin-top',offset+'px');
181 };
187 };
182 }
188 }
183 YUE.on(YUQ('.expand'),'click',function(e){
189 YUE.on(YUQ('.expand'),'click',function(e){
184 var elem = e.currentTarget.parentNode.parentNode;
190 var elem = e.currentTarget.parentNode.parentNode;
185 YUD.setStyle(e.currentTarget,'display','none');
191 YUD.setStyle(e.currentTarget,'display','none');
186 YUD.setStyle(elem,'height','auto');
192 YUD.setStyle(elem,'height','auto');
187
193
188 //redraw the graph, max_w and jsdata are global vars
194 //redraw the graph, max_w and jsdata are global vars
189 set_canvas(max_w);
195 set_canvas(max_w);
190
196
191 var r = new BranchRenderer();
197 var r = new BranchRenderer();
192 r.render(jsdata,max_w);
198 r.render(jsdata,max_w);
193
199
194 })
200 })
195
201
196 // Fetch changeset details
202 // Fetch changeset details
197 YUE.on(YUD.getElementsByClassName('changed_total'),'click',function(e){
203 YUE.on(YUD.getElementsByClassName('changed_total'),'click',function(e){
198 var id = e.currentTarget.id
204 var id = e.currentTarget.id
199 var url = "${h.url('changelog_details',repo_name=c.repo_name,cs='__CS__')}"
205 var url = "${h.url('changelog_details',repo_name=c.repo_name,cs='__CS__')}"
200 var url = url.replace('__CS__',id);
206 var url = url.replace('__CS__',id);
201 ypjax(url,id,function(){tooltip_activate()});
207 ypjax(url,id,function(){tooltip_activate()});
202 });
208 });
203
209
204 // change branch filter
210 // change branch filter
205 YUE.on(YUD.get('branch_filter'),'change',function(e){
211 YUE.on(YUD.get('branch_filter'),'change',function(e){
206 var selected_branch = e.currentTarget.options[e.currentTarget.selectedIndex].value;
212 var selected_branch = e.currentTarget.options[e.currentTarget.selectedIndex].value;
207 var url_main = "${h.url('changelog_home',repo_name=c.repo_name)}";
213 var url_main = "${h.url('changelog_home',repo_name=c.repo_name)}";
208 var url = "${h.url('changelog_home',repo_name=c.repo_name,branch='__BRANCH__')}";
214 var url = "${h.url('changelog_home',repo_name=c.repo_name,branch='__BRANCH__')}";
209 var url = url.replace('__BRANCH__',selected_branch);
215 var url = url.replace('__BRANCH__',selected_branch);
210 if(selected_branch != ''){
216 if(selected_branch != ''){
211 window.location = url;
217 window.location = url;
212 }else{
218 }else{
213 window.location = url_main;
219 window.location = url_main;
214 }
220 }
215
221
216 });
222 });
217
223
218 function set_canvas(heads) {
224 function set_canvas(heads) {
219 var c = document.getElementById('graph_nodes');
225 var c = document.getElementById('graph_nodes');
220 var t = document.getElementById('graph_content');
226 var t = document.getElementById('graph_content');
221 canvas = document.getElementById('graph_canvas');
227 canvas = document.getElementById('graph_canvas');
222 var div_h = t.clientHeight;
228 var div_h = t.clientHeight;
223 c.style.height=div_h+'px';
229 c.style.height=div_h+'px';
224 canvas.setAttribute('height',div_h);
230 canvas.setAttribute('height',div_h);
225 c.style.height=max_w+'px';
231 c.style.height=max_w+'px';
226 canvas.setAttribute('width',max_w);
232 canvas.setAttribute('width',max_w);
227 };
233 };
228 var heads = 1;
234 var heads = 1;
229 var max_heads = 0;
235 var max_heads = 0;
230 var jsdata = ${c.jsdata|n};
236 var jsdata = ${c.jsdata|n};
231
237
232 for( var i=0;i<jsdata.length;i++){
238 for( var i=0;i<jsdata.length;i++){
233 var m = Math.max.apply(Math, jsdata[i][1]);
239 var m = Math.max.apply(Math, jsdata[i][1]);
234 if (m>max_heads){
240 if (m>max_heads){
235 max_heads = m;
241 max_heads = m;
236 }
242 }
237 }
243 }
238 var max_w = Math.max(100,max_heads*25);
244 var max_w = Math.max(100,max_heads*25);
239 set_canvas(max_w);
245 set_canvas(max_w);
240
246
241 var r = new BranchRenderer();
247 var r = new BranchRenderer();
242 r.render(jsdata,max_w);
248 r.render(jsdata,max_w);
243
249
244 });
250 });
245 </script>
251 </script>
246 %else:
252 %else:
247 ${_('There are no changes yet')}
253 ${_('There are no changes yet')}
248 %endif
254 %endif
249 </div>
255 </div>
250 </div>
256 </div>
251 </%def>
257 </%def>
@@ -1,83 +1,83 b''
1 <%inherit file="/base/base.html"/>
1 <%inherit file="/base/base.html"/>
2
2
3 <%def name="title()">
3 <%def name="title()">
4 ${c.repo_name} ${_('Pull request #%s') % c.pull_request.pull_request_id}
4 ${c.repo_name} ${_('Pull request #%s') % c.pull_request.pull_request_id}
5 </%def>
5 </%def>
6
6
7 <%def name="breadcrumbs_links()">
7 <%def name="breadcrumbs_links()">
8 ${h.link_to(u'Home',h.url('/'))}
8 ${h.link_to(u'Home',h.url('/'))}
9 &raquo;
9 &raquo;
10 ${h.link_to(c.repo_name,h.url('changelog_home',repo_name=c.repo_name))}
10 ${h.link_to(c.repo_name,h.url('changelog_home',repo_name=c.repo_name))}
11 &raquo;
11 &raquo;
12 ${_('Pull request #%s') % c.pull_request.pull_request_id}
12 ${_('Pull request #%s') % c.pull_request.pull_request_id}
13 </%def>
13 </%def>
14
14
15 <%def name="main()">
15 <%def name="main()">
16
16
17 <div class="box">
17 <div class="box">
18 <!-- box / title -->
18 <!-- box / title -->
19 <div class="title">
19 <div class="title">
20 ${self.breadcrumbs()}
20 ${self.breadcrumbs()}
21 </div>
21 </div>
22
22
23 <h3>${_('Title')}: ${c.pull_request.title}</h3>
23 <h3>${_('Title')}: ${c.pull_request.title}</h3>
24 <div class="changeset-status-container" style="float:left;padding:0px 20px 20px 20px">
24 <div class="changeset-status-container" style="float:left;padding:0px 20px 20px 20px">
25 %if c.current_changeset_status:
25 %if c.current_changeset_status:
26 <div title="${_('Changeset status')}" class="changeset-status-lbl">[${h.changeset_status_lbl(c.current_changeset_status)}]</div>
26 <div title="${_('Changeset status')}" class="changeset-status-lbl">[${h.changeset_status_lbl(c.current_changeset_status)}]</div>
27 <div class="changeset-status-ico"><img src="${h.url('/images/icons/flag_status_%s.png' % c.current_changeset_status)}" /></div>
27 <div class="changeset-status-ico"><img src="${h.url('/images/icons/flag_status_%s.png' % c.current_changeset_status)}" /></div>
28 %endif
28 %endif
29 </div>
29 </div>
30 <div style="padding:4px">
30 <div style="padding:4px">
31 <div>${h.fmt_date(c.pull_request.created_on)}</div>
31 <div>${h.fmt_date(c.pull_request.created_on)}</div>
32 </div>
32 </div>
33
33
34 ##DIFF
34 ##DIFF
35
35
36 <div class="table">
36 <div class="table">
37 <div id="body" class="diffblock">
37 <div id="body" class="diffblock">
38 <div style="white-space:pre-wrap;padding:5px">${h.literal(c.pull_request.description)}</div>
38 <div style="white-space:pre-wrap;padding:5px">${h.literal(c.pull_request.description)}</div>
39 </div>
39 </div>
40 <div id="changeset_compare_view_content">
40 <div id="changeset_compare_view_content">
41 ##CS
41 ##CS
42 <div style="font-size:1.1em;font-weight: bold;clear:both;padding-top:10px">${_('Incoming changesets')}</div>
42 <div style="font-size:1.1em;font-weight: bold;clear:both;padding-top:10px">${_('Incoming changesets')}</div>
43 <%include file="/compare/compare_cs.html" />
43 <%include file="/compare/compare_cs.html" />
44
44
45 ## FILES
45 ## FILES
46 <div style="font-size:1.1em;font-weight: bold;clear:both;padding-top:10px">${_('Files affected')}</div>
46 <div style="font-size:1.1em;font-weight: bold;clear:both;padding-top:10px">${_('Files affected')}</div>
47 <div class="cs_files">
47 <div class="cs_files">
48 %for fid, change, f, stat in c.files:
48 %for fid, change, f, stat in c.files:
49 <div class="cs_${change}">
49 <div class="cs_${change}">
50 <div class="node">${h.link_to(h.safe_unicode(f),h.url.current(anchor=fid))}</div>
50 <div class="node">${h.link_to(h.safe_unicode(f),h.url.current(anchor=fid))}</div>
51 <div class="changes">${h.fancy_file_stats(stat)}</div>
51 <div class="changes">${h.fancy_file_stats(stat)}</div>
52 </div>
52 </div>
53 %endfor
53 %endfor
54 </div>
54 </div>
55 </div>
55 </div>
56 </div>
56 </div>
57 <script>
57 <script>
58 var _USERS_AC_DATA = ${c.users_array|n};
58 var _USERS_AC_DATA = ${c.users_array|n};
59 var _GROUPS_AC_DATA = ${c.users_groups_array|n};
59 var _GROUPS_AC_DATA = ${c.users_groups_array|n};
60 </script>
60 </script>
61
61
62 ## diff block
62 ## diff block
63 <%namespace name="diff_block" file="/changeset/diff_block.html"/>
63 <%namespace name="diff_block" file="/changeset/diff_block.html"/>
64 %for fid, change, f, stat in c.files:
64 %for fid, change, f, stat in c.files:
65 ${diff_block.diff_block_simple([c.changes[fid]])}
65 ${diff_block.diff_block_simple([c.changes[fid]])}
66 %endfor
66 %endfor
67
67
68 ## template for inline comment form
68 ## template for inline comment form
69 <%namespace name="comment" file="/changeset/changeset_file_comment.html"/>
69 <%namespace name="comment" file="/changeset/changeset_file_comment.html"/>
70 ##${comment.comment_inline_form(c.changeset)}
70 ##${comment.comment_inline_form(c.changeset)}
71
71
72 ## render comments main comments form and it status
72 ## render comments main comments form and it status
73 ##${comment.comments(h.url('pull_request_comment', repo_name=c.repo_name, pull_request_id=c.pull_request.pull_request_id),
73 ${comment.comments(h.url('pullrequest_comment', repo_name=c.repo_name, pull_request_id=c.pull_request.pull_request_id),
74 ## c.current_changeset_status)}
74 c.current_changeset_status)}
75
75
76 </div>
76 </div>
77
77
78 <script type="text/javascript">
78 <script type="text/javascript">
79
79
80
80
81 </script>
81 </script>
82
82
83 </%def>
83 </%def>
General Comments 0
You need to be logged in to leave comments. Login now