##// END OF EJS Templates
removed obsolete admin_user tmpl context variables
marcink -
r3712:08cf7741 beta
parent child Browse files
Show More
@@ -1,150 +1,148 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.controllers.admin.ldap_settings
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 ldap controller for RhodeCode
7 7
8 8 :created_on: Nov 26, 2010
9 9 :author: marcink
10 10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software: you can redistribute it and/or modify
14 14 # it under the terms of the GNU General Public License as published by
15 15 # the Free Software Foundation, either version 3 of the License, or
16 16 # (at your option) any later version.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 25 import logging
26 26 import formencode
27 27 import traceback
28 28
29 29 from formencode import htmlfill
30 30
31 31 from pylons import request, response, session, tmpl_context as c, url
32 32 from pylons.controllers.util import abort, redirect
33 33 from pylons.i18n.translation import _
34 34
35 35 from sqlalchemy.exc import DatabaseError
36 36
37 37 from rhodecode.lib.base import BaseController, render
38 38 from rhodecode.lib import helpers as h
39 39 from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator
40 40 from rhodecode.lib.exceptions import LdapImportError
41 41 from rhodecode.model.forms import LdapSettingsForm
42 42 from rhodecode.model.db import RhodeCodeSetting
43 43 from rhodecode.model.meta import Session
44 44
45 45 log = logging.getLogger(__name__)
46 46
47 47
48 48 class LdapSettingsController(BaseController):
49 49
50 50 search_scope_choices = [('BASE', _('BASE'),),
51 51 ('ONELEVEL', _('ONELEVEL'),),
52 52 ('SUBTREE', _('SUBTREE'),),
53 53 ]
54 54 search_scope_default = 'SUBTREE'
55 55
56 56 tls_reqcert_choices = [('NEVER', _('NEVER'),),
57 57 ('ALLOW', _('ALLOW'),),
58 58 ('TRY', _('TRY'),),
59 59 ('DEMAND', _('DEMAND'),),
60 60 ('HARD', _('HARD'),),
61 61 ]
62 62 tls_reqcert_default = 'DEMAND'
63 63
64 64 tls_kind_choices = [('PLAIN', _('No encryption'),),
65 65 ('LDAPS', _('LDAPS connection'),),
66 66 ('START_TLS', _('START_TLS on LDAP connection'),)
67 67 ]
68 68
69 69 tls_kind_default = 'PLAIN'
70 70
71 71 @LoginRequired()
72 72 @HasPermissionAllDecorator('hg.admin')
73 73 def __before__(self):
74 c.admin_user = session.get('admin_user')
75 c.admin_username = session.get('admin_username')
76 74 c.search_scope_choices = self.search_scope_choices
77 75 c.tls_reqcert_choices = self.tls_reqcert_choices
78 76 c.tls_kind_choices = self.tls_kind_choices
79 77
80 78 c.search_scope_cur = self.search_scope_default
81 79 c.tls_reqcert_cur = self.tls_reqcert_default
82 80 c.tls_kind_cur = self.tls_kind_default
83 81
84 82 super(LdapSettingsController, self).__before__()
85 83
86 84 def index(self):
87 85 defaults = RhodeCodeSetting.get_ldap_settings()
88 86 c.search_scope_cur = defaults.get('ldap_search_scope')
89 87 c.tls_reqcert_cur = defaults.get('ldap_tls_reqcert')
90 88 c.tls_kind_cur = defaults.get('ldap_tls_kind')
91 89
92 90 return htmlfill.render(
93 91 render('admin/ldap/ldap.html'),
94 92 defaults=defaults,
95 93 encoding="UTF-8",
96 94 force_defaults=True,)
97 95
98 96 def ldap_settings(self):
99 97 """POST ldap create and store ldap settings"""
100 98
101 99 _form = LdapSettingsForm([x[0] for x in self.tls_reqcert_choices],
102 100 [x[0] for x in self.search_scope_choices],
103 101 [x[0] for x in self.tls_kind_choices])()
104 102 # check the ldap lib
105 103 ldap_active = False
106 104 try:
107 105 import ldap
108 106 ldap_active = True
109 107 except ImportError:
110 108 pass
111 109
112 110 try:
113 111 form_result = _form.to_python(dict(request.POST))
114 112
115 113 try:
116 114
117 115 for k, v in form_result.items():
118 116 if k.startswith('ldap_'):
119 117 if k == 'ldap_active':
120 118 v = ldap_active
121 119 setting = RhodeCodeSetting.get_by_name(k)
122 120 setting.app_settings_value = v
123 121 Session().add(setting)
124 122
125 123 Session().commit()
126 124 h.flash(_('LDAP settings updated successfully'),
127 125 category='success')
128 126 if not ldap_active:
129 127 #if ldap is missing send an info to user
130 128 h.flash(_('Unable to activate ldap. The "python-ldap" library '
131 129 'is missing.'), category='warning')
132 130
133 131 except (DatabaseError,):
134 132 raise
135 133
136 134 except formencode.Invalid, errors:
137 135 e = errors.error_dict or {}
138 136
139 137 return htmlfill.render(
140 138 render('admin/ldap/ldap.html'),
141 139 defaults=errors.value,
142 140 errors=e,
143 141 prefix_error=False,
144 142 encoding="UTF-8")
145 143 except Exception:
146 144 log.error(traceback.format_exc())
147 145 h.flash(_('Error occurred during update of ldap settings'),
148 146 category='error')
149 147
150 148 return redirect(url('ldap_home'))
@@ -1,194 +1,192 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.controllers.admin.permissions
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 permissions controller for Rhodecode
7 7
8 8 :created_on: Apr 27, 2010
9 9 :author: marcink
10 10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software: you can redistribute it and/or modify
14 14 # it under the terms of the GNU General Public License as published by
15 15 # the Free Software Foundation, either version 3 of the License, or
16 16 # (at your option) any later version.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 25
26 26 import logging
27 27 import traceback
28 28 import formencode
29 29 from formencode import htmlfill
30 30
31 31 from pylons import request, session, tmpl_context as c, url
32 32 from pylons.controllers.util import abort, redirect
33 33 from pylons.i18n.translation import _
34 34
35 35 from rhodecode.lib import helpers as h
36 36 from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator,\
37 37 AuthUser
38 38 from rhodecode.lib.base import BaseController, render
39 39 from rhodecode.model.forms import DefaultPermissionsForm
40 40 from rhodecode.model.permission import PermissionModel
41 41 from rhodecode.model.db import User, UserIpMap
42 42 from rhodecode.model.meta import Session
43 43
44 44 log = logging.getLogger(__name__)
45 45
46 46
47 47 class PermissionsController(BaseController):
48 48 """REST Controller styled on the Atom Publishing Protocol"""
49 49 # To properly map this controller, ensure your config/routing.py
50 50 # file has a resource setup:
51 51 # map.resource('permission', 'permissions')
52 52
53 53 @LoginRequired()
54 54 @HasPermissionAllDecorator('hg.admin')
55 55 def __before__(self):
56 c.admin_user = session.get('admin_user')
57 c.admin_username = session.get('admin_username')
58 56 super(PermissionsController, self).__before__()
59 57
60 58 self.repo_perms_choices = [('repository.none', _('None'),),
61 59 ('repository.read', _('Read'),),
62 60 ('repository.write', _('Write'),),
63 61 ('repository.admin', _('Admin'),)]
64 62 self.group_perms_choices = [('group.none', _('None'),),
65 63 ('group.read', _('Read'),),
66 64 ('group.write', _('Write'),),
67 65 ('group.admin', _('Admin'),)]
68 66 self.register_choices = [
69 67 ('hg.register.none',
70 68 _('Disabled')),
71 69 ('hg.register.manual_activate',
72 70 _('Allowed with manual account activation')),
73 71 ('hg.register.auto_activate',
74 72 _('Allowed with automatic account activation')), ]
75 73
76 74 self.create_choices = [('hg.create.none', _('Disabled')),
77 75 ('hg.create.repository', _('Enabled'))]
78 76
79 77 self.fork_choices = [('hg.fork.none', _('Disabled')),
80 78 ('hg.fork.repository', _('Enabled'))]
81 79
82 80 # set the global template variables
83 81 c.repo_perms_choices = self.repo_perms_choices
84 82 c.group_perms_choices = self.group_perms_choices
85 83 c.register_choices = self.register_choices
86 84 c.create_choices = self.create_choices
87 85 c.fork_choices = self.fork_choices
88 86
89 87 def index(self, format='html'):
90 88 """GET /permissions: All items in the collection"""
91 89 # url('permissions')
92 90
93 91 def create(self):
94 92 """POST /permissions: Create a new item"""
95 93 # url('permissions')
96 94
97 95 def new(self, format='html'):
98 96 """GET /permissions/new: Form to create a new item"""
99 97 # url('new_permission')
100 98
101 99 def update(self, id):
102 100 """PUT /permissions/id: Update an existing item"""
103 101 # Forms posted to this method should contain a hidden field:
104 102 # <input type="hidden" name="_method" value="PUT" />
105 103 # Or using helpers:
106 104 # h.form(url('permission', id=ID),
107 105 # method='put')
108 106 # url('permission', id=ID)
109 107 if id == 'default':
110 108 c.user = default_user = User.get_by_username('default')
111 109 c.perm_user = AuthUser(user_id=default_user.user_id)
112 110 c.user_ip_map = UserIpMap.query()\
113 111 .filter(UserIpMap.user == default_user).all()
114 112 permission_model = PermissionModel()
115 113
116 114 _form = DefaultPermissionsForm(
117 115 [x[0] for x in self.repo_perms_choices],
118 116 [x[0] for x in self.group_perms_choices],
119 117 [x[0] for x in self.register_choices],
120 118 [x[0] for x in self.create_choices],
121 119 [x[0] for x in self.fork_choices])()
122 120
123 121 try:
124 122 form_result = _form.to_python(dict(request.POST))
125 123 form_result.update({'perm_user_name': id})
126 124 permission_model.update(form_result)
127 125 Session().commit()
128 126 h.flash(_('Default permissions updated successfully'),
129 127 category='success')
130 128
131 129 except formencode.Invalid, errors:
132 130 defaults = errors.value
133 131
134 132 return htmlfill.render(
135 133 render('admin/permissions/permissions.html'),
136 134 defaults=defaults,
137 135 errors=errors.error_dict or {},
138 136 prefix_error=False,
139 137 encoding="UTF-8")
140 138 except Exception:
141 139 log.error(traceback.format_exc())
142 140 h.flash(_('Error occurred during update of permissions'),
143 141 category='error')
144 142
145 143 return redirect(url('edit_permission', id=id))
146 144
147 145 def delete(self, id):
148 146 """DELETE /permissions/id: Delete an existing item"""
149 147 # Forms posted to this method should contain a hidden field:
150 148 # <input type="hidden" name="_method" value="DELETE" />
151 149 # Or using helpers:
152 150 # h.form(url('permission', id=ID),
153 151 # method='delete')
154 152 # url('permission', id=ID)
155 153
156 154 def show(self, id, format='html'):
157 155 """GET /permissions/id: Show a specific item"""
158 156 # url('permission', id=ID)
159 157
160 158 def edit(self, id, format='html'):
161 159 """GET /permissions/id/edit: Form to edit an existing item"""
162 160 #url('edit_permission', id=ID)
163 161
164 162 #this form can only edit default user permissions
165 163 if id == 'default':
166 164 c.user = default_user = User.get_by_username('default')
167 165 defaults = {'anonymous': default_user.active}
168 166 c.perm_user = AuthUser(user_id=default_user.user_id)
169 167 c.user_ip_map = UserIpMap.query()\
170 168 .filter(UserIpMap.user == default_user).all()
171 169 for p in default_user.user_perms:
172 170 if p.permission.permission_name.startswith('repository.'):
173 171 defaults['default_repo_perm'] = p.permission.permission_name
174 172
175 173 if p.permission.permission_name.startswith('group.'):
176 174 defaults['default_group_perm'] = p.permission.permission_name
177 175
178 176 if p.permission.permission_name.startswith('hg.register.'):
179 177 defaults['default_register'] = p.permission.permission_name
180 178
181 179 if p.permission.permission_name.startswith('hg.create.'):
182 180 defaults['default_create'] = p.permission.permission_name
183 181
184 182 if p.permission.permission_name.startswith('hg.fork.'):
185 183 defaults['default_fork'] = p.permission.permission_name
186 184
187 185 return htmlfill.render(
188 186 render('admin/permissions/permissions.html'),
189 187 defaults=defaults,
190 188 encoding="UTF-8",
191 189 force_defaults=False
192 190 )
193 191 else:
194 192 return redirect(url('admin_home'))
@@ -1,606 +1,604 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.controllers.admin.repos
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 Repositories controller for RhodeCode
7 7
8 8 :created_on: Apr 7, 2010
9 9 :author: marcink
10 10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software: you can redistribute it and/or modify
14 14 # it under the terms of the GNU General Public License as published by
15 15 # the Free Software Foundation, either version 3 of the License, or
16 16 # (at your option) any later version.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 25
26 26 import logging
27 27 import traceback
28 28 import formencode
29 29 from formencode import htmlfill
30 30
31 31 from webob.exc import HTTPInternalServerError, HTTPForbidden
32 32 from pylons import request, session, tmpl_context as c, url
33 33 from pylons.controllers.util import redirect
34 34 from pylons.i18n.translation import _
35 35 from sqlalchemy.exc import IntegrityError
36 36
37 37 import rhodecode
38 38 from rhodecode.lib import helpers as h
39 39 from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator, \
40 40 HasPermissionAnyDecorator, HasRepoPermissionAllDecorator, NotAnonymous,\
41 41 HasPermissionAny, HasReposGroupPermissionAny, HasRepoPermissionAnyDecorator
42 42 from rhodecode.lib.base import BaseRepoController, render
43 43 from rhodecode.lib.utils import action_logger, repo_name_slug
44 44 from rhodecode.lib.helpers import get_token
45 45 from rhodecode.model.meta import Session
46 46 from rhodecode.model.db import User, Repository, UserFollowing, RepoGroup,\
47 47 RhodeCodeSetting, RepositoryField
48 48 from rhodecode.model.forms import RepoForm, RepoFieldForm, RepoPermsForm
49 49 from rhodecode.model.scm import ScmModel, GroupList
50 50 from rhodecode.model.repo import RepoModel
51 51 from rhodecode.lib.compat import json
52 52 from sqlalchemy.sql.expression import func
53 53 from rhodecode.lib.exceptions import AttachedForksError
54 54
55 55 log = logging.getLogger(__name__)
56 56
57 57
58 58 class ReposController(BaseRepoController):
59 59 """
60 60 REST Controller styled on the Atom Publishing Protocol"""
61 61 # To properly map this controller, ensure your config/routing.py
62 62 # file has a resource setup:
63 63 # map.resource('repo', 'repos')
64 64
65 65 @LoginRequired()
66 66 def __before__(self):
67 c.admin_user = session.get('admin_user')
68 c.admin_username = session.get('admin_username')
69 67 super(ReposController, self).__before__()
70 68
71 69 def __load_defaults(self):
72 70 acl_groups = GroupList(RepoGroup.query().all(),
73 71 perm_set=['group.write', 'group.admin'])
74 72 c.repo_groups = RepoGroup.groups_choices(groups=acl_groups)
75 73 c.repo_groups_choices = map(lambda k: unicode(k[0]), c.repo_groups)
76 74
77 75 repo_model = RepoModel()
78 76 c.users_array = repo_model.get_users_js()
79 77 c.users_groups_array = repo_model.get_users_groups_js()
80 78 choices, c.landing_revs = ScmModel().get_repo_landing_revs()
81 79 c.landing_revs_choices = choices
82 80
83 81 def __load_data(self, repo_name=None):
84 82 """
85 83 Load defaults settings for edit, and update
86 84
87 85 :param repo_name:
88 86 """
89 87 self.__load_defaults()
90 88
91 89 c.repo_info = db_repo = Repository.get_by_repo_name(repo_name)
92 90 repo = db_repo.scm_instance
93 91
94 92 if c.repo_info is None:
95 93 h.not_mapped_error(repo_name)
96 94 return redirect(url('repos'))
97 95
98 96 ##override defaults for exact repo info here git/hg etc
99 97 choices, c.landing_revs = ScmModel().get_repo_landing_revs(c.repo_info)
100 98 c.landing_revs_choices = choices
101 99
102 100 c.default_user_id = User.get_by_username('default').user_id
103 101 c.in_public_journal = UserFollowing.query()\
104 102 .filter(UserFollowing.user_id == c.default_user_id)\
105 103 .filter(UserFollowing.follows_repository == c.repo_info).scalar()
106 104
107 105 if c.repo_info.stats:
108 106 # this is on what revision we ended up so we add +1 for count
109 107 last_rev = c.repo_info.stats.stat_on_revision + 1
110 108 else:
111 109 last_rev = 0
112 110 c.stats_revision = last_rev
113 111
114 112 c.repo_last_rev = repo.count() if repo.revisions else 0
115 113
116 114 if last_rev == 0 or c.repo_last_rev == 0:
117 115 c.stats_percentage = 0
118 116 else:
119 117 c.stats_percentage = '%.2f' % ((float((last_rev)) /
120 118 c.repo_last_rev) * 100)
121 119
122 120 c.repo_fields = RepositoryField.query()\
123 121 .filter(RepositoryField.repository == db_repo).all()
124 122
125 123 defaults = RepoModel()._get_defaults(repo_name)
126 124
127 125 c.repos_list = [('', _('--REMOVE FORK--'))]
128 126 c.repos_list += [(x.repo_id, x.repo_name) for x in
129 127 Repository.query().order_by(Repository.repo_name).all()
130 128 if x.repo_id != c.repo_info.repo_id]
131 129
132 130 defaults['id_fork_of'] = db_repo.fork.repo_id if db_repo.fork else ''
133 131 return defaults
134 132
135 133 @HasPermissionAllDecorator('hg.admin')
136 134 def index(self, format='html'):
137 135 """GET /repos: All items in the collection"""
138 136 # url('repos')
139 137
140 138 c.repos_list = Repository.query()\
141 139 .order_by(func.lower(Repository.repo_name))\
142 140 .all()
143 141
144 142 repos_data = RepoModel().get_repos_as_dict(repos_list=c.repos_list,
145 143 admin=True,
146 144 super_user_actions=True)
147 145 #json used to render the grid
148 146 c.data = json.dumps(repos_data)
149 147
150 148 return render('admin/repos/repos.html')
151 149
152 150 @NotAnonymous()
153 151 def create(self):
154 152 """
155 153 POST /repos: Create a new item"""
156 154 # url('repos')
157 155
158 156 self.__load_defaults()
159 157 form_result = {}
160 158 try:
161 159 form_result = RepoForm(repo_groups=c.repo_groups_choices,
162 160 landing_revs=c.landing_revs_choices)()\
163 161 .to_python(dict(request.POST))
164 162
165 163 new_repo = RepoModel().create(form_result,
166 164 self.rhodecode_user.user_id)
167 165 if form_result['clone_uri']:
168 166 h.flash(_('Created repository %s from %s') \
169 167 % (form_result['repo_name'], form_result['clone_uri']),
170 168 category='success')
171 169 else:
172 170 repo_url = h.link_to(form_result['repo_name'],
173 171 h.url('summary_home', repo_name=form_result['repo_name_full']))
174 172 h.flash(h.literal(_('Created repository %s') % repo_url),
175 173 category='success')
176 174
177 175 if request.POST.get('user_created'):
178 176 # created by regular non admin user
179 177 action_logger(self.rhodecode_user, 'user_created_repo',
180 178 form_result['repo_name_full'], self.ip_addr,
181 179 self.sa)
182 180 else:
183 181 action_logger(self.rhodecode_user, 'admin_created_repo',
184 182 form_result['repo_name_full'], self.ip_addr,
185 183 self.sa)
186 184 Session().commit()
187 185 except formencode.Invalid, errors:
188 186 return htmlfill.render(
189 187 render('admin/repos/repo_add.html'),
190 188 defaults=errors.value,
191 189 errors=errors.error_dict or {},
192 190 prefix_error=False,
193 191 encoding="UTF-8")
194 192
195 193 except Exception:
196 194 log.error(traceback.format_exc())
197 195 msg = _('Error creating repository %s') \
198 196 % form_result.get('repo_name')
199 197 h.flash(msg, category='error')
200 198 if c.rhodecode_user.is_admin:
201 199 return redirect(url('repos'))
202 200 return redirect(url('home'))
203 201 #redirect to our new repo !
204 202 return redirect(url('summary_home', repo_name=new_repo.repo_name))
205 203
206 204 @NotAnonymous()
207 205 def create_repository(self):
208 206 """GET /_admin/create_repository: Form to create a new item"""
209 207 new_repo = request.GET.get('repo', '')
210 208 parent_group = request.GET.get('parent_group')
211 209 if not HasPermissionAny('hg.admin', 'hg.create.repository')():
212 210 #you're not super admin nor have global create permissions,
213 211 #but maybe you have at least write permission to a parent group ?
214 212 _gr = RepoGroup.get(parent_group)
215 213 gr_name = _gr.group_name if _gr else None
216 214 if not HasReposGroupPermissionAny('group.admin', 'group.write')(group_name=gr_name):
217 215 raise HTTPForbidden
218 216
219 217 acl_groups = GroupList(RepoGroup.query().all(),
220 218 perm_set=['group.write', 'group.admin'])
221 219 c.repo_groups = RepoGroup.groups_choices(groups=acl_groups)
222 220 c.repo_groups_choices = map(lambda k: unicode(k[0]), c.repo_groups)
223 221 choices, c.landing_revs = ScmModel().get_repo_landing_revs()
224 222
225 223 c.new_repo = repo_name_slug(new_repo)
226 224
227 225 ## apply the defaults from defaults page
228 226 defaults = RhodeCodeSetting.get_default_repo_settings(strip_prefix=True)
229 227 if parent_group:
230 228 defaults.update({'repo_group': parent_group})
231 229
232 230 return htmlfill.render(
233 231 render('admin/repos/repo_add.html'),
234 232 defaults=defaults,
235 233 errors={},
236 234 prefix_error=False,
237 235 encoding="UTF-8"
238 236 )
239 237
240 238 @HasRepoPermissionAllDecorator('repository.admin')
241 239 def update(self, repo_name):
242 240 """
243 241 PUT /repos/repo_name: Update an existing item"""
244 242 # Forms posted to this method should contain a hidden field:
245 243 # <input type="hidden" name="_method" value="PUT" />
246 244 # Or using helpers:
247 245 # h.form(url('repo', repo_name=ID),
248 246 # method='put')
249 247 # url('repo', repo_name=ID)
250 248 self.__load_defaults()
251 249 repo_model = RepoModel()
252 250 changed_name = repo_name
253 251 #override the choices with extracted revisions !
254 252 choices, c.landing_revs = ScmModel().get_repo_landing_revs(repo_name)
255 253 c.landing_revs_choices = choices
256 254 repo = Repository.get_by_repo_name(repo_name)
257 255 _form = RepoForm(edit=True, old_data={'repo_name': repo_name,
258 256 'repo_group': repo.group.get_dict() \
259 257 if repo.group else {}},
260 258 repo_groups=c.repo_groups_choices,
261 259 landing_revs=c.landing_revs_choices)()
262 260 try:
263 261 form_result = _form.to_python(dict(request.POST))
264 262 repo = repo_model.update(repo_name, **form_result)
265 263 ScmModel().mark_for_invalidation(repo_name)
266 264 h.flash(_('Repository %s updated successfully') % repo_name,
267 265 category='success')
268 266 changed_name = repo.repo_name
269 267 action_logger(self.rhodecode_user, 'admin_updated_repo',
270 268 changed_name, self.ip_addr, self.sa)
271 269 Session().commit()
272 270 except formencode.Invalid, errors:
273 271 defaults = self.__load_data(repo_name)
274 272 defaults.update(errors.value)
275 273 return htmlfill.render(
276 274 render('admin/repos/repo_edit.html'),
277 275 defaults=defaults,
278 276 errors=errors.error_dict or {},
279 277 prefix_error=False,
280 278 encoding="UTF-8")
281 279
282 280 except Exception:
283 281 log.error(traceback.format_exc())
284 282 h.flash(_('Error occurred during update of repository %s') \
285 283 % repo_name, category='error')
286 284 return redirect(url('edit_repo', repo_name=changed_name))
287 285
288 286 @HasRepoPermissionAllDecorator('repository.admin')
289 287 def delete(self, repo_name):
290 288 """
291 289 DELETE /repos/repo_name: Delete an existing item"""
292 290 # Forms posted to this method should contain a hidden field:
293 291 # <input type="hidden" name="_method" value="DELETE" />
294 292 # Or using helpers:
295 293 # h.form(url('repo', repo_name=ID),
296 294 # method='delete')
297 295 # url('repo', repo_name=ID)
298 296
299 297 repo_model = RepoModel()
300 298 repo = repo_model.get_by_repo_name(repo_name)
301 299 if not repo:
302 300 h.not_mapped_error(repo_name)
303 301 return redirect(url('repos'))
304 302 try:
305 303 _forks = repo.forks.count()
306 304 handle_forks = None
307 305 if _forks and request.POST.get('forks'):
308 306 do = request.POST['forks']
309 307 if do == 'detach_forks':
310 308 handle_forks = 'detach'
311 309 h.flash(_('Detached %s forks') % _forks, category='success')
312 310 elif do == 'delete_forks':
313 311 handle_forks = 'delete'
314 312 h.flash(_('Deleted %s forks') % _forks, category='success')
315 313 repo_model.delete(repo, forks=handle_forks)
316 314 action_logger(self.rhodecode_user, 'admin_deleted_repo',
317 315 repo_name, self.ip_addr, self.sa)
318 316 ScmModel().mark_for_invalidation(repo_name)
319 317 h.flash(_('Deleted repository %s') % repo_name, category='success')
320 318 Session().commit()
321 319 except AttachedForksError:
322 320 h.flash(_('Cannot delete %s it still contains attached forks')
323 321 % repo_name, category='warning')
324 322
325 323 except Exception:
326 324 log.error(traceback.format_exc())
327 325 h.flash(_('An error occurred during deletion of %s') % repo_name,
328 326 category='error')
329 327
330 328 return redirect(url('repos'))
331 329
332 330 @HasRepoPermissionAllDecorator('repository.admin')
333 331 def set_repo_perm_member(self, repo_name):
334 332 form = RepoPermsForm()().to_python(request.POST)
335 333
336 334 perms_new = form['perms_new']
337 335 perms_updates = form['perms_updates']
338 336 cur_repo = repo_name
339 337
340 338 # update permissions
341 339 for member, perm, member_type in perms_updates:
342 340 if member_type == 'user':
343 341 # this updates existing one
344 342 RepoModel().grant_user_permission(
345 343 repo=cur_repo, user=member, perm=perm
346 344 )
347 345 else:
348 346 RepoModel().grant_users_group_permission(
349 347 repo=cur_repo, group_name=member, perm=perm
350 348 )
351 349 # set new permissions
352 350 for member, perm, member_type in perms_new:
353 351 if member_type == 'user':
354 352 RepoModel().grant_user_permission(
355 353 repo=cur_repo, user=member, perm=perm
356 354 )
357 355 else:
358 356 RepoModel().grant_users_group_permission(
359 357 repo=cur_repo, group_name=member, perm=perm
360 358 )
361 359 #TODO: implement this
362 360 #action_logger(self.rhodecode_user, 'admin_changed_repo_permissions',
363 361 # repo_name, self.ip_addr, self.sa)
364 362 Session().commit()
365 363 h.flash(_('Repository permissions updated'), category='success')
366 364 return redirect(url('edit_repo', repo_name=repo_name))
367 365
368 366 @HasRepoPermissionAllDecorator('repository.admin')
369 367 def delete_perm_user(self, repo_name):
370 368 """
371 369 DELETE an existing repository permission user
372 370
373 371 :param repo_name:
374 372 """
375 373 try:
376 374 RepoModel().revoke_user_permission(repo=repo_name,
377 375 user=request.POST['user_id'])
378 376 #TODO: implement this
379 377 #action_logger(self.rhodecode_user, 'admin_revoked_repo_permissions',
380 378 # repo_name, self.ip_addr, self.sa)
381 379 Session().commit()
382 380 except Exception:
383 381 log.error(traceback.format_exc())
384 382 h.flash(_('An error occurred during deletion of repository user'),
385 383 category='error')
386 384 raise HTTPInternalServerError()
387 385
388 386 @HasRepoPermissionAllDecorator('repository.admin')
389 387 def delete_perm_users_group(self, repo_name):
390 388 """
391 389 DELETE an existing repository permission user group
392 390
393 391 :param repo_name:
394 392 """
395 393
396 394 try:
397 395 RepoModel().revoke_users_group_permission(
398 396 repo=repo_name, group_name=request.POST['users_group_id']
399 397 )
400 398 Session().commit()
401 399 except Exception:
402 400 log.error(traceback.format_exc())
403 401 h.flash(_('An error occurred during deletion of repository'
404 402 ' user groups'),
405 403 category='error')
406 404 raise HTTPInternalServerError()
407 405
408 406 @HasRepoPermissionAllDecorator('repository.admin')
409 407 def repo_stats(self, repo_name):
410 408 """
411 409 DELETE an existing repository statistics
412 410
413 411 :param repo_name:
414 412 """
415 413
416 414 try:
417 415 RepoModel().delete_stats(repo_name)
418 416 Session().commit()
419 417 except Exception, e:
420 418 log.error(traceback.format_exc())
421 419 h.flash(_('An error occurred during deletion of repository stats'),
422 420 category='error')
423 421 return redirect(url('edit_repo', repo_name=repo_name))
424 422
425 423 @HasRepoPermissionAllDecorator('repository.admin')
426 424 def repo_cache(self, repo_name):
427 425 """
428 426 INVALIDATE existing repository cache
429 427
430 428 :param repo_name:
431 429 """
432 430
433 431 try:
434 432 ScmModel().mark_for_invalidation(repo_name)
435 433 Session().commit()
436 434 except Exception, e:
437 435 log.error(traceback.format_exc())
438 436 h.flash(_('An error occurred during cache invalidation'),
439 437 category='error')
440 438 return redirect(url('edit_repo', repo_name=repo_name))
441 439
442 440 @HasRepoPermissionAllDecorator('repository.admin')
443 441 def repo_locking(self, repo_name):
444 442 """
445 443 Unlock repository when it is locked !
446 444
447 445 :param repo_name:
448 446 """
449 447
450 448 try:
451 449 repo = Repository.get_by_repo_name(repo_name)
452 450 if request.POST.get('set_lock'):
453 451 Repository.lock(repo, c.rhodecode_user.user_id)
454 452 elif request.POST.get('set_unlock'):
455 453 Repository.unlock(repo)
456 454 except Exception, e:
457 455 log.error(traceback.format_exc())
458 456 h.flash(_('An error occurred during unlocking'),
459 457 category='error')
460 458 return redirect(url('edit_repo', repo_name=repo_name))
461 459
462 460 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
463 461 def toggle_locking(self, repo_name):
464 462 """
465 463 Toggle locking of repository by simple GET call to url
466 464
467 465 :param repo_name:
468 466 """
469 467
470 468 try:
471 469 repo = Repository.get_by_repo_name(repo_name)
472 470
473 471 if repo.enable_locking:
474 472 if repo.locked[0]:
475 473 Repository.unlock(repo)
476 474 action = _('Unlocked')
477 475 else:
478 476 Repository.lock(repo, c.rhodecode_user.user_id)
479 477 action = _('Locked')
480 478
481 479 h.flash(_('Repository has been %s') % action,
482 480 category='success')
483 481 except Exception, e:
484 482 log.error(traceback.format_exc())
485 483 h.flash(_('An error occurred during unlocking'),
486 484 category='error')
487 485 return redirect(url('summary_home', repo_name=repo_name))
488 486
489 487 @HasRepoPermissionAllDecorator('repository.admin')
490 488 def repo_public_journal(self, repo_name):
491 489 """
492 490 Set's this repository to be visible in public journal,
493 491 in other words assing default user to follow this repo
494 492
495 493 :param repo_name:
496 494 """
497 495
498 496 cur_token = request.POST.get('auth_token')
499 497 token = get_token()
500 498 if cur_token == token:
501 499 try:
502 500 repo_id = Repository.get_by_repo_name(repo_name).repo_id
503 501 user_id = User.get_by_username('default').user_id
504 502 self.scm_model.toggle_following_repo(repo_id, user_id)
505 503 h.flash(_('Updated repository visibility in public journal'),
506 504 category='success')
507 505 Session().commit()
508 506 except Exception:
509 507 h.flash(_('An error occurred during setting this'
510 508 ' repository in public journal'),
511 509 category='error')
512 510
513 511 else:
514 512 h.flash(_('Token mismatch'), category='error')
515 513 return redirect(url('edit_repo', repo_name=repo_name))
516 514
517 515 @HasRepoPermissionAllDecorator('repository.admin')
518 516 def repo_pull(self, repo_name):
519 517 """
520 518 Runs task to update given repository with remote changes,
521 519 ie. make pull on remote location
522 520
523 521 :param repo_name:
524 522 """
525 523 try:
526 524 ScmModel().pull_changes(repo_name, self.rhodecode_user.username)
527 525 h.flash(_('Pulled from remote location'), category='success')
528 526 except Exception, e:
529 527 h.flash(_('An error occurred during pull from remote location'),
530 528 category='error')
531 529
532 530 return redirect(url('edit_repo', repo_name=repo_name))
533 531
534 532 @HasRepoPermissionAllDecorator('repository.admin')
535 533 def repo_as_fork(self, repo_name):
536 534 """
537 535 Mark given repository as a fork of another
538 536
539 537 :param repo_name:
540 538 """
541 539 try:
542 540 fork_id = request.POST.get('id_fork_of')
543 541 repo = ScmModel().mark_as_fork(repo_name, fork_id,
544 542 self.rhodecode_user.username)
545 543 fork = repo.fork.repo_name if repo.fork else _('Nothing')
546 544 Session().commit()
547 545 h.flash(_('Marked repo %s as fork of %s') % (repo_name, fork),
548 546 category='success')
549 547 except Exception, e:
550 548 log.error(traceback.format_exc())
551 549 h.flash(_('An error occurred during this operation'),
552 550 category='error')
553 551
554 552 return redirect(url('edit_repo', repo_name=repo_name))
555 553
556 554 @HasPermissionAllDecorator('hg.admin')
557 555 def show(self, repo_name, format='html'):
558 556 """GET /repos/repo_name: Show a specific item"""
559 557 # url('repo', repo_name=ID)
560 558
561 559 @HasRepoPermissionAllDecorator('repository.admin')
562 560 def edit(self, repo_name, format='html'):
563 561 """GET /repos/repo_name/edit: Form to edit an existing item"""
564 562 # url('edit_repo', repo_name=ID)
565 563 defaults = self.__load_data(repo_name)
566 564
567 565 return htmlfill.render(
568 566 render('admin/repos/repo_edit.html'),
569 567 defaults=defaults,
570 568 encoding="UTF-8",
571 569 force_defaults=False
572 570 )
573 571
574 572 @HasPermissionAllDecorator('hg.admin')
575 573 def create_repo_field(self, repo_name):
576 574 try:
577 575 form_result = RepoFieldForm()().to_python(dict(request.POST))
578 576 new_field = RepositoryField()
579 577 new_field.repository = Repository.get_by_repo_name(repo_name)
580 578 new_field.field_key = form_result['new_field_key']
581 579 new_field.field_type = form_result['new_field_type'] # python type
582 580 new_field.field_value = form_result['new_field_value'] # set initial blank value
583 581 new_field.field_desc = form_result['new_field_desc']
584 582 new_field.field_label = form_result['new_field_label']
585 583 Session().add(new_field)
586 584 Session().commit()
587 585
588 586 except Exception, e:
589 587 log.error(traceback.format_exc())
590 588 msg = _('An error occurred during creation of field')
591 589 if isinstance(e, formencode.Invalid):
592 590 msg += ". " + e.msg
593 591 h.flash(msg, category='error')
594 592 return redirect(url('edit_repo', repo_name=repo_name))
595 593
596 594 @HasPermissionAllDecorator('hg.admin')
597 595 def delete_repo_field(self, repo_name, field_id):
598 596 field = RepositoryField.get_or_404(field_id)
599 597 try:
600 598 Session().delete(field)
601 599 Session().commit()
602 600 except Exception, e:
603 601 log.error(traceback.format_exc())
604 602 msg = _('An error occurred during removal of field')
605 603 h.flash(msg, category='error')
606 604 return redirect(url('edit_repo', repo_name=repo_name))
@@ -1,515 +1,513 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.controllers.admin.settings
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 settings controller for rhodecode admin
7 7
8 8 :created_on: Jul 14, 2010
9 9 :author: marcink
10 10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software: you can redistribute it and/or modify
14 14 # it under the terms of the GNU General Public License as published by
15 15 # the Free Software Foundation, either version 3 of the License, or
16 16 # (at your option) any later version.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 25
26 26 import logging
27 27 import traceback
28 28 import formencode
29 29 import pkg_resources
30 30 import platform
31 31
32 32 from sqlalchemy import func
33 33 from formencode import htmlfill
34 34 from pylons import request, session, tmpl_context as c, url, config
35 35 from pylons.controllers.util import abort, redirect
36 36 from pylons.i18n.translation import _
37 37
38 38 from rhodecode.lib import helpers as h
39 39 from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator, \
40 40 HasPermissionAnyDecorator, NotAnonymous, HasPermissionAny,\
41 41 HasReposGroupPermissionAll, HasReposGroupPermissionAny, AuthUser
42 42 from rhodecode.lib.base import BaseController, render
43 43 from rhodecode.lib.celerylib import tasks, run_task
44 44 from rhodecode.lib.utils import repo2db_mapper, set_rhodecode_config, \
45 45 check_git_version
46 46 from rhodecode.model.db import RhodeCodeUi, Repository, RepoGroup, \
47 47 RhodeCodeSetting, PullRequest, PullRequestReviewers
48 48 from rhodecode.model.forms import UserForm, ApplicationSettingsForm, \
49 49 ApplicationUiSettingsForm, ApplicationVisualisationForm
50 50 from rhodecode.model.scm import ScmModel, GroupList
51 51 from rhodecode.model.user import UserModel
52 52 from rhodecode.model.repo import RepoModel
53 53 from rhodecode.model.db import User
54 54 from rhodecode.model.notification import EmailNotificationModel
55 55 from rhodecode.model.meta import Session
56 56 from rhodecode.lib.utils2 import str2bool, safe_unicode
57 57 from rhodecode.lib.compat import json
58 58 log = logging.getLogger(__name__)
59 59
60 60
61 61 class SettingsController(BaseController):
62 62 """REST Controller styled on the Atom Publishing Protocol"""
63 63 # To properly map this controller, ensure your config/routing.py
64 64 # file has a resource setup:
65 65 # map.resource('setting', 'settings', controller='admin/settings',
66 66 # path_prefix='/admin', name_prefix='admin_')
67 67
68 68 @LoginRequired()
69 69 def __before__(self):
70 c.admin_user = session.get('admin_user')
71 c.admin_username = session.get('admin_username')
70 super(SettingsController, self).__before__()
72 71 c.modules = sorted([(p.project_name, p.version)
73 72 for p in pkg_resources.working_set]
74 73 + [('git', check_git_version())],
75 74 key=lambda k: k[0].lower())
76 75 c.py_version = platform.python_version()
77 76 c.platform = platform.platform()
78 super(SettingsController, self).__before__()
79 77
80 78 @HasPermissionAllDecorator('hg.admin')
81 79 def index(self, format='html'):
82 80 """GET /admin/settings: All items in the collection"""
83 81 # url('admin_settings')
84 82
85 83 defaults = RhodeCodeSetting.get_app_settings()
86 84 defaults.update(self._get_hg_ui_settings())
87 85
88 86 return htmlfill.render(
89 87 render('admin/settings/settings.html'),
90 88 defaults=defaults,
91 89 encoding="UTF-8",
92 90 force_defaults=False
93 91 )
94 92
95 93 @HasPermissionAllDecorator('hg.admin')
96 94 def create(self):
97 95 """POST /admin/settings: Create a new item"""
98 96 # url('admin_settings')
99 97
100 98 @HasPermissionAllDecorator('hg.admin')
101 99 def new(self, format='html'):
102 100 """GET /admin/settings/new: Form to create a new item"""
103 101 # url('admin_new_setting')
104 102
105 103 @HasPermissionAllDecorator('hg.admin')
106 104 def update(self, setting_id):
107 105 """PUT /admin/settings/setting_id: Update an existing item"""
108 106 # Forms posted to this method should contain a hidden field:
109 107 # <input type="hidden" name="_method" value="PUT" />
110 108 # Or using helpers:
111 109 # h.form(url('admin_setting', setting_id=ID),
112 110 # method='put')
113 111 # url('admin_setting', setting_id=ID)
114 112
115 113 if setting_id == 'mapping':
116 114 rm_obsolete = request.POST.get('destroy', False)
117 115 log.debug('Rescanning directories with destroy=%s' % rm_obsolete)
118 116 initial = ScmModel().repo_scan()
119 117 log.debug('invalidating all repositories')
120 118 for repo_name in initial.keys():
121 119 ScmModel().mark_for_invalidation(repo_name)
122 120
123 121 added, removed = repo2db_mapper(initial, rm_obsolete)
124 122 _repr = lambda l: ', '.join(map(safe_unicode, l)) or '-'
125 123 h.flash(_('Repositories successfully '
126 124 'rescanned added: %s ; removed: %s') %
127 125 (_repr(added), _repr(removed)),
128 126 category='success')
129 127
130 128 if setting_id == 'whoosh':
131 129 repo_location = self._get_hg_ui_settings()['paths_root_path']
132 130 full_index = request.POST.get('full_index', False)
133 131 run_task(tasks.whoosh_index, repo_location, full_index)
134 132 h.flash(_('Whoosh reindex task scheduled'), category='success')
135 133
136 134 if setting_id == 'global':
137 135
138 136 application_form = ApplicationSettingsForm()()
139 137 try:
140 138 form_result = application_form.to_python(dict(request.POST))
141 139 except formencode.Invalid, errors:
142 140 return htmlfill.render(
143 141 render('admin/settings/settings.html'),
144 142 defaults=errors.value,
145 143 errors=errors.error_dict or {},
146 144 prefix_error=False,
147 145 encoding="UTF-8"
148 146 )
149 147
150 148 try:
151 149 sett1 = RhodeCodeSetting.get_by_name_or_create('title')
152 150 sett1.app_settings_value = form_result['rhodecode_title']
153 151 Session().add(sett1)
154 152
155 153 sett2 = RhodeCodeSetting.get_by_name_or_create('realm')
156 154 sett2.app_settings_value = form_result['rhodecode_realm']
157 155 Session().add(sett2)
158 156
159 157 sett3 = RhodeCodeSetting.get_by_name_or_create('ga_code')
160 158 sett3.app_settings_value = form_result['rhodecode_ga_code']
161 159 Session().add(sett3)
162 160
163 161 Session().commit()
164 162 set_rhodecode_config(config)
165 163 h.flash(_('Updated application settings'), category='success')
166 164
167 165 except Exception:
168 166 log.error(traceback.format_exc())
169 167 h.flash(_('Error occurred during updating '
170 168 'application settings'),
171 169 category='error')
172 170
173 171 if setting_id == 'visual':
174 172
175 173 application_form = ApplicationVisualisationForm()()
176 174 try:
177 175 form_result = application_form.to_python(dict(request.POST))
178 176 except formencode.Invalid, errors:
179 177 return htmlfill.render(
180 178 render('admin/settings/settings.html'),
181 179 defaults=errors.value,
182 180 errors=errors.error_dict or {},
183 181 prefix_error=False,
184 182 encoding="UTF-8"
185 183 )
186 184
187 185 try:
188 186 sett1 = RhodeCodeSetting.get_by_name_or_create('show_public_icon')
189 187 sett1.app_settings_value = \
190 188 form_result['rhodecode_show_public_icon']
191 189 Session().add(sett1)
192 190
193 191 sett2 = RhodeCodeSetting.get_by_name_or_create('show_private_icon')
194 192 sett2.app_settings_value = \
195 193 form_result['rhodecode_show_private_icon']
196 194 Session().add(sett2)
197 195
198 196 sett3 = RhodeCodeSetting.get_by_name_or_create('stylify_metatags')
199 197 sett3.app_settings_value = \
200 198 form_result['rhodecode_stylify_metatags']
201 199 Session().add(sett3)
202 200
203 201 sett4 = RhodeCodeSetting.get_by_name_or_create('lightweight_dashboard')
204 202 sett4.app_settings_value = \
205 203 form_result['rhodecode_lightweight_dashboard']
206 204 Session().add(sett4)
207 205
208 206 sett4 = RhodeCodeSetting.get_by_name_or_create('repository_fields')
209 207 sett4.app_settings_value = \
210 208 form_result['rhodecode_repository_fields']
211 209 Session().add(sett4)
212 210
213 211 Session().commit()
214 212 set_rhodecode_config(config)
215 213 h.flash(_('Updated visualisation settings'),
216 214 category='success')
217 215
218 216 except Exception:
219 217 log.error(traceback.format_exc())
220 218 h.flash(_('Error occurred during updating '
221 219 'visualisation settings'),
222 220 category='error')
223 221
224 222 if setting_id == 'vcs':
225 223 application_form = ApplicationUiSettingsForm()()
226 224 try:
227 225 form_result = application_form.to_python(dict(request.POST))
228 226 except formencode.Invalid, errors:
229 227 return htmlfill.render(
230 228 render('admin/settings/settings.html'),
231 229 defaults=errors.value,
232 230 errors=errors.error_dict or {},
233 231 prefix_error=False,
234 232 encoding="UTF-8"
235 233 )
236 234
237 235 try:
238 236 sett = RhodeCodeUi.get_by_key('push_ssl')
239 237 sett.ui_value = form_result['web_push_ssl']
240 238 Session().add(sett)
241 239
242 240 sett = RhodeCodeUi.get_by_key('/')
243 241 sett.ui_value = form_result['paths_root_path']
244 242 Session().add(sett)
245 243
246 244 #HOOKS
247 245 sett = RhodeCodeUi.get_by_key(RhodeCodeUi.HOOK_UPDATE)
248 246 sett.ui_active = form_result['hooks_changegroup_update']
249 247 Session().add(sett)
250 248
251 249 sett = RhodeCodeUi.get_by_key(RhodeCodeUi.HOOK_REPO_SIZE)
252 250 sett.ui_active = form_result['hooks_changegroup_repo_size']
253 251 Session().add(sett)
254 252
255 253 sett = RhodeCodeUi.get_by_key(RhodeCodeUi.HOOK_PUSH)
256 254 sett.ui_active = form_result['hooks_changegroup_push_logger']
257 255 Session().add(sett)
258 256
259 257 sett = RhodeCodeUi.get_by_key(RhodeCodeUi.HOOK_PULL)
260 258 sett.ui_active = form_result['hooks_outgoing_pull_logger']
261 259
262 260 Session().add(sett)
263 261
264 262 ## EXTENSIONS
265 263 sett = RhodeCodeUi.get_by_key('largefiles')
266 264 if not sett:
267 265 #make one if it's not there !
268 266 sett = RhodeCodeUi()
269 267 sett.ui_key = 'largefiles'
270 268 sett.ui_section = 'extensions'
271 269 sett.ui_active = form_result['extensions_largefiles']
272 270 Session().add(sett)
273 271
274 272 sett = RhodeCodeUi.get_by_key('hgsubversion')
275 273 if not sett:
276 274 #make one if it's not there !
277 275 sett = RhodeCodeUi()
278 276 sett.ui_key = 'hgsubversion'
279 277 sett.ui_section = 'extensions'
280 278
281 279 sett.ui_active = form_result['extensions_hgsubversion']
282 280 Session().add(sett)
283 281
284 282 # sett = RhodeCodeUi.get_by_key('hggit')
285 283 # if not sett:
286 284 # #make one if it's not there !
287 285 # sett = RhodeCodeUi()
288 286 # sett.ui_key = 'hggit'
289 287 # sett.ui_section = 'extensions'
290 288 #
291 289 # sett.ui_active = form_result['extensions_hggit']
292 290 # Session().add(sett)
293 291
294 292 Session().commit()
295 293
296 294 h.flash(_('Updated VCS settings'), category='success')
297 295
298 296 except Exception:
299 297 log.error(traceback.format_exc())
300 298 h.flash(_('Error occurred during updating '
301 299 'application settings'), category='error')
302 300
303 301 if setting_id == 'hooks':
304 302 ui_key = request.POST.get('new_hook_ui_key')
305 303 ui_value = request.POST.get('new_hook_ui_value')
306 304 try:
307 305
308 306 if ui_value and ui_key:
309 307 RhodeCodeUi.create_or_update_hook(ui_key, ui_value)
310 308 h.flash(_('Added new hook'),
311 309 category='success')
312 310
313 311 # check for edits
314 312 update = False
315 313 _d = request.POST.dict_of_lists()
316 314 for k, v in zip(_d.get('hook_ui_key', []),
317 315 _d.get('hook_ui_value_new', [])):
318 316 RhodeCodeUi.create_or_update_hook(k, v)
319 317 update = True
320 318
321 319 if update:
322 320 h.flash(_('Updated hooks'), category='success')
323 321 Session().commit()
324 322 except Exception:
325 323 log.error(traceback.format_exc())
326 324 h.flash(_('Error occurred during hook creation'),
327 325 category='error')
328 326
329 327 return redirect(url('admin_edit_setting', setting_id='hooks'))
330 328
331 329 if setting_id == 'email':
332 330 test_email = request.POST.get('test_email')
333 331 test_email_subj = 'RhodeCode TestEmail'
334 332 test_email_body = 'RhodeCode Email test'
335 333
336 334 test_email_html_body = EmailNotificationModel()\
337 335 .get_email_tmpl(EmailNotificationModel.TYPE_DEFAULT,
338 336 body=test_email_body)
339 337
340 338 recipients = [test_email] if test_email else None
341 339
342 340 run_task(tasks.send_email, recipients, test_email_subj,
343 341 test_email_body, test_email_html_body)
344 342
345 343 h.flash(_('Email task created'), category='success')
346 344 return redirect(url('admin_settings'))
347 345
348 346 @HasPermissionAllDecorator('hg.admin')
349 347 def delete(self, setting_id):
350 348 """DELETE /admin/settings/setting_id: Delete an existing item"""
351 349 # Forms posted to this method should contain a hidden field:
352 350 # <input type="hidden" name="_method" value="DELETE" />
353 351 # Or using helpers:
354 352 # h.form(url('admin_setting', setting_id=ID),
355 353 # method='delete')
356 354 # url('admin_setting', setting_id=ID)
357 355 if setting_id == 'hooks':
358 356 hook_id = request.POST.get('hook_id')
359 357 RhodeCodeUi.delete(hook_id)
360 358 Session().commit()
361 359
362 360 @HasPermissionAllDecorator('hg.admin')
363 361 def show(self, setting_id, format='html'):
364 362 """
365 363 GET /admin/settings/setting_id: Show a specific item"""
366 364 # url('admin_setting', setting_id=ID)
367 365
368 366 @HasPermissionAllDecorator('hg.admin')
369 367 def edit(self, setting_id, format='html'):
370 368 """
371 369 GET /admin/settings/setting_id/edit: Form to
372 370 edit an existing item"""
373 371 # url('admin_edit_setting', setting_id=ID)
374 372
375 373 c.hooks = RhodeCodeUi.get_builtin_hooks()
376 374 c.custom_hooks = RhodeCodeUi.get_custom_hooks()
377 375
378 376 return htmlfill.render(
379 377 render('admin/settings/hooks.html'),
380 378 defaults={},
381 379 encoding="UTF-8",
382 380 force_defaults=False
383 381 )
384 382
385 383 def _load_my_repos_data(self):
386 384 repos_list = Session().query(Repository)\
387 385 .filter(Repository.user_id ==
388 386 self.rhodecode_user.user_id)\
389 387 .order_by(func.lower(Repository.repo_name)).all()
390 388
391 389 repos_data = RepoModel().get_repos_as_dict(repos_list=repos_list,
392 390 admin=True)
393 391 #json used to render the grid
394 392 return json.dumps(repos_data)
395 393
396 394 @NotAnonymous()
397 395 def my_account(self):
398 396 """
399 397 GET /_admin/my_account Displays info about my account
400 398 """
401 399 # url('admin_settings_my_account')
402 400
403 401 c.user = User.get(self.rhodecode_user.user_id)
404 402 c.perm_user = AuthUser(user_id=self.rhodecode_user.user_id,
405 403 ip_addr=self.ip_addr)
406 404 c.ldap_dn = c.user.ldap_dn
407 405
408 406 if c.user.username == 'default':
409 407 h.flash(_("You can't edit this user since it's"
410 408 " crucial for entire application"), category='warning')
411 409 return redirect(url('users'))
412 410
413 411 #json used to render the grid
414 412 c.data = self._load_my_repos_data()
415 413
416 414 defaults = c.user.get_dict()
417 415
418 416 c.form = htmlfill.render(
419 417 render('admin/users/user_edit_my_account_form.html'),
420 418 defaults=defaults,
421 419 encoding="UTF-8",
422 420 force_defaults=False
423 421 )
424 422 return render('admin/users/user_edit_my_account.html')
425 423
426 424 @NotAnonymous()
427 425 def my_account_update(self):
428 426 """PUT /_admin/my_account_update: Update an existing item"""
429 427 # Forms posted to this method should contain a hidden field:
430 428 # <input type="hidden" name="_method" value="PUT" />
431 429 # Or using helpers:
432 430 # h.form(url('admin_settings_my_account_update'),
433 431 # method='put')
434 432 # url('admin_settings_my_account_update', id=ID)
435 433 uid = self.rhodecode_user.user_id
436 434 c.user = User.get(self.rhodecode_user.user_id)
437 435 c.perm_user = AuthUser(user_id=self.rhodecode_user.user_id,
438 436 ip_addr=self.ip_addr)
439 437 c.ldap_dn = c.user.ldap_dn
440 438 email = self.rhodecode_user.email
441 439 _form = UserForm(edit=True,
442 440 old_data={'user_id': uid, 'email': email})()
443 441 form_result = {}
444 442 try:
445 443 form_result = _form.to_python(dict(request.POST))
446 444 skip_attrs = ['admin', 'active'] # skip attr for my account
447 445 if c.ldap_dn:
448 446 #forbid updating username for ldap accounts
449 447 skip_attrs.append('username')
450 448 UserModel().update(uid, form_result, skip_attrs=skip_attrs)
451 449 h.flash(_('Your account was updated successfully'),
452 450 category='success')
453 451 Session().commit()
454 452 except formencode.Invalid, errors:
455 453 #json used to render the grid
456 454 c.data = self._load_my_repos_data()
457 455 c.form = htmlfill.render(
458 456 render('admin/users/user_edit_my_account_form.html'),
459 457 defaults=errors.value,
460 458 errors=errors.error_dict or {},
461 459 prefix_error=False,
462 460 encoding="UTF-8")
463 461 return render('admin/users/user_edit_my_account.html')
464 462 except Exception:
465 463 log.error(traceback.format_exc())
466 464 h.flash(_('Error occurred during update of user %s') \
467 465 % form_result.get('username'), category='error')
468 466
469 467 return redirect(url('my_account'))
470 468
471 469 @NotAnonymous()
472 470 def my_account_my_pullrequests(self):
473 471 c.show_closed = request.GET.get('pr_show_closed')
474 472
475 473 def _filter(pr):
476 474 s = sorted(pr, key=lambda o: o.created_on, reverse=True)
477 475 if not c.show_closed:
478 476 s = filter(lambda p: p.status != PullRequest.STATUS_CLOSED, s)
479 477 return s
480 478
481 479 c.my_pull_requests = _filter(PullRequest.query()\
482 480 .filter(PullRequest.user_id ==
483 481 self.rhodecode_user.user_id)\
484 482 .all())
485 483
486 484 c.participate_in_pull_requests = _filter([
487 485 x.pull_request for x in PullRequestReviewers.query()\
488 486 .filter(PullRequestReviewers.user_id ==
489 487 self.rhodecode_user.user_id).all()])
490 488
491 489 return render('admin/users/user_edit_my_account_pullrequests.html')
492 490
493 491 def _get_hg_ui_settings(self):
494 492 ret = RhodeCodeUi.query().all()
495 493
496 494 if not ret:
497 495 raise Exception('Could not get application ui settings !')
498 496 settings = {}
499 497 for each in ret:
500 498 k = each.ui_key
501 499 v = each.ui_value
502 500 if k == '/':
503 501 k = 'root_path'
504 502
505 503 if k == 'push_ssl':
506 504 v = str2bool(v)
507 505
508 506 if k.find('.') != -1:
509 507 k = k.replace('.', '_')
510 508
511 509 if each.ui_section in ['hooks', 'extensions']:
512 510 v = each.ui_active
513 511
514 512 settings[each.ui_section + '_' + k] = v
515 513 return settings
@@ -1,362 +1,360 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.controllers.admin.users
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 Users crud controller for pylons
7 7
8 8 :created_on: Apr 4, 2010
9 9 :author: marcink
10 10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software: you can redistribute it and/or modify
14 14 # it under the terms of the GNU General Public License as published by
15 15 # the Free Software Foundation, either version 3 of the License, or
16 16 # (at your option) any later version.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 25
26 26 import logging
27 27 import traceback
28 28 import formencode
29 29 from pylons import response
30 30
31 31 from formencode import htmlfill
32 32 from pylons import request, session, tmpl_context as c, url, config
33 33 from pylons.controllers.util import redirect
34 34 from pylons.i18n.translation import _
35 35
36 36 import rhodecode
37 37 from rhodecode.lib.exceptions import DefaultUserException, \
38 38 UserOwnsReposException
39 39 from rhodecode.lib import helpers as h
40 40 from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator, \
41 41 AuthUser
42 42 from rhodecode.lib.base import BaseController, render
43 43
44 44 from rhodecode.model.db import User, UserEmailMap, UserIpMap
45 45 from rhodecode.model.forms import UserForm
46 46 from rhodecode.model.user import UserModel
47 47 from rhodecode.model.meta import Session
48 48 from rhodecode.lib.utils import action_logger
49 49 from rhodecode.lib.compat import json
50 50 from rhodecode.lib.utils2 import datetime_to_time, str2bool
51 51
52 52 log = logging.getLogger(__name__)
53 53
54 54
55 55 class UsersController(BaseController):
56 56 """REST Controller styled on the Atom Publishing Protocol"""
57 57 # To properly map this controller, ensure your config/routing.py
58 58 # file has a resource setup:
59 59 # map.resource('user', 'users')
60 60
61 61 @LoginRequired()
62 62 @HasPermissionAllDecorator('hg.admin')
63 63 def __before__(self):
64 c.admin_user = session.get('admin_user')
65 c.admin_username = session.get('admin_username')
66 64 super(UsersController, self).__before__()
67 65 c.available_permissions = config['available_permissions']
68 66
69 67 def index(self, format='html'):
70 68 """GET /users: All items in the collection"""
71 69 # url('users')
72 70
73 71 c.users_list = User.query().order_by(User.username).all()
74 72
75 73 users_data = []
76 74 total_records = len(c.users_list)
77 75 _tmpl_lookup = rhodecode.CONFIG['pylons.app_globals'].mako_lookup
78 76 template = _tmpl_lookup.get_template('data_table/_dt_elements.html')
79 77
80 78 grav_tmpl = lambda user_email, size: (
81 79 template.get_def("user_gravatar")
82 80 .render(user_email, size, _=_, h=h, c=c))
83 81
84 82 user_lnk = lambda user_id, username: (
85 83 template.get_def("user_name")
86 84 .render(user_id, username, _=_, h=h, c=c))
87 85
88 86 user_actions = lambda user_id, username: (
89 87 template.get_def("user_actions")
90 88 .render(user_id, username, _=_, h=h, c=c))
91 89
92 90 for user in c.users_list:
93 91
94 92 users_data.append({
95 93 "gravatar": grav_tmpl(user. email, 24),
96 94 "raw_username": user.username,
97 95 "username": user_lnk(user.user_id, user.username),
98 96 "firstname": user.name,
99 97 "lastname": user.lastname,
100 98 "last_login": h.fmt_date(user.last_login),
101 99 "last_login_raw": datetime_to_time(user.last_login),
102 100 "active": h.boolicon(user.active),
103 101 "admin": h.boolicon(user.admin),
104 102 "ldap": h.boolicon(bool(user.ldap_dn)),
105 103 "action": user_actions(user.user_id, user.username),
106 104 })
107 105
108 106 c.data = json.dumps({
109 107 "totalRecords": total_records,
110 108 "startIndex": 0,
111 109 "sort": None,
112 110 "dir": "asc",
113 111 "records": users_data
114 112 })
115 113
116 114 return render('admin/users/users.html')
117 115
118 116 def create(self):
119 117 """POST /users: Create a new item"""
120 118 # url('users')
121 119
122 120 user_model = UserModel()
123 121 user_form = UserForm()()
124 122 try:
125 123 form_result = user_form.to_python(dict(request.POST))
126 124 user_model.create(form_result)
127 125 usr = form_result['username']
128 126 action_logger(self.rhodecode_user, 'admin_created_user:%s' % usr,
129 127 None, self.ip_addr, self.sa)
130 128 h.flash(_('Created user %s') % usr,
131 129 category='success')
132 130 Session().commit()
133 131 except formencode.Invalid, errors:
134 132 return htmlfill.render(
135 133 render('admin/users/user_add.html'),
136 134 defaults=errors.value,
137 135 errors=errors.error_dict or {},
138 136 prefix_error=False,
139 137 encoding="UTF-8")
140 138 except Exception:
141 139 log.error(traceback.format_exc())
142 140 h.flash(_('Error occurred during creation of user %s') \
143 141 % request.POST.get('username'), category='error')
144 142 return redirect(url('users'))
145 143
146 144 def new(self, format='html'):
147 145 """GET /users/new: Form to create a new item"""
148 146 # url('new_user')
149 147 return render('admin/users/user_add.html')
150 148
151 149 def update(self, id):
152 150 """PUT /users/id: Update an existing item"""
153 151 # Forms posted to this method should contain a hidden field:
154 152 # <input type="hidden" name="_method" value="PUT" />
155 153 # Or using helpers:
156 154 # h.form(url('update_user', id=ID),
157 155 # method='put')
158 156 # url('user', id=ID)
159 157 user_model = UserModel()
160 158 c.user = user_model.get(id)
161 159 c.ldap_dn = c.user.ldap_dn
162 160 c.perm_user = AuthUser(user_id=id, ip_addr=self.ip_addr)
163 161 _form = UserForm(edit=True, old_data={'user_id': id,
164 162 'email': c.user.email})()
165 163 form_result = {}
166 164 try:
167 165 form_result = _form.to_python(dict(request.POST))
168 166 skip_attrs = []
169 167 if c.ldap_dn:
170 168 #forbid updating username for ldap accounts
171 169 skip_attrs = ['username']
172 170 user_model.update(id, form_result, skip_attrs=skip_attrs)
173 171 usr = form_result['username']
174 172 action_logger(self.rhodecode_user, 'admin_updated_user:%s' % usr,
175 173 None, self.ip_addr, self.sa)
176 174 h.flash(_('User updated successfully'), category='success')
177 175 Session().commit()
178 176 except formencode.Invalid, errors:
179 177 c.user_email_map = UserEmailMap.query()\
180 178 .filter(UserEmailMap.user == c.user).all()
181 179 c.user_ip_map = UserIpMap.query()\
182 180 .filter(UserIpMap.user == c.user).all()
183 181 defaults = errors.value
184 182 e = errors.error_dict or {}
185 183 defaults.update({
186 184 'create_repo_perm': user_model.has_perm(id, 'hg.create.repository'),
187 185 'fork_repo_perm': user_model.has_perm(id, 'hg.fork.repository'),
188 186 '_method': 'put'
189 187 })
190 188 return htmlfill.render(
191 189 render('admin/users/user_edit.html'),
192 190 defaults=defaults,
193 191 errors=e,
194 192 prefix_error=False,
195 193 encoding="UTF-8")
196 194 except Exception:
197 195 log.error(traceback.format_exc())
198 196 h.flash(_('Error occurred during update of user %s') \
199 197 % form_result.get('username'), category='error')
200 198 return redirect(url('edit_user', id=id))
201 199
202 200 def delete(self, id):
203 201 """DELETE /users/id: Delete an existing item"""
204 202 # Forms posted to this method should contain a hidden field:
205 203 # <input type="hidden" name="_method" value="DELETE" />
206 204 # Or using helpers:
207 205 # h.form(url('delete_user', id=ID),
208 206 # method='delete')
209 207 # url('user', id=ID)
210 208 usr = User.get_or_404(id)
211 209 try:
212 210 UserModel().delete(usr)
213 211 Session().commit()
214 212 h.flash(_('Successfully deleted user'), category='success')
215 213 except (UserOwnsReposException, DefaultUserException), e:
216 214 h.flash(e, category='warning')
217 215 except Exception:
218 216 log.error(traceback.format_exc())
219 217 h.flash(_('An error occurred during deletion of user'),
220 218 category='error')
221 219 return redirect(url('users'))
222 220
223 221 def show(self, id, format='html'):
224 222 """GET /users/id: Show a specific item"""
225 223 # url('user', id=ID)
226 224
227 225 def edit(self, id, format='html'):
228 226 """GET /users/id/edit: Form to edit an existing item"""
229 227 # url('edit_user', id=ID)
230 228 c.user = User.get_or_404(id)
231 229
232 230 if c.user.username == 'default':
233 231 h.flash(_("You can't edit this user"), category='warning')
234 232 return redirect(url('users'))
235 233
236 234 c.perm_user = AuthUser(user_id=id, ip_addr=self.ip_addr)
237 235 c.user.permissions = {}
238 236 c.granted_permissions = UserModel().fill_perms(c.user)\
239 237 .permissions['global']
240 238 c.user_email_map = UserEmailMap.query()\
241 239 .filter(UserEmailMap.user == c.user).all()
242 240 c.user_ip_map = UserIpMap.query()\
243 241 .filter(UserIpMap.user == c.user).all()
244 242 user_model = UserModel()
245 243 c.ldap_dn = c.user.ldap_dn
246 244 defaults = c.user.get_dict()
247 245 defaults.update({
248 246 'create_repo_perm': user_model.has_perm(id, 'hg.create.repository'),
249 247 'fork_repo_perm': user_model.has_perm(id, 'hg.fork.repository'),
250 248 })
251 249
252 250 return htmlfill.render(
253 251 render('admin/users/user_edit.html'),
254 252 defaults=defaults,
255 253 encoding="UTF-8",
256 254 force_defaults=False
257 255 )
258 256
259 257 def update_perm(self, id):
260 258 """PUT /users_perm/id: Update an existing item"""
261 259 # url('user_perm', id=ID, method='put')
262 260 usr = User.get_or_404(id)
263 261 grant_create_perm = str2bool(request.POST.get('create_repo_perm'))
264 262 grant_fork_perm = str2bool(request.POST.get('fork_repo_perm'))
265 263 inherit_perms = str2bool(request.POST.get('inherit_default_permissions'))
266 264
267 265 user_model = UserModel()
268 266
269 267 try:
270 268 usr.inherit_default_permissions = inherit_perms
271 269 Session().add(usr)
272 270
273 271 if grant_create_perm:
274 272 user_model.revoke_perm(usr, 'hg.create.none')
275 273 user_model.grant_perm(usr, 'hg.create.repository')
276 274 h.flash(_("Granted 'repository create' permission to user"),
277 275 category='success')
278 276 else:
279 277 user_model.revoke_perm(usr, 'hg.create.repository')
280 278 user_model.grant_perm(usr, 'hg.create.none')
281 279 h.flash(_("Revoked 'repository create' permission to user"),
282 280 category='success')
283 281
284 282 if grant_fork_perm:
285 283 user_model.revoke_perm(usr, 'hg.fork.none')
286 284 user_model.grant_perm(usr, 'hg.fork.repository')
287 285 h.flash(_("Granted 'repository fork' permission to user"),
288 286 category='success')
289 287 else:
290 288 user_model.revoke_perm(usr, 'hg.fork.repository')
291 289 user_model.grant_perm(usr, 'hg.fork.none')
292 290 h.flash(_("Revoked 'repository fork' permission to user"),
293 291 category='success')
294 292
295 293 Session().commit()
296 294 except Exception:
297 295 log.error(traceback.format_exc())
298 296 h.flash(_('An error occurred during permissions saving'),
299 297 category='error')
300 298 return redirect(url('edit_user', id=id))
301 299
302 300 def add_email(self, id):
303 301 """POST /user_emails:Add an existing item"""
304 302 # url('user_emails', id=ID, method='put')
305 303
306 304 email = request.POST.get('new_email')
307 305 user_model = UserModel()
308 306
309 307 try:
310 308 user_model.add_extra_email(id, email)
311 309 Session().commit()
312 310 h.flash(_("Added email %s to user") % email, category='success')
313 311 except formencode.Invalid, error:
314 312 msg = error.error_dict['email']
315 313 h.flash(msg, category='error')
316 314 except Exception:
317 315 log.error(traceback.format_exc())
318 316 h.flash(_('An error occurred during email saving'),
319 317 category='error')
320 318 return redirect(url('edit_user', id=id))
321 319
322 320 def delete_email(self, id):
323 321 """DELETE /user_emails_delete/id: Delete an existing item"""
324 322 # url('user_emails_delete', id=ID, method='delete')
325 323 user_model = UserModel()
326 324 user_model.delete_extra_email(id, request.POST.get('del_email'))
327 325 Session().commit()
328 326 h.flash(_("Removed email from user"), category='success')
329 327 return redirect(url('edit_user', id=id))
330 328
331 329 def add_ip(self, id):
332 330 """POST /user_ips:Add an existing item"""
333 331 # url('user_ips', id=ID, method='put')
334 332
335 333 ip = request.POST.get('new_ip')
336 334 user_model = UserModel()
337 335
338 336 try:
339 337 user_model.add_extra_ip(id, ip)
340 338 Session().commit()
341 339 h.flash(_("Added ip %s to user") % ip, category='success')
342 340 except formencode.Invalid, error:
343 341 msg = error.error_dict['ip']
344 342 h.flash(msg, category='error')
345 343 except Exception:
346 344 log.error(traceback.format_exc())
347 345 h.flash(_('An error occurred during ip saving'),
348 346 category='error')
349 347 if 'default_user' in request.POST:
350 348 return redirect(url('edit_permission', id='default'))
351 349 return redirect(url('edit_user', id=id))
352 350
353 351 def delete_ip(self, id):
354 352 """DELETE /user_ips_delete/id: Delete an existing item"""
355 353 # url('user_ips_delete', id=ID, method='delete')
356 354 user_model = UserModel()
357 355 user_model.delete_extra_ip(id, request.POST.get('del_ip'))
358 356 Session().commit()
359 357 h.flash(_("Removed ip from user"), category='success')
360 358 if 'default_user' in request.POST:
361 359 return redirect(url('edit_permission', id='default'))
362 360 return redirect(url('edit_user', id=id))
@@ -1,284 +1,282 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.controllers.admin.users_groups
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 User Groups crud controller for pylons
7 7
8 8 :created_on: Jan 25, 2011
9 9 :author: marcink
10 10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software: you can redistribute it and/or modify
14 14 # it under the terms of the GNU General Public License as published by
15 15 # the Free Software Foundation, either version 3 of the License, or
16 16 # (at your option) any later version.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 25
26 26 import logging
27 27 import traceback
28 28 import formencode
29 29
30 30 from formencode import htmlfill
31 31 from pylons import request, session, tmpl_context as c, url, config
32 32 from pylons.controllers.util import abort, redirect
33 33 from pylons.i18n.translation import _
34 34
35 35 from rhodecode.lib import helpers as h
36 36 from rhodecode.lib.exceptions import UserGroupsAssignedException
37 37 from rhodecode.lib.utils2 import safe_unicode, str2bool
38 38 from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator
39 39 from rhodecode.lib.base import BaseController, render
40 40
41 41 from rhodecode.model.users_group import UserGroupModel
42 42
43 43 from rhodecode.model.db import User, UserGroup, UserGroupToPerm,\
44 44 UserGroupRepoToPerm, UserGroupRepoGroupToPerm
45 45 from rhodecode.model.forms import UserGroupForm
46 46 from rhodecode.model.meta import Session
47 47 from rhodecode.lib.utils import action_logger
48 48 from sqlalchemy.orm import joinedload
49 49
50 50 log = logging.getLogger(__name__)
51 51
52 52
53 53 class UsersGroupsController(BaseController):
54 54 """REST Controller styled on the Atom Publishing Protocol"""
55 55 # To properly map this controller, ensure your config/routing.py
56 56 # file has a resource setup:
57 57 # map.resource('users_group', 'users_groups')
58 58
59 59 @LoginRequired()
60 60 @HasPermissionAllDecorator('hg.admin')
61 61 def __before__(self):
62 c.admin_user = session.get('admin_user')
63 c.admin_username = session.get('admin_username')
64 62 super(UsersGroupsController, self).__before__()
65 63 c.available_permissions = config['available_permissions']
66 64
67 65 def index(self, format='html'):
68 66 """GET /users_groups: All items in the collection"""
69 67 # url('users_groups')
70 68 c.users_groups_list = UserGroup().query().all()
71 69 return render('admin/users_groups/users_groups.html')
72 70
73 71 def create(self):
74 72 """POST /users_groups: Create a new item"""
75 73 # url('users_groups')
76 74
77 75 users_group_form = UserGroupForm()()
78 76 try:
79 77 form_result = users_group_form.to_python(dict(request.POST))
80 78 UserGroupModel().create(name=form_result['users_group_name'],
81 79 active=form_result['users_group_active'])
82 80 gr = form_result['users_group_name']
83 81 action_logger(self.rhodecode_user,
84 82 'admin_created_users_group:%s' % gr,
85 83 None, self.ip_addr, self.sa)
86 84 h.flash(_('Created user group %s') % gr, category='success')
87 85 Session().commit()
88 86 except formencode.Invalid, errors:
89 87 return htmlfill.render(
90 88 render('admin/users_groups/users_group_add.html'),
91 89 defaults=errors.value,
92 90 errors=errors.error_dict or {},
93 91 prefix_error=False,
94 92 encoding="UTF-8")
95 93 except Exception:
96 94 log.error(traceback.format_exc())
97 95 h.flash(_('Error occurred during creation of user group %s') \
98 96 % request.POST.get('users_group_name'), category='error')
99 97
100 98 return redirect(url('users_groups'))
101 99
102 100 def new(self, format='html'):
103 101 """GET /users_groups/new: Form to create a new item"""
104 102 # url('new_users_group')
105 103 return render('admin/users_groups/users_group_add.html')
106 104
107 105 def _load_data(self, id):
108 106 c.users_group.permissions = {
109 107 'repositories': {},
110 108 'repositories_groups': {}
111 109 }
112 110
113 111 ugroup_repo_perms = UserGroupRepoToPerm.query()\
114 112 .options(joinedload(UserGroupRepoToPerm.permission))\
115 113 .options(joinedload(UserGroupRepoToPerm.repository))\
116 114 .filter(UserGroupRepoToPerm.users_group_id == id)\
117 115 .all()
118 116
119 117 for gr in ugroup_repo_perms:
120 118 c.users_group.permissions['repositories'][gr.repository.repo_name] \
121 119 = gr.permission.permission_name
122 120
123 121 ugroup_group_perms = UserGroupRepoGroupToPerm.query()\
124 122 .options(joinedload(UserGroupRepoGroupToPerm.permission))\
125 123 .options(joinedload(UserGroupRepoGroupToPerm.group))\
126 124 .filter(UserGroupRepoGroupToPerm.users_group_id == id)\
127 125 .all()
128 126
129 127 for gr in ugroup_group_perms:
130 128 c.users_group.permissions['repositories_groups'][gr.group.group_name] \
131 129 = gr.permission.permission_name
132 130
133 131 c.group_members_obj = sorted((x.user for x in c.users_group.members),
134 132 key=lambda u: u.username.lower())
135 133 c.group_members = [(x.user_id, x.username) for x in
136 134 c.group_members_obj]
137 135 c.available_members = sorted(((x.user_id, x.username) for x in
138 136 User.query().all()),
139 137 key=lambda u: u[1].lower())
140 138
141 139 def update(self, id):
142 140 """PUT /users_groups/id: Update an existing item"""
143 141 # Forms posted to this method should contain a hidden field:
144 142 # <input type="hidden" name="_method" value="PUT" />
145 143 # Or using helpers:
146 144 # h.form(url('users_group', id=ID),
147 145 # method='put')
148 146 # url('users_group', id=ID)
149 147
150 148 c.users_group = UserGroup.get_or_404(id)
151 149 self._load_data(id)
152 150
153 151 available_members = [safe_unicode(x[0]) for x in c.available_members]
154 152
155 153 users_group_form = UserGroupForm(edit=True,
156 154 old_data=c.users_group.get_dict(),
157 155 available_members=available_members)()
158 156
159 157 try:
160 158 form_result = users_group_form.to_python(request.POST)
161 159 UserGroupModel().update(c.users_group, form_result)
162 160 gr = form_result['users_group_name']
163 161 action_logger(self.rhodecode_user,
164 162 'admin_updated_users_group:%s' % gr,
165 163 None, self.ip_addr, self.sa)
166 164 h.flash(_('Updated user group %s') % gr, category='success')
167 165 Session().commit()
168 166 except formencode.Invalid, errors:
169 167 ug_model = UserGroupModel()
170 168 defaults = errors.value
171 169 e = errors.error_dict or {}
172 170 defaults.update({
173 171 'create_repo_perm': ug_model.has_perm(id,
174 172 'hg.create.repository'),
175 173 'fork_repo_perm': ug_model.has_perm(id,
176 174 'hg.fork.repository'),
177 175 '_method': 'put'
178 176 })
179 177
180 178 return htmlfill.render(
181 179 render('admin/users_groups/users_group_edit.html'),
182 180 defaults=defaults,
183 181 errors=e,
184 182 prefix_error=False,
185 183 encoding="UTF-8")
186 184 except Exception:
187 185 log.error(traceback.format_exc())
188 186 h.flash(_('Error occurred during update of user group %s') \
189 187 % request.POST.get('users_group_name'), category='error')
190 188
191 189 return redirect(url('edit_users_group', id=id))
192 190
193 191 def delete(self, id):
194 192 """DELETE /users_groups/id: Delete an existing item"""
195 193 # Forms posted to this method should contain a hidden field:
196 194 # <input type="hidden" name="_method" value="DELETE" />
197 195 # Or using helpers:
198 196 # h.form(url('users_group', id=ID),
199 197 # method='delete')
200 198 # url('users_group', id=ID)
201 199 usr_gr = UserGroup.get_or_404(id)
202 200 try:
203 201 UserGroupModel().delete(usr_gr)
204 202 Session().commit()
205 203 h.flash(_('Successfully deleted user group'), category='success')
206 204 except UserGroupsAssignedException, e:
207 205 h.flash(e, category='error')
208 206 except Exception:
209 207 log.error(traceback.format_exc())
210 208 h.flash(_('An error occurred during deletion of user group'),
211 209 category='error')
212 210 return redirect(url('users_groups'))
213 211
214 212 def show(self, id, format='html'):
215 213 """GET /users_groups/id: Show a specific item"""
216 214 # url('users_group', id=ID)
217 215
218 216 def edit(self, id, format='html'):
219 217 """GET /users_groups/id/edit: Form to edit an existing item"""
220 218 # url('edit_users_group', id=ID)
221 219
222 220 c.users_group = UserGroup.get_or_404(id)
223 221 self._load_data(id)
224 222
225 223 ug_model = UserGroupModel()
226 224 defaults = c.users_group.get_dict()
227 225 defaults.update({
228 226 'create_repo_perm': ug_model.has_perm(c.users_group,
229 227 'hg.create.repository'),
230 228 'fork_repo_perm': ug_model.has_perm(c.users_group,
231 229 'hg.fork.repository'),
232 230 })
233 231
234 232 return htmlfill.render(
235 233 render('admin/users_groups/users_group_edit.html'),
236 234 defaults=defaults,
237 235 encoding="UTF-8",
238 236 force_defaults=False
239 237 )
240 238
241 239 def update_perm(self, id):
242 240 """PUT /users_perm/id: Update an existing item"""
243 241 # url('users_group_perm', id=ID, method='put')
244 242
245 243 users_group = UserGroup.get_or_404(id)
246 244 grant_create_perm = str2bool(request.POST.get('create_repo_perm'))
247 245 grant_fork_perm = str2bool(request.POST.get('fork_repo_perm'))
248 246 inherit_perms = str2bool(request.POST.get('inherit_default_permissions'))
249 247
250 248 usergroup_model = UserGroupModel()
251 249
252 250 try:
253 251 users_group.inherit_default_permissions = inherit_perms
254 252 Session().add(users_group)
255 253
256 254 if grant_create_perm:
257 255 usergroup_model.revoke_perm(id, 'hg.create.none')
258 256 usergroup_model.grant_perm(id, 'hg.create.repository')
259 257 h.flash(_("Granted 'repository create' permission to user group"),
260 258 category='success')
261 259 else:
262 260 usergroup_model.revoke_perm(id, 'hg.create.repository')
263 261 usergroup_model.grant_perm(id, 'hg.create.none')
264 262 h.flash(_("Revoked 'repository create' permission to user group"),
265 263 category='success')
266 264
267 265 if grant_fork_perm:
268 266 usergroup_model.revoke_perm(id, 'hg.fork.none')
269 267 usergroup_model.grant_perm(id, 'hg.fork.repository')
270 268 h.flash(_("Granted 'repository fork' permission to user group"),
271 269 category='success')
272 270 else:
273 271 usergroup_model.revoke_perm(id, 'hg.fork.repository')
274 272 usergroup_model.grant_perm(id, 'hg.fork.none')
275 273 h.flash(_("Revoked 'repository fork' permission to user group"),
276 274 category='success')
277 275
278 276 Session().commit()
279 277 except Exception:
280 278 log.error(traceback.format_exc())
281 279 h.flash(_('An error occurred during permissions saving'),
282 280 category='error')
283 281
284 282 return redirect(url('edit_users_group', id=id))
@@ -1,2042 +1,2041 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.model.db_1_6_0
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 Database Models for RhodeCode <=1.5.X
7 7
8 8 :created_on: Apr 08, 2010
9 9 :author: marcink
10 10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software: you can redistribute it and/or modify
14 14 # it under the terms of the GNU General Public License as published by
15 15 # the Free Software Foundation, either version 3 of the License, or
16 16 # (at your option) any later version.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 25
26 26 import os
27 27 import logging
28 28 import datetime
29 29 import traceback
30 30 import hashlib
31 31 import time
32 32 from collections import defaultdict
33 33
34 34 from sqlalchemy import *
35 35 from sqlalchemy.ext.hybrid import hybrid_property
36 36 from sqlalchemy.orm import relationship, joinedload, class_mapper, validates
37 37 from sqlalchemy.exc import DatabaseError
38 38 from beaker.cache import cache_region, region_invalidate
39 39 from webob.exc import HTTPNotFound
40 40
41 41 from pylons.i18n.translation import lazy_ugettext as _
42 42
43 43 from rhodecode.lib.vcs import get_backend
44 44 from rhodecode.lib.vcs.utils.helpers import get_scm
45 45 from rhodecode.lib.vcs.exceptions import VCSError
46 46 from rhodecode.lib.vcs.utils.lazy import LazyProperty
47 47 from rhodecode.lib.vcs.backends.base import EmptyChangeset
48 48
49 49 from rhodecode.lib.utils2 import str2bool, safe_str, get_changeset_safe, \
50 50 safe_unicode, remove_suffix, remove_prefix, time_to_datetime, _set_extras
51 51 from rhodecode.lib.compat import json
52 52 from rhodecode.lib.caching_query import FromCache
53 53
54 54 from rhodecode.model.meta import Base, Session
55 55
56 56 URL_SEP = '/'
57 57 log = logging.getLogger(__name__)
58 58
59 59 #==============================================================================
60 60 # BASE CLASSES
61 61 #==============================================================================
62 62
63 63 _hash_key = lambda k: hashlib.md5(safe_str(k)).hexdigest()
64 64
65 65
66 66 class BaseModel(object):
67 67 """
68 68 Base Model for all classess
69 69 """
70 70
71 71 @classmethod
72 72 def _get_keys(cls):
73 73 """return column names for this model """
74 74 return class_mapper(cls).c.keys()
75 75
76 76 def get_dict(self):
77 77 """
78 78 return dict with keys and values corresponding
79 79 to this model data """
80 80
81 81 d = {}
82 82 for k in self._get_keys():
83 83 d[k] = getattr(self, k)
84 84
85 85 # also use __json__() if present to get additional fields
86 86 _json_attr = getattr(self, '__json__', None)
87 87 if _json_attr:
88 88 # update with attributes from __json__
89 89 if callable(_json_attr):
90 90 _json_attr = _json_attr()
91 91 for k, val in _json_attr.iteritems():
92 92 d[k] = val
93 93 return d
94 94
95 95 def get_appstruct(self):
96 96 """return list with keys and values tupples corresponding
97 97 to this model data """
98 98
99 99 l = []
100 100 for k in self._get_keys():
101 101 l.append((k, getattr(self, k),))
102 102 return l
103 103
104 104 def populate_obj(self, populate_dict):
105 105 """populate model with data from given populate_dict"""
106 106
107 107 for k in self._get_keys():
108 108 if k in populate_dict:
109 109 setattr(self, k, populate_dict[k])
110 110
111 111 @classmethod
112 112 def query(cls):
113 113 return Session().query(cls)
114 114
115 115 @classmethod
116 116 def get(cls, id_):
117 117 if id_:
118 118 return cls.query().get(id_)
119 119
120 120 @classmethod
121 121 def get_or_404(cls, id_):
122 122 try:
123 123 id_ = int(id_)
124 124 except (TypeError, ValueError):
125 125 raise HTTPNotFound
126 126
127 127 res = cls.query().get(id_)
128 128 if not res:
129 129 raise HTTPNotFound
130 130 return res
131 131
132 132 @classmethod
133 133 def getAll(cls):
134 134 return cls.query().all()
135 135
136 136 @classmethod
137 137 def delete(cls, id_):
138 138 obj = cls.query().get(id_)
139 139 Session().delete(obj)
140 140
141 141 def __repr__(self):
142 142 if hasattr(self, '__unicode__'):
143 143 # python repr needs to return str
144 144 return safe_str(self.__unicode__())
145 145 return '<DB:%s>' % (self.__class__.__name__)
146 146
147 147
148 148 class RhodeCodeSetting(Base, BaseModel):
149 149 __tablename__ = 'rhodecode_settings'
150 150 __table_args__ = (
151 151 UniqueConstraint('app_settings_name'),
152 152 {'extend_existing': True, 'mysql_engine': 'InnoDB',
153 153 'mysql_charset': 'utf8'}
154 154 )
155 155 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
156 156 app_settings_name = Column("app_settings_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
157 157 _app_settings_value = Column("app_settings_value", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
158 158
159 159 def __init__(self, k='', v=''):
160 160 self.app_settings_name = k
161 161 self.app_settings_value = v
162 162
163 163 @validates('_app_settings_value')
164 164 def validate_settings_value(self, key, val):
165 165 assert type(val) == unicode
166 166 return val
167 167
168 168 @hybrid_property
169 169 def app_settings_value(self):
170 170 v = self._app_settings_value
171 171 if self.app_settings_name in ["ldap_active",
172 172 "default_repo_enable_statistics",
173 173 "default_repo_enable_locking",
174 174 "default_repo_private",
175 175 "default_repo_enable_downloads"]:
176 176 v = str2bool(v)
177 177 return v
178 178
179 179 @app_settings_value.setter
180 180 def app_settings_value(self, val):
181 181 """
182 182 Setter that will always make sure we use unicode in app_settings_value
183 183
184 184 :param val:
185 185 """
186 186 self._app_settings_value = safe_unicode(val)
187 187
188 188 def __unicode__(self):
189 189 return u"<%s('%s:%s')>" % (
190 190 self.__class__.__name__,
191 191 self.app_settings_name, self.app_settings_value
192 192 )
193 193
194 194 @classmethod
195 195 def get_by_name(cls, key):
196 196 return cls.query()\
197 197 .filter(cls.app_settings_name == key).scalar()
198 198
199 199 @classmethod
200 200 def get_by_name_or_create(cls, key):
201 201 res = cls.get_by_name(key)
202 202 if not res:
203 203 res = cls(key)
204 204 return res
205 205
206 206 @classmethod
207 207 def get_app_settings(cls, cache=False):
208 208
209 209 ret = cls.query()
210 210
211 211 if cache:
212 212 ret = ret.options(FromCache("sql_cache_short", "get_hg_settings"))
213 213
214 214 if not ret:
215 215 raise Exception('Could not get application settings !')
216 216 settings = {}
217 217 for each in ret:
218 218 settings['rhodecode_' + each.app_settings_name] = \
219 219 each.app_settings_value
220 220
221 221 return settings
222 222
223 223 @classmethod
224 224 def get_ldap_settings(cls, cache=False):
225 225 ret = cls.query()\
226 226 .filter(cls.app_settings_name.startswith('ldap_')).all()
227 227 fd = {}
228 228 for row in ret:
229 229 fd.update({row.app_settings_name: row.app_settings_value})
230 230
231 231 return fd
232 232
233 233 @classmethod
234 234 def get_default_repo_settings(cls, cache=False, strip_prefix=False):
235 235 ret = cls.query()\
236 236 .filter(cls.app_settings_name.startswith('default_')).all()
237 237 fd = {}
238 238 for row in ret:
239 239 key = row.app_settings_name
240 240 if strip_prefix:
241 241 key = remove_prefix(key, prefix='default_')
242 242 fd.update({key: row.app_settings_value})
243 243
244 244 return fd
245 245
246 246
247 247 class RhodeCodeUi(Base, BaseModel):
248 248 __tablename__ = 'rhodecode_ui'
249 249 __table_args__ = (
250 250 UniqueConstraint('ui_key'),
251 251 {'extend_existing': True, 'mysql_engine': 'InnoDB',
252 252 'mysql_charset': 'utf8'}
253 253 )
254 254
255 255 HOOK_UPDATE = 'changegroup.update'
256 256 HOOK_REPO_SIZE = 'changegroup.repo_size'
257 257 HOOK_PUSH = 'changegroup.push_logger'
258 258 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
259 259 HOOK_PULL = 'outgoing.pull_logger'
260 260 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
261 261
262 262 ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
263 263 ui_section = Column("ui_section", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
264 264 ui_key = Column("ui_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
265 265 ui_value = Column("ui_value", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
266 266 ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True)
267 267
268 268 @classmethod
269 269 def get_by_key(cls, key):
270 270 return cls.query().filter(cls.ui_key == key).scalar()
271 271
272 272 @classmethod
273 273 def get_builtin_hooks(cls):
274 274 q = cls.query()
275 275 q = q.filter(cls.ui_key.in_([cls.HOOK_UPDATE, cls.HOOK_REPO_SIZE,
276 276 cls.HOOK_PUSH, cls.HOOK_PRE_PUSH,
277 277 cls.HOOK_PULL, cls.HOOK_PRE_PULL]))
278 278 return q.all()
279 279
280 280 @classmethod
281 281 def get_custom_hooks(cls):
282 282 q = cls.query()
283 283 q = q.filter(~cls.ui_key.in_([cls.HOOK_UPDATE, cls.HOOK_REPO_SIZE,
284 284 cls.HOOK_PUSH, cls.HOOK_PRE_PUSH,
285 285 cls.HOOK_PULL, cls.HOOK_PRE_PULL]))
286 286 q = q.filter(cls.ui_section == 'hooks')
287 287 return q.all()
288 288
289 289 @classmethod
290 290 def get_repos_location(cls):
291 291 return cls.get_by_key('/').ui_value
292 292
293 293 @classmethod
294 294 def create_or_update_hook(cls, key, val):
295 295 new_ui = cls.get_by_key(key) or cls()
296 296 new_ui.ui_section = 'hooks'
297 297 new_ui.ui_active = True
298 298 new_ui.ui_key = key
299 299 new_ui.ui_value = val
300 300
301 301 Session().add(new_ui)
302 302
303 303 def __repr__(self):
304 304 return '<DB:%s[%s:%s]>' % (self.__class__.__name__, self.ui_key,
305 305 self.ui_value)
306 306
307 307
308 308 class User(Base, BaseModel):
309 309 __tablename__ = 'users'
310 310 __table_args__ = (
311 311 UniqueConstraint('username'), UniqueConstraint('email'),
312 312 Index('u_username_idx', 'username'),
313 313 Index('u_email_idx', 'email'),
314 314 {'extend_existing': True, 'mysql_engine': 'InnoDB',
315 315 'mysql_charset': 'utf8'}
316 316 )
317 317 DEFAULT_USER = 'default'
318 318 DEFAULT_PERMISSIONS = [
319 319 'hg.register.manual_activate', 'hg.create.repository',
320 320 'hg.fork.repository', 'repository.read', 'group.read'
321 321 ]
322 322 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
323 323 username = Column("username", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
324 324 password = Column("password", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
325 325 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
326 326 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
327 327 name = Column("firstname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
328 328 lastname = Column("lastname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
329 329 _email = Column("email", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
330 330 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
331 331 ldap_dn = Column("ldap_dn", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
332 332 api_key = Column("api_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
333 333 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
334 334
335 335 user_log = relationship('UserLog')
336 336 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
337 337
338 338 repositories = relationship('Repository')
339 339 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
340 340 followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
341 341
342 342 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
343 343 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
344 344
345 345 group_member = relationship('UserGroupMember', cascade='all')
346 346
347 347 notifications = relationship('UserNotification', cascade='all')
348 348 # notifications assigned to this user
349 349 user_created_notifications = relationship('Notification', cascade='all')
350 350 # comments created by this user
351 351 user_comments = relationship('ChangesetComment', cascade='all')
352 352 #extra emails for this user
353 353 user_emails = relationship('UserEmailMap', cascade='all')
354 354
355 355 @hybrid_property
356 356 def email(self):
357 357 return self._email
358 358
359 359 @email.setter
360 360 def email(self, val):
361 361 self._email = val.lower() if val else None
362 362
363 363 @property
364 364 def firstname(self):
365 365 # alias for future
366 366 return self.name
367 367
368 368 @property
369 369 def emails(self):
370 370 other = UserEmailMap.query().filter(UserEmailMap.user==self).all()
371 371 return [self.email] + [x.email for x in other]
372 372
373 373 @property
374 374 def ip_addresses(self):
375 375 ret = UserIpMap.query().filter(UserIpMap.user == self).all()
376 376 return [x.ip_addr for x in ret]
377 377
378 378 @property
379 379 def username_and_name(self):
380 380 return '%s (%s %s)' % (self.username, self.firstname, self.lastname)
381 381
382 382 @property
383 383 def full_name(self):
384 384 return '%s %s' % (self.firstname, self.lastname)
385 385
386 386 @property
387 387 def full_name_or_username(self):
388 388 return ('%s %s' % (self.firstname, self.lastname)
389 389 if (self.firstname and self.lastname) else self.username)
390 390
391 391 @property
392 392 def full_contact(self):
393 393 return '%s %s <%s>' % (self.firstname, self.lastname, self.email)
394 394
395 395 @property
396 396 def short_contact(self):
397 397 return '%s %s' % (self.firstname, self.lastname)
398 398
399 399 @property
400 400 def is_admin(self):
401 401 return self.admin
402 402
403 403 @property
404 404 def AuthUser(self):
405 405 """
406 406 Returns instance of AuthUser for this user
407 407 """
408 408 from rhodecode.lib.auth import AuthUser
409 409 return AuthUser(user_id=self.user_id, api_key=self.api_key,
410 410 username=self.username)
411 411
412 412 def __unicode__(self):
413 413 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
414 414 self.user_id, self.username)
415 415
416 416 @classmethod
417 417 def get_by_username(cls, username, case_insensitive=False, cache=False):
418 418 if case_insensitive:
419 419 q = cls.query().filter(cls.username.ilike(username))
420 420 else:
421 421 q = cls.query().filter(cls.username == username)
422 422
423 423 if cache:
424 424 q = q.options(FromCache(
425 425 "sql_cache_short",
426 426 "get_user_%s" % _hash_key(username)
427 427 )
428 428 )
429 429 return q.scalar()
430 430
431 431 @classmethod
432 432 def get_by_api_key(cls, api_key, cache=False):
433 433 q = cls.query().filter(cls.api_key == api_key)
434 434
435 435 if cache:
436 436 q = q.options(FromCache("sql_cache_short",
437 437 "get_api_key_%s" % api_key))
438 438 return q.scalar()
439 439
440 440 @classmethod
441 441 def get_by_email(cls, email, case_insensitive=False, cache=False):
442 442 if case_insensitive:
443 443 q = cls.query().filter(cls.email.ilike(email))
444 444 else:
445 445 q = cls.query().filter(cls.email == email)
446 446
447 447 if cache:
448 448 q = q.options(FromCache("sql_cache_short",
449 449 "get_email_key_%s" % email))
450 450
451 451 ret = q.scalar()
452 452 if ret is None:
453 453 q = UserEmailMap.query()
454 454 # try fetching in alternate email map
455 455 if case_insensitive:
456 456 q = q.filter(UserEmailMap.email.ilike(email))
457 457 else:
458 458 q = q.filter(UserEmailMap.email == email)
459 459 q = q.options(joinedload(UserEmailMap.user))
460 460 if cache:
461 461 q = q.options(FromCache("sql_cache_short",
462 462 "get_email_map_key_%s" % email))
463 463 ret = getattr(q.scalar(), 'user', None)
464 464
465 465 return ret
466 466
467 467 @classmethod
468 468 def get_from_cs_author(cls, author):
469 469 """
470 470 Tries to get User objects out of commit author string
471 471
472 472 :param author:
473 473 """
474 474 from rhodecode.lib.helpers import email, author_name
475 475 # Valid email in the attribute passed, see if they're in the system
476 476 _email = email(author)
477 477 if _email:
478 478 user = cls.get_by_email(_email, case_insensitive=True)
479 479 if user:
480 480 return user
481 481 # Maybe we can match by username?
482 482 _author = author_name(author)
483 483 user = cls.get_by_username(_author, case_insensitive=True)
484 484 if user:
485 485 return user
486 486
487 487 def update_lastlogin(self):
488 488 """Update user lastlogin"""
489 489 self.last_login = datetime.datetime.now()
490 490 Session().add(self)
491 491 log.debug('updated user %s lastlogin' % self.username)
492 492
493 493 def get_api_data(self):
494 494 """
495 495 Common function for generating user related data for API
496 496 """
497 497 user = self
498 498 data = dict(
499 499 user_id=user.user_id,
500 500 username=user.username,
501 501 firstname=user.name,
502 502 lastname=user.lastname,
503 503 email=user.email,
504 504 emails=user.emails,
505 505 api_key=user.api_key,
506 506 active=user.active,
507 507 admin=user.admin,
508 508 ldap_dn=user.ldap_dn,
509 509 last_login=user.last_login,
510 510 ip_addresses=user.ip_addresses
511 511 )
512 512 return data
513 513
514 514 def __json__(self):
515 515 data = dict(
516 516 full_name=self.full_name,
517 517 full_name_or_username=self.full_name_or_username,
518 518 short_contact=self.short_contact,
519 519 full_contact=self.full_contact
520 520 )
521 521 data.update(self.get_api_data())
522 522 return data
523 523
524 524
525 525 class UserEmailMap(Base, BaseModel):
526 526 __tablename__ = 'user_email_map'
527 527 __table_args__ = (
528 528 Index('uem_email_idx', 'email'),
529 529 UniqueConstraint('email'),
530 530 {'extend_existing': True, 'mysql_engine': 'InnoDB',
531 531 'mysql_charset': 'utf8'}
532 532 )
533 533 __mapper_args__ = {}
534 534
535 535 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
536 536 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
537 537 _email = Column("email", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
538 538 user = relationship('User', lazy='joined')
539 539
540 540 @validates('_email')
541 541 def validate_email(self, key, email):
542 542 # check if this email is not main one
543 543 main_email = Session().query(User).filter(User.email == email).scalar()
544 544 if main_email is not None:
545 545 raise AttributeError('email %s is present is user table' % email)
546 546 return email
547 547
548 548 @hybrid_property
549 549 def email(self):
550 550 return self._email
551 551
552 552 @email.setter
553 553 def email(self, val):
554 554 self._email = val.lower() if val else None
555 555
556 556
557 557 class UserIpMap(Base, BaseModel):
558 558 __tablename__ = 'user_ip_map'
559 559 __table_args__ = (
560 560 UniqueConstraint('user_id', 'ip_addr'),
561 561 {'extend_existing': True, 'mysql_engine': 'InnoDB',
562 562 'mysql_charset': 'utf8'}
563 563 )
564 564 __mapper_args__ = {}
565 565
566 566 ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
567 567 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
568 568 ip_addr = Column("ip_addr", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
569 569 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
570 570 user = relationship('User', lazy='joined')
571 571
572 572 @classmethod
573 573 def _get_ip_range(cls, ip_addr):
574 574 from rhodecode.lib import ipaddr
575 575 net = ipaddr.IPNetwork(address=ip_addr)
576 576 return [str(net.network), str(net.broadcast)]
577 577
578 578 def __json__(self):
579 579 return dict(
580 580 ip_addr=self.ip_addr,
581 581 ip_range=self._get_ip_range(self.ip_addr)
582 582 )
583 583
584 584
585 585 class UserLog(Base, BaseModel):
586 586 __tablename__ = 'user_logs'
587 587 __table_args__ = (
588 588 {'extend_existing': True, 'mysql_engine': 'InnoDB',
589 589 'mysql_charset': 'utf8'},
590 590 )
591 591 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
592 592 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
593 593 username = Column("username", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
594 594 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True)
595 595 repository_name = Column("repository_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
596 596 user_ip = Column("user_ip", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
597 597 action = Column("action", UnicodeText(1200000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
598 598 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
599 599
600 600 @property
601 601 def action_as_day(self):
602 602 return datetime.date(*self.action_date.timetuple()[:3])
603 603
604 604 user = relationship('User')
605 605 repository = relationship('Repository', cascade='')
606 606
607 607
608 608 class UserGroup(Base, BaseModel):
609 609 __tablename__ = 'users_groups'
610 610 __table_args__ = (
611 611 {'extend_existing': True, 'mysql_engine': 'InnoDB',
612 612 'mysql_charset': 'utf8'},
613 613 )
614 614
615 615 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
616 616 users_group_name = Column("users_group_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
617 617 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
618 618 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
619 619
620 620 members = relationship('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
621 621 users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
622 622 users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
623 623
624 624 def __unicode__(self):
625 625 return u'<userGroup(%s)>' % (self.users_group_name)
626 626
627 627 @classmethod
628 628 def get_by_group_name(cls, group_name, cache=False,
629 629 case_insensitive=False):
630 630 if case_insensitive:
631 631 q = cls.query().filter(cls.users_group_name.ilike(group_name))
632 632 else:
633 633 q = cls.query().filter(cls.users_group_name == group_name)
634 634 if cache:
635 635 q = q.options(FromCache(
636 636 "sql_cache_short",
637 637 "get_user_%s" % _hash_key(group_name)
638 638 )
639 639 )
640 640 return q.scalar()
641 641
642 642 @classmethod
643 643 def get(cls, users_group_id, cache=False):
644 644 users_group = cls.query()
645 645 if cache:
646 646 users_group = users_group.options(FromCache("sql_cache_short",
647 647 "get_users_group_%s" % users_group_id))
648 648 return users_group.get(users_group_id)
649 649
650 650 def get_api_data(self):
651 651 users_group = self
652 652
653 653 data = dict(
654 654 users_group_id=users_group.users_group_id,
655 655 group_name=users_group.users_group_name,
656 656 active=users_group.users_group_active,
657 657 )
658 658
659 659 return data
660 660
661 661
662 662 class UserGroupMember(Base, BaseModel):
663 663 __tablename__ = 'users_groups_members'
664 664 __table_args__ = (
665 665 {'extend_existing': True, 'mysql_engine': 'InnoDB',
666 666 'mysql_charset': 'utf8'},
667 667 )
668 668
669 669 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
670 670 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
671 671 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
672 672
673 673 user = relationship('User', lazy='joined')
674 674 users_group = relationship('UserGroup')
675 675
676 676 def __init__(self, gr_id='', u_id=''):
677 677 self.users_group_id = gr_id
678 678 self.user_id = u_id
679 679
680 680
681 681 class RepositoryField(Base, BaseModel):
682 682 __tablename__ = 'repositories_fields'
683 683 __table_args__ = (
684 684 UniqueConstraint('repository_id', 'field_key'), # no-multi field
685 685 {'extend_existing': True, 'mysql_engine': 'InnoDB',
686 686 'mysql_charset': 'utf8'},
687 687 )
688 688 PREFIX = 'ex_' # prefix used in form to not conflict with already existing fields
689 689
690 690 repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
691 691 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
692 692 field_key = Column("field_key", String(250, convert_unicode=False, assert_unicode=None))
693 693 field_label = Column("field_label", String(1024, convert_unicode=False, assert_unicode=None), nullable=False)
694 694 field_value = Column("field_value", String(10000, convert_unicode=False, assert_unicode=None), nullable=False)
695 695 field_desc = Column("field_desc", String(1024, convert_unicode=False, assert_unicode=None), nullable=False)
696 696 field_type = Column("field_type", String(256), nullable=False, unique=None)
697 697 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
698 698
699 699 repository = relationship('Repository')
700 700
701 701 @property
702 702 def field_key_prefixed(self):
703 703 return 'ex_%s' % self.field_key
704 704
705 705 @classmethod
706 706 def un_prefix_key(cls, key):
707 707 if key.startswith(cls.PREFIX):
708 708 return key[len(cls.PREFIX):]
709 709 return key
710 710
711 711 @classmethod
712 712 def get_by_key_name(cls, key, repo):
713 713 row = cls.query()\
714 714 .filter(cls.repository == repo)\
715 715 .filter(cls.field_key == key).scalar()
716 716 return row
717 717
718 718
719 719 class Repository(Base, BaseModel):
720 720 __tablename__ = 'repositories'
721 721 __table_args__ = (
722 722 UniqueConstraint('repo_name'),
723 723 Index('r_repo_name_idx', 'repo_name'),
724 724 {'extend_existing': True, 'mysql_engine': 'InnoDB',
725 725 'mysql_charset': 'utf8'},
726 726 )
727 727
728 728 repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
729 729 repo_name = Column("repo_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
730 730 clone_uri = Column("clone_uri", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
731 731 repo_type = Column("repo_type", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default=None)
732 732 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
733 733 private = Column("private", Boolean(), nullable=True, unique=None, default=None)
734 734 enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True)
735 735 enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True)
736 736 description = Column("description", String(10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
737 737 created_on = Column('created_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
738 738 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
739 739 landing_rev = Column("landing_revision", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default=None)
740 740 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
741 741 _locked = Column("locked", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
742 742 _changeset_cache = Column("changeset_cache", LargeBinary(), nullable=True) #JSON data
743 743
744 744 fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None)
745 745 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None)
746 746
747 747 user = relationship('User')
748 748 fork = relationship('Repository', remote_side=repo_id)
749 749 group = relationship('RepoGroup')
750 750 repo_to_perm = relationship('UserRepoToPerm', cascade='all', order_by='UserRepoToPerm.repo_to_perm_id')
751 751 users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
752 752 stats = relationship('Statistics', cascade='all', uselist=False)
753 753
754 754 followers = relationship('UserFollowing',
755 755 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
756 756 cascade='all')
757 757 extra_fields = relationship('RepositoryField',
758 758 cascade="all, delete, delete-orphan")
759 759
760 760 logs = relationship('UserLog')
761 761 comments = relationship('ChangesetComment', cascade="all, delete, delete-orphan")
762 762
763 763 pull_requests_org = relationship('PullRequest',
764 764 primaryjoin='PullRequest.org_repo_id==Repository.repo_id',
765 765 cascade="all, delete, delete-orphan")
766 766
767 767 pull_requests_other = relationship('PullRequest',
768 768 primaryjoin='PullRequest.other_repo_id==Repository.repo_id',
769 769 cascade="all, delete, delete-orphan")
770 770
771 771 def __unicode__(self):
772 772 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
773 773 self.repo_name)
774 774
775 775 @hybrid_property
776 776 def locked(self):
777 777 # always should return [user_id, timelocked]
778 778 if self._locked:
779 779 _lock_info = self._locked.split(':')
780 780 return int(_lock_info[0]), _lock_info[1]
781 781 return [None, None]
782 782
783 783 @locked.setter
784 784 def locked(self, val):
785 785 if val and isinstance(val, (list, tuple)):
786 786 self._locked = ':'.join(map(str, val))
787 787 else:
788 788 self._locked = None
789 789
790 790 @hybrid_property
791 791 def changeset_cache(self):
792 792 from rhodecode.lib.vcs.backends.base import EmptyChangeset
793 793 dummy = EmptyChangeset().__json__()
794 794 if not self._changeset_cache:
795 795 return dummy
796 796 try:
797 797 return json.loads(self._changeset_cache)
798 798 except TypeError:
799 799 return dummy
800 800
801 801 @changeset_cache.setter
802 802 def changeset_cache(self, val):
803 803 try:
804 804 self._changeset_cache = json.dumps(val)
805 805 except Exception:
806 806 log.error(traceback.format_exc())
807 807
808 808 @classmethod
809 809 def url_sep(cls):
810 810 return URL_SEP
811 811
812 812 @classmethod
813 813 def normalize_repo_name(cls, repo_name):
814 814 """
815 815 Normalizes os specific repo_name to the format internally stored inside
816 816 dabatabase using URL_SEP
817 817
818 818 :param cls:
819 819 :param repo_name:
820 820 """
821 821 return cls.url_sep().join(repo_name.split(os.sep))
822 822
823 823 @classmethod
824 824 def get_by_repo_name(cls, repo_name):
825 825 q = Session().query(cls).filter(cls.repo_name == repo_name)
826 826 q = q.options(joinedload(Repository.fork))\
827 827 .options(joinedload(Repository.user))\
828 828 .options(joinedload(Repository.group))
829 829 return q.scalar()
830 830
831 831 @classmethod
832 832 def get_by_full_path(cls, repo_full_path):
833 833 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
834 834 repo_name = cls.normalize_repo_name(repo_name)
835 835 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
836 836
837 837 @classmethod
838 838 def get_repo_forks(cls, repo_id):
839 839 return cls.query().filter(Repository.fork_id == repo_id)
840 840
841 841 @classmethod
842 842 def base_path(cls):
843 843 """
844 844 Returns base path when all repos are stored
845 845
846 846 :param cls:
847 847 """
848 848 q = Session().query(RhodeCodeUi)\
849 849 .filter(RhodeCodeUi.ui_key == cls.url_sep())
850 850 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
851 851 return q.one().ui_value
852 852
853 853 @property
854 854 def forks(self):
855 855 """
856 856 Return forks of this repo
857 857 """
858 858 return Repository.get_repo_forks(self.repo_id)
859 859
860 860 @property
861 861 def parent(self):
862 862 """
863 863 Returns fork parent
864 864 """
865 865 return self.fork
866 866
867 867 @property
868 868 def just_name(self):
869 869 return self.repo_name.split(Repository.url_sep())[-1]
870 870
871 871 @property
872 872 def groups_with_parents(self):
873 873 groups = []
874 874 if self.group is None:
875 875 return groups
876 876
877 877 cur_gr = self.group
878 878 groups.insert(0, cur_gr)
879 879 while 1:
880 880 gr = getattr(cur_gr, 'parent_group', None)
881 881 cur_gr = cur_gr.parent_group
882 882 if gr is None:
883 883 break
884 884 groups.insert(0, gr)
885 885
886 886 return groups
887 887
888 888 @property
889 889 def groups_and_repo(self):
890 890 return self.groups_with_parents, self.just_name, self.repo_name
891 891
892 892 @LazyProperty
893 893 def repo_path(self):
894 894 """
895 895 Returns base full path for that repository means where it actually
896 896 exists on a filesystem
897 897 """
898 898 q = Session().query(RhodeCodeUi).filter(RhodeCodeUi.ui_key ==
899 899 Repository.url_sep())
900 900 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
901 901 return q.one().ui_value
902 902
903 903 @property
904 904 def repo_full_path(self):
905 905 p = [self.repo_path]
906 906 # we need to split the name by / since this is how we store the
907 907 # names in the database, but that eventually needs to be converted
908 908 # into a valid system path
909 909 p += self.repo_name.split(Repository.url_sep())
910 910 return os.path.join(*map(safe_unicode, p))
911 911
912 912 @property
913 913 def cache_keys(self):
914 914 """
915 915 Returns associated cache keys for that repo
916 916 """
917 917 return CacheInvalidation.query()\
918 918 .filter(CacheInvalidation.cache_args == self.repo_name)\
919 919 .order_by(CacheInvalidation.cache_key)\
920 920 .all()
921 921
922 922 def get_new_name(self, repo_name):
923 923 """
924 924 returns new full repository name based on assigned group and new new
925 925
926 926 :param group_name:
927 927 """
928 928 path_prefix = self.group.full_path_splitted if self.group else []
929 929 return Repository.url_sep().join(path_prefix + [repo_name])
930 930
931 931 @property
932 932 def _ui(self):
933 933 """
934 934 Creates an db based ui object for this repository
935 935 """
936 936 from rhodecode.lib.utils import make_ui
937 937 return make_ui('db', clear_session=False)
938 938
939 939 @classmethod
940 940 def is_valid(cls, repo_name):
941 941 """
942 942 returns True if given repo name is a valid filesystem repository
943 943
944 944 :param cls:
945 945 :param repo_name:
946 946 """
947 947 from rhodecode.lib.utils import is_valid_repo
948 948
949 949 return is_valid_repo(repo_name, cls.base_path())
950 950
951 951 def get_api_data(self):
952 952 """
953 953 Common function for generating repo api data
954 954
955 955 """
956 956 repo = self
957 957 data = dict(
958 958 repo_id=repo.repo_id,
959 959 repo_name=repo.repo_name,
960 960 repo_type=repo.repo_type,
961 961 clone_uri=repo.clone_uri,
962 962 private=repo.private,
963 963 created_on=repo.created_on,
964 964 description=repo.description,
965 965 landing_rev=repo.landing_rev,
966 966 owner=repo.user.username,
967 967 fork_of=repo.fork.repo_name if repo.fork else None,
968 968 enable_statistics=repo.enable_statistics,
969 969 enable_locking=repo.enable_locking,
970 970 enable_downloads=repo.enable_downloads,
971 971 last_changeset=repo.changeset_cache,
972 972 locked_by=User.get(self.locked[0]).get_api_data() \
973 973 if self.locked[0] else None,
974 974 locked_date=time_to_datetime(self.locked[1]) \
975 975 if self.locked[1] else None
976 976 )
977 977 rc_config = RhodeCodeSetting.get_app_settings()
978 978 repository_fields = str2bool(rc_config.get('rhodecode_repository_fields'))
979 979 if repository_fields:
980 980 for f in self.extra_fields:
981 981 data[f.field_key_prefixed] = f.field_value
982 982
983 983 return data
984 984
985 985 @classmethod
986 986 def lock(cls, repo, user_id):
987 987 repo.locked = [user_id, time.time()]
988 988 Session().add(repo)
989 989 Session().commit()
990 990
991 991 @classmethod
992 992 def unlock(cls, repo):
993 993 repo.locked = None
994 994 Session().add(repo)
995 995 Session().commit()
996 996
997 997 @classmethod
998 998 def getlock(cls, repo):
999 999 return repo.locked
1000 1000
1001 1001 @property
1002 1002 def last_db_change(self):
1003 1003 return self.updated_on
1004 1004
1005 1005 def clone_url(self, **override):
1006 1006 from pylons import url
1007 1007 from urlparse import urlparse
1008 1008 import urllib
1009 1009 parsed_url = urlparse(url('home', qualified=True))
1010 1010 default_clone_uri = '%(scheme)s://%(user)s%(pass)s%(netloc)s%(prefix)s%(path)s'
1011 1011 decoded_path = safe_unicode(urllib.unquote(parsed_url.path))
1012 1012 args = {
1013 1013 'user': '',
1014 1014 'pass': '',
1015 1015 'scheme': parsed_url.scheme,
1016 1016 'netloc': parsed_url.netloc,
1017 1017 'prefix': decoded_path,
1018 1018 'path': self.repo_name
1019 1019 }
1020 1020
1021 1021 args.update(override)
1022 1022 return default_clone_uri % args
1023 1023
1024 1024 #==========================================================================
1025 1025 # SCM PROPERTIES
1026 1026 #==========================================================================
1027 1027
1028 1028 def get_changeset(self, rev=None):
1029 1029 return get_changeset_safe(self.scm_instance, rev)
1030 1030
1031 1031 def get_landing_changeset(self):
1032 1032 """
1033 1033 Returns landing changeset, or if that doesn't exist returns the tip
1034 1034 """
1035 1035 cs = self.get_changeset(self.landing_rev) or self.get_changeset()
1036 1036 return cs
1037 1037
1038 1038 def update_changeset_cache(self, cs_cache=None):
1039 1039 """
1040 1040 Update cache of last changeset for repository, keys should be::
1041 1041
1042 1042 short_id
1043 1043 raw_id
1044 1044 revision
1045 1045 message
1046 1046 date
1047 1047 author
1048 1048
1049 1049 :param cs_cache:
1050 1050 """
1051 1051 from rhodecode.lib.vcs.backends.base import BaseChangeset
1052 1052 if cs_cache is None:
1053 1053 cs_cache = EmptyChangeset()
1054 1054 # use no-cache version here
1055 1055 scm_repo = self.scm_instance_no_cache()
1056 1056 if scm_repo:
1057 1057 cs_cache = scm_repo.get_changeset()
1058 1058
1059 1059 if isinstance(cs_cache, BaseChangeset):
1060 1060 cs_cache = cs_cache.__json__()
1061 1061
1062 1062 if (cs_cache != self.changeset_cache or not self.changeset_cache):
1063 1063 _default = datetime.datetime.fromtimestamp(0)
1064 1064 last_change = cs_cache.get('date') or _default
1065 1065 log.debug('updated repo %s with new cs cache %s'
1066 1066 % (self.repo_name, cs_cache))
1067 1067 self.updated_on = last_change
1068 1068 self.changeset_cache = cs_cache
1069 1069 Session().add(self)
1070 1070 Session().commit()
1071 1071 else:
1072 1072 log.debug('Skipping repo:%s already with latest changes'
1073 1073 % self.repo_name)
1074 1074
1075 1075 @property
1076 1076 def tip(self):
1077 1077 return self.get_changeset('tip')
1078 1078
1079 1079 @property
1080 1080 def author(self):
1081 1081 return self.tip.author
1082 1082
1083 1083 @property
1084 1084 def last_change(self):
1085 1085 return self.scm_instance.last_change
1086 1086
1087 1087 def get_comments(self, revisions=None):
1088 1088 """
1089 1089 Returns comments for this repository grouped by revisions
1090 1090
1091 1091 :param revisions: filter query by revisions only
1092 1092 """
1093 1093 cmts = ChangesetComment.query()\
1094 1094 .filter(ChangesetComment.repo == self)
1095 1095 if revisions:
1096 1096 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
1097 1097 grouped = defaultdict(list)
1098 1098 for cmt in cmts.all():
1099 1099 grouped[cmt.revision].append(cmt)
1100 1100 return grouped
1101 1101
1102 1102 def statuses(self, revisions=None):
1103 1103 """
1104 1104 Returns statuses for this repository
1105 1105
1106 1106 :param revisions: list of revisions to get statuses for
1107 1107 :type revisions: list
1108 1108 """
1109 1109
1110 1110 statuses = ChangesetStatus.query()\
1111 1111 .filter(ChangesetStatus.repo == self)\
1112 1112 .filter(ChangesetStatus.version == 0)
1113 1113 if revisions:
1114 1114 statuses = statuses.filter(ChangesetStatus.revision.in_(revisions))
1115 1115 grouped = {}
1116 1116
1117 1117 #maybe we have open new pullrequest without a status ?
1118 1118 stat = ChangesetStatus.STATUS_UNDER_REVIEW
1119 1119 status_lbl = ChangesetStatus.get_status_lbl(stat)
1120 1120 for pr in PullRequest.query().filter(PullRequest.org_repo == self).all():
1121 1121 for rev in pr.revisions:
1122 1122 pr_id = pr.pull_request_id
1123 1123 pr_repo = pr.other_repo.repo_name
1124 1124 grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
1125 1125
1126 1126 for stat in statuses.all():
1127 1127 pr_id = pr_repo = None
1128 1128 if stat.pull_request:
1129 1129 pr_id = stat.pull_request.pull_request_id
1130 1130 pr_repo = stat.pull_request.other_repo.repo_name
1131 1131 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
1132 1132 pr_id, pr_repo]
1133 1133 return grouped
1134 1134
1135 1135 def _repo_size(self):
1136 1136 from rhodecode.lib import helpers as h
1137 1137 log.debug('calculating repository size...')
1138 1138 return h.format_byte_size(self.scm_instance.size)
1139 1139
1140 1140 #==========================================================================
1141 1141 # SCM CACHE INSTANCE
1142 1142 #==========================================================================
1143 1143
1144 1144 @property
1145 1145 def invalidate(self):
1146 1146 return CacheInvalidation.invalidate(self.repo_name)
1147 1147
1148 1148 def set_invalidate(self):
1149 1149 """
1150 1150 set a cache for invalidation for this instance
1151 1151 """
1152 1152 CacheInvalidation.set_invalidate(repo_name=self.repo_name)
1153 1153
1154 1154 def scm_instance_no_cache(self):
1155 1155 return self.__get_instance()
1156 1156
1157 1157 @LazyProperty
1158 1158 def scm_instance(self):
1159 1159 import rhodecode
1160 1160 full_cache = str2bool(rhodecode.CONFIG.get('vcs_full_cache'))
1161 1161 if full_cache:
1162 1162 return self.scm_instance_cached()
1163 1163 return self.__get_instance()
1164 1164
1165 1165 def scm_instance_cached(self, cache_map=None):
1166 1166 @cache_region('long_term')
1167 1167 def _c(repo_name):
1168 1168 return self.__get_instance()
1169 1169 rn = self.repo_name
1170 1170 log.debug('Getting cached instance of repo')
1171 1171
1172 1172 if cache_map:
1173 1173 # get using prefilled cache_map
1174 1174 invalidate_repo = cache_map[self.repo_name]
1175 1175 if invalidate_repo:
1176 1176 invalidate_repo = (None if invalidate_repo.cache_active
1177 1177 else invalidate_repo)
1178 1178 else:
1179 1179 # get from invalidate
1180 1180 invalidate_repo = self.invalidate
1181 1181
1182 1182 if invalidate_repo is not None:
1183 1183 region_invalidate(_c, None, rn)
1184 1184 # update our cache
1185 1185 CacheInvalidation.set_valid(invalidate_repo.cache_key)
1186 1186 return _c(rn)
1187 1187
1188 1188 def __get_instance(self):
1189 1189 repo_full_path = self.repo_full_path
1190 1190 try:
1191 1191 alias = get_scm(repo_full_path)[0]
1192 1192 log.debug('Creating instance of %s repository from %s'
1193 1193 % (alias, repo_full_path))
1194 1194 backend = get_backend(alias)
1195 1195 except VCSError:
1196 1196 log.error(traceback.format_exc())
1197 1197 log.error('Perhaps this repository is in db and not in '
1198 1198 'filesystem run rescan repositories with '
1199 1199 '"destroy old data " option from admin panel')
1200 1200 return
1201 1201
1202 1202 if alias == 'hg':
1203 1203
1204 1204 repo = backend(safe_str(repo_full_path), create=False,
1205 1205 baseui=self._ui)
1206 1206 # skip hidden web repository
1207 1207 if repo._get_hidden():
1208 1208 return
1209 1209 else:
1210 1210 repo = backend(repo_full_path, create=False)
1211 1211
1212 1212 return repo
1213 1213
1214 1214
1215 1215 class RepoGroup(Base, BaseModel):
1216 1216 __tablename__ = 'groups'
1217 1217 __table_args__ = (
1218 1218 UniqueConstraint('group_name', 'group_parent_id'),
1219 1219 CheckConstraint('group_id != group_parent_id'),
1220 1220 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1221 1221 'mysql_charset': 'utf8'},
1222 1222 )
1223 1223 __mapper_args__ = {'order_by': 'group_name'}
1224 1224
1225 1225 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1226 1226 group_name = Column("group_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
1227 1227 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
1228 1228 group_description = Column("group_description", String(10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1229 1229 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
1230 1230
1231 1231 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
1232 1232 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
1233 1233
1234 1234 parent_group = relationship('RepoGroup', remote_side=group_id)
1235 1235
1236 1236 def __init__(self, group_name='', parent_group=None):
1237 1237 self.group_name = group_name
1238 1238 self.parent_group = parent_group
1239 1239
1240 1240 def __unicode__(self):
1241 1241 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.group_id,
1242 1242 self.group_name)
1243 1243
1244 1244 @classmethod
1245 1245 def groups_choices(cls, groups=None, show_empty_group=True):
1246 1246 from webhelpers.html import literal as _literal
1247 1247 if not groups:
1248 1248 groups = cls.query().all()
1249 1249
1250 1250 repo_groups = []
1251 1251 if show_empty_group:
1252 1252 repo_groups = [('-1', '-- %s --' % _('top level'))]
1253 1253 sep = ' &raquo; '
1254 1254 _name = lambda k: _literal(sep.join(k))
1255 1255
1256 1256 repo_groups.extend([(x.group_id, _name(x.full_path_splitted))
1257 1257 for x in groups])
1258 1258
1259 1259 repo_groups = sorted(repo_groups, key=lambda t: t[1].split(sep)[0])
1260 1260 return repo_groups
1261 1261
1262 1262 @classmethod
1263 1263 def url_sep(cls):
1264 1264 return URL_SEP
1265 1265
1266 1266 @classmethod
1267 1267 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
1268 1268 if case_insensitive:
1269 1269 gr = cls.query()\
1270 1270 .filter(cls.group_name.ilike(group_name))
1271 1271 else:
1272 1272 gr = cls.query()\
1273 1273 .filter(cls.group_name == group_name)
1274 1274 if cache:
1275 1275 gr = gr.options(FromCache(
1276 1276 "sql_cache_short",
1277 1277 "get_group_%s" % _hash_key(group_name)
1278 1278 )
1279 1279 )
1280 1280 return gr.scalar()
1281 1281
1282 1282 @property
1283 1283 def parents(self):
1284 1284 parents_recursion_limit = 5
1285 1285 groups = []
1286 1286 if self.parent_group is None:
1287 1287 return groups
1288 1288 cur_gr = self.parent_group
1289 1289 groups.insert(0, cur_gr)
1290 1290 cnt = 0
1291 1291 while 1:
1292 1292 cnt += 1
1293 1293 gr = getattr(cur_gr, 'parent_group', None)
1294 1294 cur_gr = cur_gr.parent_group
1295 1295 if gr is None:
1296 1296 break
1297 1297 if cnt == parents_recursion_limit:
1298 1298 # this will prevent accidental infinit loops
1299 1299 log.error('group nested more than %s' %
1300 1300 parents_recursion_limit)
1301 1301 break
1302 1302
1303 1303 groups.insert(0, gr)
1304 1304 return groups
1305 1305
1306 1306 @property
1307 1307 def children(self):
1308 1308 return RepoGroup.query().filter(RepoGroup.parent_group == self)
1309 1309
1310 1310 @property
1311 1311 def name(self):
1312 1312 return self.group_name.split(RepoGroup.url_sep())[-1]
1313 1313
1314 1314 @property
1315 1315 def full_path(self):
1316 1316 return self.group_name
1317 1317
1318 1318 @property
1319 1319 def full_path_splitted(self):
1320 1320 return self.group_name.split(RepoGroup.url_sep())
1321 1321
1322 1322 @property
1323 1323 def repositories(self):
1324 1324 return Repository.query()\
1325 1325 .filter(Repository.group == self)\
1326 1326 .order_by(Repository.repo_name)
1327 1327
1328 1328 @property
1329 1329 def repositories_recursive_count(self):
1330 1330 cnt = self.repositories.count()
1331 1331
1332 1332 def children_count(group):
1333 1333 cnt = 0
1334 1334 for child in group.children:
1335 1335 cnt += child.repositories.count()
1336 1336 cnt += children_count(child)
1337 1337 return cnt
1338 1338
1339 1339 return cnt + children_count(self)
1340 1340
1341 1341 def _recursive_objects(self, include_repos=True):
1342 1342 all_ = []
1343 1343
1344 1344 def _get_members(root_gr):
1345 1345 if include_repos:
1346 1346 for r in root_gr.repositories:
1347 1347 all_.append(r)
1348 1348 childs = root_gr.children.all()
1349 1349 if childs:
1350 1350 for gr in childs:
1351 1351 all_.append(gr)
1352 1352 _get_members(gr)
1353 1353
1354 1354 _get_members(self)
1355 1355 return [self] + all_
1356 1356
1357 1357 def recursive_groups_and_repos(self):
1358 1358 """
1359 1359 Recursive return all groups, with repositories in those groups
1360 1360 """
1361 1361 return self._recursive_objects()
1362 1362
1363 1363 def recursive_groups(self):
1364 1364 """
1365 1365 Returns all children groups for this group including children of children
1366 1366 """
1367 1367 return self._recursive_objects(include_repos=False)
1368 1368
1369 1369 def get_new_name(self, group_name):
1370 1370 """
1371 1371 returns new full group name based on parent and new name
1372 1372
1373 1373 :param group_name:
1374 1374 """
1375 1375 path_prefix = (self.parent_group.full_path_splitted if
1376 1376 self.parent_group else [])
1377 1377 return RepoGroup.url_sep().join(path_prefix + [group_name])
1378 1378
1379 1379
1380 1380 class Permission(Base, BaseModel):
1381 1381 __tablename__ = 'permissions'
1382 1382 __table_args__ = (
1383 1383 Index('p_perm_name_idx', 'permission_name'),
1384 1384 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1385 1385 'mysql_charset': 'utf8'},
1386 1386 )
1387 1387 PERMS = [
1388 1388 ('repository.none', _('Repository no access')),
1389 1389 ('repository.read', _('Repository read access')),
1390 1390 ('repository.write', _('Repository write access')),
1391 1391 ('repository.admin', _('Repository admin access')),
1392 1392
1393 1393 ('group.none', _('Repository group no access')),
1394 1394 ('group.read', _('Repository group read access')),
1395 1395 ('group.write', _('Repository group write access')),
1396 1396 ('group.admin', _('Repository group admin access')),
1397 1397
1398 1398 ('hg.admin', _('RhodeCode Administrator')),
1399 1399 ('hg.create.none', _('Repository creation disabled')),
1400 1400 ('hg.create.repository', _('Repository creation enabled')),
1401 1401 ('hg.fork.none', _('Repository forking disabled')),
1402 1402 ('hg.fork.repository', _('Repository forking enabled')),
1403 1403 ('hg.register.none', _('Register disabled')),
1404 1404 ('hg.register.manual_activate', _('Register new user with RhodeCode '
1405 1405 'with manual activation')),
1406 1406
1407 1407 ('hg.register.auto_activate', _('Register new user with RhodeCode '
1408 1408 'with auto activation')),
1409 1409 ]
1410 1410
1411 1411 # defines which permissions are more important higher the more important
1412 1412 PERM_WEIGHTS = {
1413 1413 'repository.none': 0,
1414 1414 'repository.read': 1,
1415 1415 'repository.write': 3,
1416 1416 'repository.admin': 4,
1417 1417
1418 1418 'group.none': 0,
1419 1419 'group.read': 1,
1420 1420 'group.write': 3,
1421 1421 'group.admin': 4,
1422 1422
1423 1423 'hg.fork.none': 0,
1424 1424 'hg.fork.repository': 1,
1425 1425 'hg.create.none': 0,
1426 1426 'hg.create.repository':1
1427 1427 }
1428 1428
1429 1429 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1430 1430 permission_name = Column("permission_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1431 1431 permission_longname = Column("permission_longname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1432 1432
1433 1433 def __unicode__(self):
1434 1434 return u"<%s('%s:%s')>" % (
1435 1435 self.__class__.__name__, self.permission_id, self.permission_name
1436 1436 )
1437 1437
1438 1438 @classmethod
1439 1439 def get_by_key(cls, key):
1440 1440 return cls.query().filter(cls.permission_name == key).scalar()
1441 1441
1442 1442 @classmethod
1443 1443 def get_default_perms(cls, default_user_id):
1444 1444 q = Session().query(UserRepoToPerm, Repository, cls)\
1445 1445 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
1446 1446 .join((cls, UserRepoToPerm.permission_id == cls.permission_id))\
1447 1447 .filter(UserRepoToPerm.user_id == default_user_id)
1448 1448
1449 1449 return q.all()
1450 1450
1451 1451 @classmethod
1452 1452 def get_default_group_perms(cls, default_user_id):
1453 1453 q = Session().query(UserRepoGroupToPerm, RepoGroup, cls)\
1454 1454 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
1455 1455 .join((cls, UserRepoGroupToPerm.permission_id == cls.permission_id))\
1456 1456 .filter(UserRepoGroupToPerm.user_id == default_user_id)
1457 1457
1458 1458 return q.all()
1459 1459
1460 1460
1461 1461 class UserRepoToPerm(Base, BaseModel):
1462 1462 __tablename__ = 'repo_to_perm'
1463 1463 __table_args__ = (
1464 1464 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
1465 1465 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1466 1466 'mysql_charset': 'utf8'}
1467 1467 )
1468 1468 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1469 1469 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1470 1470 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1471 1471 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1472 1472
1473 1473 user = relationship('User')
1474 1474 repository = relationship('Repository')
1475 1475 permission = relationship('Permission')
1476 1476
1477 1477 @classmethod
1478 1478 def create(cls, user, repository, permission):
1479 1479 n = cls()
1480 1480 n.user = user
1481 1481 n.repository = repository
1482 1482 n.permission = permission
1483 1483 Session().add(n)
1484 1484 return n
1485 1485
1486 1486 def __unicode__(self):
1487 1487 return u'<user:%s => %s >' % (self.user, self.repository)
1488 1488
1489 1489
1490 1490 class UserToPerm(Base, BaseModel):
1491 1491 __tablename__ = 'user_to_perm'
1492 1492 __table_args__ = (
1493 1493 UniqueConstraint('user_id', 'permission_id'),
1494 1494 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1495 1495 'mysql_charset': 'utf8'}
1496 1496 )
1497 1497 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1498 1498 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1499 1499 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1500 1500
1501 1501 user = relationship('User')
1502 1502 permission = relationship('Permission', lazy='joined')
1503 1503
1504 1504
1505 1505 class UserGroupRepoToPerm(Base, BaseModel):
1506 1506 __tablename__ = 'users_group_repo_to_perm'
1507 1507 __table_args__ = (
1508 1508 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
1509 1509 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1510 1510 'mysql_charset': 'utf8'}
1511 1511 )
1512 1512 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1513 1513 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1514 1514 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1515 1515 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1516 1516
1517 1517 users_group = relationship('UserGroup')
1518 1518 permission = relationship('Permission')
1519 1519 repository = relationship('Repository')
1520 1520
1521 1521 @classmethod
1522 1522 def create(cls, users_group, repository, permission):
1523 1523 n = cls()
1524 1524 n.users_group = users_group
1525 1525 n.repository = repository
1526 1526 n.permission = permission
1527 1527 Session().add(n)
1528 1528 return n
1529 1529
1530 1530 def __unicode__(self):
1531 1531 return u'<userGroup:%s => %s >' % (self.users_group, self.repository)
1532 1532
1533 1533
1534 1534 class UserGroupToPerm(Base, BaseModel):
1535 1535 __tablename__ = 'users_group_to_perm'
1536 1536 __table_args__ = (
1537 1537 UniqueConstraint('users_group_id', 'permission_id',),
1538 1538 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1539 1539 'mysql_charset': 'utf8'}
1540 1540 )
1541 1541 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1542 1542 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1543 1543 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1544 1544
1545 1545 users_group = relationship('UserGroup')
1546 1546 permission = relationship('Permission')
1547 1547
1548 1548
1549 1549 class UserRepoGroupToPerm(Base, BaseModel):
1550 1550 __tablename__ = 'user_repo_group_to_perm'
1551 1551 __table_args__ = (
1552 1552 UniqueConstraint('user_id', 'group_id', 'permission_id'),
1553 1553 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1554 1554 'mysql_charset': 'utf8'}
1555 1555 )
1556 1556
1557 1557 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1558 1558 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1559 1559 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
1560 1560 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1561 1561
1562 1562 user = relationship('User')
1563 1563 group = relationship('RepoGroup')
1564 1564 permission = relationship('Permission')
1565 1565
1566 1566
1567 1567 class UserGroupRepoGroupToPerm(Base, BaseModel):
1568 1568 __tablename__ = 'users_group_repo_group_to_perm'
1569 1569 __table_args__ = (
1570 1570 UniqueConstraint('users_group_id', 'group_id'),
1571 1571 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1572 1572 'mysql_charset': 'utf8'}
1573 1573 )
1574 1574
1575 1575 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)
1576 1576 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1577 1577 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
1578 1578 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1579 1579
1580 1580 users_group = relationship('UserGroup')
1581 1581 permission = relationship('Permission')
1582 1582 group = relationship('RepoGroup')
1583 1583
1584 1584
1585 1585 class Statistics(Base, BaseModel):
1586 1586 __tablename__ = 'statistics'
1587 1587 __table_args__ = (
1588 1588 UniqueConstraint('repository_id'),
1589 1589 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1590 1590 'mysql_charset': 'utf8'}
1591 1591 )
1592 1592 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1593 1593 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
1594 1594 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
1595 1595 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
1596 1596 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
1597 1597 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
1598 1598
1599 1599 repository = relationship('Repository', single_parent=True)
1600 1600
1601 1601
1602 1602 class UserFollowing(Base, BaseModel):
1603 1603 __tablename__ = 'user_followings'
1604 1604 __table_args__ = (
1605 1605 UniqueConstraint('user_id', 'follows_repository_id'),
1606 1606 UniqueConstraint('user_id', 'follows_user_id'),
1607 1607 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1608 1608 'mysql_charset': 'utf8'}
1609 1609 )
1610 1610
1611 1611 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1612 1612 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1613 1613 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
1614 1614 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1615 1615 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
1616 1616
1617 1617 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
1618 1618
1619 1619 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
1620 1620 follows_repository = relationship('Repository', order_by='Repository.repo_name')
1621 1621
1622 1622 @classmethod
1623 1623 def get_repo_followers(cls, repo_id):
1624 1624 return cls.query().filter(cls.follows_repo_id == repo_id)
1625 1625
1626 1626
1627 1627 class CacheInvalidation(Base, BaseModel):
1628 1628 __tablename__ = 'cache_invalidation'
1629 1629 __table_args__ = (
1630 1630 UniqueConstraint('cache_key'),
1631 1631 Index('key_idx', 'cache_key'),
1632 1632 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1633 1633 'mysql_charset': 'utf8'},
1634 1634 )
1635 1635 # cache_id, not used
1636 1636 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1637 1637 # cache_key as created by _get_cache_key
1638 1638 cache_key = Column("cache_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1639 1639 # cache_args is usually a repo_name, possibly with _README/_RSS/_ATOM suffix
1640 1640 cache_args = Column("cache_args", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1641 1641 # instance sets cache_active True when it is caching, other instances set cache_active to False to invalidate
1642 1642 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
1643 1643
1644 1644 def __init__(self, cache_key, cache_args=''):
1645 1645 self.cache_key = cache_key
1646 1646 self.cache_args = cache_args
1647 1647 self.cache_active = False
1648 1648
1649 1649 def __unicode__(self):
1650 1650 return u"<%s('%s:%s')>" % (self.__class__.__name__,
1651 1651 self.cache_id, self.cache_key)
1652 1652
1653 1653 def get_prefix(self):
1654 1654 """
1655 1655 Guess prefix that might have been used in _get_cache_key to generate self.cache_key .
1656 1656 Only used for informational purposes in repo_edit.html .
1657 1657 """
1658 1658 _split = self.cache_key.split(self.cache_args, 1)
1659 1659 if len(_split) == 2:
1660 1660 return _split[0]
1661 1661 return ''
1662 1662
1663 1663 @classmethod
1664 1664 def _get_cache_key(cls, key):
1665 1665 """
1666 1666 Wrapper for generating a unique cache key for this instance and "key".
1667 1667 """
1668 1668 import rhodecode
1669 1669 prefix = rhodecode.CONFIG.get('instance_id', '')
1670 1670 return "%s%s" % (prefix, key)
1671 1671
1672 1672 @classmethod
1673 1673 def _get_or_create_inv_obj(cls, key, repo_name, commit=True):
1674 1674 inv_obj = Session().query(cls).filter(cls.cache_key == key).scalar()
1675 1675 if not inv_obj:
1676 1676 try:
1677 1677 inv_obj = CacheInvalidation(key, repo_name)
1678 1678 Session().add(inv_obj)
1679 1679 if commit:
1680 1680 Session().commit()
1681 1681 except Exception:
1682 1682 log.error(traceback.format_exc())
1683 1683 Session().rollback()
1684 1684 return inv_obj
1685 1685
1686 1686 @classmethod
1687 1687 def invalidate(cls, key):
1688 1688 """
1689 1689 Returns Invalidation object if this given key should be invalidated
1690 1690 None otherwise. `cache_active = False` means that this cache
1691 1691 state is not valid and needs to be invalidated
1692 1692
1693 1693 :param key:
1694 1694 """
1695 1695 repo_name = key
1696 1696 repo_name = remove_suffix(repo_name, '_README')
1697 1697 repo_name = remove_suffix(repo_name, '_RSS')
1698 1698 repo_name = remove_suffix(repo_name, '_ATOM')
1699 1699
1700 1700 cache_key = cls._get_cache_key(key)
1701 1701 inv = cls._get_or_create_inv_obj(cache_key, repo_name)
1702 1702
1703 1703 if inv and not inv.cache_active:
1704 1704 return inv
1705 1705
1706 1706 @classmethod
1707 1707 def set_invalidate(cls, key=None, repo_name=None):
1708 1708 """
1709 1709 Mark this Cache key for invalidation, either by key or whole
1710 1710 cache sets based on repo_name
1711 1711
1712 1712 :param key:
1713 1713 """
1714 1714 invalidated_keys = []
1715 1715 if key:
1716 1716 assert not repo_name
1717 1717 cache_key = cls._get_cache_key(key)
1718 1718 inv_objs = Session().query(cls).filter(cls.cache_key == cache_key).all()
1719 1719 else:
1720 1720 assert repo_name
1721 1721 inv_objs = Session().query(cls).filter(cls.cache_args == repo_name).all()
1722 1722
1723 1723 try:
1724 1724 for inv_obj in inv_objs:
1725 1725 inv_obj.cache_active = False
1726 1726 log.debug('marking %s key for invalidation based on key=%s,repo_name=%s'
1727 1727 % (inv_obj, key, safe_str(repo_name)))
1728 1728 invalidated_keys.append(inv_obj.cache_key)
1729 1729 Session().add(inv_obj)
1730 1730 Session().commit()
1731 1731 except Exception:
1732 1732 log.error(traceback.format_exc())
1733 1733 Session().rollback()
1734 1734 return invalidated_keys
1735 1735
1736 1736 @classmethod
1737 1737 def set_valid(cls, key):
1738 1738 """
1739 1739 Mark this cache key as active and currently cached
1740 1740
1741 1741 :param key:
1742 1742 """
1743 1743 inv_obj = cls.query().filter(cls.cache_key == key).scalar()
1744 1744 inv_obj.cache_active = True
1745 1745 Session().add(inv_obj)
1746 1746 Session().commit()
1747 1747
1748 1748 @classmethod
1749 1749 def get_cache_map(cls):
1750 1750
1751 1751 class cachemapdict(dict):
1752 1752
1753 1753 def __init__(self, *args, **kwargs):
1754 1754 self.fixkey = kwargs.pop('fixkey', False)
1755 1755 super(cachemapdict, self).__init__(*args, **kwargs)
1756 1756
1757 1757 def __getattr__(self, name):
1758 1758 cache_key = name
1759 1759 if self.fixkey:
1760 1760 cache_key = cls._get_cache_key(name)
1761 1761 if cache_key in self.__dict__:
1762 1762 return self.__dict__[cache_key]
1763 1763 else:
1764 1764 return self[cache_key]
1765 1765
1766 1766 def __getitem__(self, name):
1767 1767 cache_key = name
1768 1768 if self.fixkey:
1769 1769 cache_key = cls._get_cache_key(name)
1770 1770 try:
1771 1771 return super(cachemapdict, self).__getitem__(cache_key)
1772 1772 except KeyError:
1773 1773 return None
1774 1774
1775 1775 cache_map = cachemapdict(fixkey=True)
1776 1776 for obj in cls.query().all():
1777 1777 cache_map[obj.cache_key] = cachemapdict(obj.get_dict())
1778 1778 return cache_map
1779 1779
1780 1780
1781 1781 class ChangesetComment(Base, BaseModel):
1782 1782 __tablename__ = 'changeset_comments'
1783 1783 __table_args__ = (
1784 1784 Index('cc_revision_idx', 'revision'),
1785 1785 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1786 1786 'mysql_charset': 'utf8'},
1787 1787 )
1788 1788 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
1789 1789 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1790 1790 revision = Column('revision', String(40), nullable=True)
1791 1791 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
1792 1792 line_no = Column('line_no', Unicode(10), nullable=True)
1793 1793 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
1794 1794 f_path = Column('f_path', Unicode(1000), nullable=True)
1795 1795 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
1796 1796 text = Column('text', UnicodeText(25000), nullable=False)
1797 1797 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1798 1798 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1799 1799
1800 1800 author = relationship('User', lazy='joined')
1801 1801 repo = relationship('Repository')
1802 1802 status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan")
1803 1803 pull_request = relationship('PullRequest', lazy='joined')
1804 1804
1805 1805 @classmethod
1806 1806 def get_users(cls, revision=None, pull_request_id=None):
1807 1807 """
1808 1808 Returns user associated with this ChangesetComment. ie those
1809 1809 who actually commented
1810 1810
1811 1811 :param cls:
1812 1812 :param revision:
1813 1813 """
1814 1814 q = Session().query(User)\
1815 1815 .join(ChangesetComment.author)
1816 1816 if revision:
1817 1817 q = q.filter(cls.revision == revision)
1818 1818 elif pull_request_id:
1819 1819 q = q.filter(cls.pull_request_id == pull_request_id)
1820 1820 return q.all()
1821 1821
1822 1822
1823 1823 class ChangesetStatus(Base, BaseModel):
1824 1824 __tablename__ = 'changeset_statuses'
1825 1825 __table_args__ = (
1826 1826 Index('cs_revision_idx', 'revision'),
1827 1827 Index('cs_version_idx', 'version'),
1828 1828 UniqueConstraint('repo_id', 'revision', 'version'),
1829 1829 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1830 1830 'mysql_charset': 'utf8'}
1831 1831 )
1832 1832 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
1833 1833 STATUS_APPROVED = 'approved'
1834 1834 STATUS_REJECTED = 'rejected'
1835 1835 STATUS_UNDER_REVIEW = 'under_review'
1836 1836
1837 1837 STATUSES = [
1838 1838 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
1839 1839 (STATUS_APPROVED, _("Approved")),
1840 1840 (STATUS_REJECTED, _("Rejected")),
1841 1841 (STATUS_UNDER_REVIEW, _("Under Review")),
1842 1842 ]
1843 1843
1844 1844 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
1845 1845 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1846 1846 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
1847 1847 revision = Column('revision', String(40), nullable=False)
1848 1848 status = Column('status', String(128), nullable=False, default=DEFAULT)
1849 1849 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
1850 1850 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
1851 1851 version = Column('version', Integer(), nullable=False, default=0)
1852 1852 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
1853 1853
1854 1854 author = relationship('User', lazy='joined')
1855 1855 repo = relationship('Repository')
1856 1856 comment = relationship('ChangesetComment', lazy='joined')
1857 1857 pull_request = relationship('PullRequest', lazy='joined')
1858 1858
1859 1859 def __unicode__(self):
1860 1860 return u"<%s('%s:%s')>" % (
1861 1861 self.__class__.__name__,
1862 1862 self.status, self.author
1863 1863 )
1864 1864
1865 1865 @classmethod
1866 1866 def get_status_lbl(cls, value):
1867 1867 return dict(cls.STATUSES).get(value)
1868 1868
1869 1869 @property
1870 1870 def status_lbl(self):
1871 1871 return ChangesetStatus.get_status_lbl(self.status)
1872 1872
1873 1873
1874 1874 class PullRequest(Base, BaseModel):
1875 1875 __tablename__ = 'pull_requests'
1876 1876 __table_args__ = (
1877 1877 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1878 1878 'mysql_charset': 'utf8'},
1879 1879 )
1880 1880
1881 1881 STATUS_NEW = u'new'
1882 1882 STATUS_OPEN = u'open'
1883 1883 STATUS_CLOSED = u'closed'
1884 1884
1885 1885 pull_request_id = Column('pull_request_id', Integer(), nullable=False, primary_key=True)
1886 1886 title = Column('title', Unicode(256), nullable=True)
1887 1887 description = Column('description', UnicodeText(10240), nullable=True)
1888 1888 status = Column('status', Unicode(256), nullable=False, default=STATUS_NEW)
1889 1889 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1890 1890 updated_on = Column('updated_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1891 1891 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
1892 1892 _revisions = Column('revisions', UnicodeText(20500)) # 500 revisions max
1893 1893 org_repo_id = Column('org_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1894 1894 org_ref = Column('org_ref', Unicode(256), nullable=False)
1895 1895 other_repo_id = Column('other_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1896 1896 other_ref = Column('other_ref', Unicode(256), nullable=False)
1897 1897
1898 1898 @hybrid_property
1899 1899 def revisions(self):
1900 1900 return self._revisions.split(':')
1901 1901
1902 1902 @revisions.setter
1903 1903 def revisions(self, val):
1904 1904 self._revisions = ':'.join(val)
1905 1905
1906 1906 @property
1907 1907 def org_ref_parts(self):
1908 1908 return self.org_ref.split(':')
1909 1909
1910 1910 @property
1911 1911 def other_ref_parts(self):
1912 1912 return self.other_ref.split(':')
1913 1913
1914 1914 author = relationship('User', lazy='joined')
1915 1915 reviewers = relationship('PullRequestReviewers',
1916 1916 cascade="all, delete, delete-orphan")
1917 1917 org_repo = relationship('Repository', primaryjoin='PullRequest.org_repo_id==Repository.repo_id')
1918 1918 other_repo = relationship('Repository', primaryjoin='PullRequest.other_repo_id==Repository.repo_id')
1919 1919 statuses = relationship('ChangesetStatus')
1920 1920 comments = relationship('ChangesetComment',
1921 1921 cascade="all, delete, delete-orphan")
1922 1922
1923 1923 def is_closed(self):
1924 1924 return self.status == self.STATUS_CLOSED
1925 1925
1926 1926 @property
1927 1927 def last_review_status(self):
1928 1928 return self.statuses[-1].status if self.statuses else ''
1929 1929
1930 1930 def __json__(self):
1931 1931 return dict(
1932 1932 revisions=self.revisions
1933 1933 )
1934 1934
1935 1935
1936 1936 class PullRequestReviewers(Base, BaseModel):
1937 1937 __tablename__ = 'pull_request_reviewers'
1938 1938 __table_args__ = (
1939 1939 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1940 1940 'mysql_charset': 'utf8'},
1941 1941 )
1942 1942
1943 1943 def __init__(self, user=None, pull_request=None):
1944 1944 self.user = user
1945 1945 self.pull_request = pull_request
1946 1946
1947 1947 pull_requests_reviewers_id = Column('pull_requests_reviewers_id', Integer(), nullable=False, primary_key=True)
1948 1948 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=False)
1949 1949 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
1950 1950
1951 1951 user = relationship('User')
1952 1952 pull_request = relationship('PullRequest')
1953 1953
1954 1954
1955 1955 class Notification(Base, BaseModel):
1956 1956 __tablename__ = 'notifications'
1957 1957 __table_args__ = (
1958 1958 Index('notification_type_idx', 'type'),
1959 1959 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1960 1960 'mysql_charset': 'utf8'},
1961 1961 )
1962 1962
1963 1963 TYPE_CHANGESET_COMMENT = u'cs_comment'
1964 1964 TYPE_MESSAGE = u'message'
1965 1965 TYPE_MENTION = u'mention'
1966 1966 TYPE_REGISTRATION = u'registration'
1967 1967 TYPE_PULL_REQUEST = u'pull_request'
1968 1968 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
1969 1969
1970 1970 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
1971 1971 subject = Column('subject', Unicode(512), nullable=True)
1972 1972 body = Column('body', UnicodeText(50000), nullable=True)
1973 1973 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
1974 1974 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1975 1975 type_ = Column('type', Unicode(256))
1976 1976
1977 1977 created_by_user = relationship('User')
1978 1978 notifications_to_users = relationship('UserNotification', lazy='joined',
1979 1979 cascade="all, delete, delete-orphan")
1980 1980
1981 1981 @property
1982 1982 def recipients(self):
1983 1983 return [x.user for x in UserNotification.query()\
1984 1984 .filter(UserNotification.notification == self)\
1985 1985 .order_by(UserNotification.user_id.asc()).all()]
1986 1986
1987 1987 @classmethod
1988 1988 def create(cls, created_by, subject, body, recipients, type_=None):
1989 1989 if type_ is None:
1990 1990 type_ = Notification.TYPE_MESSAGE
1991 1991
1992 1992 notification = cls()
1993 1993 notification.created_by_user = created_by
1994 1994 notification.subject = subject
1995 1995 notification.body = body
1996 1996 notification.type_ = type_
1997 1997 notification.created_on = datetime.datetime.now()
1998 1998
1999 1999 for u in recipients:
2000 2000 assoc = UserNotification()
2001 2001 assoc.notification = notification
2002 2002 u.notifications.append(assoc)
2003 2003 Session().add(notification)
2004 2004 return notification
2005 2005
2006 2006 @property
2007 2007 def description(self):
2008 2008 from rhodecode.model.notification import NotificationModel
2009 2009 return NotificationModel().make_description(self)
2010 2010
2011 2011
2012 2012 class UserNotification(Base, BaseModel):
2013 2013 __tablename__ = 'user_to_notification'
2014 2014 __table_args__ = (
2015 2015 UniqueConstraint('user_id', 'notification_id'),
2016 2016 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2017 2017 'mysql_charset': 'utf8'}
2018 2018 )
2019 2019 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
2020 2020 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
2021 2021 read = Column('read', Boolean, default=False)
2022 2022 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
2023 2023
2024 2024 user = relationship('User', lazy="joined")
2025 2025 notification = relationship('Notification', lazy="joined",
2026 2026 order_by=lambda: Notification.created_on.desc(),)
2027 2027
2028 2028 def mark_as_read(self):
2029 2029 self.read = True
2030 2030 Session().add(self)
2031 2031
2032 2032
2033 2033 class DbMigrateVersion(Base, BaseModel):
2034 2034 __tablename__ = 'db_migrate_version'
2035 2035 __table_args__ = (
2036 2036 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2037 2037 'mysql_charset': 'utf8'},
2038 2038 )
2039 2039 repository_id = Column('repository_id', String(250), primary_key=True)
2040 2040 repository_path = Column('repository_path', Text)
2041 2041 version = Column('version', Integer)
2042
@@ -1,26 +1,26 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.model.db_1_7_0
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 Database Models for RhodeCode <=1.7.X
7 7
8 8 :created_on: Apr 08, 2013
9 9 :author: marcink
10 10 :copyright: (C) 2010-2013 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software: you can redistribute it and/or modify
14 14 # it under the terms of the GNU General Public License as published by
15 15 # the Free Software Foundation, either version 3 of the License, or
16 16 # (at your option) any later version.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 25
26 from rhodecode.model.db import * No newline at end of file
26 from rhodecode.model.db import *
General Comments 0
You need to be logged in to leave comments. Login now