##// END OF EJS Templates
ssh: added ssh key management into my account.
marcink -
r2044:384b6b2d default
parent child Browse files
Show More
@@ -0,0 +1,160 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
21 import pytest
22
23 from rhodecode.model.db import User, UserSshKeys
24
25 from rhodecode.tests import TestController, assert_session_flash
26 from rhodecode.tests.fixture import Fixture
27
28 fixture = Fixture()
29
30
31 def route_path(name, params=None, **kwargs):
32 import urllib
33 from rhodecode.apps._base import ADMIN_PREFIX
34
35 base_url = {
36 'my_account_ssh_keys':
37 ADMIN_PREFIX + '/my_account/ssh_keys',
38 'my_account_ssh_keys_generate':
39 ADMIN_PREFIX + '/my_account/ssh_keys/generate',
40 'my_account_ssh_keys_add':
41 ADMIN_PREFIX + '/my_account/ssh_keys/new',
42 'my_account_ssh_keys_delete':
43 ADMIN_PREFIX + '/my_account/ssh_keys/delete',
44 }[name].format(**kwargs)
45
46 if params:
47 base_url = '{}?{}'.format(base_url, urllib.urlencode(params))
48 return base_url
49
50
51 class TestMyAccountSshKeysView(TestController):
52 INVALID_KEY = """\
53 ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDk+77sjDzVeB6vevJsuZds1iNU5
54 LANOa5CU5G/9JYIA6RYsWWMO7mbsR82IUckdqOHmxSykfR1D1TdluyIpQLrwgH5kb
55 n8FkVI8zBMCKakxowvN67B0R7b1BT4PPzW2JlOXei/m9W12ZY484VTow6/B+kf2Q8
56 cP8tmCJmKWZma5Em7OTUhvjyQVNz3v7HfeY5Hq0Ci4ECJ59hepFDabJvtAXg9XrI6
57 jvdphZTc30I4fG8+hBHzpeFxUGvSGNtXPUbwaAY8j/oHYrTpMgkj6pUEFsiKfC5zP
58 qPFR5HyKTCHW0nFUJnZsbyFT5hMiF/hZkJc9A0ZbdSvJwCRQ/g3bmdL
59 your_email@example.com
60 """
61 VALID_KEY = 'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDk+77sjDzVeB6vev' \
62 'JsuZds1iNU5LANOa5CU5G/9JYIA6RYsWWMO7mbsR82IUckdqOHmxSy' \
63 'kfR1D1TdluyIpQLrwgH5kbn8FkVI8zBMCKakxowvN67B0R7b1BT4PP' \
64 'zW2JlOXei/m9W12ZY484VTow6/B+kf2Q8cP8tmCJmKWZma5Em7OTUh' \
65 'vjyQVNz3v7HfeY5Hq0Ci4ECJ59hepFDabJvtAXg9XrI6jvdphZTc30' \
66 'I4fG8+hBHzpeFxUGvSGNtXPUbwaAY8j/oHYrTpMgkj6pUEFsiKfC5zPq' \
67 'PFR5HyKTCHW0nFUJnZsbyFT5hMiF/hZkJc9A0ZbdSvJwCRQ/g3bmdL ' \
68 'your_email@example.com'
69
70 def test_add_ssh_key_error(self, user_util):
71 user = user_util.create_user(password='qweqwe')
72 self.log_user(user.username, 'qweqwe')
73
74 key_data = self.INVALID_KEY
75
76 desc = 'MY SSH KEY'
77 response = self.app.post(
78 route_path('my_account_ssh_keys_add'),
79 {'description': desc, 'key_data': key_data,
80 'csrf_token': self.csrf_token})
81 assert_session_flash(response, 'An error occurred during ssh '
82 'key saving: Unable to decode the key')
83
84 def test_ssh_key_duplicate(self, user_util):
85 user = user_util.create_user(password='qweqwe')
86 self.log_user(user.username, 'qweqwe')
87 key_data = self.VALID_KEY
88
89 desc = 'MY SSH KEY'
90 response = self.app.post(
91 route_path('my_account_ssh_keys_add'),
92 {'description': desc, 'key_data': key_data,
93 'csrf_token': self.csrf_token})
94 assert_session_flash(response, 'Ssh Key successfully created')
95 response.follow() # flush session flash
96
97 # add the same key AGAIN
98 desc = 'MY SSH KEY'
99 response = self.app.post(
100 route_path('my_account_ssh_keys_add'),
101 {'description': desc, 'key_data': key_data,
102 'csrf_token': self.csrf_token})
103 assert_session_flash(response, 'An error occurred during ssh key '
104 'saving: Such key already exists, '
105 'please use a different one')
106
107 def test_add_ssh_key(self, user_util):
108 user = user_util.create_user(password='qweqwe')
109 self.log_user(user.username, 'qweqwe')
110
111 key_data = self.VALID_KEY
112
113 desc = 'MY SSH KEY'
114 response = self.app.post(
115 route_path('my_account_ssh_keys_add'),
116 {'description': desc, 'key_data': key_data,
117 'csrf_token': self.csrf_token})
118 assert_session_flash(response, 'Ssh Key successfully created')
119
120 response = response.follow()
121 response.mustcontain(desc)
122
123 def test_delete_ssh_key(self, user_util):
124 user = user_util.create_user(password='qweqwe')
125 user_id = user.user_id
126 self.log_user(user.username, 'qweqwe')
127
128 key_data = self.VALID_KEY
129
130 desc = 'MY SSH KEY'
131 response = self.app.post(
132 route_path('my_account_ssh_keys_add'),
133 {'description': desc, 'key_data': key_data,
134 'csrf_token': self.csrf_token})
135 assert_session_flash(response, 'Ssh Key successfully created')
136 response = response.follow() # flush the Session flash
137
138 # now delete our key
139 keys = UserSshKeys.query().filter(UserSshKeys.user_id == user_id).all()
140 assert 1 == len(keys)
141
142 response = self.app.post(
143 route_path('my_account_ssh_keys_delete'),
144 {'del_ssh_key': keys[0].ssh_key_id,
145 'csrf_token': self.csrf_token})
146
147 assert_session_flash(response, 'Ssh key successfully deleted')
148 keys = UserSshKeys.query().filter(UserSshKeys.user_id == user_id).all()
149 assert 0 == len(keys)
150
151 def test_generate_keypair(self, user_util):
152 user = user_util.create_user(password='qweqwe')
153 self.log_user(user.username, 'qweqwe')
154
155 response = self.app.get(
156 route_path('my_account_ssh_keys_generate'))
157
158 response.mustcontain('Private key')
159 response.mustcontain('Public key')
160 response.mustcontain('-----BEGIN RSA PRIVATE KEY-----')
@@ -0,0 +1,151 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2016-2017 RhodeCode GmbH
4 #
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
21 import logging
22
23 from pyramid.httpexceptions import HTTPFound
24 from pyramid.view import view_config
25
26 from rhodecode.apps._base import BaseAppView, DataGridAppView
27 from rhodecode.apps.ssh_support import SshKeyFileChangeEvent
28 from rhodecode.events import trigger
29 from rhodecode.lib import helpers as h
30 from rhodecode.lib import audit_logger
31 from rhodecode.lib.auth import LoginRequired, NotAnonymous, CSRFRequired
32 from rhodecode.model.db import IntegrityError, UserSshKeys
33 from rhodecode.model.meta import Session
34 from rhodecode.model.ssh_key import SshKeyModel
35
36 log = logging.getLogger(__name__)
37
38
39 class MyAccountSshKeysView(BaseAppView, DataGridAppView):
40
41 def load_default_context(self):
42 c = self._get_local_tmpl_context()
43 c.user = c.auth_user.get_instance()
44 self._register_global_c(c)
45 return c
46
47 @LoginRequired()
48 @NotAnonymous()
49 @view_config(
50 route_name='my_account_ssh_keys', request_method='GET',
51 renderer='rhodecode:templates/admin/my_account/my_account.mako')
52 def my_account_ssh_keys(self):
53 _ = self.request.translate
54
55 c = self.load_default_context()
56 c.active = 'ssh_keys'
57 c.default_key = self.request.GET.get('default_key')
58 c.user_ssh_keys = SshKeyModel().get_ssh_keys(c.user.user_id)
59 return self._get_template_context(c)
60
61 @LoginRequired()
62 @NotAnonymous()
63 @view_config(
64 route_name='my_account_ssh_keys_generate', request_method='GET',
65 renderer='rhodecode:templates/admin/my_account/my_account.mako')
66 def ssh_keys_generate_keypair(self):
67 _ = self.request.translate
68 c = self.load_default_context()
69
70 c.active = 'ssh_keys_generate'
71 comment = 'RhodeCode-SSH {}'.format(c.user.email or '')
72 c.private, c.public = SshKeyModel().generate_keypair(comment=comment)
73 c.target_form_url = h.route_path(
74 'my_account_ssh_keys', _query=dict(default_key=c.public))
75 return self._get_template_context(c)
76
77 @LoginRequired()
78 @NotAnonymous()
79 @CSRFRequired()
80 @view_config(
81 route_name='my_account_ssh_keys_add', request_method='POST',)
82 def my_account_ssh_keys_add(self):
83 _ = self.request.translate
84 c = self.load_default_context()
85
86 user_data = c.user.get_api_data()
87 key_data = self.request.POST.get('key_data')
88 description = self.request.POST.get('description')
89
90 try:
91 if not key_data:
92 raise ValueError('Please add a valid public key')
93
94 key = SshKeyModel().parse_key(key_data.strip())
95 fingerprint = key.hash_md5()
96
97 ssh_key = SshKeyModel().create(
98 c.user.user_id, fingerprint, key_data, description)
99 ssh_key_data = ssh_key.get_api_data()
100
101 audit_logger.store_web(
102 'user.edit.ssh_key.add', action_data={
103 'data': {'ssh_key': ssh_key_data, 'user': user_data}},
104 user=self._rhodecode_user, )
105 Session().commit()
106
107 # Trigger an event on change of keys.
108 trigger(SshKeyFileChangeEvent(), self.request.registry)
109
110 h.flash(_("Ssh Key successfully created"), category='success')
111
112 except IntegrityError:
113 log.exception("Exception during ssh key saving")
114 h.flash(_('An error occurred during ssh key saving: {}').format(
115 'Such key already exists, please use a different one'),
116 category='error')
117 except Exception as e:
118 log.exception("Exception during ssh key saving")
119 h.flash(_('An error occurred during ssh key saving: {}').format(e),
120 category='error')
121
122 return HTTPFound(h.route_path('my_account_ssh_keys'))
123
124 @LoginRequired()
125 @NotAnonymous()
126 @CSRFRequired()
127 @view_config(
128 route_name='my_account_ssh_keys_delete', request_method='POST')
129 def my_account_ssh_keys_delete(self):
130 _ = self.request.translate
131 c = self.load_default_context()
132
133 user_data = c.user.get_api_data()
134
135 del_ssh_key = self.request.POST.get('del_ssh_key')
136
137 if del_ssh_key:
138 ssh_key = UserSshKeys.get_or_404(del_ssh_key)
139 ssh_key_data = ssh_key.get_api_data()
140
141 SshKeyModel().delete(del_ssh_key, c.user.user_id)
142 audit_logger.store_web(
143 'user.edit.ssh_key.delete', action_data={
144 'data': {'ssh_key': ssh_key_data, 'user': user_data}},
145 user=self._rhodecode_user,)
146 Session().commit()
147 # Trigger an event on change of keys.
148 trigger(SshKeyFileChangeEvent(), self.request.registry)
149 h.flash(_("Ssh key successfully deleted"), category='success')
150
151 return HTTPFound(h.route_path('my_account_ssh_keys'))
@@ -0,0 +1,78 b''
1 <div class="panel panel-default">
2 <div class="panel-heading">
3 <h3 class="panel-title">${_('SSH Keys')}</h3>
4 </div>
5 <div class="panel-body">
6 <div class="sshkeys_wrap">
7 <table class="rctable ssh_keys">
8 <tr>
9 <th>${_('Fingerprint')}</th>
10 <th>${_('Description')}</th>
11 <th>${_('Created')}</th>
12 <th>${_('Action')}</th>
13 </tr>
14 %if c.user_ssh_keys:
15 %for ssh_key in c.user_ssh_keys:
16 <tr class="">
17 <td class="">
18 <code>${ssh_key.ssh_key_fingerprint}</code>
19 </td>
20 <td class="td-wrap">${ssh_key.description}</td>
21 <td class="td-tags">${h.format_date(ssh_key.created_on)}</td>
22
23 <td class="td-action">
24 ${h.secure_form(h.route_path('my_account_ssh_keys_delete'), method='POST', request=request)}
25 ${h.hidden('del_ssh_key', ssh_key.ssh_key_id)}
26 <button class="btn btn-link btn-danger" type="submit"
27 onclick="return confirm('${_('Confirm to remove ssh key %s') % ssh_key.ssh_key_fingerprint}');">
28 ${_('Delete')}
29 </button>
30 ${h.end_form()}
31 </td>
32 </tr>
33 %endfor
34 %else:
35 <tr><td><div class="ip">${_('No additional ssh keys specified')}</div></td></tr>
36 %endif
37 </table>
38 </div>
39
40 <div class="user_ssh_keys">
41 ${h.secure_form(h.route_path('my_account_ssh_keys_add'), method='POST', request=request)}
42 <div class="form form-vertical">
43 <!-- fields -->
44 <div class="fields">
45 <div class="field">
46 <div class="label">
47 <label for="new_email">${_('New ssh key')}:</label>
48 </div>
49 <div class="input">
50 ${h.text('description', class_='medium', placeholder=_('Description'))}
51 <a href="${h.route_path('my_account_ssh_keys_generate')}">${_('Generate random RSA key')}</a>
52 </div>
53 </div>
54
55 <div class="field">
56 <div class="textarea text-area editor">
57 ${h.textarea('key_data',c.default_key, size=30, placeholder=_("Public key, begins with 'ssh-rsa', 'ssh-dss', 'ssh-ed25519', 'ecdsa-sha2-nistp256', 'ecdsa-sha2-nistp384', or 'ecdsa-sha2-nistp521'"))}
58 </div>
59 </div>
60
61 <div class="buttons">
62 ${h.submit('save',_('Add'),class_="btn")}
63 ${h.reset('reset',_('Reset'),class_="btn")}
64 </div>
65 </div>
66 </div>
67 ${h.end_form()}
68 </div>
69 </div>
70 </div>
71
72 <script>
73
74 $(document).ready(function(){
75
76
77 });
78 </script>
@@ -0,0 +1,2 b''
1 ## share the same template, it's very simple
2 <%include file='/admin/users/user_edit_ssh_keys_generate.mako'/> No newline at end of file
@@ -1,126 +1,140 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2016-2017 RhodeCode GmbH
3 # Copyright (C) 2016-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21
21
22 from rhodecode.apps._base import ADMIN_PREFIX
22 from rhodecode.apps._base import ADMIN_PREFIX
23
23
24
24
25 def includeme(config):
25 def includeme(config):
26
26
27 config.add_route(
27 config.add_route(
28 name='my_account_profile',
28 name='my_account_profile',
29 pattern=ADMIN_PREFIX + '/my_account/profile')
29 pattern=ADMIN_PREFIX + '/my_account/profile')
30
30
31 # my account edit details
31 # my account edit details
32 config.add_route(
32 config.add_route(
33 name='my_account_edit',
33 name='my_account_edit',
34 pattern=ADMIN_PREFIX + '/my_account/edit')
34 pattern=ADMIN_PREFIX + '/my_account/edit')
35 config.add_route(
35 config.add_route(
36 name='my_account_update',
36 name='my_account_update',
37 pattern=ADMIN_PREFIX + '/my_account/update')
37 pattern=ADMIN_PREFIX + '/my_account/update')
38
38
39 # my account password
39 # my account password
40 config.add_route(
40 config.add_route(
41 name='my_account_password',
41 name='my_account_password',
42 pattern=ADMIN_PREFIX + '/my_account/password')
42 pattern=ADMIN_PREFIX + '/my_account/password')
43
43
44 config.add_route(
44 config.add_route(
45 name='my_account_password_update',
45 name='my_account_password_update',
46 pattern=ADMIN_PREFIX + '/my_account/password/update')
46 pattern=ADMIN_PREFIX + '/my_account/password/update')
47
47
48 # my account tokens
48 # my account tokens
49 config.add_route(
49 config.add_route(
50 name='my_account_auth_tokens',
50 name='my_account_auth_tokens',
51 pattern=ADMIN_PREFIX + '/my_account/auth_tokens')
51 pattern=ADMIN_PREFIX + '/my_account/auth_tokens')
52 config.add_route(
52 config.add_route(
53 name='my_account_auth_tokens_add',
53 name='my_account_auth_tokens_add',
54 pattern=ADMIN_PREFIX + '/my_account/auth_tokens/new')
54 pattern=ADMIN_PREFIX + '/my_account/auth_tokens/new')
55 config.add_route(
55 config.add_route(
56 name='my_account_auth_tokens_delete',
56 name='my_account_auth_tokens_delete',
57 pattern=ADMIN_PREFIX + '/my_account/auth_tokens/delete')
57 pattern=ADMIN_PREFIX + '/my_account/auth_tokens/delete')
58
58
59 # my account ssh keys
60 config.add_route(
61 name='my_account_ssh_keys',
62 pattern=ADMIN_PREFIX + '/my_account/ssh_keys')
63 config.add_route(
64 name='my_account_ssh_keys_generate',
65 pattern=ADMIN_PREFIX + '/my_account/ssh_keys/generate')
66 config.add_route(
67 name='my_account_ssh_keys_add',
68 pattern=ADMIN_PREFIX + '/my_account/ssh_keys/new')
69 config.add_route(
70 name='my_account_ssh_keys_delete',
71 pattern=ADMIN_PREFIX + '/my_account/ssh_keys/delete')
72
59 # my account emails
73 # my account emails
60 config.add_route(
74 config.add_route(
61 name='my_account_emails',
75 name='my_account_emails',
62 pattern=ADMIN_PREFIX + '/my_account/emails')
76 pattern=ADMIN_PREFIX + '/my_account/emails')
63 config.add_route(
77 config.add_route(
64 name='my_account_emails_add',
78 name='my_account_emails_add',
65 pattern=ADMIN_PREFIX + '/my_account/emails/new')
79 pattern=ADMIN_PREFIX + '/my_account/emails/new')
66 config.add_route(
80 config.add_route(
67 name='my_account_emails_delete',
81 name='my_account_emails_delete',
68 pattern=ADMIN_PREFIX + '/my_account/emails/delete')
82 pattern=ADMIN_PREFIX + '/my_account/emails/delete')
69
83
70 config.add_route(
84 config.add_route(
71 name='my_account_repos',
85 name='my_account_repos',
72 pattern=ADMIN_PREFIX + '/my_account/repos')
86 pattern=ADMIN_PREFIX + '/my_account/repos')
73
87
74 config.add_route(
88 config.add_route(
75 name='my_account_watched',
89 name='my_account_watched',
76 pattern=ADMIN_PREFIX + '/my_account/watched')
90 pattern=ADMIN_PREFIX + '/my_account/watched')
77
91
78 config.add_route(
92 config.add_route(
79 name='my_account_perms',
93 name='my_account_perms',
80 pattern=ADMIN_PREFIX + '/my_account/perms')
94 pattern=ADMIN_PREFIX + '/my_account/perms')
81
95
82 config.add_route(
96 config.add_route(
83 name='my_account_notifications',
97 name='my_account_notifications',
84 pattern=ADMIN_PREFIX + '/my_account/notifications')
98 pattern=ADMIN_PREFIX + '/my_account/notifications')
85
99
86 config.add_route(
100 config.add_route(
87 name='my_account_notifications_toggle_visibility',
101 name='my_account_notifications_toggle_visibility',
88 pattern=ADMIN_PREFIX + '/my_account/toggle_visibility')
102 pattern=ADMIN_PREFIX + '/my_account/toggle_visibility')
89
103
90 # my account pull requests
104 # my account pull requests
91 config.add_route(
105 config.add_route(
92 name='my_account_pullrequests',
106 name='my_account_pullrequests',
93 pattern=ADMIN_PREFIX + '/my_account/pull_requests')
107 pattern=ADMIN_PREFIX + '/my_account/pull_requests')
94 config.add_route(
108 config.add_route(
95 name='my_account_pullrequests_data',
109 name='my_account_pullrequests_data',
96 pattern=ADMIN_PREFIX + '/my_account/pull_requests/data')
110 pattern=ADMIN_PREFIX + '/my_account/pull_requests/data')
97
111
98 # notifications
112 # notifications
99 config.add_route(
113 config.add_route(
100 name='notifications_show_all',
114 name='notifications_show_all',
101 pattern=ADMIN_PREFIX + '/notifications')
115 pattern=ADMIN_PREFIX + '/notifications')
102
116
103 # notifications
117 # notifications
104 config.add_route(
118 config.add_route(
105 name='notifications_mark_all_read',
119 name='notifications_mark_all_read',
106 pattern=ADMIN_PREFIX + '/notifications/mark_all_read')
120 pattern=ADMIN_PREFIX + '/notifications/mark_all_read')
107
121
108 config.add_route(
122 config.add_route(
109 name='notifications_show',
123 name='notifications_show',
110 pattern=ADMIN_PREFIX + '/notifications/{notification_id}')
124 pattern=ADMIN_PREFIX + '/notifications/{notification_id}')
111
125
112 config.add_route(
126 config.add_route(
113 name='notifications_update',
127 name='notifications_update',
114 pattern=ADMIN_PREFIX + '/notifications/{notification_id}/update')
128 pattern=ADMIN_PREFIX + '/notifications/{notification_id}/update')
115
129
116 config.add_route(
130 config.add_route(
117 name='notifications_delete',
131 name='notifications_delete',
118 pattern=ADMIN_PREFIX + '/notifications/{notification_id}/delete')
132 pattern=ADMIN_PREFIX + '/notifications/{notification_id}/delete')
119
133
120 # channelstream test
134 # channelstream test
121 config.add_route(
135 config.add_route(
122 name='my_account_notifications_test_channelstream',
136 name='my_account_notifications_test_channelstream',
123 pattern=ADMIN_PREFIX + '/my_account/test_channelstream')
137 pattern=ADMIN_PREFIX + '/my_account/test_channelstream')
124
138
125 # Scan module for configuration decorators.
139 # Scan module for configuration decorators.
126 config.scan('.views', ignore='.tests')
140 config.scan('.views', ignore='.tests')
@@ -1,4190 +1,4191 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 Database Models for RhodeCode Enterprise
22 Database Models for RhodeCode Enterprise
23 """
23 """
24
24
25 import re
25 import re
26 import os
26 import os
27 import time
27 import time
28 import hashlib
28 import hashlib
29 import logging
29 import logging
30 import datetime
30 import datetime
31 import warnings
31 import warnings
32 import ipaddress
32 import ipaddress
33 import functools
33 import functools
34 import traceback
34 import traceback
35 import collections
35 import collections
36
36
37
37
38 from sqlalchemy import *
38 from sqlalchemy import *
39 from sqlalchemy.ext.declarative import declared_attr
39 from sqlalchemy.ext.declarative import declared_attr
40 from sqlalchemy.ext.hybrid import hybrid_property
40 from sqlalchemy.ext.hybrid import hybrid_property
41 from sqlalchemy.orm import (
41 from sqlalchemy.orm import (
42 relationship, joinedload, class_mapper, validates, aliased)
42 relationship, joinedload, class_mapper, validates, aliased)
43 from sqlalchemy.sql.expression import true
43 from sqlalchemy.sql.expression import true
44 from sqlalchemy.sql.functions import coalesce, count # noqa
44 from sqlalchemy.sql.functions import coalesce, count # noqa
45 from sqlalchemy.exc import IntegrityError # noqa
45 from beaker.cache import cache_region
46 from beaker.cache import cache_region
46 from zope.cachedescriptors.property import Lazy as LazyProperty
47 from zope.cachedescriptors.property import Lazy as LazyProperty
47
48
48 from pyramid.threadlocal import get_current_request
49 from pyramid.threadlocal import get_current_request
49
50
50 from rhodecode.translation import _
51 from rhodecode.translation import _
51 from rhodecode.lib.vcs import get_vcs_instance
52 from rhodecode.lib.vcs import get_vcs_instance
52 from rhodecode.lib.vcs.backends.base import EmptyCommit, Reference
53 from rhodecode.lib.vcs.backends.base import EmptyCommit, Reference
53 from rhodecode.lib.utils2 import (
54 from rhodecode.lib.utils2 import (
54 str2bool, safe_str, get_commit_safe, safe_unicode, md5_safe,
55 str2bool, safe_str, get_commit_safe, safe_unicode, md5_safe,
55 time_to_datetime, aslist, Optional, safe_int, get_clone_url, AttributeDict,
56 time_to_datetime, aslist, Optional, safe_int, get_clone_url, AttributeDict,
56 glob2re, StrictAttributeDict, cleaned_uri)
57 glob2re, StrictAttributeDict, cleaned_uri)
57 from rhodecode.lib.jsonalchemy import MutationObj, MutationList, JsonType
58 from rhodecode.lib.jsonalchemy import MutationObj, MutationList, JsonType
58 from rhodecode.lib.ext_json import json
59 from rhodecode.lib.ext_json import json
59 from rhodecode.lib.caching_query import FromCache
60 from rhodecode.lib.caching_query import FromCache
60 from rhodecode.lib.encrypt import AESCipher
61 from rhodecode.lib.encrypt import AESCipher
61
62
62 from rhodecode.model.meta import Base, Session
63 from rhodecode.model.meta import Base, Session
63
64
64 URL_SEP = '/'
65 URL_SEP = '/'
65 log = logging.getLogger(__name__)
66 log = logging.getLogger(__name__)
66
67
67 # =============================================================================
68 # =============================================================================
68 # BASE CLASSES
69 # BASE CLASSES
69 # =============================================================================
70 # =============================================================================
70
71
71 # this is propagated from .ini file rhodecode.encrypted_values.secret or
72 # this is propagated from .ini file rhodecode.encrypted_values.secret or
72 # beaker.session.secret if first is not set.
73 # beaker.session.secret if first is not set.
73 # and initialized at environment.py
74 # and initialized at environment.py
74 ENCRYPTION_KEY = None
75 ENCRYPTION_KEY = None
75
76
76 # used to sort permissions by types, '#' used here is not allowed to be in
77 # used to sort permissions by types, '#' used here is not allowed to be in
77 # usernames, and it's very early in sorted string.printable table.
78 # usernames, and it's very early in sorted string.printable table.
78 PERMISSION_TYPE_SORT = {
79 PERMISSION_TYPE_SORT = {
79 'admin': '####',
80 'admin': '####',
80 'write': '###',
81 'write': '###',
81 'read': '##',
82 'read': '##',
82 'none': '#',
83 'none': '#',
83 }
84 }
84
85
85
86
86 def display_sort(obj):
87 def display_sort(obj):
87 """
88 """
88 Sort function used to sort permissions in .permissions() function of
89 Sort function used to sort permissions in .permissions() function of
89 Repository, RepoGroup, UserGroup. Also it put the default user in front
90 Repository, RepoGroup, UserGroup. Also it put the default user in front
90 of all other resources
91 of all other resources
91 """
92 """
92
93
93 if obj.username == User.DEFAULT_USER:
94 if obj.username == User.DEFAULT_USER:
94 return '#####'
95 return '#####'
95 prefix = PERMISSION_TYPE_SORT.get(obj.permission.split('.')[-1], '')
96 prefix = PERMISSION_TYPE_SORT.get(obj.permission.split('.')[-1], '')
96 return prefix + obj.username
97 return prefix + obj.username
97
98
98
99
99 def _hash_key(k):
100 def _hash_key(k):
100 return md5_safe(k)
101 return md5_safe(k)
101
102
102
103
103 def in_filter_generator(qry, items, limit=500):
104 def in_filter_generator(qry, items, limit=500):
104 """
105 """
105 Splits IN() into multiple with OR
106 Splits IN() into multiple with OR
106 e.g.::
107 e.g.::
107 cnt = Repository.query().filter(
108 cnt = Repository.query().filter(
108 or_(
109 or_(
109 *in_filter_generator(Repository.repo_id, range(100000))
110 *in_filter_generator(Repository.repo_id, range(100000))
110 )).count()
111 )).count()
111 """
112 """
112 parts = []
113 parts = []
113 for chunk in xrange(0, len(items), limit):
114 for chunk in xrange(0, len(items), limit):
114 parts.append(
115 parts.append(
115 qry.in_(items[chunk: chunk + limit])
116 qry.in_(items[chunk: chunk + limit])
116 )
117 )
117
118
118 return parts
119 return parts
119
120
120
121
121 class EncryptedTextValue(TypeDecorator):
122 class EncryptedTextValue(TypeDecorator):
122 """
123 """
123 Special column for encrypted long text data, use like::
124 Special column for encrypted long text data, use like::
124
125
125 value = Column("encrypted_value", EncryptedValue(), nullable=False)
126 value = Column("encrypted_value", EncryptedValue(), nullable=False)
126
127
127 This column is intelligent so if value is in unencrypted form it return
128 This column is intelligent so if value is in unencrypted form it return
128 unencrypted form, but on save it always encrypts
129 unencrypted form, but on save it always encrypts
129 """
130 """
130 impl = Text
131 impl = Text
131
132
132 def process_bind_param(self, value, dialect):
133 def process_bind_param(self, value, dialect):
133 if not value:
134 if not value:
134 return value
135 return value
135 if value.startswith('enc$aes$') or value.startswith('enc$aes_hmac$'):
136 if value.startswith('enc$aes$') or value.startswith('enc$aes_hmac$'):
136 # protect against double encrypting if someone manually starts
137 # protect against double encrypting if someone manually starts
137 # doing
138 # doing
138 raise ValueError('value needs to be in unencrypted format, ie. '
139 raise ValueError('value needs to be in unencrypted format, ie. '
139 'not starting with enc$aes')
140 'not starting with enc$aes')
140 return 'enc$aes_hmac$%s' % AESCipher(
141 return 'enc$aes_hmac$%s' % AESCipher(
141 ENCRYPTION_KEY, hmac=True).encrypt(value)
142 ENCRYPTION_KEY, hmac=True).encrypt(value)
142
143
143 def process_result_value(self, value, dialect):
144 def process_result_value(self, value, dialect):
144 import rhodecode
145 import rhodecode
145
146
146 if not value:
147 if not value:
147 return value
148 return value
148
149
149 parts = value.split('$', 3)
150 parts = value.split('$', 3)
150 if not len(parts) == 3:
151 if not len(parts) == 3:
151 # probably not encrypted values
152 # probably not encrypted values
152 return value
153 return value
153 else:
154 else:
154 if parts[0] != 'enc':
155 if parts[0] != 'enc':
155 # parts ok but without our header ?
156 # parts ok but without our header ?
156 return value
157 return value
157 enc_strict_mode = str2bool(rhodecode.CONFIG.get(
158 enc_strict_mode = str2bool(rhodecode.CONFIG.get(
158 'rhodecode.encrypted_values.strict') or True)
159 'rhodecode.encrypted_values.strict') or True)
159 # at that stage we know it's our encryption
160 # at that stage we know it's our encryption
160 if parts[1] == 'aes':
161 if parts[1] == 'aes':
161 decrypted_data = AESCipher(ENCRYPTION_KEY).decrypt(parts[2])
162 decrypted_data = AESCipher(ENCRYPTION_KEY).decrypt(parts[2])
162 elif parts[1] == 'aes_hmac':
163 elif parts[1] == 'aes_hmac':
163 decrypted_data = AESCipher(
164 decrypted_data = AESCipher(
164 ENCRYPTION_KEY, hmac=True,
165 ENCRYPTION_KEY, hmac=True,
165 strict_verification=enc_strict_mode).decrypt(parts[2])
166 strict_verification=enc_strict_mode).decrypt(parts[2])
166 else:
167 else:
167 raise ValueError(
168 raise ValueError(
168 'Encryption type part is wrong, must be `aes` '
169 'Encryption type part is wrong, must be `aes` '
169 'or `aes_hmac`, got `%s` instead' % (parts[1]))
170 'or `aes_hmac`, got `%s` instead' % (parts[1]))
170 return decrypted_data
171 return decrypted_data
171
172
172
173
173 class BaseModel(object):
174 class BaseModel(object):
174 """
175 """
175 Base Model for all classes
176 Base Model for all classes
176 """
177 """
177
178
178 @classmethod
179 @classmethod
179 def _get_keys(cls):
180 def _get_keys(cls):
180 """return column names for this model """
181 """return column names for this model """
181 return class_mapper(cls).c.keys()
182 return class_mapper(cls).c.keys()
182
183
183 def get_dict(self):
184 def get_dict(self):
184 """
185 """
185 return dict with keys and values corresponding
186 return dict with keys and values corresponding
186 to this model data """
187 to this model data """
187
188
188 d = {}
189 d = {}
189 for k in self._get_keys():
190 for k in self._get_keys():
190 d[k] = getattr(self, k)
191 d[k] = getattr(self, k)
191
192
192 # also use __json__() if present to get additional fields
193 # also use __json__() if present to get additional fields
193 _json_attr = getattr(self, '__json__', None)
194 _json_attr = getattr(self, '__json__', None)
194 if _json_attr:
195 if _json_attr:
195 # update with attributes from __json__
196 # update with attributes from __json__
196 if callable(_json_attr):
197 if callable(_json_attr):
197 _json_attr = _json_attr()
198 _json_attr = _json_attr()
198 for k, val in _json_attr.iteritems():
199 for k, val in _json_attr.iteritems():
199 d[k] = val
200 d[k] = val
200 return d
201 return d
201
202
202 def get_appstruct(self):
203 def get_appstruct(self):
203 """return list with keys and values tuples corresponding
204 """return list with keys and values tuples corresponding
204 to this model data """
205 to this model data """
205
206
206 l = []
207 l = []
207 for k in self._get_keys():
208 for k in self._get_keys():
208 l.append((k, getattr(self, k),))
209 l.append((k, getattr(self, k),))
209 return l
210 return l
210
211
211 def populate_obj(self, populate_dict):
212 def populate_obj(self, populate_dict):
212 """populate model with data from given populate_dict"""
213 """populate model with data from given populate_dict"""
213
214
214 for k in self._get_keys():
215 for k in self._get_keys():
215 if k in populate_dict:
216 if k in populate_dict:
216 setattr(self, k, populate_dict[k])
217 setattr(self, k, populate_dict[k])
217
218
218 @classmethod
219 @classmethod
219 def query(cls):
220 def query(cls):
220 return Session().query(cls)
221 return Session().query(cls)
221
222
222 @classmethod
223 @classmethod
223 def get(cls, id_):
224 def get(cls, id_):
224 if id_:
225 if id_:
225 return cls.query().get(id_)
226 return cls.query().get(id_)
226
227
227 @classmethod
228 @classmethod
228 def get_or_404(cls, id_):
229 def get_or_404(cls, id_):
229 from pyramid.httpexceptions import HTTPNotFound
230 from pyramid.httpexceptions import HTTPNotFound
230
231
231 try:
232 try:
232 id_ = int(id_)
233 id_ = int(id_)
233 except (TypeError, ValueError):
234 except (TypeError, ValueError):
234 raise HTTPNotFound()
235 raise HTTPNotFound()
235
236
236 res = cls.query().get(id_)
237 res = cls.query().get(id_)
237 if not res:
238 if not res:
238 raise HTTPNotFound()
239 raise HTTPNotFound()
239 return res
240 return res
240
241
241 @classmethod
242 @classmethod
242 def getAll(cls):
243 def getAll(cls):
243 # deprecated and left for backward compatibility
244 # deprecated and left for backward compatibility
244 return cls.get_all()
245 return cls.get_all()
245
246
246 @classmethod
247 @classmethod
247 def get_all(cls):
248 def get_all(cls):
248 return cls.query().all()
249 return cls.query().all()
249
250
250 @classmethod
251 @classmethod
251 def delete(cls, id_):
252 def delete(cls, id_):
252 obj = cls.query().get(id_)
253 obj = cls.query().get(id_)
253 Session().delete(obj)
254 Session().delete(obj)
254
255
255 @classmethod
256 @classmethod
256 def identity_cache(cls, session, attr_name, value):
257 def identity_cache(cls, session, attr_name, value):
257 exist_in_session = []
258 exist_in_session = []
258 for (item_cls, pkey), instance in session.identity_map.items():
259 for (item_cls, pkey), instance in session.identity_map.items():
259 if cls == item_cls and getattr(instance, attr_name) == value:
260 if cls == item_cls and getattr(instance, attr_name) == value:
260 exist_in_session.append(instance)
261 exist_in_session.append(instance)
261 if exist_in_session:
262 if exist_in_session:
262 if len(exist_in_session) == 1:
263 if len(exist_in_session) == 1:
263 return exist_in_session[0]
264 return exist_in_session[0]
264 log.exception(
265 log.exception(
265 'multiple objects with attr %s and '
266 'multiple objects with attr %s and '
266 'value %s found with same name: %r',
267 'value %s found with same name: %r',
267 attr_name, value, exist_in_session)
268 attr_name, value, exist_in_session)
268
269
269 def __repr__(self):
270 def __repr__(self):
270 if hasattr(self, '__unicode__'):
271 if hasattr(self, '__unicode__'):
271 # python repr needs to return str
272 # python repr needs to return str
272 try:
273 try:
273 return safe_str(self.__unicode__())
274 return safe_str(self.__unicode__())
274 except UnicodeDecodeError:
275 except UnicodeDecodeError:
275 pass
276 pass
276 return '<DB:%s>' % (self.__class__.__name__)
277 return '<DB:%s>' % (self.__class__.__name__)
277
278
278
279
279 class RhodeCodeSetting(Base, BaseModel):
280 class RhodeCodeSetting(Base, BaseModel):
280 __tablename__ = 'rhodecode_settings'
281 __tablename__ = 'rhodecode_settings'
281 __table_args__ = (
282 __table_args__ = (
282 UniqueConstraint('app_settings_name'),
283 UniqueConstraint('app_settings_name'),
283 {'extend_existing': True, 'mysql_engine': 'InnoDB',
284 {'extend_existing': True, 'mysql_engine': 'InnoDB',
284 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
285 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
285 )
286 )
286
287
287 SETTINGS_TYPES = {
288 SETTINGS_TYPES = {
288 'str': safe_str,
289 'str': safe_str,
289 'int': safe_int,
290 'int': safe_int,
290 'unicode': safe_unicode,
291 'unicode': safe_unicode,
291 'bool': str2bool,
292 'bool': str2bool,
292 'list': functools.partial(aslist, sep=',')
293 'list': functools.partial(aslist, sep=',')
293 }
294 }
294 DEFAULT_UPDATE_URL = 'https://rhodecode.com/api/v1/info/versions'
295 DEFAULT_UPDATE_URL = 'https://rhodecode.com/api/v1/info/versions'
295 GLOBAL_CONF_KEY = 'app_settings'
296 GLOBAL_CONF_KEY = 'app_settings'
296
297
297 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
298 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
298 app_settings_name = Column("app_settings_name", String(255), nullable=True, unique=None, default=None)
299 app_settings_name = Column("app_settings_name", String(255), nullable=True, unique=None, default=None)
299 _app_settings_value = Column("app_settings_value", String(4096), nullable=True, unique=None, default=None)
300 _app_settings_value = Column("app_settings_value", String(4096), nullable=True, unique=None, default=None)
300 _app_settings_type = Column("app_settings_type", String(255), nullable=True, unique=None, default=None)
301 _app_settings_type = Column("app_settings_type", String(255), nullable=True, unique=None, default=None)
301
302
302 def __init__(self, key='', val='', type='unicode'):
303 def __init__(self, key='', val='', type='unicode'):
303 self.app_settings_name = key
304 self.app_settings_name = key
304 self.app_settings_type = type
305 self.app_settings_type = type
305 self.app_settings_value = val
306 self.app_settings_value = val
306
307
307 @validates('_app_settings_value')
308 @validates('_app_settings_value')
308 def validate_settings_value(self, key, val):
309 def validate_settings_value(self, key, val):
309 assert type(val) == unicode
310 assert type(val) == unicode
310 return val
311 return val
311
312
312 @hybrid_property
313 @hybrid_property
313 def app_settings_value(self):
314 def app_settings_value(self):
314 v = self._app_settings_value
315 v = self._app_settings_value
315 _type = self.app_settings_type
316 _type = self.app_settings_type
316 if _type:
317 if _type:
317 _type = self.app_settings_type.split('.')[0]
318 _type = self.app_settings_type.split('.')[0]
318 # decode the encrypted value
319 # decode the encrypted value
319 if 'encrypted' in self.app_settings_type:
320 if 'encrypted' in self.app_settings_type:
320 cipher = EncryptedTextValue()
321 cipher = EncryptedTextValue()
321 v = safe_unicode(cipher.process_result_value(v, None))
322 v = safe_unicode(cipher.process_result_value(v, None))
322
323
323 converter = self.SETTINGS_TYPES.get(_type) or \
324 converter = self.SETTINGS_TYPES.get(_type) or \
324 self.SETTINGS_TYPES['unicode']
325 self.SETTINGS_TYPES['unicode']
325 return converter(v)
326 return converter(v)
326
327
327 @app_settings_value.setter
328 @app_settings_value.setter
328 def app_settings_value(self, val):
329 def app_settings_value(self, val):
329 """
330 """
330 Setter that will always make sure we use unicode in app_settings_value
331 Setter that will always make sure we use unicode in app_settings_value
331
332
332 :param val:
333 :param val:
333 """
334 """
334 val = safe_unicode(val)
335 val = safe_unicode(val)
335 # encode the encrypted value
336 # encode the encrypted value
336 if 'encrypted' in self.app_settings_type:
337 if 'encrypted' in self.app_settings_type:
337 cipher = EncryptedTextValue()
338 cipher = EncryptedTextValue()
338 val = safe_unicode(cipher.process_bind_param(val, None))
339 val = safe_unicode(cipher.process_bind_param(val, None))
339 self._app_settings_value = val
340 self._app_settings_value = val
340
341
341 @hybrid_property
342 @hybrid_property
342 def app_settings_type(self):
343 def app_settings_type(self):
343 return self._app_settings_type
344 return self._app_settings_type
344
345
345 @app_settings_type.setter
346 @app_settings_type.setter
346 def app_settings_type(self, val):
347 def app_settings_type(self, val):
347 if val.split('.')[0] not in self.SETTINGS_TYPES:
348 if val.split('.')[0] not in self.SETTINGS_TYPES:
348 raise Exception('type must be one of %s got %s'
349 raise Exception('type must be one of %s got %s'
349 % (self.SETTINGS_TYPES.keys(), val))
350 % (self.SETTINGS_TYPES.keys(), val))
350 self._app_settings_type = val
351 self._app_settings_type = val
351
352
352 def __unicode__(self):
353 def __unicode__(self):
353 return u"<%s('%s:%s[%s]')>" % (
354 return u"<%s('%s:%s[%s]')>" % (
354 self.__class__.__name__,
355 self.__class__.__name__,
355 self.app_settings_name, self.app_settings_value,
356 self.app_settings_name, self.app_settings_value,
356 self.app_settings_type
357 self.app_settings_type
357 )
358 )
358
359
359
360
360 class RhodeCodeUi(Base, BaseModel):
361 class RhodeCodeUi(Base, BaseModel):
361 __tablename__ = 'rhodecode_ui'
362 __tablename__ = 'rhodecode_ui'
362 __table_args__ = (
363 __table_args__ = (
363 UniqueConstraint('ui_key'),
364 UniqueConstraint('ui_key'),
364 {'extend_existing': True, 'mysql_engine': 'InnoDB',
365 {'extend_existing': True, 'mysql_engine': 'InnoDB',
365 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
366 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
366 )
367 )
367
368
368 HOOK_REPO_SIZE = 'changegroup.repo_size'
369 HOOK_REPO_SIZE = 'changegroup.repo_size'
369 # HG
370 # HG
370 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
371 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
371 HOOK_PULL = 'outgoing.pull_logger'
372 HOOK_PULL = 'outgoing.pull_logger'
372 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
373 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
373 HOOK_PRETX_PUSH = 'pretxnchangegroup.pre_push'
374 HOOK_PRETX_PUSH = 'pretxnchangegroup.pre_push'
374 HOOK_PUSH = 'changegroup.push_logger'
375 HOOK_PUSH = 'changegroup.push_logger'
375 HOOK_PUSH_KEY = 'pushkey.key_push'
376 HOOK_PUSH_KEY = 'pushkey.key_push'
376
377
377 # TODO: johbo: Unify way how hooks are configured for git and hg,
378 # TODO: johbo: Unify way how hooks are configured for git and hg,
378 # git part is currently hardcoded.
379 # git part is currently hardcoded.
379
380
380 # SVN PATTERNS
381 # SVN PATTERNS
381 SVN_BRANCH_ID = 'vcs_svn_branch'
382 SVN_BRANCH_ID = 'vcs_svn_branch'
382 SVN_TAG_ID = 'vcs_svn_tag'
383 SVN_TAG_ID = 'vcs_svn_tag'
383
384
384 ui_id = Column(
385 ui_id = Column(
385 "ui_id", Integer(), nullable=False, unique=True, default=None,
386 "ui_id", Integer(), nullable=False, unique=True, default=None,
386 primary_key=True)
387 primary_key=True)
387 ui_section = Column(
388 ui_section = Column(
388 "ui_section", String(255), nullable=True, unique=None, default=None)
389 "ui_section", String(255), nullable=True, unique=None, default=None)
389 ui_key = Column(
390 ui_key = Column(
390 "ui_key", String(255), nullable=True, unique=None, default=None)
391 "ui_key", String(255), nullable=True, unique=None, default=None)
391 ui_value = Column(
392 ui_value = Column(
392 "ui_value", String(255), nullable=True, unique=None, default=None)
393 "ui_value", String(255), nullable=True, unique=None, default=None)
393 ui_active = Column(
394 ui_active = Column(
394 "ui_active", Boolean(), nullable=True, unique=None, default=True)
395 "ui_active", Boolean(), nullable=True, unique=None, default=True)
395
396
396 def __repr__(self):
397 def __repr__(self):
397 return '<%s[%s]%s=>%s]>' % (self.__class__.__name__, self.ui_section,
398 return '<%s[%s]%s=>%s]>' % (self.__class__.__name__, self.ui_section,
398 self.ui_key, self.ui_value)
399 self.ui_key, self.ui_value)
399
400
400
401
401 class RepoRhodeCodeSetting(Base, BaseModel):
402 class RepoRhodeCodeSetting(Base, BaseModel):
402 __tablename__ = 'repo_rhodecode_settings'
403 __tablename__ = 'repo_rhodecode_settings'
403 __table_args__ = (
404 __table_args__ = (
404 UniqueConstraint(
405 UniqueConstraint(
405 'app_settings_name', 'repository_id',
406 'app_settings_name', 'repository_id',
406 name='uq_repo_rhodecode_setting_name_repo_id'),
407 name='uq_repo_rhodecode_setting_name_repo_id'),
407 {'extend_existing': True, 'mysql_engine': 'InnoDB',
408 {'extend_existing': True, 'mysql_engine': 'InnoDB',
408 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
409 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
409 )
410 )
410
411
411 repository_id = Column(
412 repository_id = Column(
412 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
413 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
413 nullable=False)
414 nullable=False)
414 app_settings_id = Column(
415 app_settings_id = Column(
415 "app_settings_id", Integer(), nullable=False, unique=True,
416 "app_settings_id", Integer(), nullable=False, unique=True,
416 default=None, primary_key=True)
417 default=None, primary_key=True)
417 app_settings_name = Column(
418 app_settings_name = Column(
418 "app_settings_name", String(255), nullable=True, unique=None,
419 "app_settings_name", String(255), nullable=True, unique=None,
419 default=None)
420 default=None)
420 _app_settings_value = Column(
421 _app_settings_value = Column(
421 "app_settings_value", String(4096), nullable=True, unique=None,
422 "app_settings_value", String(4096), nullable=True, unique=None,
422 default=None)
423 default=None)
423 _app_settings_type = Column(
424 _app_settings_type = Column(
424 "app_settings_type", String(255), nullable=True, unique=None,
425 "app_settings_type", String(255), nullable=True, unique=None,
425 default=None)
426 default=None)
426
427
427 repository = relationship('Repository')
428 repository = relationship('Repository')
428
429
429 def __init__(self, repository_id, key='', val='', type='unicode'):
430 def __init__(self, repository_id, key='', val='', type='unicode'):
430 self.repository_id = repository_id
431 self.repository_id = repository_id
431 self.app_settings_name = key
432 self.app_settings_name = key
432 self.app_settings_type = type
433 self.app_settings_type = type
433 self.app_settings_value = val
434 self.app_settings_value = val
434
435
435 @validates('_app_settings_value')
436 @validates('_app_settings_value')
436 def validate_settings_value(self, key, val):
437 def validate_settings_value(self, key, val):
437 assert type(val) == unicode
438 assert type(val) == unicode
438 return val
439 return val
439
440
440 @hybrid_property
441 @hybrid_property
441 def app_settings_value(self):
442 def app_settings_value(self):
442 v = self._app_settings_value
443 v = self._app_settings_value
443 type_ = self.app_settings_type
444 type_ = self.app_settings_type
444 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
445 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
445 converter = SETTINGS_TYPES.get(type_) or SETTINGS_TYPES['unicode']
446 converter = SETTINGS_TYPES.get(type_) or SETTINGS_TYPES['unicode']
446 return converter(v)
447 return converter(v)
447
448
448 @app_settings_value.setter
449 @app_settings_value.setter
449 def app_settings_value(self, val):
450 def app_settings_value(self, val):
450 """
451 """
451 Setter that will always make sure we use unicode in app_settings_value
452 Setter that will always make sure we use unicode in app_settings_value
452
453
453 :param val:
454 :param val:
454 """
455 """
455 self._app_settings_value = safe_unicode(val)
456 self._app_settings_value = safe_unicode(val)
456
457
457 @hybrid_property
458 @hybrid_property
458 def app_settings_type(self):
459 def app_settings_type(self):
459 return self._app_settings_type
460 return self._app_settings_type
460
461
461 @app_settings_type.setter
462 @app_settings_type.setter
462 def app_settings_type(self, val):
463 def app_settings_type(self, val):
463 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
464 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
464 if val not in SETTINGS_TYPES:
465 if val not in SETTINGS_TYPES:
465 raise Exception('type must be one of %s got %s'
466 raise Exception('type must be one of %s got %s'
466 % (SETTINGS_TYPES.keys(), val))
467 % (SETTINGS_TYPES.keys(), val))
467 self._app_settings_type = val
468 self._app_settings_type = val
468
469
469 def __unicode__(self):
470 def __unicode__(self):
470 return u"<%s('%s:%s:%s[%s]')>" % (
471 return u"<%s('%s:%s:%s[%s]')>" % (
471 self.__class__.__name__, self.repository.repo_name,
472 self.__class__.__name__, self.repository.repo_name,
472 self.app_settings_name, self.app_settings_value,
473 self.app_settings_name, self.app_settings_value,
473 self.app_settings_type
474 self.app_settings_type
474 )
475 )
475
476
476
477
477 class RepoRhodeCodeUi(Base, BaseModel):
478 class RepoRhodeCodeUi(Base, BaseModel):
478 __tablename__ = 'repo_rhodecode_ui'
479 __tablename__ = 'repo_rhodecode_ui'
479 __table_args__ = (
480 __table_args__ = (
480 UniqueConstraint(
481 UniqueConstraint(
481 'repository_id', 'ui_section', 'ui_key',
482 'repository_id', 'ui_section', 'ui_key',
482 name='uq_repo_rhodecode_ui_repository_id_section_key'),
483 name='uq_repo_rhodecode_ui_repository_id_section_key'),
483 {'extend_existing': True, 'mysql_engine': 'InnoDB',
484 {'extend_existing': True, 'mysql_engine': 'InnoDB',
484 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
485 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
485 )
486 )
486
487
487 repository_id = Column(
488 repository_id = Column(
488 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
489 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
489 nullable=False)
490 nullable=False)
490 ui_id = Column(
491 ui_id = Column(
491 "ui_id", Integer(), nullable=False, unique=True, default=None,
492 "ui_id", Integer(), nullable=False, unique=True, default=None,
492 primary_key=True)
493 primary_key=True)
493 ui_section = Column(
494 ui_section = Column(
494 "ui_section", String(255), nullable=True, unique=None, default=None)
495 "ui_section", String(255), nullable=True, unique=None, default=None)
495 ui_key = Column(
496 ui_key = Column(
496 "ui_key", String(255), nullable=True, unique=None, default=None)
497 "ui_key", String(255), nullable=True, unique=None, default=None)
497 ui_value = Column(
498 ui_value = Column(
498 "ui_value", String(255), nullable=True, unique=None, default=None)
499 "ui_value", String(255), nullable=True, unique=None, default=None)
499 ui_active = Column(
500 ui_active = Column(
500 "ui_active", Boolean(), nullable=True, unique=None, default=True)
501 "ui_active", Boolean(), nullable=True, unique=None, default=True)
501
502
502 repository = relationship('Repository')
503 repository = relationship('Repository')
503
504
504 def __repr__(self):
505 def __repr__(self):
505 return '<%s[%s:%s]%s=>%s]>' % (
506 return '<%s[%s:%s]%s=>%s]>' % (
506 self.__class__.__name__, self.repository.repo_name,
507 self.__class__.__name__, self.repository.repo_name,
507 self.ui_section, self.ui_key, self.ui_value)
508 self.ui_section, self.ui_key, self.ui_value)
508
509
509
510
510 class User(Base, BaseModel):
511 class User(Base, BaseModel):
511 __tablename__ = 'users'
512 __tablename__ = 'users'
512 __table_args__ = (
513 __table_args__ = (
513 UniqueConstraint('username'), UniqueConstraint('email'),
514 UniqueConstraint('username'), UniqueConstraint('email'),
514 Index('u_username_idx', 'username'),
515 Index('u_username_idx', 'username'),
515 Index('u_email_idx', 'email'),
516 Index('u_email_idx', 'email'),
516 {'extend_existing': True, 'mysql_engine': 'InnoDB',
517 {'extend_existing': True, 'mysql_engine': 'InnoDB',
517 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
518 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
518 )
519 )
519 DEFAULT_USER = 'default'
520 DEFAULT_USER = 'default'
520 DEFAULT_USER_EMAIL = 'anonymous@rhodecode.org'
521 DEFAULT_USER_EMAIL = 'anonymous@rhodecode.org'
521 DEFAULT_GRAVATAR_URL = 'https://secure.gravatar.com/avatar/{md5email}?d=identicon&s={size}'
522 DEFAULT_GRAVATAR_URL = 'https://secure.gravatar.com/avatar/{md5email}?d=identicon&s={size}'
522
523
523 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
524 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
524 username = Column("username", String(255), nullable=True, unique=None, default=None)
525 username = Column("username", String(255), nullable=True, unique=None, default=None)
525 password = Column("password", String(255), nullable=True, unique=None, default=None)
526 password = Column("password", String(255), nullable=True, unique=None, default=None)
526 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
527 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
527 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
528 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
528 name = Column("firstname", String(255), nullable=True, unique=None, default=None)
529 name = Column("firstname", String(255), nullable=True, unique=None, default=None)
529 lastname = Column("lastname", String(255), nullable=True, unique=None, default=None)
530 lastname = Column("lastname", String(255), nullable=True, unique=None, default=None)
530 _email = Column("email", String(255), nullable=True, unique=None, default=None)
531 _email = Column("email", String(255), nullable=True, unique=None, default=None)
531 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
532 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
532 last_activity = Column('last_activity', DateTime(timezone=False), nullable=True, unique=None, default=None)
533 last_activity = Column('last_activity', DateTime(timezone=False), nullable=True, unique=None, default=None)
533
534
534 extern_type = Column("extern_type", String(255), nullable=True, unique=None, default=None)
535 extern_type = Column("extern_type", String(255), nullable=True, unique=None, default=None)
535 extern_name = Column("extern_name", String(255), nullable=True, unique=None, default=None)
536 extern_name = Column("extern_name", String(255), nullable=True, unique=None, default=None)
536 _api_key = Column("api_key", String(255), nullable=True, unique=None, default=None)
537 _api_key = Column("api_key", String(255), nullable=True, unique=None, default=None)
537 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
538 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
538 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
539 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
539 _user_data = Column("user_data", LargeBinary(), nullable=True) # JSON data
540 _user_data = Column("user_data", LargeBinary(), nullable=True) # JSON data
540
541
541 user_log = relationship('UserLog')
542 user_log = relationship('UserLog')
542 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
543 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
543
544
544 repositories = relationship('Repository')
545 repositories = relationship('Repository')
545 repository_groups = relationship('RepoGroup')
546 repository_groups = relationship('RepoGroup')
546 user_groups = relationship('UserGroup')
547 user_groups = relationship('UserGroup')
547
548
548 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
549 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
549 followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
550 followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
550
551
551 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
552 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
552 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
553 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
553 user_group_to_perm = relationship('UserUserGroupToPerm', primaryjoin='UserUserGroupToPerm.user_id==User.user_id', cascade='all')
554 user_group_to_perm = relationship('UserUserGroupToPerm', primaryjoin='UserUserGroupToPerm.user_id==User.user_id', cascade='all')
554
555
555 group_member = relationship('UserGroupMember', cascade='all')
556 group_member = relationship('UserGroupMember', cascade='all')
556
557
557 notifications = relationship('UserNotification', cascade='all')
558 notifications = relationship('UserNotification', cascade='all')
558 # notifications assigned to this user
559 # notifications assigned to this user
559 user_created_notifications = relationship('Notification', cascade='all')
560 user_created_notifications = relationship('Notification', cascade='all')
560 # comments created by this user
561 # comments created by this user
561 user_comments = relationship('ChangesetComment', cascade='all')
562 user_comments = relationship('ChangesetComment', cascade='all')
562 # user profile extra info
563 # user profile extra info
563 user_emails = relationship('UserEmailMap', cascade='all')
564 user_emails = relationship('UserEmailMap', cascade='all')
564 user_ip_map = relationship('UserIpMap', cascade='all')
565 user_ip_map = relationship('UserIpMap', cascade='all')
565 user_auth_tokens = relationship('UserApiKeys', cascade='all')
566 user_auth_tokens = relationship('UserApiKeys', cascade='all')
566 user_ssh_keys = relationship('UserSshKeys', cascade='all')
567 user_ssh_keys = relationship('UserSshKeys', cascade='all')
567
568
568 # gists
569 # gists
569 user_gists = relationship('Gist', cascade='all')
570 user_gists = relationship('Gist', cascade='all')
570 # user pull requests
571 # user pull requests
571 user_pull_requests = relationship('PullRequest', cascade='all')
572 user_pull_requests = relationship('PullRequest', cascade='all')
572 # external identities
573 # external identities
573 extenal_identities = relationship(
574 extenal_identities = relationship(
574 'ExternalIdentity',
575 'ExternalIdentity',
575 primaryjoin="User.user_id==ExternalIdentity.local_user_id",
576 primaryjoin="User.user_id==ExternalIdentity.local_user_id",
576 cascade='all')
577 cascade='all')
577
578
578 def __unicode__(self):
579 def __unicode__(self):
579 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
580 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
580 self.user_id, self.username)
581 self.user_id, self.username)
581
582
582 @hybrid_property
583 @hybrid_property
583 def email(self):
584 def email(self):
584 return self._email
585 return self._email
585
586
586 @email.setter
587 @email.setter
587 def email(self, val):
588 def email(self, val):
588 self._email = val.lower() if val else None
589 self._email = val.lower() if val else None
589
590
590 @hybrid_property
591 @hybrid_property
591 def first_name(self):
592 def first_name(self):
592 from rhodecode.lib import helpers as h
593 from rhodecode.lib import helpers as h
593 if self.name:
594 if self.name:
594 return h.escape(self.name)
595 return h.escape(self.name)
595 return self.name
596 return self.name
596
597
597 @hybrid_property
598 @hybrid_property
598 def last_name(self):
599 def last_name(self):
599 from rhodecode.lib import helpers as h
600 from rhodecode.lib import helpers as h
600 if self.lastname:
601 if self.lastname:
601 return h.escape(self.lastname)
602 return h.escape(self.lastname)
602 return self.lastname
603 return self.lastname
603
604
604 @hybrid_property
605 @hybrid_property
605 def api_key(self):
606 def api_key(self):
606 """
607 """
607 Fetch if exist an auth-token with role ALL connected to this user
608 Fetch if exist an auth-token with role ALL connected to this user
608 """
609 """
609 user_auth_token = UserApiKeys.query()\
610 user_auth_token = UserApiKeys.query()\
610 .filter(UserApiKeys.user_id == self.user_id)\
611 .filter(UserApiKeys.user_id == self.user_id)\
611 .filter(or_(UserApiKeys.expires == -1,
612 .filter(or_(UserApiKeys.expires == -1,
612 UserApiKeys.expires >= time.time()))\
613 UserApiKeys.expires >= time.time()))\
613 .filter(UserApiKeys.role == UserApiKeys.ROLE_ALL).first()
614 .filter(UserApiKeys.role == UserApiKeys.ROLE_ALL).first()
614 if user_auth_token:
615 if user_auth_token:
615 user_auth_token = user_auth_token.api_key
616 user_auth_token = user_auth_token.api_key
616
617
617 return user_auth_token
618 return user_auth_token
618
619
619 @api_key.setter
620 @api_key.setter
620 def api_key(self, val):
621 def api_key(self, val):
621 # don't allow to set API key this is deprecated for now
622 # don't allow to set API key this is deprecated for now
622 self._api_key = None
623 self._api_key = None
623
624
624 @property
625 @property
625 def reviewer_pull_requests(self):
626 def reviewer_pull_requests(self):
626 return PullRequestReviewers.query() \
627 return PullRequestReviewers.query() \
627 .options(joinedload(PullRequestReviewers.pull_request)) \
628 .options(joinedload(PullRequestReviewers.pull_request)) \
628 .filter(PullRequestReviewers.user_id == self.user_id) \
629 .filter(PullRequestReviewers.user_id == self.user_id) \
629 .all()
630 .all()
630
631
631 @property
632 @property
632 def firstname(self):
633 def firstname(self):
633 # alias for future
634 # alias for future
634 return self.name
635 return self.name
635
636
636 @property
637 @property
637 def emails(self):
638 def emails(self):
638 other = UserEmailMap.query()\
639 other = UserEmailMap.query()\
639 .filter(UserEmailMap.user == self) \
640 .filter(UserEmailMap.user == self) \
640 .order_by(UserEmailMap.email_id.asc()) \
641 .order_by(UserEmailMap.email_id.asc()) \
641 .all()
642 .all()
642 return [self.email] + [x.email for x in other]
643 return [self.email] + [x.email for x in other]
643
644
644 @property
645 @property
645 def auth_tokens(self):
646 def auth_tokens(self):
646 auth_tokens = self.get_auth_tokens()
647 auth_tokens = self.get_auth_tokens()
647 return [x.api_key for x in auth_tokens]
648 return [x.api_key for x in auth_tokens]
648
649
649 def get_auth_tokens(self):
650 def get_auth_tokens(self):
650 return UserApiKeys.query()\
651 return UserApiKeys.query()\
651 .filter(UserApiKeys.user == self)\
652 .filter(UserApiKeys.user == self)\
652 .order_by(UserApiKeys.user_api_key_id.asc())\
653 .order_by(UserApiKeys.user_api_key_id.asc())\
653 .all()
654 .all()
654
655
655 @property
656 @property
656 def feed_token(self):
657 def feed_token(self):
657 return self.get_feed_token()
658 return self.get_feed_token()
658
659
659 def get_feed_token(self):
660 def get_feed_token(self):
660 feed_tokens = UserApiKeys.query()\
661 feed_tokens = UserApiKeys.query()\
661 .filter(UserApiKeys.user == self)\
662 .filter(UserApiKeys.user == self)\
662 .filter(UserApiKeys.role == UserApiKeys.ROLE_FEED)\
663 .filter(UserApiKeys.role == UserApiKeys.ROLE_FEED)\
663 .all()
664 .all()
664 if feed_tokens:
665 if feed_tokens:
665 return feed_tokens[0].api_key
666 return feed_tokens[0].api_key
666 return 'NO_FEED_TOKEN_AVAILABLE'
667 return 'NO_FEED_TOKEN_AVAILABLE'
667
668
668 @classmethod
669 @classmethod
669 def extra_valid_auth_tokens(cls, user, role=None):
670 def extra_valid_auth_tokens(cls, user, role=None):
670 tokens = UserApiKeys.query().filter(UserApiKeys.user == user)\
671 tokens = UserApiKeys.query().filter(UserApiKeys.user == user)\
671 .filter(or_(UserApiKeys.expires == -1,
672 .filter(or_(UserApiKeys.expires == -1,
672 UserApiKeys.expires >= time.time()))
673 UserApiKeys.expires >= time.time()))
673 if role:
674 if role:
674 tokens = tokens.filter(or_(UserApiKeys.role == role,
675 tokens = tokens.filter(or_(UserApiKeys.role == role,
675 UserApiKeys.role == UserApiKeys.ROLE_ALL))
676 UserApiKeys.role == UserApiKeys.ROLE_ALL))
676 return tokens.all()
677 return tokens.all()
677
678
678 def authenticate_by_token(self, auth_token, roles=None, scope_repo_id=None):
679 def authenticate_by_token(self, auth_token, roles=None, scope_repo_id=None):
679 from rhodecode.lib import auth
680 from rhodecode.lib import auth
680
681
681 log.debug('Trying to authenticate user: %s via auth-token, '
682 log.debug('Trying to authenticate user: %s via auth-token, '
682 'and roles: %s', self, roles)
683 'and roles: %s', self, roles)
683
684
684 if not auth_token:
685 if not auth_token:
685 return False
686 return False
686
687
687 crypto_backend = auth.crypto_backend()
688 crypto_backend = auth.crypto_backend()
688
689
689 roles = (roles or []) + [UserApiKeys.ROLE_ALL]
690 roles = (roles or []) + [UserApiKeys.ROLE_ALL]
690 tokens_q = UserApiKeys.query()\
691 tokens_q = UserApiKeys.query()\
691 .filter(UserApiKeys.user_id == self.user_id)\
692 .filter(UserApiKeys.user_id == self.user_id)\
692 .filter(or_(UserApiKeys.expires == -1,
693 .filter(or_(UserApiKeys.expires == -1,
693 UserApiKeys.expires >= time.time()))
694 UserApiKeys.expires >= time.time()))
694
695
695 tokens_q = tokens_q.filter(UserApiKeys.role.in_(roles))
696 tokens_q = tokens_q.filter(UserApiKeys.role.in_(roles))
696
697
697 plain_tokens = []
698 plain_tokens = []
698 hash_tokens = []
699 hash_tokens = []
699
700
700 for token in tokens_q.all():
701 for token in tokens_q.all():
701 # verify scope first
702 # verify scope first
702 if token.repo_id:
703 if token.repo_id:
703 # token has a scope, we need to verify it
704 # token has a scope, we need to verify it
704 if scope_repo_id != token.repo_id:
705 if scope_repo_id != token.repo_id:
705 log.debug(
706 log.debug(
706 'Scope mismatch: token has a set repo scope: %s, '
707 'Scope mismatch: token has a set repo scope: %s, '
707 'and calling scope is:%s, skipping further checks',
708 'and calling scope is:%s, skipping further checks',
708 token.repo, scope_repo_id)
709 token.repo, scope_repo_id)
709 # token has a scope, and it doesn't match, skip token
710 # token has a scope, and it doesn't match, skip token
710 continue
711 continue
711
712
712 if token.api_key.startswith(crypto_backend.ENC_PREF):
713 if token.api_key.startswith(crypto_backend.ENC_PREF):
713 hash_tokens.append(token.api_key)
714 hash_tokens.append(token.api_key)
714 else:
715 else:
715 plain_tokens.append(token.api_key)
716 plain_tokens.append(token.api_key)
716
717
717 is_plain_match = auth_token in plain_tokens
718 is_plain_match = auth_token in plain_tokens
718 if is_plain_match:
719 if is_plain_match:
719 return True
720 return True
720
721
721 for hashed in hash_tokens:
722 for hashed in hash_tokens:
722 # TODO(marcink): this is expensive to calculate, but most secure
723 # TODO(marcink): this is expensive to calculate, but most secure
723 match = crypto_backend.hash_check(auth_token, hashed)
724 match = crypto_backend.hash_check(auth_token, hashed)
724 if match:
725 if match:
725 return True
726 return True
726
727
727 return False
728 return False
728
729
729 @property
730 @property
730 def ip_addresses(self):
731 def ip_addresses(self):
731 ret = UserIpMap.query().filter(UserIpMap.user == self).all()
732 ret = UserIpMap.query().filter(UserIpMap.user == self).all()
732 return [x.ip_addr for x in ret]
733 return [x.ip_addr for x in ret]
733
734
734 @property
735 @property
735 def username_and_name(self):
736 def username_and_name(self):
736 return '%s (%s %s)' % (self.username, self.first_name, self.last_name)
737 return '%s (%s %s)' % (self.username, self.first_name, self.last_name)
737
738
738 @property
739 @property
739 def username_or_name_or_email(self):
740 def username_or_name_or_email(self):
740 full_name = self.full_name if self.full_name is not ' ' else None
741 full_name = self.full_name if self.full_name is not ' ' else None
741 return self.username or full_name or self.email
742 return self.username or full_name or self.email
742
743
743 @property
744 @property
744 def full_name(self):
745 def full_name(self):
745 return '%s %s' % (self.first_name, self.last_name)
746 return '%s %s' % (self.first_name, self.last_name)
746
747
747 @property
748 @property
748 def full_name_or_username(self):
749 def full_name_or_username(self):
749 return ('%s %s' % (self.first_name, self.last_name)
750 return ('%s %s' % (self.first_name, self.last_name)
750 if (self.first_name and self.last_name) else self.username)
751 if (self.first_name and self.last_name) else self.username)
751
752
752 @property
753 @property
753 def full_contact(self):
754 def full_contact(self):
754 return '%s %s <%s>' % (self.first_name, self.last_name, self.email)
755 return '%s %s <%s>' % (self.first_name, self.last_name, self.email)
755
756
756 @property
757 @property
757 def short_contact(self):
758 def short_contact(self):
758 return '%s %s' % (self.first_name, self.last_name)
759 return '%s %s' % (self.first_name, self.last_name)
759
760
760 @property
761 @property
761 def is_admin(self):
762 def is_admin(self):
762 return self.admin
763 return self.admin
763
764
764 def AuthUser(self, **kwargs):
765 def AuthUser(self, **kwargs):
765 """
766 """
766 Returns instance of AuthUser for this user
767 Returns instance of AuthUser for this user
767 """
768 """
768 from rhodecode.lib.auth import AuthUser
769 from rhodecode.lib.auth import AuthUser
769 return AuthUser(user_id=self.user_id, username=self.username, **kwargs)
770 return AuthUser(user_id=self.user_id, username=self.username, **kwargs)
770
771
771 @hybrid_property
772 @hybrid_property
772 def user_data(self):
773 def user_data(self):
773 if not self._user_data:
774 if not self._user_data:
774 return {}
775 return {}
775
776
776 try:
777 try:
777 return json.loads(self._user_data)
778 return json.loads(self._user_data)
778 except TypeError:
779 except TypeError:
779 return {}
780 return {}
780
781
781 @user_data.setter
782 @user_data.setter
782 def user_data(self, val):
783 def user_data(self, val):
783 if not isinstance(val, dict):
784 if not isinstance(val, dict):
784 raise Exception('user_data must be dict, got %s' % type(val))
785 raise Exception('user_data must be dict, got %s' % type(val))
785 try:
786 try:
786 self._user_data = json.dumps(val)
787 self._user_data = json.dumps(val)
787 except Exception:
788 except Exception:
788 log.error(traceback.format_exc())
789 log.error(traceback.format_exc())
789
790
790 @classmethod
791 @classmethod
791 def get_by_username(cls, username, case_insensitive=False,
792 def get_by_username(cls, username, case_insensitive=False,
792 cache=False, identity_cache=False):
793 cache=False, identity_cache=False):
793 session = Session()
794 session = Session()
794
795
795 if case_insensitive:
796 if case_insensitive:
796 q = cls.query().filter(
797 q = cls.query().filter(
797 func.lower(cls.username) == func.lower(username))
798 func.lower(cls.username) == func.lower(username))
798 else:
799 else:
799 q = cls.query().filter(cls.username == username)
800 q = cls.query().filter(cls.username == username)
800
801
801 if cache:
802 if cache:
802 if identity_cache:
803 if identity_cache:
803 val = cls.identity_cache(session, 'username', username)
804 val = cls.identity_cache(session, 'username', username)
804 if val:
805 if val:
805 return val
806 return val
806 else:
807 else:
807 cache_key = "get_user_by_name_%s" % _hash_key(username)
808 cache_key = "get_user_by_name_%s" % _hash_key(username)
808 q = q.options(
809 q = q.options(
809 FromCache("sql_cache_short", cache_key))
810 FromCache("sql_cache_short", cache_key))
810
811
811 return q.scalar()
812 return q.scalar()
812
813
813 @classmethod
814 @classmethod
814 def get_by_auth_token(cls, auth_token, cache=False):
815 def get_by_auth_token(cls, auth_token, cache=False):
815 q = UserApiKeys.query()\
816 q = UserApiKeys.query()\
816 .filter(UserApiKeys.api_key == auth_token)\
817 .filter(UserApiKeys.api_key == auth_token)\
817 .filter(or_(UserApiKeys.expires == -1,
818 .filter(or_(UserApiKeys.expires == -1,
818 UserApiKeys.expires >= time.time()))
819 UserApiKeys.expires >= time.time()))
819 if cache:
820 if cache:
820 q = q.options(
821 q = q.options(
821 FromCache("sql_cache_short", "get_auth_token_%s" % auth_token))
822 FromCache("sql_cache_short", "get_auth_token_%s" % auth_token))
822
823
823 match = q.first()
824 match = q.first()
824 if match:
825 if match:
825 return match.user
826 return match.user
826
827
827 @classmethod
828 @classmethod
828 def get_by_email(cls, email, case_insensitive=False, cache=False):
829 def get_by_email(cls, email, case_insensitive=False, cache=False):
829
830
830 if case_insensitive:
831 if case_insensitive:
831 q = cls.query().filter(func.lower(cls.email) == func.lower(email))
832 q = cls.query().filter(func.lower(cls.email) == func.lower(email))
832
833
833 else:
834 else:
834 q = cls.query().filter(cls.email == email)
835 q = cls.query().filter(cls.email == email)
835
836
836 email_key = _hash_key(email)
837 email_key = _hash_key(email)
837 if cache:
838 if cache:
838 q = q.options(
839 q = q.options(
839 FromCache("sql_cache_short", "get_email_key_%s" % email_key))
840 FromCache("sql_cache_short", "get_email_key_%s" % email_key))
840
841
841 ret = q.scalar()
842 ret = q.scalar()
842 if ret is None:
843 if ret is None:
843 q = UserEmailMap.query()
844 q = UserEmailMap.query()
844 # try fetching in alternate email map
845 # try fetching in alternate email map
845 if case_insensitive:
846 if case_insensitive:
846 q = q.filter(func.lower(UserEmailMap.email) == func.lower(email))
847 q = q.filter(func.lower(UserEmailMap.email) == func.lower(email))
847 else:
848 else:
848 q = q.filter(UserEmailMap.email == email)
849 q = q.filter(UserEmailMap.email == email)
849 q = q.options(joinedload(UserEmailMap.user))
850 q = q.options(joinedload(UserEmailMap.user))
850 if cache:
851 if cache:
851 q = q.options(
852 q = q.options(
852 FromCache("sql_cache_short", "get_email_map_key_%s" % email_key))
853 FromCache("sql_cache_short", "get_email_map_key_%s" % email_key))
853 ret = getattr(q.scalar(), 'user', None)
854 ret = getattr(q.scalar(), 'user', None)
854
855
855 return ret
856 return ret
856
857
857 @classmethod
858 @classmethod
858 def get_from_cs_author(cls, author):
859 def get_from_cs_author(cls, author):
859 """
860 """
860 Tries to get User objects out of commit author string
861 Tries to get User objects out of commit author string
861
862
862 :param author:
863 :param author:
863 """
864 """
864 from rhodecode.lib.helpers import email, author_name
865 from rhodecode.lib.helpers import email, author_name
865 # Valid email in the attribute passed, see if they're in the system
866 # Valid email in the attribute passed, see if they're in the system
866 _email = email(author)
867 _email = email(author)
867 if _email:
868 if _email:
868 user = cls.get_by_email(_email, case_insensitive=True)
869 user = cls.get_by_email(_email, case_insensitive=True)
869 if user:
870 if user:
870 return user
871 return user
871 # Maybe we can match by username?
872 # Maybe we can match by username?
872 _author = author_name(author)
873 _author = author_name(author)
873 user = cls.get_by_username(_author, case_insensitive=True)
874 user = cls.get_by_username(_author, case_insensitive=True)
874 if user:
875 if user:
875 return user
876 return user
876
877
877 def update_userdata(self, **kwargs):
878 def update_userdata(self, **kwargs):
878 usr = self
879 usr = self
879 old = usr.user_data
880 old = usr.user_data
880 old.update(**kwargs)
881 old.update(**kwargs)
881 usr.user_data = old
882 usr.user_data = old
882 Session().add(usr)
883 Session().add(usr)
883 log.debug('updated userdata with ', kwargs)
884 log.debug('updated userdata with ', kwargs)
884
885
885 def update_lastlogin(self):
886 def update_lastlogin(self):
886 """Update user lastlogin"""
887 """Update user lastlogin"""
887 self.last_login = datetime.datetime.now()
888 self.last_login = datetime.datetime.now()
888 Session().add(self)
889 Session().add(self)
889 log.debug('updated user %s lastlogin', self.username)
890 log.debug('updated user %s lastlogin', self.username)
890
891
891 def update_lastactivity(self):
892 def update_lastactivity(self):
892 """Update user lastactivity"""
893 """Update user lastactivity"""
893 self.last_activity = datetime.datetime.now()
894 self.last_activity = datetime.datetime.now()
894 Session().add(self)
895 Session().add(self)
895 log.debug('updated user %s lastactivity', self.username)
896 log.debug('updated user %s lastactivity', self.username)
896
897
897 def update_password(self, new_password):
898 def update_password(self, new_password):
898 from rhodecode.lib.auth import get_crypt_password
899 from rhodecode.lib.auth import get_crypt_password
899
900
900 self.password = get_crypt_password(new_password)
901 self.password = get_crypt_password(new_password)
901 Session().add(self)
902 Session().add(self)
902
903
903 @classmethod
904 @classmethod
904 def get_first_super_admin(cls):
905 def get_first_super_admin(cls):
905 user = User.query().filter(User.admin == true()).first()
906 user = User.query().filter(User.admin == true()).first()
906 if user is None:
907 if user is None:
907 raise Exception('FATAL: Missing administrative account!')
908 raise Exception('FATAL: Missing administrative account!')
908 return user
909 return user
909
910
910 @classmethod
911 @classmethod
911 def get_all_super_admins(cls):
912 def get_all_super_admins(cls):
912 """
913 """
913 Returns all admin accounts sorted by username
914 Returns all admin accounts sorted by username
914 """
915 """
915 return User.query().filter(User.admin == true())\
916 return User.query().filter(User.admin == true())\
916 .order_by(User.username.asc()).all()
917 .order_by(User.username.asc()).all()
917
918
918 @classmethod
919 @classmethod
919 def get_default_user(cls, cache=False, refresh=False):
920 def get_default_user(cls, cache=False, refresh=False):
920 user = User.get_by_username(User.DEFAULT_USER, cache=cache)
921 user = User.get_by_username(User.DEFAULT_USER, cache=cache)
921 if user is None:
922 if user is None:
922 raise Exception('FATAL: Missing default account!')
923 raise Exception('FATAL: Missing default account!')
923 if refresh:
924 if refresh:
924 # The default user might be based on outdated state which
925 # The default user might be based on outdated state which
925 # has been loaded from the cache.
926 # has been loaded from the cache.
926 # A call to refresh() ensures that the
927 # A call to refresh() ensures that the
927 # latest state from the database is used.
928 # latest state from the database is used.
928 Session().refresh(user)
929 Session().refresh(user)
929 return user
930 return user
930
931
931 def _get_default_perms(self, user, suffix=''):
932 def _get_default_perms(self, user, suffix=''):
932 from rhodecode.model.permission import PermissionModel
933 from rhodecode.model.permission import PermissionModel
933 return PermissionModel().get_default_perms(user.user_perms, suffix)
934 return PermissionModel().get_default_perms(user.user_perms, suffix)
934
935
935 def get_default_perms(self, suffix=''):
936 def get_default_perms(self, suffix=''):
936 return self._get_default_perms(self, suffix)
937 return self._get_default_perms(self, suffix)
937
938
938 def get_api_data(self, include_secrets=False, details='full'):
939 def get_api_data(self, include_secrets=False, details='full'):
939 """
940 """
940 Common function for generating user related data for API
941 Common function for generating user related data for API
941
942
942 :param include_secrets: By default secrets in the API data will be replaced
943 :param include_secrets: By default secrets in the API data will be replaced
943 by a placeholder value to prevent exposing this data by accident. In case
944 by a placeholder value to prevent exposing this data by accident. In case
944 this data shall be exposed, set this flag to ``True``.
945 this data shall be exposed, set this flag to ``True``.
945
946
946 :param details: details can be 'basic|full' basic gives only a subset of
947 :param details: details can be 'basic|full' basic gives only a subset of
947 the available user information that includes user_id, name and emails.
948 the available user information that includes user_id, name and emails.
948 """
949 """
949 user = self
950 user = self
950 user_data = self.user_data
951 user_data = self.user_data
951 data = {
952 data = {
952 'user_id': user.user_id,
953 'user_id': user.user_id,
953 'username': user.username,
954 'username': user.username,
954 'firstname': user.name,
955 'firstname': user.name,
955 'lastname': user.lastname,
956 'lastname': user.lastname,
956 'email': user.email,
957 'email': user.email,
957 'emails': user.emails,
958 'emails': user.emails,
958 }
959 }
959 if details == 'basic':
960 if details == 'basic':
960 return data
961 return data
961
962
962 auth_token_length = 40
963 auth_token_length = 40
963 auth_token_replacement = '*' * auth_token_length
964 auth_token_replacement = '*' * auth_token_length
964
965
965 extras = {
966 extras = {
966 'auth_tokens': [auth_token_replacement],
967 'auth_tokens': [auth_token_replacement],
967 'active': user.active,
968 'active': user.active,
968 'admin': user.admin,
969 'admin': user.admin,
969 'extern_type': user.extern_type,
970 'extern_type': user.extern_type,
970 'extern_name': user.extern_name,
971 'extern_name': user.extern_name,
971 'last_login': user.last_login,
972 'last_login': user.last_login,
972 'last_activity': user.last_activity,
973 'last_activity': user.last_activity,
973 'ip_addresses': user.ip_addresses,
974 'ip_addresses': user.ip_addresses,
974 'language': user_data.get('language')
975 'language': user_data.get('language')
975 }
976 }
976 data.update(extras)
977 data.update(extras)
977
978
978 if include_secrets:
979 if include_secrets:
979 data['auth_tokens'] = user.auth_tokens
980 data['auth_tokens'] = user.auth_tokens
980 return data
981 return data
981
982
982 def __json__(self):
983 def __json__(self):
983 data = {
984 data = {
984 'full_name': self.full_name,
985 'full_name': self.full_name,
985 'full_name_or_username': self.full_name_or_username,
986 'full_name_or_username': self.full_name_or_username,
986 'short_contact': self.short_contact,
987 'short_contact': self.short_contact,
987 'full_contact': self.full_contact,
988 'full_contact': self.full_contact,
988 }
989 }
989 data.update(self.get_api_data())
990 data.update(self.get_api_data())
990 return data
991 return data
991
992
992
993
993 class UserApiKeys(Base, BaseModel):
994 class UserApiKeys(Base, BaseModel):
994 __tablename__ = 'user_api_keys'
995 __tablename__ = 'user_api_keys'
995 __table_args__ = (
996 __table_args__ = (
996 Index('uak_api_key_idx', 'api_key', unique=True),
997 Index('uak_api_key_idx', 'api_key', unique=True),
997 Index('uak_api_key_expires_idx', 'api_key', 'expires'),
998 Index('uak_api_key_expires_idx', 'api_key', 'expires'),
998 {'extend_existing': True, 'mysql_engine': 'InnoDB',
999 {'extend_existing': True, 'mysql_engine': 'InnoDB',
999 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1000 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1000 )
1001 )
1001 __mapper_args__ = {}
1002 __mapper_args__ = {}
1002
1003
1003 # ApiKey role
1004 # ApiKey role
1004 ROLE_ALL = 'token_role_all'
1005 ROLE_ALL = 'token_role_all'
1005 ROLE_HTTP = 'token_role_http'
1006 ROLE_HTTP = 'token_role_http'
1006 ROLE_VCS = 'token_role_vcs'
1007 ROLE_VCS = 'token_role_vcs'
1007 ROLE_API = 'token_role_api'
1008 ROLE_API = 'token_role_api'
1008 ROLE_FEED = 'token_role_feed'
1009 ROLE_FEED = 'token_role_feed'
1009 ROLE_PASSWORD_RESET = 'token_password_reset'
1010 ROLE_PASSWORD_RESET = 'token_password_reset'
1010
1011
1011 ROLES = [ROLE_ALL, ROLE_HTTP, ROLE_VCS, ROLE_API, ROLE_FEED]
1012 ROLES = [ROLE_ALL, ROLE_HTTP, ROLE_VCS, ROLE_API, ROLE_FEED]
1012
1013
1013 user_api_key_id = Column("user_api_key_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1014 user_api_key_id = Column("user_api_key_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1014 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1015 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1015 api_key = Column("api_key", String(255), nullable=False, unique=True)
1016 api_key = Column("api_key", String(255), nullable=False, unique=True)
1016 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
1017 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
1017 expires = Column('expires', Float(53), nullable=False)
1018 expires = Column('expires', Float(53), nullable=False)
1018 role = Column('role', String(255), nullable=True)
1019 role = Column('role', String(255), nullable=True)
1019 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1020 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1020
1021
1021 # scope columns
1022 # scope columns
1022 repo_id = Column(
1023 repo_id = Column(
1023 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
1024 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
1024 nullable=True, unique=None, default=None)
1025 nullable=True, unique=None, default=None)
1025 repo = relationship('Repository', lazy='joined')
1026 repo = relationship('Repository', lazy='joined')
1026
1027
1027 repo_group_id = Column(
1028 repo_group_id = Column(
1028 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
1029 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
1029 nullable=True, unique=None, default=None)
1030 nullable=True, unique=None, default=None)
1030 repo_group = relationship('RepoGroup', lazy='joined')
1031 repo_group = relationship('RepoGroup', lazy='joined')
1031
1032
1032 user = relationship('User', lazy='joined')
1033 user = relationship('User', lazy='joined')
1033
1034
1034 def __unicode__(self):
1035 def __unicode__(self):
1035 return u"<%s('%s')>" % (self.__class__.__name__, self.role)
1036 return u"<%s('%s')>" % (self.__class__.__name__, self.role)
1036
1037
1037 def __json__(self):
1038 def __json__(self):
1038 data = {
1039 data = {
1039 'auth_token': self.api_key,
1040 'auth_token': self.api_key,
1040 'role': self.role,
1041 'role': self.role,
1041 'scope': self.scope_humanized,
1042 'scope': self.scope_humanized,
1042 'expired': self.expired
1043 'expired': self.expired
1043 }
1044 }
1044 return data
1045 return data
1045
1046
1046 def get_api_data(self, include_secrets=False):
1047 def get_api_data(self, include_secrets=False):
1047 data = self.__json__()
1048 data = self.__json__()
1048 if include_secrets:
1049 if include_secrets:
1049 return data
1050 return data
1050 else:
1051 else:
1051 data['auth_token'] = self.token_obfuscated
1052 data['auth_token'] = self.token_obfuscated
1052 return data
1053 return data
1053
1054
1054 @hybrid_property
1055 @hybrid_property
1055 def description_safe(self):
1056 def description_safe(self):
1056 from rhodecode.lib import helpers as h
1057 from rhodecode.lib import helpers as h
1057 return h.escape(self.description)
1058 return h.escape(self.description)
1058
1059
1059 @property
1060 @property
1060 def expired(self):
1061 def expired(self):
1061 if self.expires == -1:
1062 if self.expires == -1:
1062 return False
1063 return False
1063 return time.time() > self.expires
1064 return time.time() > self.expires
1064
1065
1065 @classmethod
1066 @classmethod
1066 def _get_role_name(cls, role):
1067 def _get_role_name(cls, role):
1067 return {
1068 return {
1068 cls.ROLE_ALL: _('all'),
1069 cls.ROLE_ALL: _('all'),
1069 cls.ROLE_HTTP: _('http/web interface'),
1070 cls.ROLE_HTTP: _('http/web interface'),
1070 cls.ROLE_VCS: _('vcs (git/hg/svn protocol)'),
1071 cls.ROLE_VCS: _('vcs (git/hg/svn protocol)'),
1071 cls.ROLE_API: _('api calls'),
1072 cls.ROLE_API: _('api calls'),
1072 cls.ROLE_FEED: _('feed access'),
1073 cls.ROLE_FEED: _('feed access'),
1073 }.get(role, role)
1074 }.get(role, role)
1074
1075
1075 @property
1076 @property
1076 def role_humanized(self):
1077 def role_humanized(self):
1077 return self._get_role_name(self.role)
1078 return self._get_role_name(self.role)
1078
1079
1079 def _get_scope(self):
1080 def _get_scope(self):
1080 if self.repo:
1081 if self.repo:
1081 return repr(self.repo)
1082 return repr(self.repo)
1082 if self.repo_group:
1083 if self.repo_group:
1083 return repr(self.repo_group) + ' (recursive)'
1084 return repr(self.repo_group) + ' (recursive)'
1084 return 'global'
1085 return 'global'
1085
1086
1086 @property
1087 @property
1087 def scope_humanized(self):
1088 def scope_humanized(self):
1088 return self._get_scope()
1089 return self._get_scope()
1089
1090
1090 @property
1091 @property
1091 def token_obfuscated(self):
1092 def token_obfuscated(self):
1092 if self.api_key:
1093 if self.api_key:
1093 return self.api_key[:4] + "****"
1094 return self.api_key[:4] + "****"
1094
1095
1095
1096
1096 class UserEmailMap(Base, BaseModel):
1097 class UserEmailMap(Base, BaseModel):
1097 __tablename__ = 'user_email_map'
1098 __tablename__ = 'user_email_map'
1098 __table_args__ = (
1099 __table_args__ = (
1099 Index('uem_email_idx', 'email'),
1100 Index('uem_email_idx', 'email'),
1100 UniqueConstraint('email'),
1101 UniqueConstraint('email'),
1101 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1102 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1102 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1103 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1103 )
1104 )
1104 __mapper_args__ = {}
1105 __mapper_args__ = {}
1105
1106
1106 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1107 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1107 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1108 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1108 _email = Column("email", String(255), nullable=True, unique=False, default=None)
1109 _email = Column("email", String(255), nullable=True, unique=False, default=None)
1109 user = relationship('User', lazy='joined')
1110 user = relationship('User', lazy='joined')
1110
1111
1111 @validates('_email')
1112 @validates('_email')
1112 def validate_email(self, key, email):
1113 def validate_email(self, key, email):
1113 # check if this email is not main one
1114 # check if this email is not main one
1114 main_email = Session().query(User).filter(User.email == email).scalar()
1115 main_email = Session().query(User).filter(User.email == email).scalar()
1115 if main_email is not None:
1116 if main_email is not None:
1116 raise AttributeError('email %s is present is user table' % email)
1117 raise AttributeError('email %s is present is user table' % email)
1117 return email
1118 return email
1118
1119
1119 @hybrid_property
1120 @hybrid_property
1120 def email(self):
1121 def email(self):
1121 return self._email
1122 return self._email
1122
1123
1123 @email.setter
1124 @email.setter
1124 def email(self, val):
1125 def email(self, val):
1125 self._email = val.lower() if val else None
1126 self._email = val.lower() if val else None
1126
1127
1127
1128
1128 class UserIpMap(Base, BaseModel):
1129 class UserIpMap(Base, BaseModel):
1129 __tablename__ = 'user_ip_map'
1130 __tablename__ = 'user_ip_map'
1130 __table_args__ = (
1131 __table_args__ = (
1131 UniqueConstraint('user_id', 'ip_addr'),
1132 UniqueConstraint('user_id', 'ip_addr'),
1132 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1133 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1133 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1134 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1134 )
1135 )
1135 __mapper_args__ = {}
1136 __mapper_args__ = {}
1136
1137
1137 ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1138 ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1138 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1139 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1139 ip_addr = Column("ip_addr", String(255), nullable=True, unique=False, default=None)
1140 ip_addr = Column("ip_addr", String(255), nullable=True, unique=False, default=None)
1140 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
1141 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
1141 description = Column("description", String(10000), nullable=True, unique=None, default=None)
1142 description = Column("description", String(10000), nullable=True, unique=None, default=None)
1142 user = relationship('User', lazy='joined')
1143 user = relationship('User', lazy='joined')
1143
1144
1144 @hybrid_property
1145 @hybrid_property
1145 def description_safe(self):
1146 def description_safe(self):
1146 from rhodecode.lib import helpers as h
1147 from rhodecode.lib import helpers as h
1147 return h.escape(self.description)
1148 return h.escape(self.description)
1148
1149
1149 @classmethod
1150 @classmethod
1150 def _get_ip_range(cls, ip_addr):
1151 def _get_ip_range(cls, ip_addr):
1151 net = ipaddress.ip_network(safe_unicode(ip_addr), strict=False)
1152 net = ipaddress.ip_network(safe_unicode(ip_addr), strict=False)
1152 return [str(net.network_address), str(net.broadcast_address)]
1153 return [str(net.network_address), str(net.broadcast_address)]
1153
1154
1154 def __json__(self):
1155 def __json__(self):
1155 return {
1156 return {
1156 'ip_addr': self.ip_addr,
1157 'ip_addr': self.ip_addr,
1157 'ip_range': self._get_ip_range(self.ip_addr),
1158 'ip_range': self._get_ip_range(self.ip_addr),
1158 }
1159 }
1159
1160
1160 def __unicode__(self):
1161 def __unicode__(self):
1161 return u"<%s('user_id:%s=>%s')>" % (self.__class__.__name__,
1162 return u"<%s('user_id:%s=>%s')>" % (self.__class__.__name__,
1162 self.user_id, self.ip_addr)
1163 self.user_id, self.ip_addr)
1163
1164
1164
1165
1165 class UserSshKeys(Base, BaseModel):
1166 class UserSshKeys(Base, BaseModel):
1166 __tablename__ = 'user_ssh_keys'
1167 __tablename__ = 'user_ssh_keys'
1167 __table_args__ = (
1168 __table_args__ = (
1168 Index('usk_ssh_key_fingerprint_idx', 'ssh_key_fingerprint'),
1169 Index('usk_ssh_key_fingerprint_idx', 'ssh_key_fingerprint'),
1169
1170
1170 UniqueConstraint('ssh_key_fingerprint'),
1171 UniqueConstraint('ssh_key_fingerprint'),
1171
1172
1172 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1173 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1173 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1174 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
1174 )
1175 )
1175 __mapper_args__ = {}
1176 __mapper_args__ = {}
1176
1177
1177 ssh_key_id = Column('ssh_key_id', Integer(), nullable=False, unique=True, default=None, primary_key=True)
1178 ssh_key_id = Column('ssh_key_id', Integer(), nullable=False, unique=True, default=None, primary_key=True)
1178 ssh_key_data = Column('ssh_key_data', String(10240), nullable=False, unique=None, default=None)
1179 ssh_key_data = Column('ssh_key_data', String(10240), nullable=False, unique=None, default=None)
1179 ssh_key_fingerprint = Column('ssh_key_fingerprint', String(1024), nullable=False, unique=None, default=None)
1180 ssh_key_fingerprint = Column('ssh_key_fingerprint', String(1024), nullable=False, unique=None, default=None)
1180
1181
1181 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
1182 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
1182
1183
1183 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1184 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1184 accessed_on = Column('accessed_on', DateTime(timezone=False), nullable=True, default=None)
1185 accessed_on = Column('accessed_on', DateTime(timezone=False), nullable=True, default=None)
1185 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1186 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1186
1187
1187 user = relationship('User', lazy='joined')
1188 user = relationship('User', lazy='joined')
1188
1189
1189 def __json__(self):
1190 def __json__(self):
1190 data = {
1191 data = {
1191 'ssh_fingerprint': self.ssh_key_fingerprint,
1192 'ssh_fingerprint': self.ssh_key_fingerprint,
1192 'description': self.description,
1193 'description': self.description,
1193 'created_on': self.created_on
1194 'created_on': self.created_on
1194 }
1195 }
1195 return data
1196 return data
1196
1197
1197 def get_api_data(self):
1198 def get_api_data(self):
1198 data = self.__json__()
1199 data = self.__json__()
1199 return data
1200 return data
1200
1201
1201
1202
1202 class UserLog(Base, BaseModel):
1203 class UserLog(Base, BaseModel):
1203 __tablename__ = 'user_logs'
1204 __tablename__ = 'user_logs'
1204 __table_args__ = (
1205 __table_args__ = (
1205 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1206 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1206 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1207 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1207 )
1208 )
1208 VERSION_1 = 'v1'
1209 VERSION_1 = 'v1'
1209 VERSION_2 = 'v2'
1210 VERSION_2 = 'v2'
1210 VERSIONS = [VERSION_1, VERSION_2]
1211 VERSIONS = [VERSION_1, VERSION_2]
1211
1212
1212 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1213 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1213 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1214 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1214 username = Column("username", String(255), nullable=True, unique=None, default=None)
1215 username = Column("username", String(255), nullable=True, unique=None, default=None)
1215 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True)
1216 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True)
1216 repository_name = Column("repository_name", String(255), nullable=True, unique=None, default=None)
1217 repository_name = Column("repository_name", String(255), nullable=True, unique=None, default=None)
1217 user_ip = Column("user_ip", String(255), nullable=True, unique=None, default=None)
1218 user_ip = Column("user_ip", String(255), nullable=True, unique=None, default=None)
1218 action = Column("action", Text().with_variant(Text(1200000), 'mysql'), nullable=True, unique=None, default=None)
1219 action = Column("action", Text().with_variant(Text(1200000), 'mysql'), nullable=True, unique=None, default=None)
1219 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
1220 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
1220
1221
1221 version = Column("version", String(255), nullable=True, default=VERSION_1)
1222 version = Column("version", String(255), nullable=True, default=VERSION_1)
1222 user_data = Column('user_data_json', MutationObj.as_mutable(JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
1223 user_data = Column('user_data_json', MutationObj.as_mutable(JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
1223 action_data = Column('action_data_json', MutationObj.as_mutable(JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
1224 action_data = Column('action_data_json', MutationObj.as_mutable(JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
1224
1225
1225 def __unicode__(self):
1226 def __unicode__(self):
1226 return u"<%s('id:%s:%s')>" % (
1227 return u"<%s('id:%s:%s')>" % (
1227 self.__class__.__name__, self.repository_name, self.action)
1228 self.__class__.__name__, self.repository_name, self.action)
1228
1229
1229 def __json__(self):
1230 def __json__(self):
1230 return {
1231 return {
1231 'user_id': self.user_id,
1232 'user_id': self.user_id,
1232 'username': self.username,
1233 'username': self.username,
1233 'repository_id': self.repository_id,
1234 'repository_id': self.repository_id,
1234 'repository_name': self.repository_name,
1235 'repository_name': self.repository_name,
1235 'user_ip': self.user_ip,
1236 'user_ip': self.user_ip,
1236 'action_date': self.action_date,
1237 'action_date': self.action_date,
1237 'action': self.action,
1238 'action': self.action,
1238 }
1239 }
1239
1240
1240 @property
1241 @property
1241 def action_as_day(self):
1242 def action_as_day(self):
1242 return datetime.date(*self.action_date.timetuple()[:3])
1243 return datetime.date(*self.action_date.timetuple()[:3])
1243
1244
1244 user = relationship('User')
1245 user = relationship('User')
1245 repository = relationship('Repository', cascade='')
1246 repository = relationship('Repository', cascade='')
1246
1247
1247
1248
1248 class UserGroup(Base, BaseModel):
1249 class UserGroup(Base, BaseModel):
1249 __tablename__ = 'users_groups'
1250 __tablename__ = 'users_groups'
1250 __table_args__ = (
1251 __table_args__ = (
1251 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1252 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1252 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1253 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1253 )
1254 )
1254
1255
1255 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1256 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1256 users_group_name = Column("users_group_name", String(255), nullable=False, unique=True, default=None)
1257 users_group_name = Column("users_group_name", String(255), nullable=False, unique=True, default=None)
1257 user_group_description = Column("user_group_description", String(10000), nullable=True, unique=None, default=None)
1258 user_group_description = Column("user_group_description", String(10000), nullable=True, unique=None, default=None)
1258 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
1259 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
1259 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
1260 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
1260 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
1261 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
1261 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1262 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1262 _group_data = Column("group_data", LargeBinary(), nullable=True) # JSON data
1263 _group_data = Column("group_data", LargeBinary(), nullable=True) # JSON data
1263
1264
1264 members = relationship('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
1265 members = relationship('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
1265 users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
1266 users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
1266 users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1267 users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1267 users_group_repo_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
1268 users_group_repo_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
1268 user_user_group_to_perm = relationship('UserUserGroupToPerm', cascade='all')
1269 user_user_group_to_perm = relationship('UserUserGroupToPerm', cascade='all')
1269 user_group_user_group_to_perm = relationship('UserGroupUserGroupToPerm ', primaryjoin="UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id", cascade='all')
1270 user_group_user_group_to_perm = relationship('UserGroupUserGroupToPerm ', primaryjoin="UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id", cascade='all')
1270
1271
1271 user = relationship('User', primaryjoin="User.user_id==UserGroup.user_id")
1272 user = relationship('User', primaryjoin="User.user_id==UserGroup.user_id")
1272
1273
1273 @classmethod
1274 @classmethod
1274 def _load_group_data(cls, column):
1275 def _load_group_data(cls, column):
1275 if not column:
1276 if not column:
1276 return {}
1277 return {}
1277
1278
1278 try:
1279 try:
1279 return json.loads(column) or {}
1280 return json.loads(column) or {}
1280 except TypeError:
1281 except TypeError:
1281 return {}
1282 return {}
1282
1283
1283 @hybrid_property
1284 @hybrid_property
1284 def description_safe(self):
1285 def description_safe(self):
1285 from rhodecode.lib import helpers as h
1286 from rhodecode.lib import helpers as h
1286 return h.escape(self.description)
1287 return h.escape(self.description)
1287
1288
1288 @hybrid_property
1289 @hybrid_property
1289 def group_data(self):
1290 def group_data(self):
1290 return self._load_group_data(self._group_data)
1291 return self._load_group_data(self._group_data)
1291
1292
1292 @group_data.expression
1293 @group_data.expression
1293 def group_data(self, **kwargs):
1294 def group_data(self, **kwargs):
1294 return self._group_data
1295 return self._group_data
1295
1296
1296 @group_data.setter
1297 @group_data.setter
1297 def group_data(self, val):
1298 def group_data(self, val):
1298 try:
1299 try:
1299 self._group_data = json.dumps(val)
1300 self._group_data = json.dumps(val)
1300 except Exception:
1301 except Exception:
1301 log.error(traceback.format_exc())
1302 log.error(traceback.format_exc())
1302
1303
1303 def __unicode__(self):
1304 def __unicode__(self):
1304 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
1305 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
1305 self.users_group_id,
1306 self.users_group_id,
1306 self.users_group_name)
1307 self.users_group_name)
1307
1308
1308 @classmethod
1309 @classmethod
1309 def get_by_group_name(cls, group_name, cache=False,
1310 def get_by_group_name(cls, group_name, cache=False,
1310 case_insensitive=False):
1311 case_insensitive=False):
1311 if case_insensitive:
1312 if case_insensitive:
1312 q = cls.query().filter(func.lower(cls.users_group_name) ==
1313 q = cls.query().filter(func.lower(cls.users_group_name) ==
1313 func.lower(group_name))
1314 func.lower(group_name))
1314
1315
1315 else:
1316 else:
1316 q = cls.query().filter(cls.users_group_name == group_name)
1317 q = cls.query().filter(cls.users_group_name == group_name)
1317 if cache:
1318 if cache:
1318 q = q.options(
1319 q = q.options(
1319 FromCache("sql_cache_short", "get_group_%s" % _hash_key(group_name)))
1320 FromCache("sql_cache_short", "get_group_%s" % _hash_key(group_name)))
1320 return q.scalar()
1321 return q.scalar()
1321
1322
1322 @classmethod
1323 @classmethod
1323 def get(cls, user_group_id, cache=False):
1324 def get(cls, user_group_id, cache=False):
1324 user_group = cls.query()
1325 user_group = cls.query()
1325 if cache:
1326 if cache:
1326 user_group = user_group.options(
1327 user_group = user_group.options(
1327 FromCache("sql_cache_short", "get_users_group_%s" % user_group_id))
1328 FromCache("sql_cache_short", "get_users_group_%s" % user_group_id))
1328 return user_group.get(user_group_id)
1329 return user_group.get(user_group_id)
1329
1330
1330 def permissions(self, with_admins=True, with_owner=True):
1331 def permissions(self, with_admins=True, with_owner=True):
1331 q = UserUserGroupToPerm.query().filter(UserUserGroupToPerm.user_group == self)
1332 q = UserUserGroupToPerm.query().filter(UserUserGroupToPerm.user_group == self)
1332 q = q.options(joinedload(UserUserGroupToPerm.user_group),
1333 q = q.options(joinedload(UserUserGroupToPerm.user_group),
1333 joinedload(UserUserGroupToPerm.user),
1334 joinedload(UserUserGroupToPerm.user),
1334 joinedload(UserUserGroupToPerm.permission),)
1335 joinedload(UserUserGroupToPerm.permission),)
1335
1336
1336 # get owners and admins and permissions. We do a trick of re-writing
1337 # get owners and admins and permissions. We do a trick of re-writing
1337 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1338 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1338 # has a global reference and changing one object propagates to all
1339 # has a global reference and changing one object propagates to all
1339 # others. This means if admin is also an owner admin_row that change
1340 # others. This means if admin is also an owner admin_row that change
1340 # would propagate to both objects
1341 # would propagate to both objects
1341 perm_rows = []
1342 perm_rows = []
1342 for _usr in q.all():
1343 for _usr in q.all():
1343 usr = AttributeDict(_usr.user.get_dict())
1344 usr = AttributeDict(_usr.user.get_dict())
1344 usr.permission = _usr.permission.permission_name
1345 usr.permission = _usr.permission.permission_name
1345 perm_rows.append(usr)
1346 perm_rows.append(usr)
1346
1347
1347 # filter the perm rows by 'default' first and then sort them by
1348 # filter the perm rows by 'default' first and then sort them by
1348 # admin,write,read,none permissions sorted again alphabetically in
1349 # admin,write,read,none permissions sorted again alphabetically in
1349 # each group
1350 # each group
1350 perm_rows = sorted(perm_rows, key=display_sort)
1351 perm_rows = sorted(perm_rows, key=display_sort)
1351
1352
1352 _admin_perm = 'usergroup.admin'
1353 _admin_perm = 'usergroup.admin'
1353 owner_row = []
1354 owner_row = []
1354 if with_owner:
1355 if with_owner:
1355 usr = AttributeDict(self.user.get_dict())
1356 usr = AttributeDict(self.user.get_dict())
1356 usr.owner_row = True
1357 usr.owner_row = True
1357 usr.permission = _admin_perm
1358 usr.permission = _admin_perm
1358 owner_row.append(usr)
1359 owner_row.append(usr)
1359
1360
1360 super_admin_rows = []
1361 super_admin_rows = []
1361 if with_admins:
1362 if with_admins:
1362 for usr in User.get_all_super_admins():
1363 for usr in User.get_all_super_admins():
1363 # if this admin is also owner, don't double the record
1364 # if this admin is also owner, don't double the record
1364 if usr.user_id == owner_row[0].user_id:
1365 if usr.user_id == owner_row[0].user_id:
1365 owner_row[0].admin_row = True
1366 owner_row[0].admin_row = True
1366 else:
1367 else:
1367 usr = AttributeDict(usr.get_dict())
1368 usr = AttributeDict(usr.get_dict())
1368 usr.admin_row = True
1369 usr.admin_row = True
1369 usr.permission = _admin_perm
1370 usr.permission = _admin_perm
1370 super_admin_rows.append(usr)
1371 super_admin_rows.append(usr)
1371
1372
1372 return super_admin_rows + owner_row + perm_rows
1373 return super_admin_rows + owner_row + perm_rows
1373
1374
1374 def permission_user_groups(self):
1375 def permission_user_groups(self):
1375 q = UserGroupUserGroupToPerm.query().filter(UserGroupUserGroupToPerm.target_user_group == self)
1376 q = UserGroupUserGroupToPerm.query().filter(UserGroupUserGroupToPerm.target_user_group == self)
1376 q = q.options(joinedload(UserGroupUserGroupToPerm.user_group),
1377 q = q.options(joinedload(UserGroupUserGroupToPerm.user_group),
1377 joinedload(UserGroupUserGroupToPerm.target_user_group),
1378 joinedload(UserGroupUserGroupToPerm.target_user_group),
1378 joinedload(UserGroupUserGroupToPerm.permission),)
1379 joinedload(UserGroupUserGroupToPerm.permission),)
1379
1380
1380 perm_rows = []
1381 perm_rows = []
1381 for _user_group in q.all():
1382 for _user_group in q.all():
1382 usr = AttributeDict(_user_group.user_group.get_dict())
1383 usr = AttributeDict(_user_group.user_group.get_dict())
1383 usr.permission = _user_group.permission.permission_name
1384 usr.permission = _user_group.permission.permission_name
1384 perm_rows.append(usr)
1385 perm_rows.append(usr)
1385
1386
1386 return perm_rows
1387 return perm_rows
1387
1388
1388 def _get_default_perms(self, user_group, suffix=''):
1389 def _get_default_perms(self, user_group, suffix=''):
1389 from rhodecode.model.permission import PermissionModel
1390 from rhodecode.model.permission import PermissionModel
1390 return PermissionModel().get_default_perms(user_group.users_group_to_perm, suffix)
1391 return PermissionModel().get_default_perms(user_group.users_group_to_perm, suffix)
1391
1392
1392 def get_default_perms(self, suffix=''):
1393 def get_default_perms(self, suffix=''):
1393 return self._get_default_perms(self, suffix)
1394 return self._get_default_perms(self, suffix)
1394
1395
1395 def get_api_data(self, with_group_members=True, include_secrets=False):
1396 def get_api_data(self, with_group_members=True, include_secrets=False):
1396 """
1397 """
1397 :param include_secrets: See :meth:`User.get_api_data`, this parameter is
1398 :param include_secrets: See :meth:`User.get_api_data`, this parameter is
1398 basically forwarded.
1399 basically forwarded.
1399
1400
1400 """
1401 """
1401 user_group = self
1402 user_group = self
1402 data = {
1403 data = {
1403 'users_group_id': user_group.users_group_id,
1404 'users_group_id': user_group.users_group_id,
1404 'group_name': user_group.users_group_name,
1405 'group_name': user_group.users_group_name,
1405 'group_description': user_group.user_group_description,
1406 'group_description': user_group.user_group_description,
1406 'active': user_group.users_group_active,
1407 'active': user_group.users_group_active,
1407 'owner': user_group.user.username,
1408 'owner': user_group.user.username,
1408 'owner_email': user_group.user.email,
1409 'owner_email': user_group.user.email,
1409 }
1410 }
1410
1411
1411 if with_group_members:
1412 if with_group_members:
1412 users = []
1413 users = []
1413 for user in user_group.members:
1414 for user in user_group.members:
1414 user = user.user
1415 user = user.user
1415 users.append(user.get_api_data(include_secrets=include_secrets))
1416 users.append(user.get_api_data(include_secrets=include_secrets))
1416 data['users'] = users
1417 data['users'] = users
1417
1418
1418 return data
1419 return data
1419
1420
1420
1421
1421 class UserGroupMember(Base, BaseModel):
1422 class UserGroupMember(Base, BaseModel):
1422 __tablename__ = 'users_groups_members'
1423 __tablename__ = 'users_groups_members'
1423 __table_args__ = (
1424 __table_args__ = (
1424 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1425 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1425 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1426 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1426 )
1427 )
1427
1428
1428 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1429 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1429 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1430 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1430 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1431 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1431
1432
1432 user = relationship('User', lazy='joined')
1433 user = relationship('User', lazy='joined')
1433 users_group = relationship('UserGroup')
1434 users_group = relationship('UserGroup')
1434
1435
1435 def __init__(self, gr_id='', u_id=''):
1436 def __init__(self, gr_id='', u_id=''):
1436 self.users_group_id = gr_id
1437 self.users_group_id = gr_id
1437 self.user_id = u_id
1438 self.user_id = u_id
1438
1439
1439
1440
1440 class RepositoryField(Base, BaseModel):
1441 class RepositoryField(Base, BaseModel):
1441 __tablename__ = 'repositories_fields'
1442 __tablename__ = 'repositories_fields'
1442 __table_args__ = (
1443 __table_args__ = (
1443 UniqueConstraint('repository_id', 'field_key'), # no-multi field
1444 UniqueConstraint('repository_id', 'field_key'), # no-multi field
1444 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1445 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1445 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1446 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1446 )
1447 )
1447 PREFIX = 'ex_' # prefix used in form to not conflict with already existing fields
1448 PREFIX = 'ex_' # prefix used in form to not conflict with already existing fields
1448
1449
1449 repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1450 repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1450 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1451 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1451 field_key = Column("field_key", String(250))
1452 field_key = Column("field_key", String(250))
1452 field_label = Column("field_label", String(1024), nullable=False)
1453 field_label = Column("field_label", String(1024), nullable=False)
1453 field_value = Column("field_value", String(10000), nullable=False)
1454 field_value = Column("field_value", String(10000), nullable=False)
1454 field_desc = Column("field_desc", String(1024), nullable=False)
1455 field_desc = Column("field_desc", String(1024), nullable=False)
1455 field_type = Column("field_type", String(255), nullable=False, unique=None)
1456 field_type = Column("field_type", String(255), nullable=False, unique=None)
1456 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1457 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1457
1458
1458 repository = relationship('Repository')
1459 repository = relationship('Repository')
1459
1460
1460 @property
1461 @property
1461 def field_key_prefixed(self):
1462 def field_key_prefixed(self):
1462 return 'ex_%s' % self.field_key
1463 return 'ex_%s' % self.field_key
1463
1464
1464 @classmethod
1465 @classmethod
1465 def un_prefix_key(cls, key):
1466 def un_prefix_key(cls, key):
1466 if key.startswith(cls.PREFIX):
1467 if key.startswith(cls.PREFIX):
1467 return key[len(cls.PREFIX):]
1468 return key[len(cls.PREFIX):]
1468 return key
1469 return key
1469
1470
1470 @classmethod
1471 @classmethod
1471 def get_by_key_name(cls, key, repo):
1472 def get_by_key_name(cls, key, repo):
1472 row = cls.query()\
1473 row = cls.query()\
1473 .filter(cls.repository == repo)\
1474 .filter(cls.repository == repo)\
1474 .filter(cls.field_key == key).scalar()
1475 .filter(cls.field_key == key).scalar()
1475 return row
1476 return row
1476
1477
1477
1478
1478 class Repository(Base, BaseModel):
1479 class Repository(Base, BaseModel):
1479 __tablename__ = 'repositories'
1480 __tablename__ = 'repositories'
1480 __table_args__ = (
1481 __table_args__ = (
1481 Index('r_repo_name_idx', 'repo_name', mysql_length=255),
1482 Index('r_repo_name_idx', 'repo_name', mysql_length=255),
1482 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1483 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1483 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1484 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
1484 )
1485 )
1485 DEFAULT_CLONE_URI = '{scheme}://{user}@{netloc}/{repo}'
1486 DEFAULT_CLONE_URI = '{scheme}://{user}@{netloc}/{repo}'
1486 DEFAULT_CLONE_URI_ID = '{scheme}://{user}@{netloc}/_{repoid}'
1487 DEFAULT_CLONE_URI_ID = '{scheme}://{user}@{netloc}/_{repoid}'
1487
1488
1488 STATE_CREATED = 'repo_state_created'
1489 STATE_CREATED = 'repo_state_created'
1489 STATE_PENDING = 'repo_state_pending'
1490 STATE_PENDING = 'repo_state_pending'
1490 STATE_ERROR = 'repo_state_error'
1491 STATE_ERROR = 'repo_state_error'
1491
1492
1492 LOCK_AUTOMATIC = 'lock_auto'
1493 LOCK_AUTOMATIC = 'lock_auto'
1493 LOCK_API = 'lock_api'
1494 LOCK_API = 'lock_api'
1494 LOCK_WEB = 'lock_web'
1495 LOCK_WEB = 'lock_web'
1495 LOCK_PULL = 'lock_pull'
1496 LOCK_PULL = 'lock_pull'
1496
1497
1497 NAME_SEP = URL_SEP
1498 NAME_SEP = URL_SEP
1498
1499
1499 repo_id = Column(
1500 repo_id = Column(
1500 "repo_id", Integer(), nullable=False, unique=True, default=None,
1501 "repo_id", Integer(), nullable=False, unique=True, default=None,
1501 primary_key=True)
1502 primary_key=True)
1502 _repo_name = Column(
1503 _repo_name = Column(
1503 "repo_name", Text(), nullable=False, default=None)
1504 "repo_name", Text(), nullable=False, default=None)
1504 _repo_name_hash = Column(
1505 _repo_name_hash = Column(
1505 "repo_name_hash", String(255), nullable=False, unique=True)
1506 "repo_name_hash", String(255), nullable=False, unique=True)
1506 repo_state = Column("repo_state", String(255), nullable=True)
1507 repo_state = Column("repo_state", String(255), nullable=True)
1507
1508
1508 clone_uri = Column(
1509 clone_uri = Column(
1509 "clone_uri", EncryptedTextValue(), nullable=True, unique=False,
1510 "clone_uri", EncryptedTextValue(), nullable=True, unique=False,
1510 default=None)
1511 default=None)
1511 repo_type = Column(
1512 repo_type = Column(
1512 "repo_type", String(255), nullable=False, unique=False, default=None)
1513 "repo_type", String(255), nullable=False, unique=False, default=None)
1513 user_id = Column(
1514 user_id = Column(
1514 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
1515 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
1515 unique=False, default=None)
1516 unique=False, default=None)
1516 private = Column(
1517 private = Column(
1517 "private", Boolean(), nullable=True, unique=None, default=None)
1518 "private", Boolean(), nullable=True, unique=None, default=None)
1518 enable_statistics = Column(
1519 enable_statistics = Column(
1519 "statistics", Boolean(), nullable=True, unique=None, default=True)
1520 "statistics", Boolean(), nullable=True, unique=None, default=True)
1520 enable_downloads = Column(
1521 enable_downloads = Column(
1521 "downloads", Boolean(), nullable=True, unique=None, default=True)
1522 "downloads", Boolean(), nullable=True, unique=None, default=True)
1522 description = Column(
1523 description = Column(
1523 "description", String(10000), nullable=True, unique=None, default=None)
1524 "description", String(10000), nullable=True, unique=None, default=None)
1524 created_on = Column(
1525 created_on = Column(
1525 'created_on', DateTime(timezone=False), nullable=True, unique=None,
1526 'created_on', DateTime(timezone=False), nullable=True, unique=None,
1526 default=datetime.datetime.now)
1527 default=datetime.datetime.now)
1527 updated_on = Column(
1528 updated_on = Column(
1528 'updated_on', DateTime(timezone=False), nullable=True, unique=None,
1529 'updated_on', DateTime(timezone=False), nullable=True, unique=None,
1529 default=datetime.datetime.now)
1530 default=datetime.datetime.now)
1530 _landing_revision = Column(
1531 _landing_revision = Column(
1531 "landing_revision", String(255), nullable=False, unique=False,
1532 "landing_revision", String(255), nullable=False, unique=False,
1532 default=None)
1533 default=None)
1533 enable_locking = Column(
1534 enable_locking = Column(
1534 "enable_locking", Boolean(), nullable=False, unique=None,
1535 "enable_locking", Boolean(), nullable=False, unique=None,
1535 default=False)
1536 default=False)
1536 _locked = Column(
1537 _locked = Column(
1537 "locked", String(255), nullable=True, unique=False, default=None)
1538 "locked", String(255), nullable=True, unique=False, default=None)
1538 _changeset_cache = Column(
1539 _changeset_cache = Column(
1539 "changeset_cache", LargeBinary(), nullable=True) # JSON data
1540 "changeset_cache", LargeBinary(), nullable=True) # JSON data
1540
1541
1541 fork_id = Column(
1542 fork_id = Column(
1542 "fork_id", Integer(), ForeignKey('repositories.repo_id'),
1543 "fork_id", Integer(), ForeignKey('repositories.repo_id'),
1543 nullable=True, unique=False, default=None)
1544 nullable=True, unique=False, default=None)
1544 group_id = Column(
1545 group_id = Column(
1545 "group_id", Integer(), ForeignKey('groups.group_id'), nullable=True,
1546 "group_id", Integer(), ForeignKey('groups.group_id'), nullable=True,
1546 unique=False, default=None)
1547 unique=False, default=None)
1547
1548
1548 user = relationship('User', lazy='joined')
1549 user = relationship('User', lazy='joined')
1549 fork = relationship('Repository', remote_side=repo_id, lazy='joined')
1550 fork = relationship('Repository', remote_side=repo_id, lazy='joined')
1550 group = relationship('RepoGroup', lazy='joined')
1551 group = relationship('RepoGroup', lazy='joined')
1551 repo_to_perm = relationship(
1552 repo_to_perm = relationship(
1552 'UserRepoToPerm', cascade='all',
1553 'UserRepoToPerm', cascade='all',
1553 order_by='UserRepoToPerm.repo_to_perm_id')
1554 order_by='UserRepoToPerm.repo_to_perm_id')
1554 users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1555 users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1555 stats = relationship('Statistics', cascade='all', uselist=False)
1556 stats = relationship('Statistics', cascade='all', uselist=False)
1556
1557
1557 followers = relationship(
1558 followers = relationship(
1558 'UserFollowing',
1559 'UserFollowing',
1559 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
1560 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
1560 cascade='all')
1561 cascade='all')
1561 extra_fields = relationship(
1562 extra_fields = relationship(
1562 'RepositoryField', cascade="all, delete, delete-orphan")
1563 'RepositoryField', cascade="all, delete, delete-orphan")
1563 logs = relationship('UserLog')
1564 logs = relationship('UserLog')
1564 comments = relationship(
1565 comments = relationship(
1565 'ChangesetComment', cascade="all, delete, delete-orphan")
1566 'ChangesetComment', cascade="all, delete, delete-orphan")
1566 pull_requests_source = relationship(
1567 pull_requests_source = relationship(
1567 'PullRequest',
1568 'PullRequest',
1568 primaryjoin='PullRequest.source_repo_id==Repository.repo_id',
1569 primaryjoin='PullRequest.source_repo_id==Repository.repo_id',
1569 cascade="all, delete, delete-orphan")
1570 cascade="all, delete, delete-orphan")
1570 pull_requests_target = relationship(
1571 pull_requests_target = relationship(
1571 'PullRequest',
1572 'PullRequest',
1572 primaryjoin='PullRequest.target_repo_id==Repository.repo_id',
1573 primaryjoin='PullRequest.target_repo_id==Repository.repo_id',
1573 cascade="all, delete, delete-orphan")
1574 cascade="all, delete, delete-orphan")
1574 ui = relationship('RepoRhodeCodeUi', cascade="all")
1575 ui = relationship('RepoRhodeCodeUi', cascade="all")
1575 settings = relationship('RepoRhodeCodeSetting', cascade="all")
1576 settings = relationship('RepoRhodeCodeSetting', cascade="all")
1576 integrations = relationship('Integration',
1577 integrations = relationship('Integration',
1577 cascade="all, delete, delete-orphan")
1578 cascade="all, delete, delete-orphan")
1578
1579
1579 def __unicode__(self):
1580 def __unicode__(self):
1580 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
1581 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
1581 safe_unicode(self.repo_name))
1582 safe_unicode(self.repo_name))
1582
1583
1583 @hybrid_property
1584 @hybrid_property
1584 def description_safe(self):
1585 def description_safe(self):
1585 from rhodecode.lib import helpers as h
1586 from rhodecode.lib import helpers as h
1586 return h.escape(self.description)
1587 return h.escape(self.description)
1587
1588
1588 @hybrid_property
1589 @hybrid_property
1589 def landing_rev(self):
1590 def landing_rev(self):
1590 # always should return [rev_type, rev]
1591 # always should return [rev_type, rev]
1591 if self._landing_revision:
1592 if self._landing_revision:
1592 _rev_info = self._landing_revision.split(':')
1593 _rev_info = self._landing_revision.split(':')
1593 if len(_rev_info) < 2:
1594 if len(_rev_info) < 2:
1594 _rev_info.insert(0, 'rev')
1595 _rev_info.insert(0, 'rev')
1595 return [_rev_info[0], _rev_info[1]]
1596 return [_rev_info[0], _rev_info[1]]
1596 return [None, None]
1597 return [None, None]
1597
1598
1598 @landing_rev.setter
1599 @landing_rev.setter
1599 def landing_rev(self, val):
1600 def landing_rev(self, val):
1600 if ':' not in val:
1601 if ':' not in val:
1601 raise ValueError('value must be delimited with `:` and consist '
1602 raise ValueError('value must be delimited with `:` and consist '
1602 'of <rev_type>:<rev>, got %s instead' % val)
1603 'of <rev_type>:<rev>, got %s instead' % val)
1603 self._landing_revision = val
1604 self._landing_revision = val
1604
1605
1605 @hybrid_property
1606 @hybrid_property
1606 def locked(self):
1607 def locked(self):
1607 if self._locked:
1608 if self._locked:
1608 user_id, timelocked, reason = self._locked.split(':')
1609 user_id, timelocked, reason = self._locked.split(':')
1609 lock_values = int(user_id), timelocked, reason
1610 lock_values = int(user_id), timelocked, reason
1610 else:
1611 else:
1611 lock_values = [None, None, None]
1612 lock_values = [None, None, None]
1612 return lock_values
1613 return lock_values
1613
1614
1614 @locked.setter
1615 @locked.setter
1615 def locked(self, val):
1616 def locked(self, val):
1616 if val and isinstance(val, (list, tuple)):
1617 if val and isinstance(val, (list, tuple)):
1617 self._locked = ':'.join(map(str, val))
1618 self._locked = ':'.join(map(str, val))
1618 else:
1619 else:
1619 self._locked = None
1620 self._locked = None
1620
1621
1621 @hybrid_property
1622 @hybrid_property
1622 def changeset_cache(self):
1623 def changeset_cache(self):
1623 from rhodecode.lib.vcs.backends.base import EmptyCommit
1624 from rhodecode.lib.vcs.backends.base import EmptyCommit
1624 dummy = EmptyCommit().__json__()
1625 dummy = EmptyCommit().__json__()
1625 if not self._changeset_cache:
1626 if not self._changeset_cache:
1626 return dummy
1627 return dummy
1627 try:
1628 try:
1628 return json.loads(self._changeset_cache)
1629 return json.loads(self._changeset_cache)
1629 except TypeError:
1630 except TypeError:
1630 return dummy
1631 return dummy
1631 except Exception:
1632 except Exception:
1632 log.error(traceback.format_exc())
1633 log.error(traceback.format_exc())
1633 return dummy
1634 return dummy
1634
1635
1635 @changeset_cache.setter
1636 @changeset_cache.setter
1636 def changeset_cache(self, val):
1637 def changeset_cache(self, val):
1637 try:
1638 try:
1638 self._changeset_cache = json.dumps(val)
1639 self._changeset_cache = json.dumps(val)
1639 except Exception:
1640 except Exception:
1640 log.error(traceback.format_exc())
1641 log.error(traceback.format_exc())
1641
1642
1642 @hybrid_property
1643 @hybrid_property
1643 def repo_name(self):
1644 def repo_name(self):
1644 return self._repo_name
1645 return self._repo_name
1645
1646
1646 @repo_name.setter
1647 @repo_name.setter
1647 def repo_name(self, value):
1648 def repo_name(self, value):
1648 self._repo_name = value
1649 self._repo_name = value
1649 self._repo_name_hash = hashlib.sha1(safe_str(value)).hexdigest()
1650 self._repo_name_hash = hashlib.sha1(safe_str(value)).hexdigest()
1650
1651
1651 @classmethod
1652 @classmethod
1652 def normalize_repo_name(cls, repo_name):
1653 def normalize_repo_name(cls, repo_name):
1653 """
1654 """
1654 Normalizes os specific repo_name to the format internally stored inside
1655 Normalizes os specific repo_name to the format internally stored inside
1655 database using URL_SEP
1656 database using URL_SEP
1656
1657
1657 :param cls:
1658 :param cls:
1658 :param repo_name:
1659 :param repo_name:
1659 """
1660 """
1660 return cls.NAME_SEP.join(repo_name.split(os.sep))
1661 return cls.NAME_SEP.join(repo_name.split(os.sep))
1661
1662
1662 @classmethod
1663 @classmethod
1663 def get_by_repo_name(cls, repo_name, cache=False, identity_cache=False):
1664 def get_by_repo_name(cls, repo_name, cache=False, identity_cache=False):
1664 session = Session()
1665 session = Session()
1665 q = session.query(cls).filter(cls.repo_name == repo_name)
1666 q = session.query(cls).filter(cls.repo_name == repo_name)
1666
1667
1667 if cache:
1668 if cache:
1668 if identity_cache:
1669 if identity_cache:
1669 val = cls.identity_cache(session, 'repo_name', repo_name)
1670 val = cls.identity_cache(session, 'repo_name', repo_name)
1670 if val:
1671 if val:
1671 return val
1672 return val
1672 else:
1673 else:
1673 cache_key = "get_repo_by_name_%s" % _hash_key(repo_name)
1674 cache_key = "get_repo_by_name_%s" % _hash_key(repo_name)
1674 q = q.options(
1675 q = q.options(
1675 FromCache("sql_cache_short", cache_key))
1676 FromCache("sql_cache_short", cache_key))
1676
1677
1677 return q.scalar()
1678 return q.scalar()
1678
1679
1679 @classmethod
1680 @classmethod
1680 def get_by_full_path(cls, repo_full_path):
1681 def get_by_full_path(cls, repo_full_path):
1681 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
1682 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
1682 repo_name = cls.normalize_repo_name(repo_name)
1683 repo_name = cls.normalize_repo_name(repo_name)
1683 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
1684 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
1684
1685
1685 @classmethod
1686 @classmethod
1686 def get_repo_forks(cls, repo_id):
1687 def get_repo_forks(cls, repo_id):
1687 return cls.query().filter(Repository.fork_id == repo_id)
1688 return cls.query().filter(Repository.fork_id == repo_id)
1688
1689
1689 @classmethod
1690 @classmethod
1690 def base_path(cls):
1691 def base_path(cls):
1691 """
1692 """
1692 Returns base path when all repos are stored
1693 Returns base path when all repos are stored
1693
1694
1694 :param cls:
1695 :param cls:
1695 """
1696 """
1696 q = Session().query(RhodeCodeUi)\
1697 q = Session().query(RhodeCodeUi)\
1697 .filter(RhodeCodeUi.ui_key == cls.NAME_SEP)
1698 .filter(RhodeCodeUi.ui_key == cls.NAME_SEP)
1698 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1699 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1699 return q.one().ui_value
1700 return q.one().ui_value
1700
1701
1701 @classmethod
1702 @classmethod
1702 def is_valid(cls, repo_name):
1703 def is_valid(cls, repo_name):
1703 """
1704 """
1704 returns True if given repo name is a valid filesystem repository
1705 returns True if given repo name is a valid filesystem repository
1705
1706
1706 :param cls:
1707 :param cls:
1707 :param repo_name:
1708 :param repo_name:
1708 """
1709 """
1709 from rhodecode.lib.utils import is_valid_repo
1710 from rhodecode.lib.utils import is_valid_repo
1710
1711
1711 return is_valid_repo(repo_name, cls.base_path())
1712 return is_valid_repo(repo_name, cls.base_path())
1712
1713
1713 @classmethod
1714 @classmethod
1714 def get_all_repos(cls, user_id=Optional(None), group_id=Optional(None),
1715 def get_all_repos(cls, user_id=Optional(None), group_id=Optional(None),
1715 case_insensitive=True):
1716 case_insensitive=True):
1716 q = Repository.query()
1717 q = Repository.query()
1717
1718
1718 if not isinstance(user_id, Optional):
1719 if not isinstance(user_id, Optional):
1719 q = q.filter(Repository.user_id == user_id)
1720 q = q.filter(Repository.user_id == user_id)
1720
1721
1721 if not isinstance(group_id, Optional):
1722 if not isinstance(group_id, Optional):
1722 q = q.filter(Repository.group_id == group_id)
1723 q = q.filter(Repository.group_id == group_id)
1723
1724
1724 if case_insensitive:
1725 if case_insensitive:
1725 q = q.order_by(func.lower(Repository.repo_name))
1726 q = q.order_by(func.lower(Repository.repo_name))
1726 else:
1727 else:
1727 q = q.order_by(Repository.repo_name)
1728 q = q.order_by(Repository.repo_name)
1728 return q.all()
1729 return q.all()
1729
1730
1730 @property
1731 @property
1731 def forks(self):
1732 def forks(self):
1732 """
1733 """
1733 Return forks of this repo
1734 Return forks of this repo
1734 """
1735 """
1735 return Repository.get_repo_forks(self.repo_id)
1736 return Repository.get_repo_forks(self.repo_id)
1736
1737
1737 @property
1738 @property
1738 def parent(self):
1739 def parent(self):
1739 """
1740 """
1740 Returns fork parent
1741 Returns fork parent
1741 """
1742 """
1742 return self.fork
1743 return self.fork
1743
1744
1744 @property
1745 @property
1745 def just_name(self):
1746 def just_name(self):
1746 return self.repo_name.split(self.NAME_SEP)[-1]
1747 return self.repo_name.split(self.NAME_SEP)[-1]
1747
1748
1748 @property
1749 @property
1749 def groups_with_parents(self):
1750 def groups_with_parents(self):
1750 groups = []
1751 groups = []
1751 if self.group is None:
1752 if self.group is None:
1752 return groups
1753 return groups
1753
1754
1754 cur_gr = self.group
1755 cur_gr = self.group
1755 groups.insert(0, cur_gr)
1756 groups.insert(0, cur_gr)
1756 while 1:
1757 while 1:
1757 gr = getattr(cur_gr, 'parent_group', None)
1758 gr = getattr(cur_gr, 'parent_group', None)
1758 cur_gr = cur_gr.parent_group
1759 cur_gr = cur_gr.parent_group
1759 if gr is None:
1760 if gr is None:
1760 break
1761 break
1761 groups.insert(0, gr)
1762 groups.insert(0, gr)
1762
1763
1763 return groups
1764 return groups
1764
1765
1765 @property
1766 @property
1766 def groups_and_repo(self):
1767 def groups_and_repo(self):
1767 return self.groups_with_parents, self
1768 return self.groups_with_parents, self
1768
1769
1769 @LazyProperty
1770 @LazyProperty
1770 def repo_path(self):
1771 def repo_path(self):
1771 """
1772 """
1772 Returns base full path for that repository means where it actually
1773 Returns base full path for that repository means where it actually
1773 exists on a filesystem
1774 exists on a filesystem
1774 """
1775 """
1775 q = Session().query(RhodeCodeUi).filter(
1776 q = Session().query(RhodeCodeUi).filter(
1776 RhodeCodeUi.ui_key == self.NAME_SEP)
1777 RhodeCodeUi.ui_key == self.NAME_SEP)
1777 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1778 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1778 return q.one().ui_value
1779 return q.one().ui_value
1779
1780
1780 @property
1781 @property
1781 def repo_full_path(self):
1782 def repo_full_path(self):
1782 p = [self.repo_path]
1783 p = [self.repo_path]
1783 # we need to split the name by / since this is how we store the
1784 # we need to split the name by / since this is how we store the
1784 # names in the database, but that eventually needs to be converted
1785 # names in the database, but that eventually needs to be converted
1785 # into a valid system path
1786 # into a valid system path
1786 p += self.repo_name.split(self.NAME_SEP)
1787 p += self.repo_name.split(self.NAME_SEP)
1787 return os.path.join(*map(safe_unicode, p))
1788 return os.path.join(*map(safe_unicode, p))
1788
1789
1789 @property
1790 @property
1790 def cache_keys(self):
1791 def cache_keys(self):
1791 """
1792 """
1792 Returns associated cache keys for that repo
1793 Returns associated cache keys for that repo
1793 """
1794 """
1794 return CacheKey.query()\
1795 return CacheKey.query()\
1795 .filter(CacheKey.cache_args == self.repo_name)\
1796 .filter(CacheKey.cache_args == self.repo_name)\
1796 .order_by(CacheKey.cache_key)\
1797 .order_by(CacheKey.cache_key)\
1797 .all()
1798 .all()
1798
1799
1799 def get_new_name(self, repo_name):
1800 def get_new_name(self, repo_name):
1800 """
1801 """
1801 returns new full repository name based on assigned group and new new
1802 returns new full repository name based on assigned group and new new
1802
1803
1803 :param group_name:
1804 :param group_name:
1804 """
1805 """
1805 path_prefix = self.group.full_path_splitted if self.group else []
1806 path_prefix = self.group.full_path_splitted if self.group else []
1806 return self.NAME_SEP.join(path_prefix + [repo_name])
1807 return self.NAME_SEP.join(path_prefix + [repo_name])
1807
1808
1808 @property
1809 @property
1809 def _config(self):
1810 def _config(self):
1810 """
1811 """
1811 Returns db based config object.
1812 Returns db based config object.
1812 """
1813 """
1813 from rhodecode.lib.utils import make_db_config
1814 from rhodecode.lib.utils import make_db_config
1814 return make_db_config(clear_session=False, repo=self)
1815 return make_db_config(clear_session=False, repo=self)
1815
1816
1816 def permissions(self, with_admins=True, with_owner=True):
1817 def permissions(self, with_admins=True, with_owner=True):
1817 q = UserRepoToPerm.query().filter(UserRepoToPerm.repository == self)
1818 q = UserRepoToPerm.query().filter(UserRepoToPerm.repository == self)
1818 q = q.options(joinedload(UserRepoToPerm.repository),
1819 q = q.options(joinedload(UserRepoToPerm.repository),
1819 joinedload(UserRepoToPerm.user),
1820 joinedload(UserRepoToPerm.user),
1820 joinedload(UserRepoToPerm.permission),)
1821 joinedload(UserRepoToPerm.permission),)
1821
1822
1822 # get owners and admins and permissions. We do a trick of re-writing
1823 # get owners and admins and permissions. We do a trick of re-writing
1823 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1824 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1824 # has a global reference and changing one object propagates to all
1825 # has a global reference and changing one object propagates to all
1825 # others. This means if admin is also an owner admin_row that change
1826 # others. This means if admin is also an owner admin_row that change
1826 # would propagate to both objects
1827 # would propagate to both objects
1827 perm_rows = []
1828 perm_rows = []
1828 for _usr in q.all():
1829 for _usr in q.all():
1829 usr = AttributeDict(_usr.user.get_dict())
1830 usr = AttributeDict(_usr.user.get_dict())
1830 usr.permission = _usr.permission.permission_name
1831 usr.permission = _usr.permission.permission_name
1831 perm_rows.append(usr)
1832 perm_rows.append(usr)
1832
1833
1833 # filter the perm rows by 'default' first and then sort them by
1834 # filter the perm rows by 'default' first and then sort them by
1834 # admin,write,read,none permissions sorted again alphabetically in
1835 # admin,write,read,none permissions sorted again alphabetically in
1835 # each group
1836 # each group
1836 perm_rows = sorted(perm_rows, key=display_sort)
1837 perm_rows = sorted(perm_rows, key=display_sort)
1837
1838
1838 _admin_perm = 'repository.admin'
1839 _admin_perm = 'repository.admin'
1839 owner_row = []
1840 owner_row = []
1840 if with_owner:
1841 if with_owner:
1841 usr = AttributeDict(self.user.get_dict())
1842 usr = AttributeDict(self.user.get_dict())
1842 usr.owner_row = True
1843 usr.owner_row = True
1843 usr.permission = _admin_perm
1844 usr.permission = _admin_perm
1844 owner_row.append(usr)
1845 owner_row.append(usr)
1845
1846
1846 super_admin_rows = []
1847 super_admin_rows = []
1847 if with_admins:
1848 if with_admins:
1848 for usr in User.get_all_super_admins():
1849 for usr in User.get_all_super_admins():
1849 # if this admin is also owner, don't double the record
1850 # if this admin is also owner, don't double the record
1850 if usr.user_id == owner_row[0].user_id:
1851 if usr.user_id == owner_row[0].user_id:
1851 owner_row[0].admin_row = True
1852 owner_row[0].admin_row = True
1852 else:
1853 else:
1853 usr = AttributeDict(usr.get_dict())
1854 usr = AttributeDict(usr.get_dict())
1854 usr.admin_row = True
1855 usr.admin_row = True
1855 usr.permission = _admin_perm
1856 usr.permission = _admin_perm
1856 super_admin_rows.append(usr)
1857 super_admin_rows.append(usr)
1857
1858
1858 return super_admin_rows + owner_row + perm_rows
1859 return super_admin_rows + owner_row + perm_rows
1859
1860
1860 def permission_user_groups(self):
1861 def permission_user_groups(self):
1861 q = UserGroupRepoToPerm.query().filter(
1862 q = UserGroupRepoToPerm.query().filter(
1862 UserGroupRepoToPerm.repository == self)
1863 UserGroupRepoToPerm.repository == self)
1863 q = q.options(joinedload(UserGroupRepoToPerm.repository),
1864 q = q.options(joinedload(UserGroupRepoToPerm.repository),
1864 joinedload(UserGroupRepoToPerm.users_group),
1865 joinedload(UserGroupRepoToPerm.users_group),
1865 joinedload(UserGroupRepoToPerm.permission),)
1866 joinedload(UserGroupRepoToPerm.permission),)
1866
1867
1867 perm_rows = []
1868 perm_rows = []
1868 for _user_group in q.all():
1869 for _user_group in q.all():
1869 usr = AttributeDict(_user_group.users_group.get_dict())
1870 usr = AttributeDict(_user_group.users_group.get_dict())
1870 usr.permission = _user_group.permission.permission_name
1871 usr.permission = _user_group.permission.permission_name
1871 perm_rows.append(usr)
1872 perm_rows.append(usr)
1872
1873
1873 return perm_rows
1874 return perm_rows
1874
1875
1875 def get_api_data(self, include_secrets=False):
1876 def get_api_data(self, include_secrets=False):
1876 """
1877 """
1877 Common function for generating repo api data
1878 Common function for generating repo api data
1878
1879
1879 :param include_secrets: See :meth:`User.get_api_data`.
1880 :param include_secrets: See :meth:`User.get_api_data`.
1880
1881
1881 """
1882 """
1882 # TODO: mikhail: Here there is an anti-pattern, we probably need to
1883 # TODO: mikhail: Here there is an anti-pattern, we probably need to
1883 # move this methods on models level.
1884 # move this methods on models level.
1884 from rhodecode.model.settings import SettingsModel
1885 from rhodecode.model.settings import SettingsModel
1885 from rhodecode.model.repo import RepoModel
1886 from rhodecode.model.repo import RepoModel
1886
1887
1887 repo = self
1888 repo = self
1888 _user_id, _time, _reason = self.locked
1889 _user_id, _time, _reason = self.locked
1889
1890
1890 data = {
1891 data = {
1891 'repo_id': repo.repo_id,
1892 'repo_id': repo.repo_id,
1892 'repo_name': repo.repo_name,
1893 'repo_name': repo.repo_name,
1893 'repo_type': repo.repo_type,
1894 'repo_type': repo.repo_type,
1894 'clone_uri': repo.clone_uri or '',
1895 'clone_uri': repo.clone_uri or '',
1895 'url': RepoModel().get_url(self),
1896 'url': RepoModel().get_url(self),
1896 'private': repo.private,
1897 'private': repo.private,
1897 'created_on': repo.created_on,
1898 'created_on': repo.created_on,
1898 'description': repo.description_safe,
1899 'description': repo.description_safe,
1899 'landing_rev': repo.landing_rev,
1900 'landing_rev': repo.landing_rev,
1900 'owner': repo.user.username,
1901 'owner': repo.user.username,
1901 'fork_of': repo.fork.repo_name if repo.fork else None,
1902 'fork_of': repo.fork.repo_name if repo.fork else None,
1902 'fork_of_id': repo.fork.repo_id if repo.fork else None,
1903 'fork_of_id': repo.fork.repo_id if repo.fork else None,
1903 'enable_statistics': repo.enable_statistics,
1904 'enable_statistics': repo.enable_statistics,
1904 'enable_locking': repo.enable_locking,
1905 'enable_locking': repo.enable_locking,
1905 'enable_downloads': repo.enable_downloads,
1906 'enable_downloads': repo.enable_downloads,
1906 'last_changeset': repo.changeset_cache,
1907 'last_changeset': repo.changeset_cache,
1907 'locked_by': User.get(_user_id).get_api_data(
1908 'locked_by': User.get(_user_id).get_api_data(
1908 include_secrets=include_secrets) if _user_id else None,
1909 include_secrets=include_secrets) if _user_id else None,
1909 'locked_date': time_to_datetime(_time) if _time else None,
1910 'locked_date': time_to_datetime(_time) if _time else None,
1910 'lock_reason': _reason if _reason else None,
1911 'lock_reason': _reason if _reason else None,
1911 }
1912 }
1912
1913
1913 # TODO: mikhail: should be per-repo settings here
1914 # TODO: mikhail: should be per-repo settings here
1914 rc_config = SettingsModel().get_all_settings()
1915 rc_config = SettingsModel().get_all_settings()
1915 repository_fields = str2bool(
1916 repository_fields = str2bool(
1916 rc_config.get('rhodecode_repository_fields'))
1917 rc_config.get('rhodecode_repository_fields'))
1917 if repository_fields:
1918 if repository_fields:
1918 for f in self.extra_fields:
1919 for f in self.extra_fields:
1919 data[f.field_key_prefixed] = f.field_value
1920 data[f.field_key_prefixed] = f.field_value
1920
1921
1921 return data
1922 return data
1922
1923
1923 @classmethod
1924 @classmethod
1924 def lock(cls, repo, user_id, lock_time=None, lock_reason=None):
1925 def lock(cls, repo, user_id, lock_time=None, lock_reason=None):
1925 if not lock_time:
1926 if not lock_time:
1926 lock_time = time.time()
1927 lock_time = time.time()
1927 if not lock_reason:
1928 if not lock_reason:
1928 lock_reason = cls.LOCK_AUTOMATIC
1929 lock_reason = cls.LOCK_AUTOMATIC
1929 repo.locked = [user_id, lock_time, lock_reason]
1930 repo.locked = [user_id, lock_time, lock_reason]
1930 Session().add(repo)
1931 Session().add(repo)
1931 Session().commit()
1932 Session().commit()
1932
1933
1933 @classmethod
1934 @classmethod
1934 def unlock(cls, repo):
1935 def unlock(cls, repo):
1935 repo.locked = None
1936 repo.locked = None
1936 Session().add(repo)
1937 Session().add(repo)
1937 Session().commit()
1938 Session().commit()
1938
1939
1939 @classmethod
1940 @classmethod
1940 def getlock(cls, repo):
1941 def getlock(cls, repo):
1941 return repo.locked
1942 return repo.locked
1942
1943
1943 def is_user_lock(self, user_id):
1944 def is_user_lock(self, user_id):
1944 if self.lock[0]:
1945 if self.lock[0]:
1945 lock_user_id = safe_int(self.lock[0])
1946 lock_user_id = safe_int(self.lock[0])
1946 user_id = safe_int(user_id)
1947 user_id = safe_int(user_id)
1947 # both are ints, and they are equal
1948 # both are ints, and they are equal
1948 return all([lock_user_id, user_id]) and lock_user_id == user_id
1949 return all([lock_user_id, user_id]) and lock_user_id == user_id
1949
1950
1950 return False
1951 return False
1951
1952
1952 def get_locking_state(self, action, user_id, only_when_enabled=True):
1953 def get_locking_state(self, action, user_id, only_when_enabled=True):
1953 """
1954 """
1954 Checks locking on this repository, if locking is enabled and lock is
1955 Checks locking on this repository, if locking is enabled and lock is
1955 present returns a tuple of make_lock, locked, locked_by.
1956 present returns a tuple of make_lock, locked, locked_by.
1956 make_lock can have 3 states None (do nothing) True, make lock
1957 make_lock can have 3 states None (do nothing) True, make lock
1957 False release lock, This value is later propagated to hooks, which
1958 False release lock, This value is later propagated to hooks, which
1958 do the locking. Think about this as signals passed to hooks what to do.
1959 do the locking. Think about this as signals passed to hooks what to do.
1959
1960
1960 """
1961 """
1961 # TODO: johbo: This is part of the business logic and should be moved
1962 # TODO: johbo: This is part of the business logic and should be moved
1962 # into the RepositoryModel.
1963 # into the RepositoryModel.
1963
1964
1964 if action not in ('push', 'pull'):
1965 if action not in ('push', 'pull'):
1965 raise ValueError("Invalid action value: %s" % repr(action))
1966 raise ValueError("Invalid action value: %s" % repr(action))
1966
1967
1967 # defines if locked error should be thrown to user
1968 # defines if locked error should be thrown to user
1968 currently_locked = False
1969 currently_locked = False
1969 # defines if new lock should be made, tri-state
1970 # defines if new lock should be made, tri-state
1970 make_lock = None
1971 make_lock = None
1971 repo = self
1972 repo = self
1972 user = User.get(user_id)
1973 user = User.get(user_id)
1973
1974
1974 lock_info = repo.locked
1975 lock_info = repo.locked
1975
1976
1976 if repo and (repo.enable_locking or not only_when_enabled):
1977 if repo and (repo.enable_locking or not only_when_enabled):
1977 if action == 'push':
1978 if action == 'push':
1978 # check if it's already locked !, if it is compare users
1979 # check if it's already locked !, if it is compare users
1979 locked_by_user_id = lock_info[0]
1980 locked_by_user_id = lock_info[0]
1980 if user.user_id == locked_by_user_id:
1981 if user.user_id == locked_by_user_id:
1981 log.debug(
1982 log.debug(
1982 'Got `push` action from user %s, now unlocking', user)
1983 'Got `push` action from user %s, now unlocking', user)
1983 # unlock if we have push from user who locked
1984 # unlock if we have push from user who locked
1984 make_lock = False
1985 make_lock = False
1985 else:
1986 else:
1986 # we're not the same user who locked, ban with
1987 # we're not the same user who locked, ban with
1987 # code defined in settings (default is 423 HTTP Locked) !
1988 # code defined in settings (default is 423 HTTP Locked) !
1988 log.debug('Repo %s is currently locked by %s', repo, user)
1989 log.debug('Repo %s is currently locked by %s', repo, user)
1989 currently_locked = True
1990 currently_locked = True
1990 elif action == 'pull':
1991 elif action == 'pull':
1991 # [0] user [1] date
1992 # [0] user [1] date
1992 if lock_info[0] and lock_info[1]:
1993 if lock_info[0] and lock_info[1]:
1993 log.debug('Repo %s is currently locked by %s', repo, user)
1994 log.debug('Repo %s is currently locked by %s', repo, user)
1994 currently_locked = True
1995 currently_locked = True
1995 else:
1996 else:
1996 log.debug('Setting lock on repo %s by %s', repo, user)
1997 log.debug('Setting lock on repo %s by %s', repo, user)
1997 make_lock = True
1998 make_lock = True
1998
1999
1999 else:
2000 else:
2000 log.debug('Repository %s do not have locking enabled', repo)
2001 log.debug('Repository %s do not have locking enabled', repo)
2001
2002
2002 log.debug('FINAL locking values make_lock:%s,locked:%s,locked_by:%s',
2003 log.debug('FINAL locking values make_lock:%s,locked:%s,locked_by:%s',
2003 make_lock, currently_locked, lock_info)
2004 make_lock, currently_locked, lock_info)
2004
2005
2005 from rhodecode.lib.auth import HasRepoPermissionAny
2006 from rhodecode.lib.auth import HasRepoPermissionAny
2006 perm_check = HasRepoPermissionAny('repository.write', 'repository.admin')
2007 perm_check = HasRepoPermissionAny('repository.write', 'repository.admin')
2007 if make_lock and not perm_check(repo_name=repo.repo_name, user=user):
2008 if make_lock and not perm_check(repo_name=repo.repo_name, user=user):
2008 # if we don't have at least write permission we cannot make a lock
2009 # if we don't have at least write permission we cannot make a lock
2009 log.debug('lock state reset back to FALSE due to lack '
2010 log.debug('lock state reset back to FALSE due to lack '
2010 'of at least read permission')
2011 'of at least read permission')
2011 make_lock = False
2012 make_lock = False
2012
2013
2013 return make_lock, currently_locked, lock_info
2014 return make_lock, currently_locked, lock_info
2014
2015
2015 @property
2016 @property
2016 def last_db_change(self):
2017 def last_db_change(self):
2017 return self.updated_on
2018 return self.updated_on
2018
2019
2019 @property
2020 @property
2020 def clone_uri_hidden(self):
2021 def clone_uri_hidden(self):
2021 clone_uri = self.clone_uri
2022 clone_uri = self.clone_uri
2022 if clone_uri:
2023 if clone_uri:
2023 import urlobject
2024 import urlobject
2024 url_obj = urlobject.URLObject(cleaned_uri(clone_uri))
2025 url_obj = urlobject.URLObject(cleaned_uri(clone_uri))
2025 if url_obj.password:
2026 if url_obj.password:
2026 clone_uri = url_obj.with_password('*****')
2027 clone_uri = url_obj.with_password('*****')
2027 return clone_uri
2028 return clone_uri
2028
2029
2029 def clone_url(self, **override):
2030 def clone_url(self, **override):
2030 from rhodecode.model.settings import SettingsModel
2031 from rhodecode.model.settings import SettingsModel
2031
2032
2032 uri_tmpl = None
2033 uri_tmpl = None
2033 if 'with_id' in override:
2034 if 'with_id' in override:
2034 uri_tmpl = self.DEFAULT_CLONE_URI_ID
2035 uri_tmpl = self.DEFAULT_CLONE_URI_ID
2035 del override['with_id']
2036 del override['with_id']
2036
2037
2037 if 'uri_tmpl' in override:
2038 if 'uri_tmpl' in override:
2038 uri_tmpl = override['uri_tmpl']
2039 uri_tmpl = override['uri_tmpl']
2039 del override['uri_tmpl']
2040 del override['uri_tmpl']
2040
2041
2041 # we didn't override our tmpl from **overrides
2042 # we didn't override our tmpl from **overrides
2042 if not uri_tmpl:
2043 if not uri_tmpl:
2043 rc_config = SettingsModel().get_all_settings(cache=True)
2044 rc_config = SettingsModel().get_all_settings(cache=True)
2044 uri_tmpl = rc_config.get(
2045 uri_tmpl = rc_config.get(
2045 'rhodecode_clone_uri_tmpl') or self.DEFAULT_CLONE_URI
2046 'rhodecode_clone_uri_tmpl') or self.DEFAULT_CLONE_URI
2046
2047
2047 request = get_current_request()
2048 request = get_current_request()
2048 return get_clone_url(request=request,
2049 return get_clone_url(request=request,
2049 uri_tmpl=uri_tmpl,
2050 uri_tmpl=uri_tmpl,
2050 repo_name=self.repo_name,
2051 repo_name=self.repo_name,
2051 repo_id=self.repo_id, **override)
2052 repo_id=self.repo_id, **override)
2052
2053
2053 def set_state(self, state):
2054 def set_state(self, state):
2054 self.repo_state = state
2055 self.repo_state = state
2055 Session().add(self)
2056 Session().add(self)
2056 #==========================================================================
2057 #==========================================================================
2057 # SCM PROPERTIES
2058 # SCM PROPERTIES
2058 #==========================================================================
2059 #==========================================================================
2059
2060
2060 def get_commit(self, commit_id=None, commit_idx=None, pre_load=None):
2061 def get_commit(self, commit_id=None, commit_idx=None, pre_load=None):
2061 return get_commit_safe(
2062 return get_commit_safe(
2062 self.scm_instance(), commit_id, commit_idx, pre_load=pre_load)
2063 self.scm_instance(), commit_id, commit_idx, pre_load=pre_load)
2063
2064
2064 def get_changeset(self, rev=None, pre_load=None):
2065 def get_changeset(self, rev=None, pre_load=None):
2065 warnings.warn("Use get_commit", DeprecationWarning)
2066 warnings.warn("Use get_commit", DeprecationWarning)
2066 commit_id = None
2067 commit_id = None
2067 commit_idx = None
2068 commit_idx = None
2068 if isinstance(rev, basestring):
2069 if isinstance(rev, basestring):
2069 commit_id = rev
2070 commit_id = rev
2070 else:
2071 else:
2071 commit_idx = rev
2072 commit_idx = rev
2072 return self.get_commit(commit_id=commit_id, commit_idx=commit_idx,
2073 return self.get_commit(commit_id=commit_id, commit_idx=commit_idx,
2073 pre_load=pre_load)
2074 pre_load=pre_load)
2074
2075
2075 def get_landing_commit(self):
2076 def get_landing_commit(self):
2076 """
2077 """
2077 Returns landing commit, or if that doesn't exist returns the tip
2078 Returns landing commit, or if that doesn't exist returns the tip
2078 """
2079 """
2079 _rev_type, _rev = self.landing_rev
2080 _rev_type, _rev = self.landing_rev
2080 commit = self.get_commit(_rev)
2081 commit = self.get_commit(_rev)
2081 if isinstance(commit, EmptyCommit):
2082 if isinstance(commit, EmptyCommit):
2082 return self.get_commit()
2083 return self.get_commit()
2083 return commit
2084 return commit
2084
2085
2085 def update_commit_cache(self, cs_cache=None, config=None):
2086 def update_commit_cache(self, cs_cache=None, config=None):
2086 """
2087 """
2087 Update cache of last changeset for repository, keys should be::
2088 Update cache of last changeset for repository, keys should be::
2088
2089
2089 short_id
2090 short_id
2090 raw_id
2091 raw_id
2091 revision
2092 revision
2092 parents
2093 parents
2093 message
2094 message
2094 date
2095 date
2095 author
2096 author
2096
2097
2097 :param cs_cache:
2098 :param cs_cache:
2098 """
2099 """
2099 from rhodecode.lib.vcs.backends.base import BaseChangeset
2100 from rhodecode.lib.vcs.backends.base import BaseChangeset
2100 if cs_cache is None:
2101 if cs_cache is None:
2101 # use no-cache version here
2102 # use no-cache version here
2102 scm_repo = self.scm_instance(cache=False, config=config)
2103 scm_repo = self.scm_instance(cache=False, config=config)
2103 if scm_repo:
2104 if scm_repo:
2104 cs_cache = scm_repo.get_commit(
2105 cs_cache = scm_repo.get_commit(
2105 pre_load=["author", "date", "message", "parents"])
2106 pre_load=["author", "date", "message", "parents"])
2106 else:
2107 else:
2107 cs_cache = EmptyCommit()
2108 cs_cache = EmptyCommit()
2108
2109
2109 if isinstance(cs_cache, BaseChangeset):
2110 if isinstance(cs_cache, BaseChangeset):
2110 cs_cache = cs_cache.__json__()
2111 cs_cache = cs_cache.__json__()
2111
2112
2112 def is_outdated(new_cs_cache):
2113 def is_outdated(new_cs_cache):
2113 if (new_cs_cache['raw_id'] != self.changeset_cache['raw_id'] or
2114 if (new_cs_cache['raw_id'] != self.changeset_cache['raw_id'] or
2114 new_cs_cache['revision'] != self.changeset_cache['revision']):
2115 new_cs_cache['revision'] != self.changeset_cache['revision']):
2115 return True
2116 return True
2116 return False
2117 return False
2117
2118
2118 # check if we have maybe already latest cached revision
2119 # check if we have maybe already latest cached revision
2119 if is_outdated(cs_cache) or not self.changeset_cache:
2120 if is_outdated(cs_cache) or not self.changeset_cache:
2120 _default = datetime.datetime.fromtimestamp(0)
2121 _default = datetime.datetime.fromtimestamp(0)
2121 last_change = cs_cache.get('date') or _default
2122 last_change = cs_cache.get('date') or _default
2122 log.debug('updated repo %s with new cs cache %s',
2123 log.debug('updated repo %s with new cs cache %s',
2123 self.repo_name, cs_cache)
2124 self.repo_name, cs_cache)
2124 self.updated_on = last_change
2125 self.updated_on = last_change
2125 self.changeset_cache = cs_cache
2126 self.changeset_cache = cs_cache
2126 Session().add(self)
2127 Session().add(self)
2127 Session().commit()
2128 Session().commit()
2128 else:
2129 else:
2129 log.debug('Skipping update_commit_cache for repo:`%s` '
2130 log.debug('Skipping update_commit_cache for repo:`%s` '
2130 'commit already with latest changes', self.repo_name)
2131 'commit already with latest changes', self.repo_name)
2131
2132
2132 @property
2133 @property
2133 def tip(self):
2134 def tip(self):
2134 return self.get_commit('tip')
2135 return self.get_commit('tip')
2135
2136
2136 @property
2137 @property
2137 def author(self):
2138 def author(self):
2138 return self.tip.author
2139 return self.tip.author
2139
2140
2140 @property
2141 @property
2141 def last_change(self):
2142 def last_change(self):
2142 return self.scm_instance().last_change
2143 return self.scm_instance().last_change
2143
2144
2144 def get_comments(self, revisions=None):
2145 def get_comments(self, revisions=None):
2145 """
2146 """
2146 Returns comments for this repository grouped by revisions
2147 Returns comments for this repository grouped by revisions
2147
2148
2148 :param revisions: filter query by revisions only
2149 :param revisions: filter query by revisions only
2149 """
2150 """
2150 cmts = ChangesetComment.query()\
2151 cmts = ChangesetComment.query()\
2151 .filter(ChangesetComment.repo == self)
2152 .filter(ChangesetComment.repo == self)
2152 if revisions:
2153 if revisions:
2153 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
2154 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
2154 grouped = collections.defaultdict(list)
2155 grouped = collections.defaultdict(list)
2155 for cmt in cmts.all():
2156 for cmt in cmts.all():
2156 grouped[cmt.revision].append(cmt)
2157 grouped[cmt.revision].append(cmt)
2157 return grouped
2158 return grouped
2158
2159
2159 def statuses(self, revisions=None):
2160 def statuses(self, revisions=None):
2160 """
2161 """
2161 Returns statuses for this repository
2162 Returns statuses for this repository
2162
2163
2163 :param revisions: list of revisions to get statuses for
2164 :param revisions: list of revisions to get statuses for
2164 """
2165 """
2165 statuses = ChangesetStatus.query()\
2166 statuses = ChangesetStatus.query()\
2166 .filter(ChangesetStatus.repo == self)\
2167 .filter(ChangesetStatus.repo == self)\
2167 .filter(ChangesetStatus.version == 0)
2168 .filter(ChangesetStatus.version == 0)
2168
2169
2169 if revisions:
2170 if revisions:
2170 # Try doing the filtering in chunks to avoid hitting limits
2171 # Try doing the filtering in chunks to avoid hitting limits
2171 size = 500
2172 size = 500
2172 status_results = []
2173 status_results = []
2173 for chunk in xrange(0, len(revisions), size):
2174 for chunk in xrange(0, len(revisions), size):
2174 status_results += statuses.filter(
2175 status_results += statuses.filter(
2175 ChangesetStatus.revision.in_(
2176 ChangesetStatus.revision.in_(
2176 revisions[chunk: chunk+size])
2177 revisions[chunk: chunk+size])
2177 ).all()
2178 ).all()
2178 else:
2179 else:
2179 status_results = statuses.all()
2180 status_results = statuses.all()
2180
2181
2181 grouped = {}
2182 grouped = {}
2182
2183
2183 # maybe we have open new pullrequest without a status?
2184 # maybe we have open new pullrequest without a status?
2184 stat = ChangesetStatus.STATUS_UNDER_REVIEW
2185 stat = ChangesetStatus.STATUS_UNDER_REVIEW
2185 status_lbl = ChangesetStatus.get_status_lbl(stat)
2186 status_lbl = ChangesetStatus.get_status_lbl(stat)
2186 for pr in PullRequest.query().filter(PullRequest.source_repo == self).all():
2187 for pr in PullRequest.query().filter(PullRequest.source_repo == self).all():
2187 for rev in pr.revisions:
2188 for rev in pr.revisions:
2188 pr_id = pr.pull_request_id
2189 pr_id = pr.pull_request_id
2189 pr_repo = pr.target_repo.repo_name
2190 pr_repo = pr.target_repo.repo_name
2190 grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
2191 grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
2191
2192
2192 for stat in status_results:
2193 for stat in status_results:
2193 pr_id = pr_repo = None
2194 pr_id = pr_repo = None
2194 if stat.pull_request:
2195 if stat.pull_request:
2195 pr_id = stat.pull_request.pull_request_id
2196 pr_id = stat.pull_request.pull_request_id
2196 pr_repo = stat.pull_request.target_repo.repo_name
2197 pr_repo = stat.pull_request.target_repo.repo_name
2197 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
2198 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
2198 pr_id, pr_repo]
2199 pr_id, pr_repo]
2199 return grouped
2200 return grouped
2200
2201
2201 # ==========================================================================
2202 # ==========================================================================
2202 # SCM CACHE INSTANCE
2203 # SCM CACHE INSTANCE
2203 # ==========================================================================
2204 # ==========================================================================
2204
2205
2205 def scm_instance(self, **kwargs):
2206 def scm_instance(self, **kwargs):
2206 import rhodecode
2207 import rhodecode
2207
2208
2208 # Passing a config will not hit the cache currently only used
2209 # Passing a config will not hit the cache currently only used
2209 # for repo2dbmapper
2210 # for repo2dbmapper
2210 config = kwargs.pop('config', None)
2211 config = kwargs.pop('config', None)
2211 cache = kwargs.pop('cache', None)
2212 cache = kwargs.pop('cache', None)
2212 full_cache = str2bool(rhodecode.CONFIG.get('vcs_full_cache'))
2213 full_cache = str2bool(rhodecode.CONFIG.get('vcs_full_cache'))
2213 # if cache is NOT defined use default global, else we have a full
2214 # if cache is NOT defined use default global, else we have a full
2214 # control over cache behaviour
2215 # control over cache behaviour
2215 if cache is None and full_cache and not config:
2216 if cache is None and full_cache and not config:
2216 return self._get_instance_cached()
2217 return self._get_instance_cached()
2217 return self._get_instance(cache=bool(cache), config=config)
2218 return self._get_instance(cache=bool(cache), config=config)
2218
2219
2219 def _get_instance_cached(self):
2220 def _get_instance_cached(self):
2220 @cache_region('long_term')
2221 @cache_region('long_term')
2221 def _get_repo(cache_key):
2222 def _get_repo(cache_key):
2222 return self._get_instance()
2223 return self._get_instance()
2223
2224
2224 invalidator_context = CacheKey.repo_context_cache(
2225 invalidator_context = CacheKey.repo_context_cache(
2225 _get_repo, self.repo_name, None, thread_scoped=True)
2226 _get_repo, self.repo_name, None, thread_scoped=True)
2226
2227
2227 with invalidator_context as context:
2228 with invalidator_context as context:
2228 context.invalidate()
2229 context.invalidate()
2229 repo = context.compute()
2230 repo = context.compute()
2230
2231
2231 return repo
2232 return repo
2232
2233
2233 def _get_instance(self, cache=True, config=None):
2234 def _get_instance(self, cache=True, config=None):
2234 config = config or self._config
2235 config = config or self._config
2235 custom_wire = {
2236 custom_wire = {
2236 'cache': cache # controls the vcs.remote cache
2237 'cache': cache # controls the vcs.remote cache
2237 }
2238 }
2238 repo = get_vcs_instance(
2239 repo = get_vcs_instance(
2239 repo_path=safe_str(self.repo_full_path),
2240 repo_path=safe_str(self.repo_full_path),
2240 config=config,
2241 config=config,
2241 with_wire=custom_wire,
2242 with_wire=custom_wire,
2242 create=False,
2243 create=False,
2243 _vcs_alias=self.repo_type)
2244 _vcs_alias=self.repo_type)
2244
2245
2245 return repo
2246 return repo
2246
2247
2247 def __json__(self):
2248 def __json__(self):
2248 return {'landing_rev': self.landing_rev}
2249 return {'landing_rev': self.landing_rev}
2249
2250
2250 def get_dict(self):
2251 def get_dict(self):
2251
2252
2252 # Since we transformed `repo_name` to a hybrid property, we need to
2253 # Since we transformed `repo_name` to a hybrid property, we need to
2253 # keep compatibility with the code which uses `repo_name` field.
2254 # keep compatibility with the code which uses `repo_name` field.
2254
2255
2255 result = super(Repository, self).get_dict()
2256 result = super(Repository, self).get_dict()
2256 result['repo_name'] = result.pop('_repo_name', None)
2257 result['repo_name'] = result.pop('_repo_name', None)
2257 return result
2258 return result
2258
2259
2259
2260
2260 class RepoGroup(Base, BaseModel):
2261 class RepoGroup(Base, BaseModel):
2261 __tablename__ = 'groups'
2262 __tablename__ = 'groups'
2262 __table_args__ = (
2263 __table_args__ = (
2263 UniqueConstraint('group_name', 'group_parent_id'),
2264 UniqueConstraint('group_name', 'group_parent_id'),
2264 CheckConstraint('group_id != group_parent_id'),
2265 CheckConstraint('group_id != group_parent_id'),
2265 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2266 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2266 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2267 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2267 )
2268 )
2268 __mapper_args__ = {'order_by': 'group_name'}
2269 __mapper_args__ = {'order_by': 'group_name'}
2269
2270
2270 CHOICES_SEPARATOR = '/' # used to generate select2 choices for nested groups
2271 CHOICES_SEPARATOR = '/' # used to generate select2 choices for nested groups
2271
2272
2272 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2273 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2273 group_name = Column("group_name", String(255), nullable=False, unique=True, default=None)
2274 group_name = Column("group_name", String(255), nullable=False, unique=True, default=None)
2274 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
2275 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
2275 group_description = Column("group_description", String(10000), nullable=True, unique=None, default=None)
2276 group_description = Column("group_description", String(10000), nullable=True, unique=None, default=None)
2276 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
2277 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
2277 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
2278 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
2278 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2279 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2279 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
2280 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
2280 personal = Column('personal', Boolean(), nullable=True, unique=None, default=None)
2281 personal = Column('personal', Boolean(), nullable=True, unique=None, default=None)
2281
2282
2282 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
2283 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
2283 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
2284 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
2284 parent_group = relationship('RepoGroup', remote_side=group_id)
2285 parent_group = relationship('RepoGroup', remote_side=group_id)
2285 user = relationship('User')
2286 user = relationship('User')
2286 integrations = relationship('Integration',
2287 integrations = relationship('Integration',
2287 cascade="all, delete, delete-orphan")
2288 cascade="all, delete, delete-orphan")
2288
2289
2289 def __init__(self, group_name='', parent_group=None):
2290 def __init__(self, group_name='', parent_group=None):
2290 self.group_name = group_name
2291 self.group_name = group_name
2291 self.parent_group = parent_group
2292 self.parent_group = parent_group
2292
2293
2293 def __unicode__(self):
2294 def __unicode__(self):
2294 return u"<%s('id:%s:%s')>" % (
2295 return u"<%s('id:%s:%s')>" % (
2295 self.__class__.__name__, self.group_id, self.group_name)
2296 self.__class__.__name__, self.group_id, self.group_name)
2296
2297
2297 @hybrid_property
2298 @hybrid_property
2298 def description_safe(self):
2299 def description_safe(self):
2299 from rhodecode.lib import helpers as h
2300 from rhodecode.lib import helpers as h
2300 return h.escape(self.group_description)
2301 return h.escape(self.group_description)
2301
2302
2302 @classmethod
2303 @classmethod
2303 def _generate_choice(cls, repo_group):
2304 def _generate_choice(cls, repo_group):
2304 from webhelpers.html import literal as _literal
2305 from webhelpers.html import literal as _literal
2305 _name = lambda k: _literal(cls.CHOICES_SEPARATOR.join(k))
2306 _name = lambda k: _literal(cls.CHOICES_SEPARATOR.join(k))
2306 return repo_group.group_id, _name(repo_group.full_path_splitted)
2307 return repo_group.group_id, _name(repo_group.full_path_splitted)
2307
2308
2308 @classmethod
2309 @classmethod
2309 def groups_choices(cls, groups=None, show_empty_group=True):
2310 def groups_choices(cls, groups=None, show_empty_group=True):
2310 if not groups:
2311 if not groups:
2311 groups = cls.query().all()
2312 groups = cls.query().all()
2312
2313
2313 repo_groups = []
2314 repo_groups = []
2314 if show_empty_group:
2315 if show_empty_group:
2315 repo_groups = [(-1, u'-- %s --' % _('No parent'))]
2316 repo_groups = [(-1, u'-- %s --' % _('No parent'))]
2316
2317
2317 repo_groups.extend([cls._generate_choice(x) for x in groups])
2318 repo_groups.extend([cls._generate_choice(x) for x in groups])
2318
2319
2319 repo_groups = sorted(
2320 repo_groups = sorted(
2320 repo_groups, key=lambda t: t[1].split(cls.CHOICES_SEPARATOR)[0])
2321 repo_groups, key=lambda t: t[1].split(cls.CHOICES_SEPARATOR)[0])
2321 return repo_groups
2322 return repo_groups
2322
2323
2323 @classmethod
2324 @classmethod
2324 def url_sep(cls):
2325 def url_sep(cls):
2325 return URL_SEP
2326 return URL_SEP
2326
2327
2327 @classmethod
2328 @classmethod
2328 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
2329 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
2329 if case_insensitive:
2330 if case_insensitive:
2330 gr = cls.query().filter(func.lower(cls.group_name)
2331 gr = cls.query().filter(func.lower(cls.group_name)
2331 == func.lower(group_name))
2332 == func.lower(group_name))
2332 else:
2333 else:
2333 gr = cls.query().filter(cls.group_name == group_name)
2334 gr = cls.query().filter(cls.group_name == group_name)
2334 if cache:
2335 if cache:
2335 name_key = _hash_key(group_name)
2336 name_key = _hash_key(group_name)
2336 gr = gr.options(
2337 gr = gr.options(
2337 FromCache("sql_cache_short", "get_group_%s" % name_key))
2338 FromCache("sql_cache_short", "get_group_%s" % name_key))
2338 return gr.scalar()
2339 return gr.scalar()
2339
2340
2340 @classmethod
2341 @classmethod
2341 def get_user_personal_repo_group(cls, user_id):
2342 def get_user_personal_repo_group(cls, user_id):
2342 user = User.get(user_id)
2343 user = User.get(user_id)
2343 if user.username == User.DEFAULT_USER:
2344 if user.username == User.DEFAULT_USER:
2344 return None
2345 return None
2345
2346
2346 return cls.query()\
2347 return cls.query()\
2347 .filter(cls.personal == true()) \
2348 .filter(cls.personal == true()) \
2348 .filter(cls.user == user).scalar()
2349 .filter(cls.user == user).scalar()
2349
2350
2350 @classmethod
2351 @classmethod
2351 def get_all_repo_groups(cls, user_id=Optional(None), group_id=Optional(None),
2352 def get_all_repo_groups(cls, user_id=Optional(None), group_id=Optional(None),
2352 case_insensitive=True):
2353 case_insensitive=True):
2353 q = RepoGroup.query()
2354 q = RepoGroup.query()
2354
2355
2355 if not isinstance(user_id, Optional):
2356 if not isinstance(user_id, Optional):
2356 q = q.filter(RepoGroup.user_id == user_id)
2357 q = q.filter(RepoGroup.user_id == user_id)
2357
2358
2358 if not isinstance(group_id, Optional):
2359 if not isinstance(group_id, Optional):
2359 q = q.filter(RepoGroup.group_parent_id == group_id)
2360 q = q.filter(RepoGroup.group_parent_id == group_id)
2360
2361
2361 if case_insensitive:
2362 if case_insensitive:
2362 q = q.order_by(func.lower(RepoGroup.group_name))
2363 q = q.order_by(func.lower(RepoGroup.group_name))
2363 else:
2364 else:
2364 q = q.order_by(RepoGroup.group_name)
2365 q = q.order_by(RepoGroup.group_name)
2365 return q.all()
2366 return q.all()
2366
2367
2367 @property
2368 @property
2368 def parents(self):
2369 def parents(self):
2369 parents_recursion_limit = 10
2370 parents_recursion_limit = 10
2370 groups = []
2371 groups = []
2371 if self.parent_group is None:
2372 if self.parent_group is None:
2372 return groups
2373 return groups
2373 cur_gr = self.parent_group
2374 cur_gr = self.parent_group
2374 groups.insert(0, cur_gr)
2375 groups.insert(0, cur_gr)
2375 cnt = 0
2376 cnt = 0
2376 while 1:
2377 while 1:
2377 cnt += 1
2378 cnt += 1
2378 gr = getattr(cur_gr, 'parent_group', None)
2379 gr = getattr(cur_gr, 'parent_group', None)
2379 cur_gr = cur_gr.parent_group
2380 cur_gr = cur_gr.parent_group
2380 if gr is None:
2381 if gr is None:
2381 break
2382 break
2382 if cnt == parents_recursion_limit:
2383 if cnt == parents_recursion_limit:
2383 # this will prevent accidental infinit loops
2384 # this will prevent accidental infinit loops
2384 log.error(('more than %s parents found for group %s, stopping '
2385 log.error(('more than %s parents found for group %s, stopping '
2385 'recursive parent fetching' % (parents_recursion_limit, self)))
2386 'recursive parent fetching' % (parents_recursion_limit, self)))
2386 break
2387 break
2387
2388
2388 groups.insert(0, gr)
2389 groups.insert(0, gr)
2389 return groups
2390 return groups
2390
2391
2391 @property
2392 @property
2392 def last_db_change(self):
2393 def last_db_change(self):
2393 return self.updated_on
2394 return self.updated_on
2394
2395
2395 @property
2396 @property
2396 def children(self):
2397 def children(self):
2397 return RepoGroup.query().filter(RepoGroup.parent_group == self)
2398 return RepoGroup.query().filter(RepoGroup.parent_group == self)
2398
2399
2399 @property
2400 @property
2400 def name(self):
2401 def name(self):
2401 return self.group_name.split(RepoGroup.url_sep())[-1]
2402 return self.group_name.split(RepoGroup.url_sep())[-1]
2402
2403
2403 @property
2404 @property
2404 def full_path(self):
2405 def full_path(self):
2405 return self.group_name
2406 return self.group_name
2406
2407
2407 @property
2408 @property
2408 def full_path_splitted(self):
2409 def full_path_splitted(self):
2409 return self.group_name.split(RepoGroup.url_sep())
2410 return self.group_name.split(RepoGroup.url_sep())
2410
2411
2411 @property
2412 @property
2412 def repositories(self):
2413 def repositories(self):
2413 return Repository.query()\
2414 return Repository.query()\
2414 .filter(Repository.group == self)\
2415 .filter(Repository.group == self)\
2415 .order_by(Repository.repo_name)
2416 .order_by(Repository.repo_name)
2416
2417
2417 @property
2418 @property
2418 def repositories_recursive_count(self):
2419 def repositories_recursive_count(self):
2419 cnt = self.repositories.count()
2420 cnt = self.repositories.count()
2420
2421
2421 def children_count(group):
2422 def children_count(group):
2422 cnt = 0
2423 cnt = 0
2423 for child in group.children:
2424 for child in group.children:
2424 cnt += child.repositories.count()
2425 cnt += child.repositories.count()
2425 cnt += children_count(child)
2426 cnt += children_count(child)
2426 return cnt
2427 return cnt
2427
2428
2428 return cnt + children_count(self)
2429 return cnt + children_count(self)
2429
2430
2430 def _recursive_objects(self, include_repos=True):
2431 def _recursive_objects(self, include_repos=True):
2431 all_ = []
2432 all_ = []
2432
2433
2433 def _get_members(root_gr):
2434 def _get_members(root_gr):
2434 if include_repos:
2435 if include_repos:
2435 for r in root_gr.repositories:
2436 for r in root_gr.repositories:
2436 all_.append(r)
2437 all_.append(r)
2437 childs = root_gr.children.all()
2438 childs = root_gr.children.all()
2438 if childs:
2439 if childs:
2439 for gr in childs:
2440 for gr in childs:
2440 all_.append(gr)
2441 all_.append(gr)
2441 _get_members(gr)
2442 _get_members(gr)
2442
2443
2443 _get_members(self)
2444 _get_members(self)
2444 return [self] + all_
2445 return [self] + all_
2445
2446
2446 def recursive_groups_and_repos(self):
2447 def recursive_groups_and_repos(self):
2447 """
2448 """
2448 Recursive return all groups, with repositories in those groups
2449 Recursive return all groups, with repositories in those groups
2449 """
2450 """
2450 return self._recursive_objects()
2451 return self._recursive_objects()
2451
2452
2452 def recursive_groups(self):
2453 def recursive_groups(self):
2453 """
2454 """
2454 Returns all children groups for this group including children of children
2455 Returns all children groups for this group including children of children
2455 """
2456 """
2456 return self._recursive_objects(include_repos=False)
2457 return self._recursive_objects(include_repos=False)
2457
2458
2458 def get_new_name(self, group_name):
2459 def get_new_name(self, group_name):
2459 """
2460 """
2460 returns new full group name based on parent and new name
2461 returns new full group name based on parent and new name
2461
2462
2462 :param group_name:
2463 :param group_name:
2463 """
2464 """
2464 path_prefix = (self.parent_group.full_path_splitted if
2465 path_prefix = (self.parent_group.full_path_splitted if
2465 self.parent_group else [])
2466 self.parent_group else [])
2466 return RepoGroup.url_sep().join(path_prefix + [group_name])
2467 return RepoGroup.url_sep().join(path_prefix + [group_name])
2467
2468
2468 def permissions(self, with_admins=True, with_owner=True):
2469 def permissions(self, with_admins=True, with_owner=True):
2469 q = UserRepoGroupToPerm.query().filter(UserRepoGroupToPerm.group == self)
2470 q = UserRepoGroupToPerm.query().filter(UserRepoGroupToPerm.group == self)
2470 q = q.options(joinedload(UserRepoGroupToPerm.group),
2471 q = q.options(joinedload(UserRepoGroupToPerm.group),
2471 joinedload(UserRepoGroupToPerm.user),
2472 joinedload(UserRepoGroupToPerm.user),
2472 joinedload(UserRepoGroupToPerm.permission),)
2473 joinedload(UserRepoGroupToPerm.permission),)
2473
2474
2474 # get owners and admins and permissions. We do a trick of re-writing
2475 # get owners and admins and permissions. We do a trick of re-writing
2475 # objects from sqlalchemy to named-tuples due to sqlalchemy session
2476 # objects from sqlalchemy to named-tuples due to sqlalchemy session
2476 # has a global reference and changing one object propagates to all
2477 # has a global reference and changing one object propagates to all
2477 # others. This means if admin is also an owner admin_row that change
2478 # others. This means if admin is also an owner admin_row that change
2478 # would propagate to both objects
2479 # would propagate to both objects
2479 perm_rows = []
2480 perm_rows = []
2480 for _usr in q.all():
2481 for _usr in q.all():
2481 usr = AttributeDict(_usr.user.get_dict())
2482 usr = AttributeDict(_usr.user.get_dict())
2482 usr.permission = _usr.permission.permission_name
2483 usr.permission = _usr.permission.permission_name
2483 perm_rows.append(usr)
2484 perm_rows.append(usr)
2484
2485
2485 # filter the perm rows by 'default' first and then sort them by
2486 # filter the perm rows by 'default' first and then sort them by
2486 # admin,write,read,none permissions sorted again alphabetically in
2487 # admin,write,read,none permissions sorted again alphabetically in
2487 # each group
2488 # each group
2488 perm_rows = sorted(perm_rows, key=display_sort)
2489 perm_rows = sorted(perm_rows, key=display_sort)
2489
2490
2490 _admin_perm = 'group.admin'
2491 _admin_perm = 'group.admin'
2491 owner_row = []
2492 owner_row = []
2492 if with_owner:
2493 if with_owner:
2493 usr = AttributeDict(self.user.get_dict())
2494 usr = AttributeDict(self.user.get_dict())
2494 usr.owner_row = True
2495 usr.owner_row = True
2495 usr.permission = _admin_perm
2496 usr.permission = _admin_perm
2496 owner_row.append(usr)
2497 owner_row.append(usr)
2497
2498
2498 super_admin_rows = []
2499 super_admin_rows = []
2499 if with_admins:
2500 if with_admins:
2500 for usr in User.get_all_super_admins():
2501 for usr in User.get_all_super_admins():
2501 # if this admin is also owner, don't double the record
2502 # if this admin is also owner, don't double the record
2502 if usr.user_id == owner_row[0].user_id:
2503 if usr.user_id == owner_row[0].user_id:
2503 owner_row[0].admin_row = True
2504 owner_row[0].admin_row = True
2504 else:
2505 else:
2505 usr = AttributeDict(usr.get_dict())
2506 usr = AttributeDict(usr.get_dict())
2506 usr.admin_row = True
2507 usr.admin_row = True
2507 usr.permission = _admin_perm
2508 usr.permission = _admin_perm
2508 super_admin_rows.append(usr)
2509 super_admin_rows.append(usr)
2509
2510
2510 return super_admin_rows + owner_row + perm_rows
2511 return super_admin_rows + owner_row + perm_rows
2511
2512
2512 def permission_user_groups(self):
2513 def permission_user_groups(self):
2513 q = UserGroupRepoGroupToPerm.query().filter(UserGroupRepoGroupToPerm.group == self)
2514 q = UserGroupRepoGroupToPerm.query().filter(UserGroupRepoGroupToPerm.group == self)
2514 q = q.options(joinedload(UserGroupRepoGroupToPerm.group),
2515 q = q.options(joinedload(UserGroupRepoGroupToPerm.group),
2515 joinedload(UserGroupRepoGroupToPerm.users_group),
2516 joinedload(UserGroupRepoGroupToPerm.users_group),
2516 joinedload(UserGroupRepoGroupToPerm.permission),)
2517 joinedload(UserGroupRepoGroupToPerm.permission),)
2517
2518
2518 perm_rows = []
2519 perm_rows = []
2519 for _user_group in q.all():
2520 for _user_group in q.all():
2520 usr = AttributeDict(_user_group.users_group.get_dict())
2521 usr = AttributeDict(_user_group.users_group.get_dict())
2521 usr.permission = _user_group.permission.permission_name
2522 usr.permission = _user_group.permission.permission_name
2522 perm_rows.append(usr)
2523 perm_rows.append(usr)
2523
2524
2524 return perm_rows
2525 return perm_rows
2525
2526
2526 def get_api_data(self):
2527 def get_api_data(self):
2527 """
2528 """
2528 Common function for generating api data
2529 Common function for generating api data
2529
2530
2530 """
2531 """
2531 group = self
2532 group = self
2532 data = {
2533 data = {
2533 'group_id': group.group_id,
2534 'group_id': group.group_id,
2534 'group_name': group.group_name,
2535 'group_name': group.group_name,
2535 'group_description': group.description_safe,
2536 'group_description': group.description_safe,
2536 'parent_group': group.parent_group.group_name if group.parent_group else None,
2537 'parent_group': group.parent_group.group_name if group.parent_group else None,
2537 'repositories': [x.repo_name for x in group.repositories],
2538 'repositories': [x.repo_name for x in group.repositories],
2538 'owner': group.user.username,
2539 'owner': group.user.username,
2539 }
2540 }
2540 return data
2541 return data
2541
2542
2542
2543
2543 class Permission(Base, BaseModel):
2544 class Permission(Base, BaseModel):
2544 __tablename__ = 'permissions'
2545 __tablename__ = 'permissions'
2545 __table_args__ = (
2546 __table_args__ = (
2546 Index('p_perm_name_idx', 'permission_name'),
2547 Index('p_perm_name_idx', 'permission_name'),
2547 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2548 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2548 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2549 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
2549 )
2550 )
2550 PERMS = [
2551 PERMS = [
2551 ('hg.admin', _('RhodeCode Super Administrator')),
2552 ('hg.admin', _('RhodeCode Super Administrator')),
2552
2553
2553 ('repository.none', _('Repository no access')),
2554 ('repository.none', _('Repository no access')),
2554 ('repository.read', _('Repository read access')),
2555 ('repository.read', _('Repository read access')),
2555 ('repository.write', _('Repository write access')),
2556 ('repository.write', _('Repository write access')),
2556 ('repository.admin', _('Repository admin access')),
2557 ('repository.admin', _('Repository admin access')),
2557
2558
2558 ('group.none', _('Repository group no access')),
2559 ('group.none', _('Repository group no access')),
2559 ('group.read', _('Repository group read access')),
2560 ('group.read', _('Repository group read access')),
2560 ('group.write', _('Repository group write access')),
2561 ('group.write', _('Repository group write access')),
2561 ('group.admin', _('Repository group admin access')),
2562 ('group.admin', _('Repository group admin access')),
2562
2563
2563 ('usergroup.none', _('User group no access')),
2564 ('usergroup.none', _('User group no access')),
2564 ('usergroup.read', _('User group read access')),
2565 ('usergroup.read', _('User group read access')),
2565 ('usergroup.write', _('User group write access')),
2566 ('usergroup.write', _('User group write access')),
2566 ('usergroup.admin', _('User group admin access')),
2567 ('usergroup.admin', _('User group admin access')),
2567
2568
2568 ('hg.repogroup.create.false', _('Repository Group creation disabled')),
2569 ('hg.repogroup.create.false', _('Repository Group creation disabled')),
2569 ('hg.repogroup.create.true', _('Repository Group creation enabled')),
2570 ('hg.repogroup.create.true', _('Repository Group creation enabled')),
2570
2571
2571 ('hg.usergroup.create.false', _('User Group creation disabled')),
2572 ('hg.usergroup.create.false', _('User Group creation disabled')),
2572 ('hg.usergroup.create.true', _('User Group creation enabled')),
2573 ('hg.usergroup.create.true', _('User Group creation enabled')),
2573
2574
2574 ('hg.create.none', _('Repository creation disabled')),
2575 ('hg.create.none', _('Repository creation disabled')),
2575 ('hg.create.repository', _('Repository creation enabled')),
2576 ('hg.create.repository', _('Repository creation enabled')),
2576 ('hg.create.write_on_repogroup.true', _('Repository creation enabled with write permission to a repository group')),
2577 ('hg.create.write_on_repogroup.true', _('Repository creation enabled with write permission to a repository group')),
2577 ('hg.create.write_on_repogroup.false', _('Repository creation disabled with write permission to a repository group')),
2578 ('hg.create.write_on_repogroup.false', _('Repository creation disabled with write permission to a repository group')),
2578
2579
2579 ('hg.fork.none', _('Repository forking disabled')),
2580 ('hg.fork.none', _('Repository forking disabled')),
2580 ('hg.fork.repository', _('Repository forking enabled')),
2581 ('hg.fork.repository', _('Repository forking enabled')),
2581
2582
2582 ('hg.register.none', _('Registration disabled')),
2583 ('hg.register.none', _('Registration disabled')),
2583 ('hg.register.manual_activate', _('User Registration with manual account activation')),
2584 ('hg.register.manual_activate', _('User Registration with manual account activation')),
2584 ('hg.register.auto_activate', _('User Registration with automatic account activation')),
2585 ('hg.register.auto_activate', _('User Registration with automatic account activation')),
2585
2586
2586 ('hg.password_reset.enabled', _('Password reset enabled')),
2587 ('hg.password_reset.enabled', _('Password reset enabled')),
2587 ('hg.password_reset.hidden', _('Password reset hidden')),
2588 ('hg.password_reset.hidden', _('Password reset hidden')),
2588 ('hg.password_reset.disabled', _('Password reset disabled')),
2589 ('hg.password_reset.disabled', _('Password reset disabled')),
2589
2590
2590 ('hg.extern_activate.manual', _('Manual activation of external account')),
2591 ('hg.extern_activate.manual', _('Manual activation of external account')),
2591 ('hg.extern_activate.auto', _('Automatic activation of external account')),
2592 ('hg.extern_activate.auto', _('Automatic activation of external account')),
2592
2593
2593 ('hg.inherit_default_perms.false', _('Inherit object permissions from default user disabled')),
2594 ('hg.inherit_default_perms.false', _('Inherit object permissions from default user disabled')),
2594 ('hg.inherit_default_perms.true', _('Inherit object permissions from default user enabled')),
2595 ('hg.inherit_default_perms.true', _('Inherit object permissions from default user enabled')),
2595 ]
2596 ]
2596
2597
2597 # definition of system default permissions for DEFAULT user
2598 # definition of system default permissions for DEFAULT user
2598 DEFAULT_USER_PERMISSIONS = [
2599 DEFAULT_USER_PERMISSIONS = [
2599 'repository.read',
2600 'repository.read',
2600 'group.read',
2601 'group.read',
2601 'usergroup.read',
2602 'usergroup.read',
2602 'hg.create.repository',
2603 'hg.create.repository',
2603 'hg.repogroup.create.false',
2604 'hg.repogroup.create.false',
2604 'hg.usergroup.create.false',
2605 'hg.usergroup.create.false',
2605 'hg.create.write_on_repogroup.true',
2606 'hg.create.write_on_repogroup.true',
2606 'hg.fork.repository',
2607 'hg.fork.repository',
2607 'hg.register.manual_activate',
2608 'hg.register.manual_activate',
2608 'hg.password_reset.enabled',
2609 'hg.password_reset.enabled',
2609 'hg.extern_activate.auto',
2610 'hg.extern_activate.auto',
2610 'hg.inherit_default_perms.true',
2611 'hg.inherit_default_perms.true',
2611 ]
2612 ]
2612
2613
2613 # defines which permissions are more important higher the more important
2614 # defines which permissions are more important higher the more important
2614 # Weight defines which permissions are more important.
2615 # Weight defines which permissions are more important.
2615 # The higher number the more important.
2616 # The higher number the more important.
2616 PERM_WEIGHTS = {
2617 PERM_WEIGHTS = {
2617 'repository.none': 0,
2618 'repository.none': 0,
2618 'repository.read': 1,
2619 'repository.read': 1,
2619 'repository.write': 3,
2620 'repository.write': 3,
2620 'repository.admin': 4,
2621 'repository.admin': 4,
2621
2622
2622 'group.none': 0,
2623 'group.none': 0,
2623 'group.read': 1,
2624 'group.read': 1,
2624 'group.write': 3,
2625 'group.write': 3,
2625 'group.admin': 4,
2626 'group.admin': 4,
2626
2627
2627 'usergroup.none': 0,
2628 'usergroup.none': 0,
2628 'usergroup.read': 1,
2629 'usergroup.read': 1,
2629 'usergroup.write': 3,
2630 'usergroup.write': 3,
2630 'usergroup.admin': 4,
2631 'usergroup.admin': 4,
2631
2632
2632 'hg.repogroup.create.false': 0,
2633 'hg.repogroup.create.false': 0,
2633 'hg.repogroup.create.true': 1,
2634 'hg.repogroup.create.true': 1,
2634
2635
2635 'hg.usergroup.create.false': 0,
2636 'hg.usergroup.create.false': 0,
2636 'hg.usergroup.create.true': 1,
2637 'hg.usergroup.create.true': 1,
2637
2638
2638 'hg.fork.none': 0,
2639 'hg.fork.none': 0,
2639 'hg.fork.repository': 1,
2640 'hg.fork.repository': 1,
2640 'hg.create.none': 0,
2641 'hg.create.none': 0,
2641 'hg.create.repository': 1
2642 'hg.create.repository': 1
2642 }
2643 }
2643
2644
2644 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2645 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2645 permission_name = Column("permission_name", String(255), nullable=True, unique=None, default=None)
2646 permission_name = Column("permission_name", String(255), nullable=True, unique=None, default=None)
2646 permission_longname = Column("permission_longname", String(255), nullable=True, unique=None, default=None)
2647 permission_longname = Column("permission_longname", String(255), nullable=True, unique=None, default=None)
2647
2648
2648 def __unicode__(self):
2649 def __unicode__(self):
2649 return u"<%s('%s:%s')>" % (
2650 return u"<%s('%s:%s')>" % (
2650 self.__class__.__name__, self.permission_id, self.permission_name
2651 self.__class__.__name__, self.permission_id, self.permission_name
2651 )
2652 )
2652
2653
2653 @classmethod
2654 @classmethod
2654 def get_by_key(cls, key):
2655 def get_by_key(cls, key):
2655 return cls.query().filter(cls.permission_name == key).scalar()
2656 return cls.query().filter(cls.permission_name == key).scalar()
2656
2657
2657 @classmethod
2658 @classmethod
2658 def get_default_repo_perms(cls, user_id, repo_id=None):
2659 def get_default_repo_perms(cls, user_id, repo_id=None):
2659 q = Session().query(UserRepoToPerm, Repository, Permission)\
2660 q = Session().query(UserRepoToPerm, Repository, Permission)\
2660 .join((Permission, UserRepoToPerm.permission_id == Permission.permission_id))\
2661 .join((Permission, UserRepoToPerm.permission_id == Permission.permission_id))\
2661 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
2662 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
2662 .filter(UserRepoToPerm.user_id == user_id)
2663 .filter(UserRepoToPerm.user_id == user_id)
2663 if repo_id:
2664 if repo_id:
2664 q = q.filter(UserRepoToPerm.repository_id == repo_id)
2665 q = q.filter(UserRepoToPerm.repository_id == repo_id)
2665 return q.all()
2666 return q.all()
2666
2667
2667 @classmethod
2668 @classmethod
2668 def get_default_repo_perms_from_user_group(cls, user_id, repo_id=None):
2669 def get_default_repo_perms_from_user_group(cls, user_id, repo_id=None):
2669 q = Session().query(UserGroupRepoToPerm, Repository, Permission)\
2670 q = Session().query(UserGroupRepoToPerm, Repository, Permission)\
2670 .join(
2671 .join(
2671 Permission,
2672 Permission,
2672 UserGroupRepoToPerm.permission_id == Permission.permission_id)\
2673 UserGroupRepoToPerm.permission_id == Permission.permission_id)\
2673 .join(
2674 .join(
2674 Repository,
2675 Repository,
2675 UserGroupRepoToPerm.repository_id == Repository.repo_id)\
2676 UserGroupRepoToPerm.repository_id == Repository.repo_id)\
2676 .join(
2677 .join(
2677 UserGroup,
2678 UserGroup,
2678 UserGroupRepoToPerm.users_group_id ==
2679 UserGroupRepoToPerm.users_group_id ==
2679 UserGroup.users_group_id)\
2680 UserGroup.users_group_id)\
2680 .join(
2681 .join(
2681 UserGroupMember,
2682 UserGroupMember,
2682 UserGroupRepoToPerm.users_group_id ==
2683 UserGroupRepoToPerm.users_group_id ==
2683 UserGroupMember.users_group_id)\
2684 UserGroupMember.users_group_id)\
2684 .filter(
2685 .filter(
2685 UserGroupMember.user_id == user_id,
2686 UserGroupMember.user_id == user_id,
2686 UserGroup.users_group_active == true())
2687 UserGroup.users_group_active == true())
2687 if repo_id:
2688 if repo_id:
2688 q = q.filter(UserGroupRepoToPerm.repository_id == repo_id)
2689 q = q.filter(UserGroupRepoToPerm.repository_id == repo_id)
2689 return q.all()
2690 return q.all()
2690
2691
2691 @classmethod
2692 @classmethod
2692 def get_default_group_perms(cls, user_id, repo_group_id=None):
2693 def get_default_group_perms(cls, user_id, repo_group_id=None):
2693 q = Session().query(UserRepoGroupToPerm, RepoGroup, Permission)\
2694 q = Session().query(UserRepoGroupToPerm, RepoGroup, Permission)\
2694 .join((Permission, UserRepoGroupToPerm.permission_id == Permission.permission_id))\
2695 .join((Permission, UserRepoGroupToPerm.permission_id == Permission.permission_id))\
2695 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
2696 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
2696 .filter(UserRepoGroupToPerm.user_id == user_id)
2697 .filter(UserRepoGroupToPerm.user_id == user_id)
2697 if repo_group_id:
2698 if repo_group_id:
2698 q = q.filter(UserRepoGroupToPerm.group_id == repo_group_id)
2699 q = q.filter(UserRepoGroupToPerm.group_id == repo_group_id)
2699 return q.all()
2700 return q.all()
2700
2701
2701 @classmethod
2702 @classmethod
2702 def get_default_group_perms_from_user_group(
2703 def get_default_group_perms_from_user_group(
2703 cls, user_id, repo_group_id=None):
2704 cls, user_id, repo_group_id=None):
2704 q = Session().query(UserGroupRepoGroupToPerm, RepoGroup, Permission)\
2705 q = Session().query(UserGroupRepoGroupToPerm, RepoGroup, Permission)\
2705 .join(
2706 .join(
2706 Permission,
2707 Permission,
2707 UserGroupRepoGroupToPerm.permission_id ==
2708 UserGroupRepoGroupToPerm.permission_id ==
2708 Permission.permission_id)\
2709 Permission.permission_id)\
2709 .join(
2710 .join(
2710 RepoGroup,
2711 RepoGroup,
2711 UserGroupRepoGroupToPerm.group_id == RepoGroup.group_id)\
2712 UserGroupRepoGroupToPerm.group_id == RepoGroup.group_id)\
2712 .join(
2713 .join(
2713 UserGroup,
2714 UserGroup,
2714 UserGroupRepoGroupToPerm.users_group_id ==
2715 UserGroupRepoGroupToPerm.users_group_id ==
2715 UserGroup.users_group_id)\
2716 UserGroup.users_group_id)\
2716 .join(
2717 .join(
2717 UserGroupMember,
2718 UserGroupMember,
2718 UserGroupRepoGroupToPerm.users_group_id ==
2719 UserGroupRepoGroupToPerm.users_group_id ==
2719 UserGroupMember.users_group_id)\
2720 UserGroupMember.users_group_id)\
2720 .filter(
2721 .filter(
2721 UserGroupMember.user_id == user_id,
2722 UserGroupMember.user_id == user_id,
2722 UserGroup.users_group_active == true())
2723 UserGroup.users_group_active == true())
2723 if repo_group_id:
2724 if repo_group_id:
2724 q = q.filter(UserGroupRepoGroupToPerm.group_id == repo_group_id)
2725 q = q.filter(UserGroupRepoGroupToPerm.group_id == repo_group_id)
2725 return q.all()
2726 return q.all()
2726
2727
2727 @classmethod
2728 @classmethod
2728 def get_default_user_group_perms(cls, user_id, user_group_id=None):
2729 def get_default_user_group_perms(cls, user_id, user_group_id=None):
2729 q = Session().query(UserUserGroupToPerm, UserGroup, Permission)\
2730 q = Session().query(UserUserGroupToPerm, UserGroup, Permission)\
2730 .join((Permission, UserUserGroupToPerm.permission_id == Permission.permission_id))\
2731 .join((Permission, UserUserGroupToPerm.permission_id == Permission.permission_id))\
2731 .join((UserGroup, UserUserGroupToPerm.user_group_id == UserGroup.users_group_id))\
2732 .join((UserGroup, UserUserGroupToPerm.user_group_id == UserGroup.users_group_id))\
2732 .filter(UserUserGroupToPerm.user_id == user_id)
2733 .filter(UserUserGroupToPerm.user_id == user_id)
2733 if user_group_id:
2734 if user_group_id:
2734 q = q.filter(UserUserGroupToPerm.user_group_id == user_group_id)
2735 q = q.filter(UserUserGroupToPerm.user_group_id == user_group_id)
2735 return q.all()
2736 return q.all()
2736
2737
2737 @classmethod
2738 @classmethod
2738 def get_default_user_group_perms_from_user_group(
2739 def get_default_user_group_perms_from_user_group(
2739 cls, user_id, user_group_id=None):
2740 cls, user_id, user_group_id=None):
2740 TargetUserGroup = aliased(UserGroup, name='target_user_group')
2741 TargetUserGroup = aliased(UserGroup, name='target_user_group')
2741 q = Session().query(UserGroupUserGroupToPerm, UserGroup, Permission)\
2742 q = Session().query(UserGroupUserGroupToPerm, UserGroup, Permission)\
2742 .join(
2743 .join(
2743 Permission,
2744 Permission,
2744 UserGroupUserGroupToPerm.permission_id ==
2745 UserGroupUserGroupToPerm.permission_id ==
2745 Permission.permission_id)\
2746 Permission.permission_id)\
2746 .join(
2747 .join(
2747 TargetUserGroup,
2748 TargetUserGroup,
2748 UserGroupUserGroupToPerm.target_user_group_id ==
2749 UserGroupUserGroupToPerm.target_user_group_id ==
2749 TargetUserGroup.users_group_id)\
2750 TargetUserGroup.users_group_id)\
2750 .join(
2751 .join(
2751 UserGroup,
2752 UserGroup,
2752 UserGroupUserGroupToPerm.user_group_id ==
2753 UserGroupUserGroupToPerm.user_group_id ==
2753 UserGroup.users_group_id)\
2754 UserGroup.users_group_id)\
2754 .join(
2755 .join(
2755 UserGroupMember,
2756 UserGroupMember,
2756 UserGroupUserGroupToPerm.user_group_id ==
2757 UserGroupUserGroupToPerm.user_group_id ==
2757 UserGroupMember.users_group_id)\
2758 UserGroupMember.users_group_id)\
2758 .filter(
2759 .filter(
2759 UserGroupMember.user_id == user_id,
2760 UserGroupMember.user_id == user_id,
2760 UserGroup.users_group_active == true())
2761 UserGroup.users_group_active == true())
2761 if user_group_id:
2762 if user_group_id:
2762 q = q.filter(
2763 q = q.filter(
2763 UserGroupUserGroupToPerm.user_group_id == user_group_id)
2764 UserGroupUserGroupToPerm.user_group_id == user_group_id)
2764
2765
2765 return q.all()
2766 return q.all()
2766
2767
2767
2768
2768 class UserRepoToPerm(Base, BaseModel):
2769 class UserRepoToPerm(Base, BaseModel):
2769 __tablename__ = 'repo_to_perm'
2770 __tablename__ = 'repo_to_perm'
2770 __table_args__ = (
2771 __table_args__ = (
2771 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
2772 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
2772 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2773 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2773 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2774 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2774 )
2775 )
2775 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2776 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2776 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2777 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2777 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2778 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2778 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
2779 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
2779
2780
2780 user = relationship('User')
2781 user = relationship('User')
2781 repository = relationship('Repository')
2782 repository = relationship('Repository')
2782 permission = relationship('Permission')
2783 permission = relationship('Permission')
2783
2784
2784 @classmethod
2785 @classmethod
2785 def create(cls, user, repository, permission):
2786 def create(cls, user, repository, permission):
2786 n = cls()
2787 n = cls()
2787 n.user = user
2788 n.user = user
2788 n.repository = repository
2789 n.repository = repository
2789 n.permission = permission
2790 n.permission = permission
2790 Session().add(n)
2791 Session().add(n)
2791 return n
2792 return n
2792
2793
2793 def __unicode__(self):
2794 def __unicode__(self):
2794 return u'<%s => %s >' % (self.user, self.repository)
2795 return u'<%s => %s >' % (self.user, self.repository)
2795
2796
2796
2797
2797 class UserUserGroupToPerm(Base, BaseModel):
2798 class UserUserGroupToPerm(Base, BaseModel):
2798 __tablename__ = 'user_user_group_to_perm'
2799 __tablename__ = 'user_user_group_to_perm'
2799 __table_args__ = (
2800 __table_args__ = (
2800 UniqueConstraint('user_id', 'user_group_id', 'permission_id'),
2801 UniqueConstraint('user_id', 'user_group_id', 'permission_id'),
2801 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2802 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2802 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2803 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2803 )
2804 )
2804 user_user_group_to_perm_id = Column("user_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2805 user_user_group_to_perm_id = Column("user_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2805 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2806 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2806 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2807 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2807 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2808 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2808
2809
2809 user = relationship('User')
2810 user = relationship('User')
2810 user_group = relationship('UserGroup')
2811 user_group = relationship('UserGroup')
2811 permission = relationship('Permission')
2812 permission = relationship('Permission')
2812
2813
2813 @classmethod
2814 @classmethod
2814 def create(cls, user, user_group, permission):
2815 def create(cls, user, user_group, permission):
2815 n = cls()
2816 n = cls()
2816 n.user = user
2817 n.user = user
2817 n.user_group = user_group
2818 n.user_group = user_group
2818 n.permission = permission
2819 n.permission = permission
2819 Session().add(n)
2820 Session().add(n)
2820 return n
2821 return n
2821
2822
2822 def __unicode__(self):
2823 def __unicode__(self):
2823 return u'<%s => %s >' % (self.user, self.user_group)
2824 return u'<%s => %s >' % (self.user, self.user_group)
2824
2825
2825
2826
2826 class UserToPerm(Base, BaseModel):
2827 class UserToPerm(Base, BaseModel):
2827 __tablename__ = 'user_to_perm'
2828 __tablename__ = 'user_to_perm'
2828 __table_args__ = (
2829 __table_args__ = (
2829 UniqueConstraint('user_id', 'permission_id'),
2830 UniqueConstraint('user_id', 'permission_id'),
2830 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2831 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2831 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2832 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2832 )
2833 )
2833 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2834 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2834 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2835 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2835 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2836 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2836
2837
2837 user = relationship('User')
2838 user = relationship('User')
2838 permission = relationship('Permission', lazy='joined')
2839 permission = relationship('Permission', lazy='joined')
2839
2840
2840 def __unicode__(self):
2841 def __unicode__(self):
2841 return u'<%s => %s >' % (self.user, self.permission)
2842 return u'<%s => %s >' % (self.user, self.permission)
2842
2843
2843
2844
2844 class UserGroupRepoToPerm(Base, BaseModel):
2845 class UserGroupRepoToPerm(Base, BaseModel):
2845 __tablename__ = 'users_group_repo_to_perm'
2846 __tablename__ = 'users_group_repo_to_perm'
2846 __table_args__ = (
2847 __table_args__ = (
2847 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
2848 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
2848 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2849 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2849 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2850 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2850 )
2851 )
2851 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2852 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2852 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2853 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2853 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2854 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2854 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
2855 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
2855
2856
2856 users_group = relationship('UserGroup')
2857 users_group = relationship('UserGroup')
2857 permission = relationship('Permission')
2858 permission = relationship('Permission')
2858 repository = relationship('Repository')
2859 repository = relationship('Repository')
2859
2860
2860 @classmethod
2861 @classmethod
2861 def create(cls, users_group, repository, permission):
2862 def create(cls, users_group, repository, permission):
2862 n = cls()
2863 n = cls()
2863 n.users_group = users_group
2864 n.users_group = users_group
2864 n.repository = repository
2865 n.repository = repository
2865 n.permission = permission
2866 n.permission = permission
2866 Session().add(n)
2867 Session().add(n)
2867 return n
2868 return n
2868
2869
2869 def __unicode__(self):
2870 def __unicode__(self):
2870 return u'<UserGroupRepoToPerm:%s => %s >' % (self.users_group, self.repository)
2871 return u'<UserGroupRepoToPerm:%s => %s >' % (self.users_group, self.repository)
2871
2872
2872
2873
2873 class UserGroupUserGroupToPerm(Base, BaseModel):
2874 class UserGroupUserGroupToPerm(Base, BaseModel):
2874 __tablename__ = 'user_group_user_group_to_perm'
2875 __tablename__ = 'user_group_user_group_to_perm'
2875 __table_args__ = (
2876 __table_args__ = (
2876 UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'),
2877 UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'),
2877 CheckConstraint('target_user_group_id != user_group_id'),
2878 CheckConstraint('target_user_group_id != user_group_id'),
2878 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2879 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2879 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2880 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2880 )
2881 )
2881 user_group_user_group_to_perm_id = Column("user_group_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2882 user_group_user_group_to_perm_id = Column("user_group_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2882 target_user_group_id = Column("target_user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2883 target_user_group_id = Column("target_user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2883 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2884 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2884 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2885 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2885
2886
2886 target_user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id')
2887 target_user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id')
2887 user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.user_group_id==UserGroup.users_group_id')
2888 user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.user_group_id==UserGroup.users_group_id')
2888 permission = relationship('Permission')
2889 permission = relationship('Permission')
2889
2890
2890 @classmethod
2891 @classmethod
2891 def create(cls, target_user_group, user_group, permission):
2892 def create(cls, target_user_group, user_group, permission):
2892 n = cls()
2893 n = cls()
2893 n.target_user_group = target_user_group
2894 n.target_user_group = target_user_group
2894 n.user_group = user_group
2895 n.user_group = user_group
2895 n.permission = permission
2896 n.permission = permission
2896 Session().add(n)
2897 Session().add(n)
2897 return n
2898 return n
2898
2899
2899 def __unicode__(self):
2900 def __unicode__(self):
2900 return u'<UserGroupUserGroup:%s => %s >' % (self.target_user_group, self.user_group)
2901 return u'<UserGroupUserGroup:%s => %s >' % (self.target_user_group, self.user_group)
2901
2902
2902
2903
2903 class UserGroupToPerm(Base, BaseModel):
2904 class UserGroupToPerm(Base, BaseModel):
2904 __tablename__ = 'users_group_to_perm'
2905 __tablename__ = 'users_group_to_perm'
2905 __table_args__ = (
2906 __table_args__ = (
2906 UniqueConstraint('users_group_id', 'permission_id',),
2907 UniqueConstraint('users_group_id', 'permission_id',),
2907 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2908 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2908 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2909 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2909 )
2910 )
2910 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2911 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2911 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2912 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2912 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2913 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2913
2914
2914 users_group = relationship('UserGroup')
2915 users_group = relationship('UserGroup')
2915 permission = relationship('Permission')
2916 permission = relationship('Permission')
2916
2917
2917
2918
2918 class UserRepoGroupToPerm(Base, BaseModel):
2919 class UserRepoGroupToPerm(Base, BaseModel):
2919 __tablename__ = 'user_repo_group_to_perm'
2920 __tablename__ = 'user_repo_group_to_perm'
2920 __table_args__ = (
2921 __table_args__ = (
2921 UniqueConstraint('user_id', 'group_id', 'permission_id'),
2922 UniqueConstraint('user_id', 'group_id', 'permission_id'),
2922 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2923 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2923 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2924 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2924 )
2925 )
2925
2926
2926 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2927 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2927 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2928 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
2928 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
2929 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
2929 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2930 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2930
2931
2931 user = relationship('User')
2932 user = relationship('User')
2932 group = relationship('RepoGroup')
2933 group = relationship('RepoGroup')
2933 permission = relationship('Permission')
2934 permission = relationship('Permission')
2934
2935
2935 @classmethod
2936 @classmethod
2936 def create(cls, user, repository_group, permission):
2937 def create(cls, user, repository_group, permission):
2937 n = cls()
2938 n = cls()
2938 n.user = user
2939 n.user = user
2939 n.group = repository_group
2940 n.group = repository_group
2940 n.permission = permission
2941 n.permission = permission
2941 Session().add(n)
2942 Session().add(n)
2942 return n
2943 return n
2943
2944
2944
2945
2945 class UserGroupRepoGroupToPerm(Base, BaseModel):
2946 class UserGroupRepoGroupToPerm(Base, BaseModel):
2946 __tablename__ = 'users_group_repo_group_to_perm'
2947 __tablename__ = 'users_group_repo_group_to_perm'
2947 __table_args__ = (
2948 __table_args__ = (
2948 UniqueConstraint('users_group_id', 'group_id'),
2949 UniqueConstraint('users_group_id', 'group_id'),
2949 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2950 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2950 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2951 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2951 )
2952 )
2952
2953
2953 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)
2954 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)
2954 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2955 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
2955 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
2956 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
2956 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2957 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
2957
2958
2958 users_group = relationship('UserGroup')
2959 users_group = relationship('UserGroup')
2959 permission = relationship('Permission')
2960 permission = relationship('Permission')
2960 group = relationship('RepoGroup')
2961 group = relationship('RepoGroup')
2961
2962
2962 @classmethod
2963 @classmethod
2963 def create(cls, user_group, repository_group, permission):
2964 def create(cls, user_group, repository_group, permission):
2964 n = cls()
2965 n = cls()
2965 n.users_group = user_group
2966 n.users_group = user_group
2966 n.group = repository_group
2967 n.group = repository_group
2967 n.permission = permission
2968 n.permission = permission
2968 Session().add(n)
2969 Session().add(n)
2969 return n
2970 return n
2970
2971
2971 def __unicode__(self):
2972 def __unicode__(self):
2972 return u'<UserGroupRepoGroupToPerm:%s => %s >' % (self.users_group, self.group)
2973 return u'<UserGroupRepoGroupToPerm:%s => %s >' % (self.users_group, self.group)
2973
2974
2974
2975
2975 class Statistics(Base, BaseModel):
2976 class Statistics(Base, BaseModel):
2976 __tablename__ = 'statistics'
2977 __tablename__ = 'statistics'
2977 __table_args__ = (
2978 __table_args__ = (
2978 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2979 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2979 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2980 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2980 )
2981 )
2981 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2982 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2982 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
2983 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
2983 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
2984 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
2984 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
2985 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
2985 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
2986 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
2986 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
2987 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
2987
2988
2988 repository = relationship('Repository', single_parent=True)
2989 repository = relationship('Repository', single_parent=True)
2989
2990
2990
2991
2991 class UserFollowing(Base, BaseModel):
2992 class UserFollowing(Base, BaseModel):
2992 __tablename__ = 'user_followings'
2993 __tablename__ = 'user_followings'
2993 __table_args__ = (
2994 __table_args__ = (
2994 UniqueConstraint('user_id', 'follows_repository_id'),
2995 UniqueConstraint('user_id', 'follows_repository_id'),
2995 UniqueConstraint('user_id', 'follows_user_id'),
2996 UniqueConstraint('user_id', 'follows_user_id'),
2996 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2997 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2997 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2998 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
2998 )
2999 )
2999
3000
3000 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3001 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3001 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3002 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3002 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
3003 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
3003 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
3004 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
3004 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
3005 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
3005
3006
3006 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
3007 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
3007
3008
3008 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
3009 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
3009 follows_repository = relationship('Repository', order_by='Repository.repo_name')
3010 follows_repository = relationship('Repository', order_by='Repository.repo_name')
3010
3011
3011 @classmethod
3012 @classmethod
3012 def get_repo_followers(cls, repo_id):
3013 def get_repo_followers(cls, repo_id):
3013 return cls.query().filter(cls.follows_repo_id == repo_id)
3014 return cls.query().filter(cls.follows_repo_id == repo_id)
3014
3015
3015
3016
3016 class CacheKey(Base, BaseModel):
3017 class CacheKey(Base, BaseModel):
3017 __tablename__ = 'cache_invalidation'
3018 __tablename__ = 'cache_invalidation'
3018 __table_args__ = (
3019 __table_args__ = (
3019 UniqueConstraint('cache_key'),
3020 UniqueConstraint('cache_key'),
3020 Index('key_idx', 'cache_key'),
3021 Index('key_idx', 'cache_key'),
3021 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3022 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3022 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3023 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3023 )
3024 )
3024 CACHE_TYPE_ATOM = 'ATOM'
3025 CACHE_TYPE_ATOM = 'ATOM'
3025 CACHE_TYPE_RSS = 'RSS'
3026 CACHE_TYPE_RSS = 'RSS'
3026 CACHE_TYPE_README = 'README'
3027 CACHE_TYPE_README = 'README'
3027
3028
3028 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3029 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3029 cache_key = Column("cache_key", String(255), nullable=True, unique=None, default=None)
3030 cache_key = Column("cache_key", String(255), nullable=True, unique=None, default=None)
3030 cache_args = Column("cache_args", String(255), nullable=True, unique=None, default=None)
3031 cache_args = Column("cache_args", String(255), nullable=True, unique=None, default=None)
3031 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
3032 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
3032
3033
3033 def __init__(self, cache_key, cache_args=''):
3034 def __init__(self, cache_key, cache_args=''):
3034 self.cache_key = cache_key
3035 self.cache_key = cache_key
3035 self.cache_args = cache_args
3036 self.cache_args = cache_args
3036 self.cache_active = False
3037 self.cache_active = False
3037
3038
3038 def __unicode__(self):
3039 def __unicode__(self):
3039 return u"<%s('%s:%s[%s]')>" % (
3040 return u"<%s('%s:%s[%s]')>" % (
3040 self.__class__.__name__,
3041 self.__class__.__name__,
3041 self.cache_id, self.cache_key, self.cache_active)
3042 self.cache_id, self.cache_key, self.cache_active)
3042
3043
3043 def _cache_key_partition(self):
3044 def _cache_key_partition(self):
3044 prefix, repo_name, suffix = self.cache_key.partition(self.cache_args)
3045 prefix, repo_name, suffix = self.cache_key.partition(self.cache_args)
3045 return prefix, repo_name, suffix
3046 return prefix, repo_name, suffix
3046
3047
3047 def get_prefix(self):
3048 def get_prefix(self):
3048 """
3049 """
3049 Try to extract prefix from existing cache key. The key could consist
3050 Try to extract prefix from existing cache key. The key could consist
3050 of prefix, repo_name, suffix
3051 of prefix, repo_name, suffix
3051 """
3052 """
3052 # this returns prefix, repo_name, suffix
3053 # this returns prefix, repo_name, suffix
3053 return self._cache_key_partition()[0]
3054 return self._cache_key_partition()[0]
3054
3055
3055 def get_suffix(self):
3056 def get_suffix(self):
3056 """
3057 """
3057 get suffix that might have been used in _get_cache_key to
3058 get suffix that might have been used in _get_cache_key to
3058 generate self.cache_key. Only used for informational purposes
3059 generate self.cache_key. Only used for informational purposes
3059 in repo_edit.mako.
3060 in repo_edit.mako.
3060 """
3061 """
3061 # prefix, repo_name, suffix
3062 # prefix, repo_name, suffix
3062 return self._cache_key_partition()[2]
3063 return self._cache_key_partition()[2]
3063
3064
3064 @classmethod
3065 @classmethod
3065 def delete_all_cache(cls):
3066 def delete_all_cache(cls):
3066 """
3067 """
3067 Delete all cache keys from database.
3068 Delete all cache keys from database.
3068 Should only be run when all instances are down and all entries
3069 Should only be run when all instances are down and all entries
3069 thus stale.
3070 thus stale.
3070 """
3071 """
3071 cls.query().delete()
3072 cls.query().delete()
3072 Session().commit()
3073 Session().commit()
3073
3074
3074 @classmethod
3075 @classmethod
3075 def get_cache_key(cls, repo_name, cache_type):
3076 def get_cache_key(cls, repo_name, cache_type):
3076 """
3077 """
3077
3078
3078 Generate a cache key for this process of RhodeCode instance.
3079 Generate a cache key for this process of RhodeCode instance.
3079 Prefix most likely will be process id or maybe explicitly set
3080 Prefix most likely will be process id or maybe explicitly set
3080 instance_id from .ini file.
3081 instance_id from .ini file.
3081 """
3082 """
3082 import rhodecode
3083 import rhodecode
3083 prefix = safe_unicode(rhodecode.CONFIG.get('instance_id') or '')
3084 prefix = safe_unicode(rhodecode.CONFIG.get('instance_id') or '')
3084
3085
3085 repo_as_unicode = safe_unicode(repo_name)
3086 repo_as_unicode = safe_unicode(repo_name)
3086 key = u'{}_{}'.format(repo_as_unicode, cache_type) \
3087 key = u'{}_{}'.format(repo_as_unicode, cache_type) \
3087 if cache_type else repo_as_unicode
3088 if cache_type else repo_as_unicode
3088
3089
3089 return u'{}{}'.format(prefix, key)
3090 return u'{}{}'.format(prefix, key)
3090
3091
3091 @classmethod
3092 @classmethod
3092 def set_invalidate(cls, repo_name, delete=False):
3093 def set_invalidate(cls, repo_name, delete=False):
3093 """
3094 """
3094 Mark all caches of a repo as invalid in the database.
3095 Mark all caches of a repo as invalid in the database.
3095 """
3096 """
3096
3097
3097 try:
3098 try:
3098 qry = Session().query(cls).filter(cls.cache_args == repo_name)
3099 qry = Session().query(cls).filter(cls.cache_args == repo_name)
3099 if delete:
3100 if delete:
3100 log.debug('cache objects deleted for repo %s',
3101 log.debug('cache objects deleted for repo %s',
3101 safe_str(repo_name))
3102 safe_str(repo_name))
3102 qry.delete()
3103 qry.delete()
3103 else:
3104 else:
3104 log.debug('cache objects marked as invalid for repo %s',
3105 log.debug('cache objects marked as invalid for repo %s',
3105 safe_str(repo_name))
3106 safe_str(repo_name))
3106 qry.update({"cache_active": False})
3107 qry.update({"cache_active": False})
3107
3108
3108 Session().commit()
3109 Session().commit()
3109 except Exception:
3110 except Exception:
3110 log.exception(
3111 log.exception(
3111 'Cache key invalidation failed for repository %s',
3112 'Cache key invalidation failed for repository %s',
3112 safe_str(repo_name))
3113 safe_str(repo_name))
3113 Session().rollback()
3114 Session().rollback()
3114
3115
3115 @classmethod
3116 @classmethod
3116 def get_active_cache(cls, cache_key):
3117 def get_active_cache(cls, cache_key):
3117 inv_obj = cls.query().filter(cls.cache_key == cache_key).scalar()
3118 inv_obj = cls.query().filter(cls.cache_key == cache_key).scalar()
3118 if inv_obj:
3119 if inv_obj:
3119 return inv_obj
3120 return inv_obj
3120 return None
3121 return None
3121
3122
3122 @classmethod
3123 @classmethod
3123 def repo_context_cache(cls, compute_func, repo_name, cache_type,
3124 def repo_context_cache(cls, compute_func, repo_name, cache_type,
3124 thread_scoped=False):
3125 thread_scoped=False):
3125 """
3126 """
3126 @cache_region('long_term')
3127 @cache_region('long_term')
3127 def _heavy_calculation(cache_key):
3128 def _heavy_calculation(cache_key):
3128 return 'result'
3129 return 'result'
3129
3130
3130 cache_context = CacheKey.repo_context_cache(
3131 cache_context = CacheKey.repo_context_cache(
3131 _heavy_calculation, repo_name, cache_type)
3132 _heavy_calculation, repo_name, cache_type)
3132
3133
3133 with cache_context as context:
3134 with cache_context as context:
3134 context.invalidate()
3135 context.invalidate()
3135 computed = context.compute()
3136 computed = context.compute()
3136
3137
3137 assert computed == 'result'
3138 assert computed == 'result'
3138 """
3139 """
3139 from rhodecode.lib import caches
3140 from rhodecode.lib import caches
3140 return caches.InvalidationContext(
3141 return caches.InvalidationContext(
3141 compute_func, repo_name, cache_type, thread_scoped=thread_scoped)
3142 compute_func, repo_name, cache_type, thread_scoped=thread_scoped)
3142
3143
3143
3144
3144 class ChangesetComment(Base, BaseModel):
3145 class ChangesetComment(Base, BaseModel):
3145 __tablename__ = 'changeset_comments'
3146 __tablename__ = 'changeset_comments'
3146 __table_args__ = (
3147 __table_args__ = (
3147 Index('cc_revision_idx', 'revision'),
3148 Index('cc_revision_idx', 'revision'),
3148 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3149 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3149 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3150 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3150 )
3151 )
3151
3152
3152 COMMENT_OUTDATED = u'comment_outdated'
3153 COMMENT_OUTDATED = u'comment_outdated'
3153 COMMENT_TYPE_NOTE = u'note'
3154 COMMENT_TYPE_NOTE = u'note'
3154 COMMENT_TYPE_TODO = u'todo'
3155 COMMENT_TYPE_TODO = u'todo'
3155 COMMENT_TYPES = [COMMENT_TYPE_NOTE, COMMENT_TYPE_TODO]
3156 COMMENT_TYPES = [COMMENT_TYPE_NOTE, COMMENT_TYPE_TODO]
3156
3157
3157 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
3158 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
3158 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3159 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3159 revision = Column('revision', String(40), nullable=True)
3160 revision = Column('revision', String(40), nullable=True)
3160 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3161 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3161 pull_request_version_id = Column("pull_request_version_id", Integer(), ForeignKey('pull_request_versions.pull_request_version_id'), nullable=True)
3162 pull_request_version_id = Column("pull_request_version_id", Integer(), ForeignKey('pull_request_versions.pull_request_version_id'), nullable=True)
3162 line_no = Column('line_no', Unicode(10), nullable=True)
3163 line_no = Column('line_no', Unicode(10), nullable=True)
3163 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
3164 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
3164 f_path = Column('f_path', Unicode(1000), nullable=True)
3165 f_path = Column('f_path', Unicode(1000), nullable=True)
3165 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
3166 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
3166 text = Column('text', UnicodeText().with_variant(UnicodeText(25000), 'mysql'), nullable=False)
3167 text = Column('text', UnicodeText().with_variant(UnicodeText(25000), 'mysql'), nullable=False)
3167 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3168 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3168 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3169 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3169 renderer = Column('renderer', Unicode(64), nullable=True)
3170 renderer = Column('renderer', Unicode(64), nullable=True)
3170 display_state = Column('display_state', Unicode(128), nullable=True)
3171 display_state = Column('display_state', Unicode(128), nullable=True)
3171
3172
3172 comment_type = Column('comment_type', Unicode(128), nullable=True, default=COMMENT_TYPE_NOTE)
3173 comment_type = Column('comment_type', Unicode(128), nullable=True, default=COMMENT_TYPE_NOTE)
3173 resolved_comment_id = Column('resolved_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'), nullable=True)
3174 resolved_comment_id = Column('resolved_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'), nullable=True)
3174 resolved_comment = relationship('ChangesetComment', remote_side=comment_id, backref='resolved_by')
3175 resolved_comment = relationship('ChangesetComment', remote_side=comment_id, backref='resolved_by')
3175 author = relationship('User', lazy='joined')
3176 author = relationship('User', lazy='joined')
3176 repo = relationship('Repository')
3177 repo = relationship('Repository')
3177 status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan", lazy='joined')
3178 status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan", lazy='joined')
3178 pull_request = relationship('PullRequest', lazy='joined')
3179 pull_request = relationship('PullRequest', lazy='joined')
3179 pull_request_version = relationship('PullRequestVersion')
3180 pull_request_version = relationship('PullRequestVersion')
3180
3181
3181 @classmethod
3182 @classmethod
3182 def get_users(cls, revision=None, pull_request_id=None):
3183 def get_users(cls, revision=None, pull_request_id=None):
3183 """
3184 """
3184 Returns user associated with this ChangesetComment. ie those
3185 Returns user associated with this ChangesetComment. ie those
3185 who actually commented
3186 who actually commented
3186
3187
3187 :param cls:
3188 :param cls:
3188 :param revision:
3189 :param revision:
3189 """
3190 """
3190 q = Session().query(User)\
3191 q = Session().query(User)\
3191 .join(ChangesetComment.author)
3192 .join(ChangesetComment.author)
3192 if revision:
3193 if revision:
3193 q = q.filter(cls.revision == revision)
3194 q = q.filter(cls.revision == revision)
3194 elif pull_request_id:
3195 elif pull_request_id:
3195 q = q.filter(cls.pull_request_id == pull_request_id)
3196 q = q.filter(cls.pull_request_id == pull_request_id)
3196 return q.all()
3197 return q.all()
3197
3198
3198 @classmethod
3199 @classmethod
3199 def get_index_from_version(cls, pr_version, versions):
3200 def get_index_from_version(cls, pr_version, versions):
3200 num_versions = [x.pull_request_version_id for x in versions]
3201 num_versions = [x.pull_request_version_id for x in versions]
3201 try:
3202 try:
3202 return num_versions.index(pr_version) +1
3203 return num_versions.index(pr_version) +1
3203 except (IndexError, ValueError):
3204 except (IndexError, ValueError):
3204 return
3205 return
3205
3206
3206 @property
3207 @property
3207 def outdated(self):
3208 def outdated(self):
3208 return self.display_state == self.COMMENT_OUTDATED
3209 return self.display_state == self.COMMENT_OUTDATED
3209
3210
3210 def outdated_at_version(self, version):
3211 def outdated_at_version(self, version):
3211 """
3212 """
3212 Checks if comment is outdated for given pull request version
3213 Checks if comment is outdated for given pull request version
3213 """
3214 """
3214 return self.outdated and self.pull_request_version_id != version
3215 return self.outdated and self.pull_request_version_id != version
3215
3216
3216 def older_than_version(self, version):
3217 def older_than_version(self, version):
3217 """
3218 """
3218 Checks if comment is made from previous version than given
3219 Checks if comment is made from previous version than given
3219 """
3220 """
3220 if version is None:
3221 if version is None:
3221 return self.pull_request_version_id is not None
3222 return self.pull_request_version_id is not None
3222
3223
3223 return self.pull_request_version_id < version
3224 return self.pull_request_version_id < version
3224
3225
3225 @property
3226 @property
3226 def resolved(self):
3227 def resolved(self):
3227 return self.resolved_by[0] if self.resolved_by else None
3228 return self.resolved_by[0] if self.resolved_by else None
3228
3229
3229 @property
3230 @property
3230 def is_todo(self):
3231 def is_todo(self):
3231 return self.comment_type == self.COMMENT_TYPE_TODO
3232 return self.comment_type == self.COMMENT_TYPE_TODO
3232
3233
3233 @property
3234 @property
3234 def is_inline(self):
3235 def is_inline(self):
3235 return self.line_no and self.f_path
3236 return self.line_no and self.f_path
3236
3237
3237 def get_index_version(self, versions):
3238 def get_index_version(self, versions):
3238 return self.get_index_from_version(
3239 return self.get_index_from_version(
3239 self.pull_request_version_id, versions)
3240 self.pull_request_version_id, versions)
3240
3241
3241 def __repr__(self):
3242 def __repr__(self):
3242 if self.comment_id:
3243 if self.comment_id:
3243 return '<DB:Comment #%s>' % self.comment_id
3244 return '<DB:Comment #%s>' % self.comment_id
3244 else:
3245 else:
3245 return '<DB:Comment at %#x>' % id(self)
3246 return '<DB:Comment at %#x>' % id(self)
3246
3247
3247 def get_api_data(self):
3248 def get_api_data(self):
3248 comment = self
3249 comment = self
3249 data = {
3250 data = {
3250 'comment_id': comment.comment_id,
3251 'comment_id': comment.comment_id,
3251 'comment_type': comment.comment_type,
3252 'comment_type': comment.comment_type,
3252 'comment_text': comment.text,
3253 'comment_text': comment.text,
3253 'comment_status': comment.status_change,
3254 'comment_status': comment.status_change,
3254 'comment_f_path': comment.f_path,
3255 'comment_f_path': comment.f_path,
3255 'comment_lineno': comment.line_no,
3256 'comment_lineno': comment.line_no,
3256 'comment_author': comment.author,
3257 'comment_author': comment.author,
3257 'comment_created_on': comment.created_on
3258 'comment_created_on': comment.created_on
3258 }
3259 }
3259 return data
3260 return data
3260
3261
3261 def __json__(self):
3262 def __json__(self):
3262 data = dict()
3263 data = dict()
3263 data.update(self.get_api_data())
3264 data.update(self.get_api_data())
3264 return data
3265 return data
3265
3266
3266
3267
3267 class ChangesetStatus(Base, BaseModel):
3268 class ChangesetStatus(Base, BaseModel):
3268 __tablename__ = 'changeset_statuses'
3269 __tablename__ = 'changeset_statuses'
3269 __table_args__ = (
3270 __table_args__ = (
3270 Index('cs_revision_idx', 'revision'),
3271 Index('cs_revision_idx', 'revision'),
3271 Index('cs_version_idx', 'version'),
3272 Index('cs_version_idx', 'version'),
3272 UniqueConstraint('repo_id', 'revision', 'version'),
3273 UniqueConstraint('repo_id', 'revision', 'version'),
3273 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3274 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3274 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3275 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3275 )
3276 )
3276 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
3277 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
3277 STATUS_APPROVED = 'approved'
3278 STATUS_APPROVED = 'approved'
3278 STATUS_REJECTED = 'rejected'
3279 STATUS_REJECTED = 'rejected'
3279 STATUS_UNDER_REVIEW = 'under_review'
3280 STATUS_UNDER_REVIEW = 'under_review'
3280
3281
3281 STATUSES = [
3282 STATUSES = [
3282 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
3283 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
3283 (STATUS_APPROVED, _("Approved")),
3284 (STATUS_APPROVED, _("Approved")),
3284 (STATUS_REJECTED, _("Rejected")),
3285 (STATUS_REJECTED, _("Rejected")),
3285 (STATUS_UNDER_REVIEW, _("Under Review")),
3286 (STATUS_UNDER_REVIEW, _("Under Review")),
3286 ]
3287 ]
3287
3288
3288 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
3289 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
3289 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3290 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3290 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
3291 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
3291 revision = Column('revision', String(40), nullable=False)
3292 revision = Column('revision', String(40), nullable=False)
3292 status = Column('status', String(128), nullable=False, default=DEFAULT)
3293 status = Column('status', String(128), nullable=False, default=DEFAULT)
3293 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
3294 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
3294 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
3295 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
3295 version = Column('version', Integer(), nullable=False, default=0)
3296 version = Column('version', Integer(), nullable=False, default=0)
3296 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3297 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3297
3298
3298 author = relationship('User', lazy='joined')
3299 author = relationship('User', lazy='joined')
3299 repo = relationship('Repository')
3300 repo = relationship('Repository')
3300 comment = relationship('ChangesetComment', lazy='joined')
3301 comment = relationship('ChangesetComment', lazy='joined')
3301 pull_request = relationship('PullRequest', lazy='joined')
3302 pull_request = relationship('PullRequest', lazy='joined')
3302
3303
3303 def __unicode__(self):
3304 def __unicode__(self):
3304 return u"<%s('%s[v%s]:%s')>" % (
3305 return u"<%s('%s[v%s]:%s')>" % (
3305 self.__class__.__name__,
3306 self.__class__.__name__,
3306 self.status, self.version, self.author
3307 self.status, self.version, self.author
3307 )
3308 )
3308
3309
3309 @classmethod
3310 @classmethod
3310 def get_status_lbl(cls, value):
3311 def get_status_lbl(cls, value):
3311 return dict(cls.STATUSES).get(value)
3312 return dict(cls.STATUSES).get(value)
3312
3313
3313 @property
3314 @property
3314 def status_lbl(self):
3315 def status_lbl(self):
3315 return ChangesetStatus.get_status_lbl(self.status)
3316 return ChangesetStatus.get_status_lbl(self.status)
3316
3317
3317 def get_api_data(self):
3318 def get_api_data(self):
3318 status = self
3319 status = self
3319 data = {
3320 data = {
3320 'status_id': status.changeset_status_id,
3321 'status_id': status.changeset_status_id,
3321 'status': status.status,
3322 'status': status.status,
3322 }
3323 }
3323 return data
3324 return data
3324
3325
3325 def __json__(self):
3326 def __json__(self):
3326 data = dict()
3327 data = dict()
3327 data.update(self.get_api_data())
3328 data.update(self.get_api_data())
3328 return data
3329 return data
3329
3330
3330
3331
3331 class _PullRequestBase(BaseModel):
3332 class _PullRequestBase(BaseModel):
3332 """
3333 """
3333 Common attributes of pull request and version entries.
3334 Common attributes of pull request and version entries.
3334 """
3335 """
3335
3336
3336 # .status values
3337 # .status values
3337 STATUS_NEW = u'new'
3338 STATUS_NEW = u'new'
3338 STATUS_OPEN = u'open'
3339 STATUS_OPEN = u'open'
3339 STATUS_CLOSED = u'closed'
3340 STATUS_CLOSED = u'closed'
3340
3341
3341 title = Column('title', Unicode(255), nullable=True)
3342 title = Column('title', Unicode(255), nullable=True)
3342 description = Column(
3343 description = Column(
3343 'description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'),
3344 'description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'),
3344 nullable=True)
3345 nullable=True)
3345 # new/open/closed status of pull request (not approve/reject/etc)
3346 # new/open/closed status of pull request (not approve/reject/etc)
3346 status = Column('status', Unicode(255), nullable=False, default=STATUS_NEW)
3347 status = Column('status', Unicode(255), nullable=False, default=STATUS_NEW)
3347 created_on = Column(
3348 created_on = Column(
3348 'created_on', DateTime(timezone=False), nullable=False,
3349 'created_on', DateTime(timezone=False), nullable=False,
3349 default=datetime.datetime.now)
3350 default=datetime.datetime.now)
3350 updated_on = Column(
3351 updated_on = Column(
3351 'updated_on', DateTime(timezone=False), nullable=False,
3352 'updated_on', DateTime(timezone=False), nullable=False,
3352 default=datetime.datetime.now)
3353 default=datetime.datetime.now)
3353
3354
3354 @declared_attr
3355 @declared_attr
3355 def user_id(cls):
3356 def user_id(cls):
3356 return Column(
3357 return Column(
3357 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
3358 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
3358 unique=None)
3359 unique=None)
3359
3360
3360 # 500 revisions max
3361 # 500 revisions max
3361 _revisions = Column(
3362 _revisions = Column(
3362 'revisions', UnicodeText().with_variant(UnicodeText(20500), 'mysql'))
3363 'revisions', UnicodeText().with_variant(UnicodeText(20500), 'mysql'))
3363
3364
3364 @declared_attr
3365 @declared_attr
3365 def source_repo_id(cls):
3366 def source_repo_id(cls):
3366 # TODO: dan: rename column to source_repo_id
3367 # TODO: dan: rename column to source_repo_id
3367 return Column(
3368 return Column(
3368 'org_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3369 'org_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3369 nullable=False)
3370 nullable=False)
3370
3371
3371 source_ref = Column('org_ref', Unicode(255), nullable=False)
3372 source_ref = Column('org_ref', Unicode(255), nullable=False)
3372
3373
3373 @declared_attr
3374 @declared_attr
3374 def target_repo_id(cls):
3375 def target_repo_id(cls):
3375 # TODO: dan: rename column to target_repo_id
3376 # TODO: dan: rename column to target_repo_id
3376 return Column(
3377 return Column(
3377 'other_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3378 'other_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3378 nullable=False)
3379 nullable=False)
3379
3380
3380 target_ref = Column('other_ref', Unicode(255), nullable=False)
3381 target_ref = Column('other_ref', Unicode(255), nullable=False)
3381 _shadow_merge_ref = Column('shadow_merge_ref', Unicode(255), nullable=True)
3382 _shadow_merge_ref = Column('shadow_merge_ref', Unicode(255), nullable=True)
3382
3383
3383 # TODO: dan: rename column to last_merge_source_rev
3384 # TODO: dan: rename column to last_merge_source_rev
3384 _last_merge_source_rev = Column(
3385 _last_merge_source_rev = Column(
3385 'last_merge_org_rev', String(40), nullable=True)
3386 'last_merge_org_rev', String(40), nullable=True)
3386 # TODO: dan: rename column to last_merge_target_rev
3387 # TODO: dan: rename column to last_merge_target_rev
3387 _last_merge_target_rev = Column(
3388 _last_merge_target_rev = Column(
3388 'last_merge_other_rev', String(40), nullable=True)
3389 'last_merge_other_rev', String(40), nullable=True)
3389 _last_merge_status = Column('merge_status', Integer(), nullable=True)
3390 _last_merge_status = Column('merge_status', Integer(), nullable=True)
3390 merge_rev = Column('merge_rev', String(40), nullable=True)
3391 merge_rev = Column('merge_rev', String(40), nullable=True)
3391
3392
3392 reviewer_data = Column(
3393 reviewer_data = Column(
3393 'reviewer_data_json', MutationObj.as_mutable(
3394 'reviewer_data_json', MutationObj.as_mutable(
3394 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
3395 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
3395
3396
3396 @property
3397 @property
3397 def reviewer_data_json(self):
3398 def reviewer_data_json(self):
3398 return json.dumps(self.reviewer_data)
3399 return json.dumps(self.reviewer_data)
3399
3400
3400 @hybrid_property
3401 @hybrid_property
3401 def description_safe(self):
3402 def description_safe(self):
3402 from rhodecode.lib import helpers as h
3403 from rhodecode.lib import helpers as h
3403 return h.escape(self.description)
3404 return h.escape(self.description)
3404
3405
3405 @hybrid_property
3406 @hybrid_property
3406 def revisions(self):
3407 def revisions(self):
3407 return self._revisions.split(':') if self._revisions else []
3408 return self._revisions.split(':') if self._revisions else []
3408
3409
3409 @revisions.setter
3410 @revisions.setter
3410 def revisions(self, val):
3411 def revisions(self, val):
3411 self._revisions = ':'.join(val)
3412 self._revisions = ':'.join(val)
3412
3413
3413 @hybrid_property
3414 @hybrid_property
3414 def last_merge_status(self):
3415 def last_merge_status(self):
3415 return safe_int(self._last_merge_status)
3416 return safe_int(self._last_merge_status)
3416
3417
3417 @last_merge_status.setter
3418 @last_merge_status.setter
3418 def last_merge_status(self, val):
3419 def last_merge_status(self, val):
3419 self._last_merge_status = val
3420 self._last_merge_status = val
3420
3421
3421 @declared_attr
3422 @declared_attr
3422 def author(cls):
3423 def author(cls):
3423 return relationship('User', lazy='joined')
3424 return relationship('User', lazy='joined')
3424
3425
3425 @declared_attr
3426 @declared_attr
3426 def source_repo(cls):
3427 def source_repo(cls):
3427 return relationship(
3428 return relationship(
3428 'Repository',
3429 'Repository',
3429 primaryjoin='%s.source_repo_id==Repository.repo_id' % cls.__name__)
3430 primaryjoin='%s.source_repo_id==Repository.repo_id' % cls.__name__)
3430
3431
3431 @property
3432 @property
3432 def source_ref_parts(self):
3433 def source_ref_parts(self):
3433 return self.unicode_to_reference(self.source_ref)
3434 return self.unicode_to_reference(self.source_ref)
3434
3435
3435 @declared_attr
3436 @declared_attr
3436 def target_repo(cls):
3437 def target_repo(cls):
3437 return relationship(
3438 return relationship(
3438 'Repository',
3439 'Repository',
3439 primaryjoin='%s.target_repo_id==Repository.repo_id' % cls.__name__)
3440 primaryjoin='%s.target_repo_id==Repository.repo_id' % cls.__name__)
3440
3441
3441 @property
3442 @property
3442 def target_ref_parts(self):
3443 def target_ref_parts(self):
3443 return self.unicode_to_reference(self.target_ref)
3444 return self.unicode_to_reference(self.target_ref)
3444
3445
3445 @property
3446 @property
3446 def shadow_merge_ref(self):
3447 def shadow_merge_ref(self):
3447 return self.unicode_to_reference(self._shadow_merge_ref)
3448 return self.unicode_to_reference(self._shadow_merge_ref)
3448
3449
3449 @shadow_merge_ref.setter
3450 @shadow_merge_ref.setter
3450 def shadow_merge_ref(self, ref):
3451 def shadow_merge_ref(self, ref):
3451 self._shadow_merge_ref = self.reference_to_unicode(ref)
3452 self._shadow_merge_ref = self.reference_to_unicode(ref)
3452
3453
3453 def unicode_to_reference(self, raw):
3454 def unicode_to_reference(self, raw):
3454 """
3455 """
3455 Convert a unicode (or string) to a reference object.
3456 Convert a unicode (or string) to a reference object.
3456 If unicode evaluates to False it returns None.
3457 If unicode evaluates to False it returns None.
3457 """
3458 """
3458 if raw:
3459 if raw:
3459 refs = raw.split(':')
3460 refs = raw.split(':')
3460 return Reference(*refs)
3461 return Reference(*refs)
3461 else:
3462 else:
3462 return None
3463 return None
3463
3464
3464 def reference_to_unicode(self, ref):
3465 def reference_to_unicode(self, ref):
3465 """
3466 """
3466 Convert a reference object to unicode.
3467 Convert a reference object to unicode.
3467 If reference is None it returns None.
3468 If reference is None it returns None.
3468 """
3469 """
3469 if ref:
3470 if ref:
3470 return u':'.join(ref)
3471 return u':'.join(ref)
3471 else:
3472 else:
3472 return None
3473 return None
3473
3474
3474 def get_api_data(self, with_merge_state=True):
3475 def get_api_data(self, with_merge_state=True):
3475 from rhodecode.model.pull_request import PullRequestModel
3476 from rhodecode.model.pull_request import PullRequestModel
3476
3477
3477 pull_request = self
3478 pull_request = self
3478 if with_merge_state:
3479 if with_merge_state:
3479 merge_status = PullRequestModel().merge_status(pull_request)
3480 merge_status = PullRequestModel().merge_status(pull_request)
3480 merge_state = {
3481 merge_state = {
3481 'status': merge_status[0],
3482 'status': merge_status[0],
3482 'message': safe_unicode(merge_status[1]),
3483 'message': safe_unicode(merge_status[1]),
3483 }
3484 }
3484 else:
3485 else:
3485 merge_state = {'status': 'not_available',
3486 merge_state = {'status': 'not_available',
3486 'message': 'not_available'}
3487 'message': 'not_available'}
3487
3488
3488 merge_data = {
3489 merge_data = {
3489 'clone_url': PullRequestModel().get_shadow_clone_url(pull_request),
3490 'clone_url': PullRequestModel().get_shadow_clone_url(pull_request),
3490 'reference': (
3491 'reference': (
3491 pull_request.shadow_merge_ref._asdict()
3492 pull_request.shadow_merge_ref._asdict()
3492 if pull_request.shadow_merge_ref else None),
3493 if pull_request.shadow_merge_ref else None),
3493 }
3494 }
3494
3495
3495 data = {
3496 data = {
3496 'pull_request_id': pull_request.pull_request_id,
3497 'pull_request_id': pull_request.pull_request_id,
3497 'url': PullRequestModel().get_url(pull_request),
3498 'url': PullRequestModel().get_url(pull_request),
3498 'title': pull_request.title,
3499 'title': pull_request.title,
3499 'description': pull_request.description,
3500 'description': pull_request.description,
3500 'status': pull_request.status,
3501 'status': pull_request.status,
3501 'created_on': pull_request.created_on,
3502 'created_on': pull_request.created_on,
3502 'updated_on': pull_request.updated_on,
3503 'updated_on': pull_request.updated_on,
3503 'commit_ids': pull_request.revisions,
3504 'commit_ids': pull_request.revisions,
3504 'review_status': pull_request.calculated_review_status(),
3505 'review_status': pull_request.calculated_review_status(),
3505 'mergeable': merge_state,
3506 'mergeable': merge_state,
3506 'source': {
3507 'source': {
3507 'clone_url': pull_request.source_repo.clone_url(),
3508 'clone_url': pull_request.source_repo.clone_url(),
3508 'repository': pull_request.source_repo.repo_name,
3509 'repository': pull_request.source_repo.repo_name,
3509 'reference': {
3510 'reference': {
3510 'name': pull_request.source_ref_parts.name,
3511 'name': pull_request.source_ref_parts.name,
3511 'type': pull_request.source_ref_parts.type,
3512 'type': pull_request.source_ref_parts.type,
3512 'commit_id': pull_request.source_ref_parts.commit_id,
3513 'commit_id': pull_request.source_ref_parts.commit_id,
3513 },
3514 },
3514 },
3515 },
3515 'target': {
3516 'target': {
3516 'clone_url': pull_request.target_repo.clone_url(),
3517 'clone_url': pull_request.target_repo.clone_url(),
3517 'repository': pull_request.target_repo.repo_name,
3518 'repository': pull_request.target_repo.repo_name,
3518 'reference': {
3519 'reference': {
3519 'name': pull_request.target_ref_parts.name,
3520 'name': pull_request.target_ref_parts.name,
3520 'type': pull_request.target_ref_parts.type,
3521 'type': pull_request.target_ref_parts.type,
3521 'commit_id': pull_request.target_ref_parts.commit_id,
3522 'commit_id': pull_request.target_ref_parts.commit_id,
3522 },
3523 },
3523 },
3524 },
3524 'merge': merge_data,
3525 'merge': merge_data,
3525 'author': pull_request.author.get_api_data(include_secrets=False,
3526 'author': pull_request.author.get_api_data(include_secrets=False,
3526 details='basic'),
3527 details='basic'),
3527 'reviewers': [
3528 'reviewers': [
3528 {
3529 {
3529 'user': reviewer.get_api_data(include_secrets=False,
3530 'user': reviewer.get_api_data(include_secrets=False,
3530 details='basic'),
3531 details='basic'),
3531 'reasons': reasons,
3532 'reasons': reasons,
3532 'review_status': st[0][1].status if st else 'not_reviewed',
3533 'review_status': st[0][1].status if st else 'not_reviewed',
3533 }
3534 }
3534 for reviewer, reasons, mandatory, st in
3535 for reviewer, reasons, mandatory, st in
3535 pull_request.reviewers_statuses()
3536 pull_request.reviewers_statuses()
3536 ]
3537 ]
3537 }
3538 }
3538
3539
3539 return data
3540 return data
3540
3541
3541
3542
3542 class PullRequest(Base, _PullRequestBase):
3543 class PullRequest(Base, _PullRequestBase):
3543 __tablename__ = 'pull_requests'
3544 __tablename__ = 'pull_requests'
3544 __table_args__ = (
3545 __table_args__ = (
3545 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3546 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3546 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3547 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3547 )
3548 )
3548
3549
3549 pull_request_id = Column(
3550 pull_request_id = Column(
3550 'pull_request_id', Integer(), nullable=False, primary_key=True)
3551 'pull_request_id', Integer(), nullable=False, primary_key=True)
3551
3552
3552 def __repr__(self):
3553 def __repr__(self):
3553 if self.pull_request_id:
3554 if self.pull_request_id:
3554 return '<DB:PullRequest #%s>' % self.pull_request_id
3555 return '<DB:PullRequest #%s>' % self.pull_request_id
3555 else:
3556 else:
3556 return '<DB:PullRequest at %#x>' % id(self)
3557 return '<DB:PullRequest at %#x>' % id(self)
3557
3558
3558 reviewers = relationship('PullRequestReviewers',
3559 reviewers = relationship('PullRequestReviewers',
3559 cascade="all, delete, delete-orphan")
3560 cascade="all, delete, delete-orphan")
3560 statuses = relationship('ChangesetStatus',
3561 statuses = relationship('ChangesetStatus',
3561 cascade="all, delete, delete-orphan")
3562 cascade="all, delete, delete-orphan")
3562 comments = relationship('ChangesetComment',
3563 comments = relationship('ChangesetComment',
3563 cascade="all, delete, delete-orphan")
3564 cascade="all, delete, delete-orphan")
3564 versions = relationship('PullRequestVersion',
3565 versions = relationship('PullRequestVersion',
3565 cascade="all, delete, delete-orphan",
3566 cascade="all, delete, delete-orphan",
3566 lazy='dynamic')
3567 lazy='dynamic')
3567
3568
3568 @classmethod
3569 @classmethod
3569 def get_pr_display_object(cls, pull_request_obj, org_pull_request_obj,
3570 def get_pr_display_object(cls, pull_request_obj, org_pull_request_obj,
3570 internal_methods=None):
3571 internal_methods=None):
3571
3572
3572 class PullRequestDisplay(object):
3573 class PullRequestDisplay(object):
3573 """
3574 """
3574 Special object wrapper for showing PullRequest data via Versions
3575 Special object wrapper for showing PullRequest data via Versions
3575 It mimics PR object as close as possible. This is read only object
3576 It mimics PR object as close as possible. This is read only object
3576 just for display
3577 just for display
3577 """
3578 """
3578
3579
3579 def __init__(self, attrs, internal=None):
3580 def __init__(self, attrs, internal=None):
3580 self.attrs = attrs
3581 self.attrs = attrs
3581 # internal have priority over the given ones via attrs
3582 # internal have priority over the given ones via attrs
3582 self.internal = internal or ['versions']
3583 self.internal = internal or ['versions']
3583
3584
3584 def __getattr__(self, item):
3585 def __getattr__(self, item):
3585 if item in self.internal:
3586 if item in self.internal:
3586 return getattr(self, item)
3587 return getattr(self, item)
3587 try:
3588 try:
3588 return self.attrs[item]
3589 return self.attrs[item]
3589 except KeyError:
3590 except KeyError:
3590 raise AttributeError(
3591 raise AttributeError(
3591 '%s object has no attribute %s' % (self, item))
3592 '%s object has no attribute %s' % (self, item))
3592
3593
3593 def __repr__(self):
3594 def __repr__(self):
3594 return '<DB:PullRequestDisplay #%s>' % self.attrs.get('pull_request_id')
3595 return '<DB:PullRequestDisplay #%s>' % self.attrs.get('pull_request_id')
3595
3596
3596 def versions(self):
3597 def versions(self):
3597 return pull_request_obj.versions.order_by(
3598 return pull_request_obj.versions.order_by(
3598 PullRequestVersion.pull_request_version_id).all()
3599 PullRequestVersion.pull_request_version_id).all()
3599
3600
3600 def is_closed(self):
3601 def is_closed(self):
3601 return pull_request_obj.is_closed()
3602 return pull_request_obj.is_closed()
3602
3603
3603 @property
3604 @property
3604 def pull_request_version_id(self):
3605 def pull_request_version_id(self):
3605 return getattr(pull_request_obj, 'pull_request_version_id', None)
3606 return getattr(pull_request_obj, 'pull_request_version_id', None)
3606
3607
3607 attrs = StrictAttributeDict(pull_request_obj.get_api_data())
3608 attrs = StrictAttributeDict(pull_request_obj.get_api_data())
3608
3609
3609 attrs.author = StrictAttributeDict(
3610 attrs.author = StrictAttributeDict(
3610 pull_request_obj.author.get_api_data())
3611 pull_request_obj.author.get_api_data())
3611 if pull_request_obj.target_repo:
3612 if pull_request_obj.target_repo:
3612 attrs.target_repo = StrictAttributeDict(
3613 attrs.target_repo = StrictAttributeDict(
3613 pull_request_obj.target_repo.get_api_data())
3614 pull_request_obj.target_repo.get_api_data())
3614 attrs.target_repo.clone_url = pull_request_obj.target_repo.clone_url
3615 attrs.target_repo.clone_url = pull_request_obj.target_repo.clone_url
3615
3616
3616 if pull_request_obj.source_repo:
3617 if pull_request_obj.source_repo:
3617 attrs.source_repo = StrictAttributeDict(
3618 attrs.source_repo = StrictAttributeDict(
3618 pull_request_obj.source_repo.get_api_data())
3619 pull_request_obj.source_repo.get_api_data())
3619 attrs.source_repo.clone_url = pull_request_obj.source_repo.clone_url
3620 attrs.source_repo.clone_url = pull_request_obj.source_repo.clone_url
3620
3621
3621 attrs.source_ref_parts = pull_request_obj.source_ref_parts
3622 attrs.source_ref_parts = pull_request_obj.source_ref_parts
3622 attrs.target_ref_parts = pull_request_obj.target_ref_parts
3623 attrs.target_ref_parts = pull_request_obj.target_ref_parts
3623 attrs.revisions = pull_request_obj.revisions
3624 attrs.revisions = pull_request_obj.revisions
3624
3625
3625 attrs.shadow_merge_ref = org_pull_request_obj.shadow_merge_ref
3626 attrs.shadow_merge_ref = org_pull_request_obj.shadow_merge_ref
3626 attrs.reviewer_data = org_pull_request_obj.reviewer_data
3627 attrs.reviewer_data = org_pull_request_obj.reviewer_data
3627 attrs.reviewer_data_json = org_pull_request_obj.reviewer_data_json
3628 attrs.reviewer_data_json = org_pull_request_obj.reviewer_data_json
3628
3629
3629 return PullRequestDisplay(attrs, internal=internal_methods)
3630 return PullRequestDisplay(attrs, internal=internal_methods)
3630
3631
3631 def is_closed(self):
3632 def is_closed(self):
3632 return self.status == self.STATUS_CLOSED
3633 return self.status == self.STATUS_CLOSED
3633
3634
3634 def __json__(self):
3635 def __json__(self):
3635 return {
3636 return {
3636 'revisions': self.revisions,
3637 'revisions': self.revisions,
3637 }
3638 }
3638
3639
3639 def calculated_review_status(self):
3640 def calculated_review_status(self):
3640 from rhodecode.model.changeset_status import ChangesetStatusModel
3641 from rhodecode.model.changeset_status import ChangesetStatusModel
3641 return ChangesetStatusModel().calculated_review_status(self)
3642 return ChangesetStatusModel().calculated_review_status(self)
3642
3643
3643 def reviewers_statuses(self):
3644 def reviewers_statuses(self):
3644 from rhodecode.model.changeset_status import ChangesetStatusModel
3645 from rhodecode.model.changeset_status import ChangesetStatusModel
3645 return ChangesetStatusModel().reviewers_statuses(self)
3646 return ChangesetStatusModel().reviewers_statuses(self)
3646
3647
3647 @property
3648 @property
3648 def workspace_id(self):
3649 def workspace_id(self):
3649 from rhodecode.model.pull_request import PullRequestModel
3650 from rhodecode.model.pull_request import PullRequestModel
3650 return PullRequestModel()._workspace_id(self)
3651 return PullRequestModel()._workspace_id(self)
3651
3652
3652 def get_shadow_repo(self):
3653 def get_shadow_repo(self):
3653 workspace_id = self.workspace_id
3654 workspace_id = self.workspace_id
3654 vcs_obj = self.target_repo.scm_instance()
3655 vcs_obj = self.target_repo.scm_instance()
3655 shadow_repository_path = vcs_obj._get_shadow_repository_path(
3656 shadow_repository_path = vcs_obj._get_shadow_repository_path(
3656 workspace_id)
3657 workspace_id)
3657 return vcs_obj._get_shadow_instance(shadow_repository_path)
3658 return vcs_obj._get_shadow_instance(shadow_repository_path)
3658
3659
3659
3660
3660 class PullRequestVersion(Base, _PullRequestBase):
3661 class PullRequestVersion(Base, _PullRequestBase):
3661 __tablename__ = 'pull_request_versions'
3662 __tablename__ = 'pull_request_versions'
3662 __table_args__ = (
3663 __table_args__ = (
3663 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3664 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3664 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3665 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3665 )
3666 )
3666
3667
3667 pull_request_version_id = Column(
3668 pull_request_version_id = Column(
3668 'pull_request_version_id', Integer(), nullable=False, primary_key=True)
3669 'pull_request_version_id', Integer(), nullable=False, primary_key=True)
3669 pull_request_id = Column(
3670 pull_request_id = Column(
3670 'pull_request_id', Integer(),
3671 'pull_request_id', Integer(),
3671 ForeignKey('pull_requests.pull_request_id'), nullable=False)
3672 ForeignKey('pull_requests.pull_request_id'), nullable=False)
3672 pull_request = relationship('PullRequest')
3673 pull_request = relationship('PullRequest')
3673
3674
3674 def __repr__(self):
3675 def __repr__(self):
3675 if self.pull_request_version_id:
3676 if self.pull_request_version_id:
3676 return '<DB:PullRequestVersion #%s>' % self.pull_request_version_id
3677 return '<DB:PullRequestVersion #%s>' % self.pull_request_version_id
3677 else:
3678 else:
3678 return '<DB:PullRequestVersion at %#x>' % id(self)
3679 return '<DB:PullRequestVersion at %#x>' % id(self)
3679
3680
3680 @property
3681 @property
3681 def reviewers(self):
3682 def reviewers(self):
3682 return self.pull_request.reviewers
3683 return self.pull_request.reviewers
3683
3684
3684 @property
3685 @property
3685 def versions(self):
3686 def versions(self):
3686 return self.pull_request.versions
3687 return self.pull_request.versions
3687
3688
3688 def is_closed(self):
3689 def is_closed(self):
3689 # calculate from original
3690 # calculate from original
3690 return self.pull_request.status == self.STATUS_CLOSED
3691 return self.pull_request.status == self.STATUS_CLOSED
3691
3692
3692 def calculated_review_status(self):
3693 def calculated_review_status(self):
3693 return self.pull_request.calculated_review_status()
3694 return self.pull_request.calculated_review_status()
3694
3695
3695 def reviewers_statuses(self):
3696 def reviewers_statuses(self):
3696 return self.pull_request.reviewers_statuses()
3697 return self.pull_request.reviewers_statuses()
3697
3698
3698
3699
3699 class PullRequestReviewers(Base, BaseModel):
3700 class PullRequestReviewers(Base, BaseModel):
3700 __tablename__ = 'pull_request_reviewers'
3701 __tablename__ = 'pull_request_reviewers'
3701 __table_args__ = (
3702 __table_args__ = (
3702 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3703 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3703 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3704 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3704 )
3705 )
3705
3706
3706 @hybrid_property
3707 @hybrid_property
3707 def reasons(self):
3708 def reasons(self):
3708 if not self._reasons:
3709 if not self._reasons:
3709 return []
3710 return []
3710 return self._reasons
3711 return self._reasons
3711
3712
3712 @reasons.setter
3713 @reasons.setter
3713 def reasons(self, val):
3714 def reasons(self, val):
3714 val = val or []
3715 val = val or []
3715 if any(not isinstance(x, basestring) for x in val):
3716 if any(not isinstance(x, basestring) for x in val):
3716 raise Exception('invalid reasons type, must be list of strings')
3717 raise Exception('invalid reasons type, must be list of strings')
3717 self._reasons = val
3718 self._reasons = val
3718
3719
3719 pull_requests_reviewers_id = Column(
3720 pull_requests_reviewers_id = Column(
3720 'pull_requests_reviewers_id', Integer(), nullable=False,
3721 'pull_requests_reviewers_id', Integer(), nullable=False,
3721 primary_key=True)
3722 primary_key=True)
3722 pull_request_id = Column(
3723 pull_request_id = Column(
3723 "pull_request_id", Integer(),
3724 "pull_request_id", Integer(),
3724 ForeignKey('pull_requests.pull_request_id'), nullable=False)
3725 ForeignKey('pull_requests.pull_request_id'), nullable=False)
3725 user_id = Column(
3726 user_id = Column(
3726 "user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
3727 "user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
3727 _reasons = Column(
3728 _reasons = Column(
3728 'reason', MutationList.as_mutable(
3729 'reason', MutationList.as_mutable(
3729 JsonType('list', dialect_map=dict(mysql=UnicodeText(16384)))))
3730 JsonType('list', dialect_map=dict(mysql=UnicodeText(16384)))))
3730 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
3731 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
3731 user = relationship('User')
3732 user = relationship('User')
3732 pull_request = relationship('PullRequest')
3733 pull_request = relationship('PullRequest')
3733
3734
3734
3735
3735 class Notification(Base, BaseModel):
3736 class Notification(Base, BaseModel):
3736 __tablename__ = 'notifications'
3737 __tablename__ = 'notifications'
3737 __table_args__ = (
3738 __table_args__ = (
3738 Index('notification_type_idx', 'type'),
3739 Index('notification_type_idx', 'type'),
3739 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3740 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3740 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3741 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
3741 )
3742 )
3742
3743
3743 TYPE_CHANGESET_COMMENT = u'cs_comment'
3744 TYPE_CHANGESET_COMMENT = u'cs_comment'
3744 TYPE_MESSAGE = u'message'
3745 TYPE_MESSAGE = u'message'
3745 TYPE_MENTION = u'mention'
3746 TYPE_MENTION = u'mention'
3746 TYPE_REGISTRATION = u'registration'
3747 TYPE_REGISTRATION = u'registration'
3747 TYPE_PULL_REQUEST = u'pull_request'
3748 TYPE_PULL_REQUEST = u'pull_request'
3748 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
3749 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
3749
3750
3750 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
3751 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
3751 subject = Column('subject', Unicode(512), nullable=True)
3752 subject = Column('subject', Unicode(512), nullable=True)
3752 body = Column('body', UnicodeText().with_variant(UnicodeText(50000), 'mysql'), nullable=True)
3753 body = Column('body', UnicodeText().with_variant(UnicodeText(50000), 'mysql'), nullable=True)
3753 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
3754 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
3754 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3755 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3755 type_ = Column('type', Unicode(255))
3756 type_ = Column('type', Unicode(255))
3756
3757
3757 created_by_user = relationship('User')
3758 created_by_user = relationship('User')
3758 notifications_to_users = relationship('UserNotification', lazy='joined',
3759 notifications_to_users = relationship('UserNotification', lazy='joined',
3759 cascade="all, delete, delete-orphan")
3760 cascade="all, delete, delete-orphan")
3760
3761
3761 @property
3762 @property
3762 def recipients(self):
3763 def recipients(self):
3763 return [x.user for x in UserNotification.query()\
3764 return [x.user for x in UserNotification.query()\
3764 .filter(UserNotification.notification == self)\
3765 .filter(UserNotification.notification == self)\
3765 .order_by(UserNotification.user_id.asc()).all()]
3766 .order_by(UserNotification.user_id.asc()).all()]
3766
3767
3767 @classmethod
3768 @classmethod
3768 def create(cls, created_by, subject, body, recipients, type_=None):
3769 def create(cls, created_by, subject, body, recipients, type_=None):
3769 if type_ is None:
3770 if type_ is None:
3770 type_ = Notification.TYPE_MESSAGE
3771 type_ = Notification.TYPE_MESSAGE
3771
3772
3772 notification = cls()
3773 notification = cls()
3773 notification.created_by_user = created_by
3774 notification.created_by_user = created_by
3774 notification.subject = subject
3775 notification.subject = subject
3775 notification.body = body
3776 notification.body = body
3776 notification.type_ = type_
3777 notification.type_ = type_
3777 notification.created_on = datetime.datetime.now()
3778 notification.created_on = datetime.datetime.now()
3778
3779
3779 for u in recipients:
3780 for u in recipients:
3780 assoc = UserNotification()
3781 assoc = UserNotification()
3781 assoc.notification = notification
3782 assoc.notification = notification
3782
3783
3783 # if created_by is inside recipients mark his notification
3784 # if created_by is inside recipients mark his notification
3784 # as read
3785 # as read
3785 if u.user_id == created_by.user_id:
3786 if u.user_id == created_by.user_id:
3786 assoc.read = True
3787 assoc.read = True
3787
3788
3788 u.notifications.append(assoc)
3789 u.notifications.append(assoc)
3789 Session().add(notification)
3790 Session().add(notification)
3790
3791
3791 return notification
3792 return notification
3792
3793
3793
3794
3794 class UserNotification(Base, BaseModel):
3795 class UserNotification(Base, BaseModel):
3795 __tablename__ = 'user_to_notification'
3796 __tablename__ = 'user_to_notification'
3796 __table_args__ = (
3797 __table_args__ = (
3797 UniqueConstraint('user_id', 'notification_id'),
3798 UniqueConstraint('user_id', 'notification_id'),
3798 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3799 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3799 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3800 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3800 )
3801 )
3801 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
3802 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
3802 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
3803 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
3803 read = Column('read', Boolean, default=False)
3804 read = Column('read', Boolean, default=False)
3804 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
3805 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
3805
3806
3806 user = relationship('User', lazy="joined")
3807 user = relationship('User', lazy="joined")
3807 notification = relationship('Notification', lazy="joined",
3808 notification = relationship('Notification', lazy="joined",
3808 order_by=lambda: Notification.created_on.desc(),)
3809 order_by=lambda: Notification.created_on.desc(),)
3809
3810
3810 def mark_as_read(self):
3811 def mark_as_read(self):
3811 self.read = True
3812 self.read = True
3812 Session().add(self)
3813 Session().add(self)
3813
3814
3814
3815
3815 class Gist(Base, BaseModel):
3816 class Gist(Base, BaseModel):
3816 __tablename__ = 'gists'
3817 __tablename__ = 'gists'
3817 __table_args__ = (
3818 __table_args__ = (
3818 Index('g_gist_access_id_idx', 'gist_access_id'),
3819 Index('g_gist_access_id_idx', 'gist_access_id'),
3819 Index('g_created_on_idx', 'created_on'),
3820 Index('g_created_on_idx', 'created_on'),
3820 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3821 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3821 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3822 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3822 )
3823 )
3823 GIST_PUBLIC = u'public'
3824 GIST_PUBLIC = u'public'
3824 GIST_PRIVATE = u'private'
3825 GIST_PRIVATE = u'private'
3825 DEFAULT_FILENAME = u'gistfile1.txt'
3826 DEFAULT_FILENAME = u'gistfile1.txt'
3826
3827
3827 ACL_LEVEL_PUBLIC = u'acl_public'
3828 ACL_LEVEL_PUBLIC = u'acl_public'
3828 ACL_LEVEL_PRIVATE = u'acl_private'
3829 ACL_LEVEL_PRIVATE = u'acl_private'
3829
3830
3830 gist_id = Column('gist_id', Integer(), primary_key=True)
3831 gist_id = Column('gist_id', Integer(), primary_key=True)
3831 gist_access_id = Column('gist_access_id', Unicode(250))
3832 gist_access_id = Column('gist_access_id', Unicode(250))
3832 gist_description = Column('gist_description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
3833 gist_description = Column('gist_description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
3833 gist_owner = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True)
3834 gist_owner = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True)
3834 gist_expires = Column('gist_expires', Float(53), nullable=False)
3835 gist_expires = Column('gist_expires', Float(53), nullable=False)
3835 gist_type = Column('gist_type', Unicode(128), nullable=False)
3836 gist_type = Column('gist_type', Unicode(128), nullable=False)
3836 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3837 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3837 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3838 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3838 acl_level = Column('acl_level', Unicode(128), nullable=True)
3839 acl_level = Column('acl_level', Unicode(128), nullable=True)
3839
3840
3840 owner = relationship('User')
3841 owner = relationship('User')
3841
3842
3842 def __repr__(self):
3843 def __repr__(self):
3843 return '<Gist:[%s]%s>' % (self.gist_type, self.gist_access_id)
3844 return '<Gist:[%s]%s>' % (self.gist_type, self.gist_access_id)
3844
3845
3845 @hybrid_property
3846 @hybrid_property
3846 def description_safe(self):
3847 def description_safe(self):
3847 from rhodecode.lib import helpers as h
3848 from rhodecode.lib import helpers as h
3848 return h.escape(self.gist_description)
3849 return h.escape(self.gist_description)
3849
3850
3850 @classmethod
3851 @classmethod
3851 def get_or_404(cls, id_):
3852 def get_or_404(cls, id_):
3852 from pyramid.httpexceptions import HTTPNotFound
3853 from pyramid.httpexceptions import HTTPNotFound
3853
3854
3854 res = cls.query().filter(cls.gist_access_id == id_).scalar()
3855 res = cls.query().filter(cls.gist_access_id == id_).scalar()
3855 if not res:
3856 if not res:
3856 raise HTTPNotFound()
3857 raise HTTPNotFound()
3857 return res
3858 return res
3858
3859
3859 @classmethod
3860 @classmethod
3860 def get_by_access_id(cls, gist_access_id):
3861 def get_by_access_id(cls, gist_access_id):
3861 return cls.query().filter(cls.gist_access_id == gist_access_id).scalar()
3862 return cls.query().filter(cls.gist_access_id == gist_access_id).scalar()
3862
3863
3863 def gist_url(self):
3864 def gist_url(self):
3864 from rhodecode.model.gist import GistModel
3865 from rhodecode.model.gist import GistModel
3865 return GistModel().get_url(self)
3866 return GistModel().get_url(self)
3866
3867
3867 @classmethod
3868 @classmethod
3868 def base_path(cls):
3869 def base_path(cls):
3869 """
3870 """
3870 Returns base path when all gists are stored
3871 Returns base path when all gists are stored
3871
3872
3872 :param cls:
3873 :param cls:
3873 """
3874 """
3874 from rhodecode.model.gist import GIST_STORE_LOC
3875 from rhodecode.model.gist import GIST_STORE_LOC
3875 q = Session().query(RhodeCodeUi)\
3876 q = Session().query(RhodeCodeUi)\
3876 .filter(RhodeCodeUi.ui_key == URL_SEP)
3877 .filter(RhodeCodeUi.ui_key == URL_SEP)
3877 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
3878 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
3878 return os.path.join(q.one().ui_value, GIST_STORE_LOC)
3879 return os.path.join(q.one().ui_value, GIST_STORE_LOC)
3879
3880
3880 def get_api_data(self):
3881 def get_api_data(self):
3881 """
3882 """
3882 Common function for generating gist related data for API
3883 Common function for generating gist related data for API
3883 """
3884 """
3884 gist = self
3885 gist = self
3885 data = {
3886 data = {
3886 'gist_id': gist.gist_id,
3887 'gist_id': gist.gist_id,
3887 'type': gist.gist_type,
3888 'type': gist.gist_type,
3888 'access_id': gist.gist_access_id,
3889 'access_id': gist.gist_access_id,
3889 'description': gist.gist_description,
3890 'description': gist.gist_description,
3890 'url': gist.gist_url(),
3891 'url': gist.gist_url(),
3891 'expires': gist.gist_expires,
3892 'expires': gist.gist_expires,
3892 'created_on': gist.created_on,
3893 'created_on': gist.created_on,
3893 'modified_at': gist.modified_at,
3894 'modified_at': gist.modified_at,
3894 'content': None,
3895 'content': None,
3895 'acl_level': gist.acl_level,
3896 'acl_level': gist.acl_level,
3896 }
3897 }
3897 return data
3898 return data
3898
3899
3899 def __json__(self):
3900 def __json__(self):
3900 data = dict(
3901 data = dict(
3901 )
3902 )
3902 data.update(self.get_api_data())
3903 data.update(self.get_api_data())
3903 return data
3904 return data
3904 # SCM functions
3905 # SCM functions
3905
3906
3906 def scm_instance(self, **kwargs):
3907 def scm_instance(self, **kwargs):
3907 full_repo_path = os.path.join(self.base_path(), self.gist_access_id)
3908 full_repo_path = os.path.join(self.base_path(), self.gist_access_id)
3908 return get_vcs_instance(
3909 return get_vcs_instance(
3909 repo_path=safe_str(full_repo_path), create=False)
3910 repo_path=safe_str(full_repo_path), create=False)
3910
3911
3911
3912
3912 class ExternalIdentity(Base, BaseModel):
3913 class ExternalIdentity(Base, BaseModel):
3913 __tablename__ = 'external_identities'
3914 __tablename__ = 'external_identities'
3914 __table_args__ = (
3915 __table_args__ = (
3915 Index('local_user_id_idx', 'local_user_id'),
3916 Index('local_user_id_idx', 'local_user_id'),
3916 Index('external_id_idx', 'external_id'),
3917 Index('external_id_idx', 'external_id'),
3917 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3918 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3918 'mysql_charset': 'utf8'})
3919 'mysql_charset': 'utf8'})
3919
3920
3920 external_id = Column('external_id', Unicode(255), default=u'',
3921 external_id = Column('external_id', Unicode(255), default=u'',
3921 primary_key=True)
3922 primary_key=True)
3922 external_username = Column('external_username', Unicode(1024), default=u'')
3923 external_username = Column('external_username', Unicode(1024), default=u'')
3923 local_user_id = Column('local_user_id', Integer(),
3924 local_user_id = Column('local_user_id', Integer(),
3924 ForeignKey('users.user_id'), primary_key=True)
3925 ForeignKey('users.user_id'), primary_key=True)
3925 provider_name = Column('provider_name', Unicode(255), default=u'',
3926 provider_name = Column('provider_name', Unicode(255), default=u'',
3926 primary_key=True)
3927 primary_key=True)
3927 access_token = Column('access_token', String(1024), default=u'')
3928 access_token = Column('access_token', String(1024), default=u'')
3928 alt_token = Column('alt_token', String(1024), default=u'')
3929 alt_token = Column('alt_token', String(1024), default=u'')
3929 token_secret = Column('token_secret', String(1024), default=u'')
3930 token_secret = Column('token_secret', String(1024), default=u'')
3930
3931
3931 @classmethod
3932 @classmethod
3932 def by_external_id_and_provider(cls, external_id, provider_name,
3933 def by_external_id_and_provider(cls, external_id, provider_name,
3933 local_user_id=None):
3934 local_user_id=None):
3934 """
3935 """
3935 Returns ExternalIdentity instance based on search params
3936 Returns ExternalIdentity instance based on search params
3936
3937
3937 :param external_id:
3938 :param external_id:
3938 :param provider_name:
3939 :param provider_name:
3939 :return: ExternalIdentity
3940 :return: ExternalIdentity
3940 """
3941 """
3941 query = cls.query()
3942 query = cls.query()
3942 query = query.filter(cls.external_id == external_id)
3943 query = query.filter(cls.external_id == external_id)
3943 query = query.filter(cls.provider_name == provider_name)
3944 query = query.filter(cls.provider_name == provider_name)
3944 if local_user_id:
3945 if local_user_id:
3945 query = query.filter(cls.local_user_id == local_user_id)
3946 query = query.filter(cls.local_user_id == local_user_id)
3946 return query.first()
3947 return query.first()
3947
3948
3948 @classmethod
3949 @classmethod
3949 def user_by_external_id_and_provider(cls, external_id, provider_name):
3950 def user_by_external_id_and_provider(cls, external_id, provider_name):
3950 """
3951 """
3951 Returns User instance based on search params
3952 Returns User instance based on search params
3952
3953
3953 :param external_id:
3954 :param external_id:
3954 :param provider_name:
3955 :param provider_name:
3955 :return: User
3956 :return: User
3956 """
3957 """
3957 query = User.query()
3958 query = User.query()
3958 query = query.filter(cls.external_id == external_id)
3959 query = query.filter(cls.external_id == external_id)
3959 query = query.filter(cls.provider_name == provider_name)
3960 query = query.filter(cls.provider_name == provider_name)
3960 query = query.filter(User.user_id == cls.local_user_id)
3961 query = query.filter(User.user_id == cls.local_user_id)
3961 return query.first()
3962 return query.first()
3962
3963
3963 @classmethod
3964 @classmethod
3964 def by_local_user_id(cls, local_user_id):
3965 def by_local_user_id(cls, local_user_id):
3965 """
3966 """
3966 Returns all tokens for user
3967 Returns all tokens for user
3967
3968
3968 :param local_user_id:
3969 :param local_user_id:
3969 :return: ExternalIdentity
3970 :return: ExternalIdentity
3970 """
3971 """
3971 query = cls.query()
3972 query = cls.query()
3972 query = query.filter(cls.local_user_id == local_user_id)
3973 query = query.filter(cls.local_user_id == local_user_id)
3973 return query
3974 return query
3974
3975
3975
3976
3976 class Integration(Base, BaseModel):
3977 class Integration(Base, BaseModel):
3977 __tablename__ = 'integrations'
3978 __tablename__ = 'integrations'
3978 __table_args__ = (
3979 __table_args__ = (
3979 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3980 {'extend_existing': True, 'mysql_engine': 'InnoDB',
3980 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3981 'mysql_charset': 'utf8', 'sqlite_autoincrement': True}
3981 )
3982 )
3982
3983
3983 integration_id = Column('integration_id', Integer(), primary_key=True)
3984 integration_id = Column('integration_id', Integer(), primary_key=True)
3984 integration_type = Column('integration_type', String(255))
3985 integration_type = Column('integration_type', String(255))
3985 enabled = Column('enabled', Boolean(), nullable=False)
3986 enabled = Column('enabled', Boolean(), nullable=False)
3986 name = Column('name', String(255), nullable=False)
3987 name = Column('name', String(255), nullable=False)
3987 child_repos_only = Column('child_repos_only', Boolean(), nullable=False,
3988 child_repos_only = Column('child_repos_only', Boolean(), nullable=False,
3988 default=False)
3989 default=False)
3989
3990
3990 settings = Column(
3991 settings = Column(
3991 'settings_json', MutationObj.as_mutable(
3992 'settings_json', MutationObj.as_mutable(
3992 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
3993 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
3993 repo_id = Column(
3994 repo_id = Column(
3994 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
3995 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
3995 nullable=True, unique=None, default=None)
3996 nullable=True, unique=None, default=None)
3996 repo = relationship('Repository', lazy='joined')
3997 repo = relationship('Repository', lazy='joined')
3997
3998
3998 repo_group_id = Column(
3999 repo_group_id = Column(
3999 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
4000 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
4000 nullable=True, unique=None, default=None)
4001 nullable=True, unique=None, default=None)
4001 repo_group = relationship('RepoGroup', lazy='joined')
4002 repo_group = relationship('RepoGroup', lazy='joined')
4002
4003
4003 @property
4004 @property
4004 def scope(self):
4005 def scope(self):
4005 if self.repo:
4006 if self.repo:
4006 return repr(self.repo)
4007 return repr(self.repo)
4007 if self.repo_group:
4008 if self.repo_group:
4008 if self.child_repos_only:
4009 if self.child_repos_only:
4009 return repr(self.repo_group) + ' (child repos only)'
4010 return repr(self.repo_group) + ' (child repos only)'
4010 else:
4011 else:
4011 return repr(self.repo_group) + ' (recursive)'
4012 return repr(self.repo_group) + ' (recursive)'
4012 if self.child_repos_only:
4013 if self.child_repos_only:
4013 return 'root_repos'
4014 return 'root_repos'
4014 return 'global'
4015 return 'global'
4015
4016
4016 def __repr__(self):
4017 def __repr__(self):
4017 return '<Integration(%r, %r)>' % (self.integration_type, self.scope)
4018 return '<Integration(%r, %r)>' % (self.integration_type, self.scope)
4018
4019
4019
4020
4020 class RepoReviewRuleUser(Base, BaseModel):
4021 class RepoReviewRuleUser(Base, BaseModel):
4021 __tablename__ = 'repo_review_rules_users'
4022 __tablename__ = 'repo_review_rules_users'
4022 __table_args__ = (
4023 __table_args__ = (
4023 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4024 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4024 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
4025 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
4025 )
4026 )
4026 repo_review_rule_user_id = Column('repo_review_rule_user_id', Integer(), primary_key=True)
4027 repo_review_rule_user_id = Column('repo_review_rule_user_id', Integer(), primary_key=True)
4027 repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
4028 repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
4028 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False)
4029 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False)
4029 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4030 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4030 user = relationship('User')
4031 user = relationship('User')
4031
4032
4032 def rule_data(self):
4033 def rule_data(self):
4033 return {
4034 return {
4034 'mandatory': self.mandatory
4035 'mandatory': self.mandatory
4035 }
4036 }
4036
4037
4037
4038
4038 class RepoReviewRuleUserGroup(Base, BaseModel):
4039 class RepoReviewRuleUserGroup(Base, BaseModel):
4039 __tablename__ = 'repo_review_rules_users_groups'
4040 __tablename__ = 'repo_review_rules_users_groups'
4040 __table_args__ = (
4041 __table_args__ = (
4041 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4042 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4042 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
4043 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
4043 )
4044 )
4044 repo_review_rule_users_group_id = Column('repo_review_rule_users_group_id', Integer(), primary_key=True)
4045 repo_review_rule_users_group_id = Column('repo_review_rule_users_group_id', Integer(), primary_key=True)
4045 repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
4046 repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
4046 users_group_id = Column("users_group_id", Integer(),ForeignKey('users_groups.users_group_id'), nullable=False)
4047 users_group_id = Column("users_group_id", Integer(),ForeignKey('users_groups.users_group_id'), nullable=False)
4047 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4048 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4048 users_group = relationship('UserGroup')
4049 users_group = relationship('UserGroup')
4049
4050
4050 def rule_data(self):
4051 def rule_data(self):
4051 return {
4052 return {
4052 'mandatory': self.mandatory
4053 'mandatory': self.mandatory
4053 }
4054 }
4054
4055
4055
4056
4056 class RepoReviewRule(Base, BaseModel):
4057 class RepoReviewRule(Base, BaseModel):
4057 __tablename__ = 'repo_review_rules'
4058 __tablename__ = 'repo_review_rules'
4058 __table_args__ = (
4059 __table_args__ = (
4059 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4060 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4060 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
4061 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
4061 )
4062 )
4062
4063
4063 repo_review_rule_id = Column(
4064 repo_review_rule_id = Column(
4064 'repo_review_rule_id', Integer(), primary_key=True)
4065 'repo_review_rule_id', Integer(), primary_key=True)
4065 repo_id = Column(
4066 repo_id = Column(
4066 "repo_id", Integer(), ForeignKey('repositories.repo_id'))
4067 "repo_id", Integer(), ForeignKey('repositories.repo_id'))
4067 repo = relationship('Repository', backref='review_rules')
4068 repo = relationship('Repository', backref='review_rules')
4068
4069
4069 _branch_pattern = Column("branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4070 _branch_pattern = Column("branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4070 _file_pattern = Column("file_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4071 _file_pattern = Column("file_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4071
4072
4072 use_authors_for_review = Column("use_authors_for_review", Boolean(), nullable=False, default=False)
4073 use_authors_for_review = Column("use_authors_for_review", Boolean(), nullable=False, default=False)
4073 forbid_author_to_review = Column("forbid_author_to_review", Boolean(), nullable=False, default=False)
4074 forbid_author_to_review = Column("forbid_author_to_review", Boolean(), nullable=False, default=False)
4074 forbid_commit_author_to_review = Column("forbid_commit_author_to_review", Boolean(), nullable=False, default=False)
4075 forbid_commit_author_to_review = Column("forbid_commit_author_to_review", Boolean(), nullable=False, default=False)
4075 forbid_adding_reviewers = Column("forbid_adding_reviewers", Boolean(), nullable=False, default=False)
4076 forbid_adding_reviewers = Column("forbid_adding_reviewers", Boolean(), nullable=False, default=False)
4076
4077
4077 rule_users = relationship('RepoReviewRuleUser')
4078 rule_users = relationship('RepoReviewRuleUser')
4078 rule_user_groups = relationship('RepoReviewRuleUserGroup')
4079 rule_user_groups = relationship('RepoReviewRuleUserGroup')
4079
4080
4080 @hybrid_property
4081 @hybrid_property
4081 def branch_pattern(self):
4082 def branch_pattern(self):
4082 return self._branch_pattern or '*'
4083 return self._branch_pattern or '*'
4083
4084
4084 def _validate_glob(self, value):
4085 def _validate_glob(self, value):
4085 re.compile('^' + glob2re(value) + '$')
4086 re.compile('^' + glob2re(value) + '$')
4086
4087
4087 @branch_pattern.setter
4088 @branch_pattern.setter
4088 def branch_pattern(self, value):
4089 def branch_pattern(self, value):
4089 self._validate_glob(value)
4090 self._validate_glob(value)
4090 self._branch_pattern = value or '*'
4091 self._branch_pattern = value or '*'
4091
4092
4092 @hybrid_property
4093 @hybrid_property
4093 def file_pattern(self):
4094 def file_pattern(self):
4094 return self._file_pattern or '*'
4095 return self._file_pattern or '*'
4095
4096
4096 @file_pattern.setter
4097 @file_pattern.setter
4097 def file_pattern(self, value):
4098 def file_pattern(self, value):
4098 self._validate_glob(value)
4099 self._validate_glob(value)
4099 self._file_pattern = value or '*'
4100 self._file_pattern = value or '*'
4100
4101
4101 def matches(self, branch, files_changed):
4102 def matches(self, branch, files_changed):
4102 """
4103 """
4103 Check if this review rule matches a branch/files in a pull request
4104 Check if this review rule matches a branch/files in a pull request
4104
4105
4105 :param branch: branch name for the commit
4106 :param branch: branch name for the commit
4106 :param files_changed: list of file paths changed in the pull request
4107 :param files_changed: list of file paths changed in the pull request
4107 """
4108 """
4108
4109
4109 branch = branch or ''
4110 branch = branch or ''
4110 files_changed = files_changed or []
4111 files_changed = files_changed or []
4111
4112
4112 branch_matches = True
4113 branch_matches = True
4113 if branch:
4114 if branch:
4114 branch_regex = re.compile('^' + glob2re(self.branch_pattern) + '$')
4115 branch_regex = re.compile('^' + glob2re(self.branch_pattern) + '$')
4115 branch_matches = bool(branch_regex.search(branch))
4116 branch_matches = bool(branch_regex.search(branch))
4116
4117
4117 files_matches = True
4118 files_matches = True
4118 if self.file_pattern != '*':
4119 if self.file_pattern != '*':
4119 files_matches = False
4120 files_matches = False
4120 file_regex = re.compile(glob2re(self.file_pattern))
4121 file_regex = re.compile(glob2re(self.file_pattern))
4121 for filename in files_changed:
4122 for filename in files_changed:
4122 if file_regex.search(filename):
4123 if file_regex.search(filename):
4123 files_matches = True
4124 files_matches = True
4124 break
4125 break
4125
4126
4126 return branch_matches and files_matches
4127 return branch_matches and files_matches
4127
4128
4128 @property
4129 @property
4129 def review_users(self):
4130 def review_users(self):
4130 """ Returns the users which this rule applies to """
4131 """ Returns the users which this rule applies to """
4131
4132
4132 users = collections.OrderedDict()
4133 users = collections.OrderedDict()
4133
4134
4134 for rule_user in self.rule_users:
4135 for rule_user in self.rule_users:
4135 if rule_user.user.active:
4136 if rule_user.user.active:
4136 if rule_user.user not in users:
4137 if rule_user.user not in users:
4137 users[rule_user.user.username] = {
4138 users[rule_user.user.username] = {
4138 'user': rule_user.user,
4139 'user': rule_user.user,
4139 'source': 'user',
4140 'source': 'user',
4140 'source_data': {},
4141 'source_data': {},
4141 'data': rule_user.rule_data()
4142 'data': rule_user.rule_data()
4142 }
4143 }
4143
4144
4144 for rule_user_group in self.rule_user_groups:
4145 for rule_user_group in self.rule_user_groups:
4145 source_data = {
4146 source_data = {
4146 'name': rule_user_group.users_group.users_group_name,
4147 'name': rule_user_group.users_group.users_group_name,
4147 'members': len(rule_user_group.users_group.members)
4148 'members': len(rule_user_group.users_group.members)
4148 }
4149 }
4149 for member in rule_user_group.users_group.members:
4150 for member in rule_user_group.users_group.members:
4150 if member.user.active:
4151 if member.user.active:
4151 users[member.user.username] = {
4152 users[member.user.username] = {
4152 'user': member.user,
4153 'user': member.user,
4153 'source': 'user_group',
4154 'source': 'user_group',
4154 'source_data': source_data,
4155 'source_data': source_data,
4155 'data': rule_user_group.rule_data()
4156 'data': rule_user_group.rule_data()
4156 }
4157 }
4157
4158
4158 return users
4159 return users
4159
4160
4160 def __repr__(self):
4161 def __repr__(self):
4161 return '<RepoReviewerRule(id=%r, repo=%r)>' % (
4162 return '<RepoReviewerRule(id=%r, repo=%r)>' % (
4162 self.repo_review_rule_id, self.repo)
4163 self.repo_review_rule_id, self.repo)
4163
4164
4164
4165
4165 class DbMigrateVersion(Base, BaseModel):
4166 class DbMigrateVersion(Base, BaseModel):
4166 __tablename__ = 'db_migrate_version'
4167 __tablename__ = 'db_migrate_version'
4167 __table_args__ = (
4168 __table_args__ = (
4168 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4169 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4169 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
4170 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
4170 )
4171 )
4171 repository_id = Column('repository_id', String(250), primary_key=True)
4172 repository_id = Column('repository_id', String(250), primary_key=True)
4172 repository_path = Column('repository_path', Text)
4173 repository_path = Column('repository_path', Text)
4173 version = Column('version', Integer)
4174 version = Column('version', Integer)
4174
4175
4175
4176
4176 class DbSession(Base, BaseModel):
4177 class DbSession(Base, BaseModel):
4177 __tablename__ = 'db_session'
4178 __tablename__ = 'db_session'
4178 __table_args__ = (
4179 __table_args__ = (
4179 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4180 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4180 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
4181 'mysql_charset': 'utf8', 'sqlite_autoincrement': True},
4181 )
4182 )
4182
4183
4183 def __repr__(self):
4184 def __repr__(self):
4184 return '<DB:DbSession({})>'.format(self.id)
4185 return '<DB:DbSession({})>'.format(self.id)
4185
4186
4186 id = Column('id', Integer())
4187 id = Column('id', Integer())
4187 namespace = Column('namespace', String(255), primary_key=True)
4188 namespace = Column('namespace', String(255), primary_key=True)
4188 accessed = Column('accessed', DateTime, nullable=False)
4189 accessed = Column('accessed', DateTime, nullable=False)
4189 created = Column('created', DateTime, nullable=False)
4190 created = Column('created', DateTime, nullable=False)
4190 data = Column('data', PickleType, nullable=False)
4191 data = Column('data', PickleType, nullable=False)
@@ -1,254 +1,258 b''
1
1
2 /******************************************************************************
2 /******************************************************************************
3 * *
3 * *
4 * DO NOT CHANGE THIS FILE MANUALLY *
4 * DO NOT CHANGE THIS FILE MANUALLY *
5 * *
5 * *
6 * *
6 * *
7 * This file is automatically generated when the app starts up with *
7 * This file is automatically generated when the app starts up with *
8 * generate_js_files = true *
8 * generate_js_files = true *
9 * *
9 * *
10 * To add a route here pass jsroute=True to the route definition in the app *
10 * To add a route here pass jsroute=True to the route definition in the app *
11 * *
11 * *
12 ******************************************************************************/
12 ******************************************************************************/
13 function registerRCRoutes() {
13 function registerRCRoutes() {
14 // routes registration
14 // routes registration
15 pyroutes.register('edit_user', '/_admin/users/%(user_id)s/edit', ['user_id']);
15 pyroutes.register('edit_user', '/_admin/users/%(user_id)s/edit', ['user_id']);
16 pyroutes.register('favicon', '/favicon.ico', []);
16 pyroutes.register('favicon', '/favicon.ico', []);
17 pyroutes.register('robots', '/robots.txt', []);
17 pyroutes.register('robots', '/robots.txt', []);
18 pyroutes.register('auth_home', '/_admin/auth*traverse', []);
18 pyroutes.register('auth_home', '/_admin/auth*traverse', []);
19 pyroutes.register('global_integrations_new', '/_admin/integrations/new', []);
19 pyroutes.register('global_integrations_new', '/_admin/integrations/new', []);
20 pyroutes.register('global_integrations_home', '/_admin/integrations', []);
20 pyroutes.register('global_integrations_home', '/_admin/integrations', []);
21 pyroutes.register('global_integrations_list', '/_admin/integrations/%(integration)s', ['integration']);
21 pyroutes.register('global_integrations_list', '/_admin/integrations/%(integration)s', ['integration']);
22 pyroutes.register('global_integrations_create', '/_admin/integrations/%(integration)s/new', ['integration']);
22 pyroutes.register('global_integrations_create', '/_admin/integrations/%(integration)s/new', ['integration']);
23 pyroutes.register('global_integrations_edit', '/_admin/integrations/%(integration)s/%(integration_id)s', ['integration', 'integration_id']);
23 pyroutes.register('global_integrations_edit', '/_admin/integrations/%(integration)s/%(integration_id)s', ['integration', 'integration_id']);
24 pyroutes.register('repo_group_integrations_home', '/%(repo_group_name)s/settings/integrations', ['repo_group_name']);
24 pyroutes.register('repo_group_integrations_home', '/%(repo_group_name)s/settings/integrations', ['repo_group_name']);
25 pyroutes.register('repo_group_integrations_new', '/%(repo_group_name)s/settings/integrations/new', ['repo_group_name']);
25 pyroutes.register('repo_group_integrations_new', '/%(repo_group_name)s/settings/integrations/new', ['repo_group_name']);
26 pyroutes.register('repo_group_integrations_list', '/%(repo_group_name)s/settings/integrations/%(integration)s', ['repo_group_name', 'integration']);
26 pyroutes.register('repo_group_integrations_list', '/%(repo_group_name)s/settings/integrations/%(integration)s', ['repo_group_name', 'integration']);
27 pyroutes.register('repo_group_integrations_create', '/%(repo_group_name)s/settings/integrations/%(integration)s/new', ['repo_group_name', 'integration']);
27 pyroutes.register('repo_group_integrations_create', '/%(repo_group_name)s/settings/integrations/%(integration)s/new', ['repo_group_name', 'integration']);
28 pyroutes.register('repo_group_integrations_edit', '/%(repo_group_name)s/settings/integrations/%(integration)s/%(integration_id)s', ['repo_group_name', 'integration', 'integration_id']);
28 pyroutes.register('repo_group_integrations_edit', '/%(repo_group_name)s/settings/integrations/%(integration)s/%(integration_id)s', ['repo_group_name', 'integration', 'integration_id']);
29 pyroutes.register('repo_integrations_home', '/%(repo_name)s/settings/integrations', ['repo_name']);
29 pyroutes.register('repo_integrations_home', '/%(repo_name)s/settings/integrations', ['repo_name']);
30 pyroutes.register('repo_integrations_new', '/%(repo_name)s/settings/integrations/new', ['repo_name']);
30 pyroutes.register('repo_integrations_new', '/%(repo_name)s/settings/integrations/new', ['repo_name']);
31 pyroutes.register('repo_integrations_list', '/%(repo_name)s/settings/integrations/%(integration)s', ['repo_name', 'integration']);
31 pyroutes.register('repo_integrations_list', '/%(repo_name)s/settings/integrations/%(integration)s', ['repo_name', 'integration']);
32 pyroutes.register('repo_integrations_create', '/%(repo_name)s/settings/integrations/%(integration)s/new', ['repo_name', 'integration']);
32 pyroutes.register('repo_integrations_create', '/%(repo_name)s/settings/integrations/%(integration)s/new', ['repo_name', 'integration']);
33 pyroutes.register('repo_integrations_edit', '/%(repo_name)s/settings/integrations/%(integration)s/%(integration_id)s', ['repo_name', 'integration', 'integration_id']);
33 pyroutes.register('repo_integrations_edit', '/%(repo_name)s/settings/integrations/%(integration)s/%(integration_id)s', ['repo_name', 'integration', 'integration_id']);
34 pyroutes.register('ops_ping', '/_admin/ops/ping', []);
34 pyroutes.register('ops_ping', '/_admin/ops/ping', []);
35 pyroutes.register('ops_error_test', '/_admin/ops/error', []);
35 pyroutes.register('ops_error_test', '/_admin/ops/error', []);
36 pyroutes.register('ops_redirect_test', '/_admin/ops/redirect', []);
36 pyroutes.register('ops_redirect_test', '/_admin/ops/redirect', []);
37 pyroutes.register('admin_home', '/_admin', []);
37 pyroutes.register('admin_home', '/_admin', []);
38 pyroutes.register('admin_audit_logs', '/_admin/audit_logs', []);
38 pyroutes.register('admin_audit_logs', '/_admin/audit_logs', []);
39 pyroutes.register('pull_requests_global_0', '/_admin/pull_requests/%(pull_request_id)s', ['pull_request_id']);
39 pyroutes.register('pull_requests_global_0', '/_admin/pull_requests/%(pull_request_id)s', ['pull_request_id']);
40 pyroutes.register('pull_requests_global_1', '/_admin/pull-requests/%(pull_request_id)s', ['pull_request_id']);
40 pyroutes.register('pull_requests_global_1', '/_admin/pull-requests/%(pull_request_id)s', ['pull_request_id']);
41 pyroutes.register('pull_requests_global', '/_admin/pull-request/%(pull_request_id)s', ['pull_request_id']);
41 pyroutes.register('pull_requests_global', '/_admin/pull-request/%(pull_request_id)s', ['pull_request_id']);
42 pyroutes.register('admin_settings_open_source', '/_admin/settings/open_source', []);
42 pyroutes.register('admin_settings_open_source', '/_admin/settings/open_source', []);
43 pyroutes.register('admin_settings_vcs_svn_generate_cfg', '/_admin/settings/vcs/svn_generate_cfg', []);
43 pyroutes.register('admin_settings_vcs_svn_generate_cfg', '/_admin/settings/vcs/svn_generate_cfg', []);
44 pyroutes.register('admin_settings_system', '/_admin/settings/system', []);
44 pyroutes.register('admin_settings_system', '/_admin/settings/system', []);
45 pyroutes.register('admin_settings_system_update', '/_admin/settings/system/updates', []);
45 pyroutes.register('admin_settings_system_update', '/_admin/settings/system/updates', []);
46 pyroutes.register('admin_settings_sessions', '/_admin/settings/sessions', []);
46 pyroutes.register('admin_settings_sessions', '/_admin/settings/sessions', []);
47 pyroutes.register('admin_settings_sessions_cleanup', '/_admin/settings/sessions/cleanup', []);
47 pyroutes.register('admin_settings_sessions_cleanup', '/_admin/settings/sessions/cleanup', []);
48 pyroutes.register('admin_settings_process_management', '/_admin/settings/process_management', []);
48 pyroutes.register('admin_settings_process_management', '/_admin/settings/process_management', []);
49 pyroutes.register('admin_settings_process_management_signal', '/_admin/settings/process_management/signal', []);
49 pyroutes.register('admin_settings_process_management_signal', '/_admin/settings/process_management/signal', []);
50 pyroutes.register('admin_permissions_application', '/_admin/permissions/application', []);
50 pyroutes.register('admin_permissions_application', '/_admin/permissions/application', []);
51 pyroutes.register('admin_permissions_application_update', '/_admin/permissions/application/update', []);
51 pyroutes.register('admin_permissions_application_update', '/_admin/permissions/application/update', []);
52 pyroutes.register('admin_permissions_global', '/_admin/permissions/global', []);
52 pyroutes.register('admin_permissions_global', '/_admin/permissions/global', []);
53 pyroutes.register('admin_permissions_global_update', '/_admin/permissions/global/update', []);
53 pyroutes.register('admin_permissions_global_update', '/_admin/permissions/global/update', []);
54 pyroutes.register('admin_permissions_object', '/_admin/permissions/object', []);
54 pyroutes.register('admin_permissions_object', '/_admin/permissions/object', []);
55 pyroutes.register('admin_permissions_object_update', '/_admin/permissions/object/update', []);
55 pyroutes.register('admin_permissions_object_update', '/_admin/permissions/object/update', []);
56 pyroutes.register('admin_permissions_ips', '/_admin/permissions/ips', []);
56 pyroutes.register('admin_permissions_ips', '/_admin/permissions/ips', []);
57 pyroutes.register('admin_permissions_overview', '/_admin/permissions/overview', []);
57 pyroutes.register('admin_permissions_overview', '/_admin/permissions/overview', []);
58 pyroutes.register('admin_permissions_auth_token_access', '/_admin/permissions/auth_token_access', []);
58 pyroutes.register('admin_permissions_auth_token_access', '/_admin/permissions/auth_token_access', []);
59 pyroutes.register('admin_permissions_ssh_keys', '/_admin/permissions/ssh_keys', []);
59 pyroutes.register('admin_permissions_ssh_keys', '/_admin/permissions/ssh_keys', []);
60 pyroutes.register('admin_permissions_ssh_keys_data', '/_admin/permissions/ssh_keys/data', []);
60 pyroutes.register('admin_permissions_ssh_keys_data', '/_admin/permissions/ssh_keys/data', []);
61 pyroutes.register('admin_permissions_ssh_keys_update', '/_admin/permissions/ssh_keys/update', []);
61 pyroutes.register('admin_permissions_ssh_keys_update', '/_admin/permissions/ssh_keys/update', []);
62 pyroutes.register('users', '/_admin/users', []);
62 pyroutes.register('users', '/_admin/users', []);
63 pyroutes.register('users_data', '/_admin/users_data', []);
63 pyroutes.register('users_data', '/_admin/users_data', []);
64 pyroutes.register('edit_user_auth_tokens', '/_admin/users/%(user_id)s/edit/auth_tokens', ['user_id']);
64 pyroutes.register('edit_user_auth_tokens', '/_admin/users/%(user_id)s/edit/auth_tokens', ['user_id']);
65 pyroutes.register('edit_user_auth_tokens_add', '/_admin/users/%(user_id)s/edit/auth_tokens/new', ['user_id']);
65 pyroutes.register('edit_user_auth_tokens_add', '/_admin/users/%(user_id)s/edit/auth_tokens/new', ['user_id']);
66 pyroutes.register('edit_user_auth_tokens_delete', '/_admin/users/%(user_id)s/edit/auth_tokens/delete', ['user_id']);
66 pyroutes.register('edit_user_auth_tokens_delete', '/_admin/users/%(user_id)s/edit/auth_tokens/delete', ['user_id']);
67 pyroutes.register('edit_user_ssh_keys', '/_admin/users/%(user_id)s/edit/ssh_keys', ['user_id']);
67 pyroutes.register('edit_user_ssh_keys', '/_admin/users/%(user_id)s/edit/ssh_keys', ['user_id']);
68 pyroutes.register('edit_user_ssh_keys_generate_keypair', '/_admin/users/%(user_id)s/edit/ssh_keys/generate', ['user_id']);
68 pyroutes.register('edit_user_ssh_keys_generate_keypair', '/_admin/users/%(user_id)s/edit/ssh_keys/generate', ['user_id']);
69 pyroutes.register('edit_user_ssh_keys_add', '/_admin/users/%(user_id)s/edit/ssh_keys/new', ['user_id']);
69 pyroutes.register('edit_user_ssh_keys_add', '/_admin/users/%(user_id)s/edit/ssh_keys/new', ['user_id']);
70 pyroutes.register('edit_user_ssh_keys_delete', '/_admin/users/%(user_id)s/edit/ssh_keys/delete', ['user_id']);
70 pyroutes.register('edit_user_ssh_keys_delete', '/_admin/users/%(user_id)s/edit/ssh_keys/delete', ['user_id']);
71 pyroutes.register('edit_user_emails', '/_admin/users/%(user_id)s/edit/emails', ['user_id']);
71 pyroutes.register('edit_user_emails', '/_admin/users/%(user_id)s/edit/emails', ['user_id']);
72 pyroutes.register('edit_user_emails_add', '/_admin/users/%(user_id)s/edit/emails/new', ['user_id']);
72 pyroutes.register('edit_user_emails_add', '/_admin/users/%(user_id)s/edit/emails/new', ['user_id']);
73 pyroutes.register('edit_user_emails_delete', '/_admin/users/%(user_id)s/edit/emails/delete', ['user_id']);
73 pyroutes.register('edit_user_emails_delete', '/_admin/users/%(user_id)s/edit/emails/delete', ['user_id']);
74 pyroutes.register('edit_user_ips', '/_admin/users/%(user_id)s/edit/ips', ['user_id']);
74 pyroutes.register('edit_user_ips', '/_admin/users/%(user_id)s/edit/ips', ['user_id']);
75 pyroutes.register('edit_user_ips_add', '/_admin/users/%(user_id)s/edit/ips/new', ['user_id']);
75 pyroutes.register('edit_user_ips_add', '/_admin/users/%(user_id)s/edit/ips/new', ['user_id']);
76 pyroutes.register('edit_user_ips_delete', '/_admin/users/%(user_id)s/edit/ips/delete', ['user_id']);
76 pyroutes.register('edit_user_ips_delete', '/_admin/users/%(user_id)s/edit/ips/delete', ['user_id']);
77 pyroutes.register('edit_user_perms_summary', '/_admin/users/%(user_id)s/edit/permissions_summary', ['user_id']);
77 pyroutes.register('edit_user_perms_summary', '/_admin/users/%(user_id)s/edit/permissions_summary', ['user_id']);
78 pyroutes.register('edit_user_perms_summary_json', '/_admin/users/%(user_id)s/edit/permissions_summary/json', ['user_id']);
78 pyroutes.register('edit_user_perms_summary_json', '/_admin/users/%(user_id)s/edit/permissions_summary/json', ['user_id']);
79 pyroutes.register('edit_user_groups_management', '/_admin/users/%(user_id)s/edit/groups_management', ['user_id']);
79 pyroutes.register('edit_user_groups_management', '/_admin/users/%(user_id)s/edit/groups_management', ['user_id']);
80 pyroutes.register('edit_user_groups_management_updates', '/_admin/users/%(user_id)s/edit/edit_user_groups_management/updates', ['user_id']);
80 pyroutes.register('edit_user_groups_management_updates', '/_admin/users/%(user_id)s/edit/edit_user_groups_management/updates', ['user_id']);
81 pyroutes.register('edit_user_audit_logs', '/_admin/users/%(user_id)s/edit/audit', ['user_id']);
81 pyroutes.register('edit_user_audit_logs', '/_admin/users/%(user_id)s/edit/audit', ['user_id']);
82 pyroutes.register('user_groups', '/_admin/user_groups', []);
82 pyroutes.register('user_groups', '/_admin/user_groups', []);
83 pyroutes.register('user_groups_data', '/_admin/user_groups_data', []);
83 pyroutes.register('user_groups_data', '/_admin/user_groups_data', []);
84 pyroutes.register('user_group_members_data', '/_admin/user_groups/%(user_group_id)s/members', ['user_group_id']);
84 pyroutes.register('user_group_members_data', '/_admin/user_groups/%(user_group_id)s/members', ['user_group_id']);
85 pyroutes.register('edit_user_group_perms_summary', '/_admin/user_groups/%(user_group_id)s/edit/permissions_summary', ['user_group_id']);
85 pyroutes.register('edit_user_group_perms_summary', '/_admin/user_groups/%(user_group_id)s/edit/permissions_summary', ['user_group_id']);
86 pyroutes.register('edit_user_group_perms_summary_json', '/_admin/user_groups/%(user_group_id)s/edit/permissions_summary/json', ['user_group_id']);
86 pyroutes.register('edit_user_group_perms_summary_json', '/_admin/user_groups/%(user_group_id)s/edit/permissions_summary/json', ['user_group_id']);
87 pyroutes.register('repos', '/_admin/repos', []);
87 pyroutes.register('repos', '/_admin/repos', []);
88 pyroutes.register('repo_new', '/_admin/repos/new', []);
88 pyroutes.register('repo_new', '/_admin/repos/new', []);
89 pyroutes.register('repo_create', '/_admin/repos/create', []);
89 pyroutes.register('repo_create', '/_admin/repos/create', []);
90 pyroutes.register('channelstream_connect', '/_admin/channelstream/connect', []);
90 pyroutes.register('channelstream_connect', '/_admin/channelstream/connect', []);
91 pyroutes.register('channelstream_subscribe', '/_admin/channelstream/subscribe', []);
91 pyroutes.register('channelstream_subscribe', '/_admin/channelstream/subscribe', []);
92 pyroutes.register('channelstream_proxy', '/_channelstream', []);
92 pyroutes.register('channelstream_proxy', '/_channelstream', []);
93 pyroutes.register('login', '/_admin/login', []);
93 pyroutes.register('login', '/_admin/login', []);
94 pyroutes.register('logout', '/_admin/logout', []);
94 pyroutes.register('logout', '/_admin/logout', []);
95 pyroutes.register('register', '/_admin/register', []);
95 pyroutes.register('register', '/_admin/register', []);
96 pyroutes.register('reset_password', '/_admin/password_reset', []);
96 pyroutes.register('reset_password', '/_admin/password_reset', []);
97 pyroutes.register('reset_password_confirmation', '/_admin/password_reset_confirmation', []);
97 pyroutes.register('reset_password_confirmation', '/_admin/password_reset_confirmation', []);
98 pyroutes.register('home', '/', []);
98 pyroutes.register('home', '/', []);
99 pyroutes.register('user_autocomplete_data', '/_users', []);
99 pyroutes.register('user_autocomplete_data', '/_users', []);
100 pyroutes.register('user_group_autocomplete_data', '/_user_groups', []);
100 pyroutes.register('user_group_autocomplete_data', '/_user_groups', []);
101 pyroutes.register('repo_list_data', '/_repos', []);
101 pyroutes.register('repo_list_data', '/_repos', []);
102 pyroutes.register('goto_switcher_data', '/_goto_data', []);
102 pyroutes.register('goto_switcher_data', '/_goto_data', []);
103 pyroutes.register('journal', '/_admin/journal', []);
103 pyroutes.register('journal', '/_admin/journal', []);
104 pyroutes.register('journal_rss', '/_admin/journal/rss', []);
104 pyroutes.register('journal_rss', '/_admin/journal/rss', []);
105 pyroutes.register('journal_atom', '/_admin/journal/atom', []);
105 pyroutes.register('journal_atom', '/_admin/journal/atom', []);
106 pyroutes.register('journal_public', '/_admin/public_journal', []);
106 pyroutes.register('journal_public', '/_admin/public_journal', []);
107 pyroutes.register('journal_public_atom', '/_admin/public_journal/atom', []);
107 pyroutes.register('journal_public_atom', '/_admin/public_journal/atom', []);
108 pyroutes.register('journal_public_atom_old', '/_admin/public_journal_atom', []);
108 pyroutes.register('journal_public_atom_old', '/_admin/public_journal_atom', []);
109 pyroutes.register('journal_public_rss', '/_admin/public_journal/rss', []);
109 pyroutes.register('journal_public_rss', '/_admin/public_journal/rss', []);
110 pyroutes.register('journal_public_rss_old', '/_admin/public_journal_rss', []);
110 pyroutes.register('journal_public_rss_old', '/_admin/public_journal_rss', []);
111 pyroutes.register('toggle_following', '/_admin/toggle_following', []);
111 pyroutes.register('toggle_following', '/_admin/toggle_following', []);
112 pyroutes.register('repo_creating', '/%(repo_name)s/repo_creating', ['repo_name']);
112 pyroutes.register('repo_creating', '/%(repo_name)s/repo_creating', ['repo_name']);
113 pyroutes.register('repo_creating_check', '/%(repo_name)s/repo_creating_check', ['repo_name']);
113 pyroutes.register('repo_creating_check', '/%(repo_name)s/repo_creating_check', ['repo_name']);
114 pyroutes.register('repo_summary_explicit', '/%(repo_name)s/summary', ['repo_name']);
114 pyroutes.register('repo_summary_explicit', '/%(repo_name)s/summary', ['repo_name']);
115 pyroutes.register('repo_summary_commits', '/%(repo_name)s/summary-commits', ['repo_name']);
115 pyroutes.register('repo_summary_commits', '/%(repo_name)s/summary-commits', ['repo_name']);
116 pyroutes.register('repo_commit', '/%(repo_name)s/changeset/%(commit_id)s', ['repo_name', 'commit_id']);
116 pyroutes.register('repo_commit', '/%(repo_name)s/changeset/%(commit_id)s', ['repo_name', 'commit_id']);
117 pyroutes.register('repo_commit_children', '/%(repo_name)s/changeset_children/%(commit_id)s', ['repo_name', 'commit_id']);
117 pyroutes.register('repo_commit_children', '/%(repo_name)s/changeset_children/%(commit_id)s', ['repo_name', 'commit_id']);
118 pyroutes.register('repo_commit_parents', '/%(repo_name)s/changeset_parents/%(commit_id)s', ['repo_name', 'commit_id']);
118 pyroutes.register('repo_commit_parents', '/%(repo_name)s/changeset_parents/%(commit_id)s', ['repo_name', 'commit_id']);
119 pyroutes.register('repo_commit_raw', '/%(repo_name)s/changeset-diff/%(commit_id)s', ['repo_name', 'commit_id']);
119 pyroutes.register('repo_commit_raw', '/%(repo_name)s/changeset-diff/%(commit_id)s', ['repo_name', 'commit_id']);
120 pyroutes.register('repo_commit_patch', '/%(repo_name)s/changeset-patch/%(commit_id)s', ['repo_name', 'commit_id']);
120 pyroutes.register('repo_commit_patch', '/%(repo_name)s/changeset-patch/%(commit_id)s', ['repo_name', 'commit_id']);
121 pyroutes.register('repo_commit_download', '/%(repo_name)s/changeset-download/%(commit_id)s', ['repo_name', 'commit_id']);
121 pyroutes.register('repo_commit_download', '/%(repo_name)s/changeset-download/%(commit_id)s', ['repo_name', 'commit_id']);
122 pyroutes.register('repo_commit_data', '/%(repo_name)s/changeset-data/%(commit_id)s', ['repo_name', 'commit_id']);
122 pyroutes.register('repo_commit_data', '/%(repo_name)s/changeset-data/%(commit_id)s', ['repo_name', 'commit_id']);
123 pyroutes.register('repo_commit_comment_create', '/%(repo_name)s/changeset/%(commit_id)s/comment/create', ['repo_name', 'commit_id']);
123 pyroutes.register('repo_commit_comment_create', '/%(repo_name)s/changeset/%(commit_id)s/comment/create', ['repo_name', 'commit_id']);
124 pyroutes.register('repo_commit_comment_preview', '/%(repo_name)s/changeset/%(commit_id)s/comment/preview', ['repo_name', 'commit_id']);
124 pyroutes.register('repo_commit_comment_preview', '/%(repo_name)s/changeset/%(commit_id)s/comment/preview', ['repo_name', 'commit_id']);
125 pyroutes.register('repo_commit_comment_delete', '/%(repo_name)s/changeset/%(commit_id)s/comment/%(comment_id)s/delete', ['repo_name', 'commit_id', 'comment_id']);
125 pyroutes.register('repo_commit_comment_delete', '/%(repo_name)s/changeset/%(commit_id)s/comment/%(comment_id)s/delete', ['repo_name', 'commit_id', 'comment_id']);
126 pyroutes.register('repo_commit_raw_deprecated', '/%(repo_name)s/raw-changeset/%(commit_id)s', ['repo_name', 'commit_id']);
126 pyroutes.register('repo_commit_raw_deprecated', '/%(repo_name)s/raw-changeset/%(commit_id)s', ['repo_name', 'commit_id']);
127 pyroutes.register('repo_archivefile', '/%(repo_name)s/archive/%(fname)s', ['repo_name', 'fname']);
127 pyroutes.register('repo_archivefile', '/%(repo_name)s/archive/%(fname)s', ['repo_name', 'fname']);
128 pyroutes.register('repo_files_diff', '/%(repo_name)s/diff/%(f_path)s', ['repo_name', 'f_path']);
128 pyroutes.register('repo_files_diff', '/%(repo_name)s/diff/%(f_path)s', ['repo_name', 'f_path']);
129 pyroutes.register('repo_files_diff_2way_redirect', '/%(repo_name)s/diff-2way/%(f_path)s', ['repo_name', 'f_path']);
129 pyroutes.register('repo_files_diff_2way_redirect', '/%(repo_name)s/diff-2way/%(f_path)s', ['repo_name', 'f_path']);
130 pyroutes.register('repo_files', '/%(repo_name)s/files/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
130 pyroutes.register('repo_files', '/%(repo_name)s/files/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
131 pyroutes.register('repo_files:default_path', '/%(repo_name)s/files/%(commit_id)s/', ['repo_name', 'commit_id']);
131 pyroutes.register('repo_files:default_path', '/%(repo_name)s/files/%(commit_id)s/', ['repo_name', 'commit_id']);
132 pyroutes.register('repo_files:default_commit', '/%(repo_name)s/files', ['repo_name']);
132 pyroutes.register('repo_files:default_commit', '/%(repo_name)s/files', ['repo_name']);
133 pyroutes.register('repo_files:rendered', '/%(repo_name)s/render/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
133 pyroutes.register('repo_files:rendered', '/%(repo_name)s/render/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
134 pyroutes.register('repo_files:annotated', '/%(repo_name)s/annotate/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
134 pyroutes.register('repo_files:annotated', '/%(repo_name)s/annotate/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
135 pyroutes.register('repo_files:annotated_previous', '/%(repo_name)s/annotate-previous/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
135 pyroutes.register('repo_files:annotated_previous', '/%(repo_name)s/annotate-previous/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
136 pyroutes.register('repo_nodetree_full', '/%(repo_name)s/nodetree_full/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
136 pyroutes.register('repo_nodetree_full', '/%(repo_name)s/nodetree_full/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
137 pyroutes.register('repo_nodetree_full:default_path', '/%(repo_name)s/nodetree_full/%(commit_id)s/', ['repo_name', 'commit_id']);
137 pyroutes.register('repo_nodetree_full:default_path', '/%(repo_name)s/nodetree_full/%(commit_id)s/', ['repo_name', 'commit_id']);
138 pyroutes.register('repo_files_nodelist', '/%(repo_name)s/nodelist/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
138 pyroutes.register('repo_files_nodelist', '/%(repo_name)s/nodelist/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
139 pyroutes.register('repo_file_raw', '/%(repo_name)s/raw/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
139 pyroutes.register('repo_file_raw', '/%(repo_name)s/raw/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
140 pyroutes.register('repo_file_download', '/%(repo_name)s/download/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
140 pyroutes.register('repo_file_download', '/%(repo_name)s/download/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
141 pyroutes.register('repo_file_download:legacy', '/%(repo_name)s/rawfile/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
141 pyroutes.register('repo_file_download:legacy', '/%(repo_name)s/rawfile/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
142 pyroutes.register('repo_file_history', '/%(repo_name)s/history/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
142 pyroutes.register('repo_file_history', '/%(repo_name)s/history/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
143 pyroutes.register('repo_file_authors', '/%(repo_name)s/authors/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
143 pyroutes.register('repo_file_authors', '/%(repo_name)s/authors/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
144 pyroutes.register('repo_files_remove_file', '/%(repo_name)s/remove_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
144 pyroutes.register('repo_files_remove_file', '/%(repo_name)s/remove_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
145 pyroutes.register('repo_files_delete_file', '/%(repo_name)s/delete_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
145 pyroutes.register('repo_files_delete_file', '/%(repo_name)s/delete_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
146 pyroutes.register('repo_files_edit_file', '/%(repo_name)s/edit_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
146 pyroutes.register('repo_files_edit_file', '/%(repo_name)s/edit_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
147 pyroutes.register('repo_files_update_file', '/%(repo_name)s/update_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
147 pyroutes.register('repo_files_update_file', '/%(repo_name)s/update_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
148 pyroutes.register('repo_files_add_file', '/%(repo_name)s/add_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
148 pyroutes.register('repo_files_add_file', '/%(repo_name)s/add_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
149 pyroutes.register('repo_files_create_file', '/%(repo_name)s/create_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
149 pyroutes.register('repo_files_create_file', '/%(repo_name)s/create_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
150 pyroutes.register('repo_refs_data', '/%(repo_name)s/refs-data', ['repo_name']);
150 pyroutes.register('repo_refs_data', '/%(repo_name)s/refs-data', ['repo_name']);
151 pyroutes.register('repo_refs_changelog_data', '/%(repo_name)s/refs-data-changelog', ['repo_name']);
151 pyroutes.register('repo_refs_changelog_data', '/%(repo_name)s/refs-data-changelog', ['repo_name']);
152 pyroutes.register('repo_stats', '/%(repo_name)s/repo_stats/%(commit_id)s', ['repo_name', 'commit_id']);
152 pyroutes.register('repo_stats', '/%(repo_name)s/repo_stats/%(commit_id)s', ['repo_name', 'commit_id']);
153 pyroutes.register('repo_changelog', '/%(repo_name)s/changelog', ['repo_name']);
153 pyroutes.register('repo_changelog', '/%(repo_name)s/changelog', ['repo_name']);
154 pyroutes.register('repo_changelog_file', '/%(repo_name)s/changelog/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
154 pyroutes.register('repo_changelog_file', '/%(repo_name)s/changelog/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
155 pyroutes.register('repo_changelog_elements', '/%(repo_name)s/changelog_elements', ['repo_name']);
155 pyroutes.register('repo_changelog_elements', '/%(repo_name)s/changelog_elements', ['repo_name']);
156 pyroutes.register('repo_compare_select', '/%(repo_name)s/compare', ['repo_name']);
156 pyroutes.register('repo_compare_select', '/%(repo_name)s/compare', ['repo_name']);
157 pyroutes.register('repo_compare', '/%(repo_name)s/compare/%(source_ref_type)s@%(source_ref)s...%(target_ref_type)s@%(target_ref)s', ['repo_name', 'source_ref_type', 'source_ref', 'target_ref_type', 'target_ref']);
157 pyroutes.register('repo_compare', '/%(repo_name)s/compare/%(source_ref_type)s@%(source_ref)s...%(target_ref_type)s@%(target_ref)s', ['repo_name', 'source_ref_type', 'source_ref', 'target_ref_type', 'target_ref']);
158 pyroutes.register('tags_home', '/%(repo_name)s/tags', ['repo_name']);
158 pyroutes.register('tags_home', '/%(repo_name)s/tags', ['repo_name']);
159 pyroutes.register('branches_home', '/%(repo_name)s/branches', ['repo_name']);
159 pyroutes.register('branches_home', '/%(repo_name)s/branches', ['repo_name']);
160 pyroutes.register('bookmarks_home', '/%(repo_name)s/bookmarks', ['repo_name']);
160 pyroutes.register('bookmarks_home', '/%(repo_name)s/bookmarks', ['repo_name']);
161 pyroutes.register('repo_fork_new', '/%(repo_name)s/fork', ['repo_name']);
161 pyroutes.register('repo_fork_new', '/%(repo_name)s/fork', ['repo_name']);
162 pyroutes.register('repo_fork_create', '/%(repo_name)s/fork/create', ['repo_name']);
162 pyroutes.register('repo_fork_create', '/%(repo_name)s/fork/create', ['repo_name']);
163 pyroutes.register('repo_forks_show_all', '/%(repo_name)s/forks', ['repo_name']);
163 pyroutes.register('repo_forks_show_all', '/%(repo_name)s/forks', ['repo_name']);
164 pyroutes.register('repo_forks_data', '/%(repo_name)s/forks/data', ['repo_name']);
164 pyroutes.register('repo_forks_data', '/%(repo_name)s/forks/data', ['repo_name']);
165 pyroutes.register('pullrequest_show', '/%(repo_name)s/pull-request/%(pull_request_id)s', ['repo_name', 'pull_request_id']);
165 pyroutes.register('pullrequest_show', '/%(repo_name)s/pull-request/%(pull_request_id)s', ['repo_name', 'pull_request_id']);
166 pyroutes.register('pullrequest_show_all', '/%(repo_name)s/pull-request', ['repo_name']);
166 pyroutes.register('pullrequest_show_all', '/%(repo_name)s/pull-request', ['repo_name']);
167 pyroutes.register('pullrequest_show_all_data', '/%(repo_name)s/pull-request-data', ['repo_name']);
167 pyroutes.register('pullrequest_show_all_data', '/%(repo_name)s/pull-request-data', ['repo_name']);
168 pyroutes.register('pullrequest_repo_refs', '/%(repo_name)s/pull-request/refs/%(target_repo_name)s', ['repo_name', 'target_repo_name']);
168 pyroutes.register('pullrequest_repo_refs', '/%(repo_name)s/pull-request/refs/%(target_repo_name)s', ['repo_name', 'target_repo_name']);
169 pyroutes.register('pullrequest_repo_destinations', '/%(repo_name)s/pull-request/repo-destinations', ['repo_name']);
169 pyroutes.register('pullrequest_repo_destinations', '/%(repo_name)s/pull-request/repo-destinations', ['repo_name']);
170 pyroutes.register('pullrequest_new', '/%(repo_name)s/pull-request/new', ['repo_name']);
170 pyroutes.register('pullrequest_new', '/%(repo_name)s/pull-request/new', ['repo_name']);
171 pyroutes.register('pullrequest_create', '/%(repo_name)s/pull-request/create', ['repo_name']);
171 pyroutes.register('pullrequest_create', '/%(repo_name)s/pull-request/create', ['repo_name']);
172 pyroutes.register('pullrequest_update', '/%(repo_name)s/pull-request/%(pull_request_id)s/update', ['repo_name', 'pull_request_id']);
172 pyroutes.register('pullrequest_update', '/%(repo_name)s/pull-request/%(pull_request_id)s/update', ['repo_name', 'pull_request_id']);
173 pyroutes.register('pullrequest_merge', '/%(repo_name)s/pull-request/%(pull_request_id)s/merge', ['repo_name', 'pull_request_id']);
173 pyroutes.register('pullrequest_merge', '/%(repo_name)s/pull-request/%(pull_request_id)s/merge', ['repo_name', 'pull_request_id']);
174 pyroutes.register('pullrequest_delete', '/%(repo_name)s/pull-request/%(pull_request_id)s/delete', ['repo_name', 'pull_request_id']);
174 pyroutes.register('pullrequest_delete', '/%(repo_name)s/pull-request/%(pull_request_id)s/delete', ['repo_name', 'pull_request_id']);
175 pyroutes.register('pullrequest_comment_create', '/%(repo_name)s/pull-request/%(pull_request_id)s/comment', ['repo_name', 'pull_request_id']);
175 pyroutes.register('pullrequest_comment_create', '/%(repo_name)s/pull-request/%(pull_request_id)s/comment', ['repo_name', 'pull_request_id']);
176 pyroutes.register('pullrequest_comment_delete', '/%(repo_name)s/pull-request/%(pull_request_id)s/comment/%(comment_id)s/delete', ['repo_name', 'pull_request_id', 'comment_id']);
176 pyroutes.register('pullrequest_comment_delete', '/%(repo_name)s/pull-request/%(pull_request_id)s/comment/%(comment_id)s/delete', ['repo_name', 'pull_request_id', 'comment_id']);
177 pyroutes.register('edit_repo', '/%(repo_name)s/settings', ['repo_name']);
177 pyroutes.register('edit_repo', '/%(repo_name)s/settings', ['repo_name']);
178 pyroutes.register('edit_repo_advanced', '/%(repo_name)s/settings/advanced', ['repo_name']);
178 pyroutes.register('edit_repo_advanced', '/%(repo_name)s/settings/advanced', ['repo_name']);
179 pyroutes.register('edit_repo_advanced_delete', '/%(repo_name)s/settings/advanced/delete', ['repo_name']);
179 pyroutes.register('edit_repo_advanced_delete', '/%(repo_name)s/settings/advanced/delete', ['repo_name']);
180 pyroutes.register('edit_repo_advanced_locking', '/%(repo_name)s/settings/advanced/locking', ['repo_name']);
180 pyroutes.register('edit_repo_advanced_locking', '/%(repo_name)s/settings/advanced/locking', ['repo_name']);
181 pyroutes.register('edit_repo_advanced_journal', '/%(repo_name)s/settings/advanced/journal', ['repo_name']);
181 pyroutes.register('edit_repo_advanced_journal', '/%(repo_name)s/settings/advanced/journal', ['repo_name']);
182 pyroutes.register('edit_repo_advanced_fork', '/%(repo_name)s/settings/advanced/fork', ['repo_name']);
182 pyroutes.register('edit_repo_advanced_fork', '/%(repo_name)s/settings/advanced/fork', ['repo_name']);
183 pyroutes.register('edit_repo_caches', '/%(repo_name)s/settings/caches', ['repo_name']);
183 pyroutes.register('edit_repo_caches', '/%(repo_name)s/settings/caches', ['repo_name']);
184 pyroutes.register('edit_repo_perms', '/%(repo_name)s/settings/permissions', ['repo_name']);
184 pyroutes.register('edit_repo_perms', '/%(repo_name)s/settings/permissions', ['repo_name']);
185 pyroutes.register('edit_repo_maintenance', '/%(repo_name)s/settings/maintenance', ['repo_name']);
185 pyroutes.register('edit_repo_maintenance', '/%(repo_name)s/settings/maintenance', ['repo_name']);
186 pyroutes.register('edit_repo_maintenance_execute', '/%(repo_name)s/settings/maintenance/execute', ['repo_name']);
186 pyroutes.register('edit_repo_maintenance_execute', '/%(repo_name)s/settings/maintenance/execute', ['repo_name']);
187 pyroutes.register('edit_repo_fields', '/%(repo_name)s/settings/fields', ['repo_name']);
187 pyroutes.register('edit_repo_fields', '/%(repo_name)s/settings/fields', ['repo_name']);
188 pyroutes.register('edit_repo_fields_create', '/%(repo_name)s/settings/fields/create', ['repo_name']);
188 pyroutes.register('edit_repo_fields_create', '/%(repo_name)s/settings/fields/create', ['repo_name']);
189 pyroutes.register('edit_repo_fields_delete', '/%(repo_name)s/settings/fields/%(field_id)s/delete', ['repo_name', 'field_id']);
189 pyroutes.register('edit_repo_fields_delete', '/%(repo_name)s/settings/fields/%(field_id)s/delete', ['repo_name', 'field_id']);
190 pyroutes.register('repo_edit_toggle_locking', '/%(repo_name)s/settings/toggle_locking', ['repo_name']);
190 pyroutes.register('repo_edit_toggle_locking', '/%(repo_name)s/settings/toggle_locking', ['repo_name']);
191 pyroutes.register('edit_repo_remote', '/%(repo_name)s/settings/remote', ['repo_name']);
191 pyroutes.register('edit_repo_remote', '/%(repo_name)s/settings/remote', ['repo_name']);
192 pyroutes.register('edit_repo_remote_pull', '/%(repo_name)s/settings/remote/pull', ['repo_name']);
192 pyroutes.register('edit_repo_remote_pull', '/%(repo_name)s/settings/remote/pull', ['repo_name']);
193 pyroutes.register('edit_repo_statistics', '/%(repo_name)s/settings/statistics', ['repo_name']);
193 pyroutes.register('edit_repo_statistics', '/%(repo_name)s/settings/statistics', ['repo_name']);
194 pyroutes.register('edit_repo_statistics_reset', '/%(repo_name)s/settings/statistics/update', ['repo_name']);
194 pyroutes.register('edit_repo_statistics_reset', '/%(repo_name)s/settings/statistics/update', ['repo_name']);
195 pyroutes.register('edit_repo_issuetracker', '/%(repo_name)s/settings/issue_trackers', ['repo_name']);
195 pyroutes.register('edit_repo_issuetracker', '/%(repo_name)s/settings/issue_trackers', ['repo_name']);
196 pyroutes.register('edit_repo_issuetracker_test', '/%(repo_name)s/settings/issue_trackers/test', ['repo_name']);
196 pyroutes.register('edit_repo_issuetracker_test', '/%(repo_name)s/settings/issue_trackers/test', ['repo_name']);
197 pyroutes.register('edit_repo_issuetracker_delete', '/%(repo_name)s/settings/issue_trackers/delete', ['repo_name']);
197 pyroutes.register('edit_repo_issuetracker_delete', '/%(repo_name)s/settings/issue_trackers/delete', ['repo_name']);
198 pyroutes.register('edit_repo_issuetracker_update', '/%(repo_name)s/settings/issue_trackers/update', ['repo_name']);
198 pyroutes.register('edit_repo_issuetracker_update', '/%(repo_name)s/settings/issue_trackers/update', ['repo_name']);
199 pyroutes.register('edit_repo_vcs', '/%(repo_name)s/settings/vcs', ['repo_name']);
199 pyroutes.register('edit_repo_vcs', '/%(repo_name)s/settings/vcs', ['repo_name']);
200 pyroutes.register('edit_repo_vcs_update', '/%(repo_name)s/settings/vcs/update', ['repo_name']);
200 pyroutes.register('edit_repo_vcs_update', '/%(repo_name)s/settings/vcs/update', ['repo_name']);
201 pyroutes.register('edit_repo_vcs_svn_pattern_delete', '/%(repo_name)s/settings/vcs/svn_pattern/delete', ['repo_name']);
201 pyroutes.register('edit_repo_vcs_svn_pattern_delete', '/%(repo_name)s/settings/vcs/svn_pattern/delete', ['repo_name']);
202 pyroutes.register('repo_reviewers', '/%(repo_name)s/settings/review/rules', ['repo_name']);
202 pyroutes.register('repo_reviewers', '/%(repo_name)s/settings/review/rules', ['repo_name']);
203 pyroutes.register('repo_default_reviewers_data', '/%(repo_name)s/settings/review/default-reviewers', ['repo_name']);
203 pyroutes.register('repo_default_reviewers_data', '/%(repo_name)s/settings/review/default-reviewers', ['repo_name']);
204 pyroutes.register('edit_repo_strip', '/%(repo_name)s/settings/strip', ['repo_name']);
204 pyroutes.register('edit_repo_strip', '/%(repo_name)s/settings/strip', ['repo_name']);
205 pyroutes.register('strip_check', '/%(repo_name)s/settings/strip_check', ['repo_name']);
205 pyroutes.register('strip_check', '/%(repo_name)s/settings/strip_check', ['repo_name']);
206 pyroutes.register('strip_execute', '/%(repo_name)s/settings/strip_execute', ['repo_name']);
206 pyroutes.register('strip_execute', '/%(repo_name)s/settings/strip_execute', ['repo_name']);
207 pyroutes.register('rss_feed_home', '/%(repo_name)s/feed/rss', ['repo_name']);
207 pyroutes.register('rss_feed_home', '/%(repo_name)s/feed/rss', ['repo_name']);
208 pyroutes.register('atom_feed_home', '/%(repo_name)s/feed/atom', ['repo_name']);
208 pyroutes.register('atom_feed_home', '/%(repo_name)s/feed/atom', ['repo_name']);
209 pyroutes.register('repo_summary', '/%(repo_name)s', ['repo_name']);
209 pyroutes.register('repo_summary', '/%(repo_name)s', ['repo_name']);
210 pyroutes.register('repo_summary_slash', '/%(repo_name)s/', ['repo_name']);
210 pyroutes.register('repo_summary_slash', '/%(repo_name)s/', ['repo_name']);
211 pyroutes.register('repo_group_home', '/%(repo_group_name)s', ['repo_group_name']);
211 pyroutes.register('repo_group_home', '/%(repo_group_name)s', ['repo_group_name']);
212 pyroutes.register('repo_group_home_slash', '/%(repo_group_name)s/', ['repo_group_name']);
212 pyroutes.register('repo_group_home_slash', '/%(repo_group_name)s/', ['repo_group_name']);
213 pyroutes.register('search', '/_admin/search', []);
213 pyroutes.register('search', '/_admin/search', []);
214 pyroutes.register('search_repo', '/%(repo_name)s/search', ['repo_name']);
214 pyroutes.register('search_repo', '/%(repo_name)s/search', ['repo_name']);
215 pyroutes.register('user_profile', '/_profiles/%(username)s', ['username']);
215 pyroutes.register('user_profile', '/_profiles/%(username)s', ['username']);
216 pyroutes.register('my_account_profile', '/_admin/my_account/profile', []);
216 pyroutes.register('my_account_profile', '/_admin/my_account/profile', []);
217 pyroutes.register('my_account_edit', '/_admin/my_account/edit', []);
217 pyroutes.register('my_account_edit', '/_admin/my_account/edit', []);
218 pyroutes.register('my_account_update', '/_admin/my_account/update', []);
218 pyroutes.register('my_account_update', '/_admin/my_account/update', []);
219 pyroutes.register('my_account_password', '/_admin/my_account/password', []);
219 pyroutes.register('my_account_password', '/_admin/my_account/password', []);
220 pyroutes.register('my_account_password_update', '/_admin/my_account/password/update', []);
220 pyroutes.register('my_account_password_update', '/_admin/my_account/password/update', []);
221 pyroutes.register('my_account_auth_tokens', '/_admin/my_account/auth_tokens', []);
221 pyroutes.register('my_account_auth_tokens', '/_admin/my_account/auth_tokens', []);
222 pyroutes.register('my_account_auth_tokens_add', '/_admin/my_account/auth_tokens/new', []);
222 pyroutes.register('my_account_auth_tokens_add', '/_admin/my_account/auth_tokens/new', []);
223 pyroutes.register('my_account_auth_tokens_delete', '/_admin/my_account/auth_tokens/delete', []);
223 pyroutes.register('my_account_auth_tokens_delete', '/_admin/my_account/auth_tokens/delete', []);
224 pyroutes.register('my_account_ssh_keys', '/_admin/my_account/ssh_keys', []);
225 pyroutes.register('my_account_ssh_keys_generate', '/_admin/my_account/ssh_keys/generate', []);
226 pyroutes.register('my_account_ssh_keys_add', '/_admin/my_account/ssh_keys/new', []);
227 pyroutes.register('my_account_ssh_keys_delete', '/_admin/my_account/ssh_keys/delete', []);
224 pyroutes.register('my_account_emails', '/_admin/my_account/emails', []);
228 pyroutes.register('my_account_emails', '/_admin/my_account/emails', []);
225 pyroutes.register('my_account_emails_add', '/_admin/my_account/emails/new', []);
229 pyroutes.register('my_account_emails_add', '/_admin/my_account/emails/new', []);
226 pyroutes.register('my_account_emails_delete', '/_admin/my_account/emails/delete', []);
230 pyroutes.register('my_account_emails_delete', '/_admin/my_account/emails/delete', []);
227 pyroutes.register('my_account_repos', '/_admin/my_account/repos', []);
231 pyroutes.register('my_account_repos', '/_admin/my_account/repos', []);
228 pyroutes.register('my_account_watched', '/_admin/my_account/watched', []);
232 pyroutes.register('my_account_watched', '/_admin/my_account/watched', []);
229 pyroutes.register('my_account_perms', '/_admin/my_account/perms', []);
233 pyroutes.register('my_account_perms', '/_admin/my_account/perms', []);
230 pyroutes.register('my_account_notifications', '/_admin/my_account/notifications', []);
234 pyroutes.register('my_account_notifications', '/_admin/my_account/notifications', []);
231 pyroutes.register('my_account_notifications_toggle_visibility', '/_admin/my_account/toggle_visibility', []);
235 pyroutes.register('my_account_notifications_toggle_visibility', '/_admin/my_account/toggle_visibility', []);
232 pyroutes.register('my_account_pullrequests', '/_admin/my_account/pull_requests', []);
236 pyroutes.register('my_account_pullrequests', '/_admin/my_account/pull_requests', []);
233 pyroutes.register('my_account_pullrequests_data', '/_admin/my_account/pull_requests/data', []);
237 pyroutes.register('my_account_pullrequests_data', '/_admin/my_account/pull_requests/data', []);
234 pyroutes.register('notifications_show_all', '/_admin/notifications', []);
238 pyroutes.register('notifications_show_all', '/_admin/notifications', []);
235 pyroutes.register('notifications_mark_all_read', '/_admin/notifications/mark_all_read', []);
239 pyroutes.register('notifications_mark_all_read', '/_admin/notifications/mark_all_read', []);
236 pyroutes.register('notifications_show', '/_admin/notifications/%(notification_id)s', ['notification_id']);
240 pyroutes.register('notifications_show', '/_admin/notifications/%(notification_id)s', ['notification_id']);
237 pyroutes.register('notifications_update', '/_admin/notifications/%(notification_id)s/update', ['notification_id']);
241 pyroutes.register('notifications_update', '/_admin/notifications/%(notification_id)s/update', ['notification_id']);
238 pyroutes.register('notifications_delete', '/_admin/notifications/%(notification_id)s/delete', ['notification_id']);
242 pyroutes.register('notifications_delete', '/_admin/notifications/%(notification_id)s/delete', ['notification_id']);
239 pyroutes.register('my_account_notifications_test_channelstream', '/_admin/my_account/test_channelstream', []);
243 pyroutes.register('my_account_notifications_test_channelstream', '/_admin/my_account/test_channelstream', []);
240 pyroutes.register('gists_show', '/_admin/gists', []);
244 pyroutes.register('gists_show', '/_admin/gists', []);
241 pyroutes.register('gists_new', '/_admin/gists/new', []);
245 pyroutes.register('gists_new', '/_admin/gists/new', []);
242 pyroutes.register('gists_create', '/_admin/gists/create', []);
246 pyroutes.register('gists_create', '/_admin/gists/create', []);
243 pyroutes.register('gist_show', '/_admin/gists/%(gist_id)s', ['gist_id']);
247 pyroutes.register('gist_show', '/_admin/gists/%(gist_id)s', ['gist_id']);
244 pyroutes.register('gist_delete', '/_admin/gists/%(gist_id)s/delete', ['gist_id']);
248 pyroutes.register('gist_delete', '/_admin/gists/%(gist_id)s/delete', ['gist_id']);
245 pyroutes.register('gist_edit', '/_admin/gists/%(gist_id)s/edit', ['gist_id']);
249 pyroutes.register('gist_edit', '/_admin/gists/%(gist_id)s/edit', ['gist_id']);
246 pyroutes.register('gist_edit_check_revision', '/_admin/gists/%(gist_id)s/edit/check_revision', ['gist_id']);
250 pyroutes.register('gist_edit_check_revision', '/_admin/gists/%(gist_id)s/edit/check_revision', ['gist_id']);
247 pyroutes.register('gist_update', '/_admin/gists/%(gist_id)s/update', ['gist_id']);
251 pyroutes.register('gist_update', '/_admin/gists/%(gist_id)s/update', ['gist_id']);
248 pyroutes.register('gist_show_rev', '/_admin/gists/%(gist_id)s/%(revision)s', ['gist_id', 'revision']);
252 pyroutes.register('gist_show_rev', '/_admin/gists/%(gist_id)s/%(revision)s', ['gist_id', 'revision']);
249 pyroutes.register('gist_show_formatted', '/_admin/gists/%(gist_id)s/%(revision)s/%(format)s', ['gist_id', 'revision', 'format']);
253 pyroutes.register('gist_show_formatted', '/_admin/gists/%(gist_id)s/%(revision)s/%(format)s', ['gist_id', 'revision', 'format']);
250 pyroutes.register('gist_show_formatted_path', '/_admin/gists/%(gist_id)s/%(revision)s/%(format)s/%(f_path)s', ['gist_id', 'revision', 'format', 'f_path']);
254 pyroutes.register('gist_show_formatted_path', '/_admin/gists/%(gist_id)s/%(revision)s/%(format)s/%(f_path)s', ['gist_id', 'revision', 'format', 'f_path']);
251 pyroutes.register('debug_style_home', '/_admin/debug_style', []);
255 pyroutes.register('debug_style_home', '/_admin/debug_style', []);
252 pyroutes.register('debug_style_template', '/_admin/debug_style/t/%(t_path)s', ['t_path']);
256 pyroutes.register('debug_style_template', '/_admin/debug_style/t/%(t_path)s', ['t_path']);
253 pyroutes.register('apiv2', '/_admin/api', []);
257 pyroutes.register('apiv2', '/_admin/api', []);
254 }
258 }
@@ -1,52 +1,53 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <%inherit file="/base/base.mako"/>
2 <%inherit file="/base/base.mako"/>
3
3
4 <%def name="title()">
4 <%def name="title()">
5 ${_('My account')} ${c.rhodecode_user.username}
5 ${_('My account')} ${c.rhodecode_user.username}
6 %if c.rhodecode_name:
6 %if c.rhodecode_name:
7 &middot; ${h.branding(c.rhodecode_name)}
7 &middot; ${h.branding(c.rhodecode_name)}
8 %endif
8 %endif
9 </%def>
9 </%def>
10
10
11 <%def name="breadcrumbs_links()">
11 <%def name="breadcrumbs_links()">
12 ${_('My Account')}
12 ${_('My Account')}
13 </%def>
13 </%def>
14
14
15 <%def name="menu_bar_nav()">
15 <%def name="menu_bar_nav()">
16 ${self.menu_items(active='my_account')}
16 ${self.menu_items(active='my_account')}
17 </%def>
17 </%def>
18
18
19 <%def name="main()">
19 <%def name="main()">
20 <div class="box">
20 <div class="box">
21 <div class="title">
21 <div class="title">
22 ${self.breadcrumbs()}
22 ${self.breadcrumbs()}
23 </div>
23 </div>
24
24
25 <div class="sidebar-col-wrapper scw-small">
25 <div class="sidebar-col-wrapper scw-small">
26 ##main
26 ##main
27 <div class="sidebar">
27 <div class="sidebar">
28 <ul class="nav nav-pills nav-stacked">
28 <ul class="nav nav-pills nav-stacked">
29 <li class="${'active' if c.active=='profile' or c.active=='profile_edit' else ''}"><a href="${h.route_path('my_account_profile')}">${_('Profile')}</a></li>
29 <li class="${'active' if c.active=='profile' or c.active=='profile_edit' else ''}"><a href="${h.route_path('my_account_profile')}">${_('Profile')}</a></li>
30 <li class="${'active' if c.active=='password' else ''}"><a href="${h.route_path('my_account_password')}">${_('Password')}</a></li>
30 <li class="${'active' if c.active=='password' else ''}"><a href="${h.route_path('my_account_password')}">${_('Password')}</a></li>
31 <li class="${'active' if c.active=='auth_tokens' else ''}"><a href="${h.route_path('my_account_auth_tokens')}">${_('Auth Tokens')}</a></li>
31 <li class="${'active' if c.active=='auth_tokens' else ''}"><a href="${h.route_path('my_account_auth_tokens')}">${_('Auth Tokens')}</a></li>
32 <li class="${'active' if c.active in ['ssh_keys', 'ssh_keys_generate'] else ''}"><a href="${h.route_path('my_account_ssh_keys')}">${_('SSH Keys')}</a></li>
32 ## TODO: Find a better integration of oauth views into navigation.
33 ## TODO: Find a better integration of oauth views into navigation.
33 <% my_account_oauth_url = h.route_path_or_none('my_account_oauth') %>
34 <% my_account_oauth_url = h.route_path_or_none('my_account_oauth') %>
34 % if my_account_oauth_url:
35 % if my_account_oauth_url:
35 <li class="${'active' if c.active=='oauth' else ''}"><a href="${my_account_oauth_url}">${_('OAuth Identities')}</a></li>
36 <li class="${'active' if c.active=='oauth' else ''}"><a href="${my_account_oauth_url}">${_('OAuth Identities')}</a></li>
36 % endif
37 % endif
37 <li class="${'active' if c.active=='emails' else ''}"><a href="${h.route_path('my_account_emails')}">${_('Emails')}</a></li>
38 <li class="${'active' if c.active=='emails' else ''}"><a href="${h.route_path('my_account_emails')}">${_('Emails')}</a></li>
38 <li class="${'active' if c.active=='repos' else ''}"><a href="${h.route_path('my_account_repos')}">${_('Repositories')}</a></li>
39 <li class="${'active' if c.active=='repos' else ''}"><a href="${h.route_path('my_account_repos')}">${_('Repositories')}</a></li>
39 <li class="${'active' if c.active=='watched' else ''}"><a href="${h.route_path('my_account_watched')}">${_('Watched')}</a></li>
40 <li class="${'active' if c.active=='watched' else ''}"><a href="${h.route_path('my_account_watched')}">${_('Watched')}</a></li>
40 <li class="${'active' if c.active=='pullrequests' else ''}"><a href="${h.route_path('my_account_pullrequests')}">${_('Pull Requests')}</a></li>
41 <li class="${'active' if c.active=='pullrequests' else ''}"><a href="${h.route_path('my_account_pullrequests')}">${_('Pull Requests')}</a></li>
41 <li class="${'active' if c.active=='perms' else ''}"><a href="${h.route_path('my_account_perms')}">${_('Permissions')}</a></li>
42 <li class="${'active' if c.active=='perms' else ''}"><a href="${h.route_path('my_account_perms')}">${_('Permissions')}</a></li>
42 <li class="${'active' if c.active=='my_notifications' else ''}"><a href="${h.route_path('my_account_notifications')}">${_('Live Notifications')}</a></li>
43 <li class="${'active' if c.active=='my_notifications' else ''}"><a href="${h.route_path('my_account_notifications')}">${_('Live Notifications')}</a></li>
43 </ul>
44 </ul>
44 </div>
45 </div>
45
46
46 <div class="main-content-full-width">
47 <div class="main-content-full-width">
47 <%include file="/admin/my_account/my_account_${c.active}.mako"/>
48 <%include file="/admin/my_account/my_account_${c.active}.mako"/>
48 </div>
49 </div>
49 </div>
50 </div>
50 </div>
51 </div>
51
52
52 </%def>
53 </%def>
@@ -1,45 +1,48 b''
1 <div class="panel panel-default">
1 <div class="panel panel-default">
2 <div class="panel-heading">
2 <div class="panel-heading">
3 <h3 class="panel-title">${_('New SSH Key generated')}</h3>
3 <h3 class="panel-title">${_('New SSH Key generated')}</h3>
4 </div>
4 </div>
5 <div class="panel-body">
5 <div class="panel-body">
6 <p>
6 <p>
7 ${_('Below is a 2048 bit generated SSH RSA key. You can use it to access RhodeCode via the SSH wrapper.')}
7 ${_('Below is a 2048 bit generated SSH RSA key. You can use it to access RhodeCode via the SSH wrapper.')}
8 </p>
8 </p>
9 <h4>${_('Private key')}</h4>
9 <h4>${_('Private key')}</h4>
10 <pre>
10 <pre>
11 # Save the content as
11 # Save the content as
12 ~/.ssh/id_rsa_rhodecode_access_priv.key
12 ~/.ssh/id_rsa_rhodecode_access_priv.key
13 # Change permissions
13 # Change permissions
14 chmod 0600 ~/.ssh/id_rsa_rhodecode_access_priv.key
14 chmod 0600 ~/.ssh/id_rsa_rhodecode_access_priv.key
15 </pre>
15 </pre>
16
16
17 <div>
17 <div>
18 <textarea style="height: 300px">${c.private}</textarea>
18 <textarea style="height: 300px">${c.private}</textarea>
19 </div>
19 </div>
20 <br/>
20 <br/>
21
21
22
22
23 <h4>${_('Public key')}</h4>
23 <h4>${_('Public key')}</h4>
24 <pre>
24 <pre>
25 # Save the content as
25 # Save the content as
26 ~/.ssh/id_rsa_rhodecode_access_pub.key
26 ~/.ssh/id_rsa_rhodecode_access_pub.key
27 # Change permissions
27 # Change permissions
28 chmod 0600 ~/.ssh/id_rsa_rhodecode_access_pub.key
28 chmod 0600 ~/.ssh/id_rsa_rhodecode_access_pub.key
29 </pre>
29 </pre>
30
30
31 <input type="text" value="${c.public}" class="large text" size="100"/>
31 <input type="text" value="${c.public}" class="large text" size="100"/>
32 <p>
32 <p>
33 <a href="${h.route_path('edit_user_ssh_keys', user_id=c.user.user_id, _query=dict(default_key=c.public))}">${_('Add this generated key')}</a>
33 % if hasattr(c, 'target_form_url'):
34 <a href="${c.target_form_url}">${_('Add this generated key')}</a>
35 % else:
36 <a href="${h.route_path('edit_user_ssh_keys', user_id=c.user.user_id, _query=dict(default_key=c.public))}">${_('Add this generated key')}</a>
37 % endif
34 </p>
38 </p>
35
36 </div>
39 </div>
37 </div>
40 </div>
38
41
39 <script>
42 <script>
40
43
41 $(document).ready(function(){
44 $(document).ready(function(){
42
45
43
46
44 });
47 });
45 </script>
48 </script>
General Comments 0
You need to be logged in to leave comments. Login now