##// END OF EJS Templates
removed users_group controller in replace for model methods,...
marcink -
r1436:88d13c1c beta
parent child Browse files
Show More
@@ -1,216 +1,214 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.controllers.admin.users_groups
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 Users Groups crud controller for pylons
7 7
8 8 :created_on: Jan 25, 2011
9 9 :author: marcink
10 10 :copyright: (C) 2009-2011 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.exceptions import UsersGroupsAssignedException
36 36 from rhodecode.lib import helpers as h
37 37 from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator
38 38 from rhodecode.lib.base import BaseController, render
39 39
40 40 from rhodecode.model.db import User, UsersGroup, Permission, UsersGroupToPerm
41 41 from rhodecode.model.forms import UserForm, UsersGroupForm
42 from rhodecode.model.users_group import UsersGroupModel
43 42
44 43 log = logging.getLogger(__name__)
45 44
46 45
47 46 class UsersGroupsController(BaseController):
48 47 """REST Controller styled on the Atom Publishing Protocol"""
49 48 # To properly map this controller, ensure your config/routing.py
50 49 # file has a resource setup:
51 50 # map.resource('users_group', 'users_groups')
52 51
53 52 @LoginRequired()
54 53 @HasPermissionAllDecorator('hg.admin')
55 54 def __before__(self):
56 55 c.admin_user = session.get('admin_user')
57 56 c.admin_username = session.get('admin_username')
58 57 super(UsersGroupsController, self).__before__()
59 58 c.available_permissions = config['available_permissions']
60 59
61 60 def index(self, format='html'):
62 61 """GET /users_groups: All items in the collection"""
63 62 # url('users_groups')
64 63 c.users_groups_list = self.sa.query(UsersGroup).all()
65 64 return render('admin/users_groups/users_groups.html')
66 65
67 66 def create(self):
68 67 """POST /users_groups: Create a new item"""
69 68 # url('users_groups')
70 users_group_model = UsersGroupModel()
69
71 70 users_group_form = UsersGroupForm()()
72 71 try:
73 72 form_result = users_group_form.to_python(dict(request.POST))
74 users_group_model.create(form_result)
73 UsersGroup.create(form_result)
75 74 h.flash(_('created users group %s') \
76 75 % form_result['users_group_name'], category='success')
77 76 #action_logger(self.rhodecode_user, 'new_user', '', '', self.sa)
78 77 except formencode.Invalid, errors:
79 78 return htmlfill.render(
80 79 render('admin/users_groups/users_group_add.html'),
81 80 defaults=errors.value,
82 81 errors=errors.error_dict or {},
83 82 prefix_error=False,
84 83 encoding="UTF-8")
85 84 except Exception:
86 85 log.error(traceback.format_exc())
87 86 h.flash(_('error occurred during creation of users group %s') \
88 87 % request.POST.get('users_group_name'), category='error')
89 88
90 89 return redirect(url('users_groups'))
91 90
92 91 def new(self, format='html'):
93 92 """GET /users_groups/new: Form to create a new item"""
94 93 # url('new_users_group')
95 94 return render('admin/users_groups/users_group_add.html')
96 95
97 96 def update(self, id):
98 97 """PUT /users_groups/id: Update an existing item"""
99 98 # Forms posted to this method should contain a hidden field:
100 99 # <input type="hidden" name="_method" value="PUT" />
101 100 # Or using helpers:
102 101 # h.form(url('users_group', id=ID),
103 102 # method='put')
104 103 # url('users_group', id=ID)
105 104
106 users_group_model = UsersGroupModel()
107 c.users_group = users_group_model.get(id)
105 c.users_group = UsersGroup.get(id)
108 106 c.group_members = [(x.user_id, x.user.username) for x in
109 107 c.users_group.members]
110 108
111 109 c.available_members = [(x.user_id, x.username) for x in
112 110 self.sa.query(User).all()]
113 111 users_group_form = UsersGroupForm(edit=True,
114 112 old_data=c.users_group.get_dict(),
115 113 available_members=[str(x[0]) for x
116 114 in c.available_members])()
117 115
118 116 try:
119 117 form_result = users_group_form.to_python(request.POST)
120 users_group_model.update(id, form_result)
118 UsersGroup.update(id, form_result)
121 119 h.flash(_('updated users group %s') \
122 120 % form_result['users_group_name'],
123 121 category='success')
124 122 #action_logger(self.rhodecode_user, 'new_user', '', '', self.sa)
125 123 except formencode.Invalid, errors:
126 124 e = errors.error_dict or {}
127 125
128 126 perm = Permission.get_by_key('hg.create.repository')
129 127 e.update({'create_repo_perm':
130 128 UsersGroupToPerm.has_perm(id, perm)})
131 129
132 130 return htmlfill.render(
133 131 render('admin/users_groups/users_group_edit.html'),
134 132 defaults=errors.value,
135 133 errors=e,
136 134 prefix_error=False,
137 135 encoding="UTF-8")
138 136 except Exception:
139 137 log.error(traceback.format_exc())
140 138 h.flash(_('error occurred during update of users group %s') \
141 139 % request.POST.get('users_group_name'), category='error')
142 140
143 141 return redirect(url('users_groups'))
144 142
145 143 def delete(self, id):
146 144 """DELETE /users_groups/id: Delete an existing item"""
147 145 # Forms posted to this method should contain a hidden field:
148 146 # <input type="hidden" name="_method" value="DELETE" />
149 147 # Or using helpers:
150 148 # h.form(url('users_group', id=ID),
151 149 # method='delete')
152 150 # url('users_group', id=ID)
153 users_group_model = UsersGroupModel()
151
154 152 try:
155 users_group_model.delete(id)
153 UsersGroup.delete(id)
156 154 h.flash(_('successfully deleted users group'), category='success')
157 155 except UsersGroupsAssignedException, e:
158 156 h.flash(e, category='error')
159 157 except Exception:
160 158 h.flash(_('An error occurred during deletion of users group'),
161 159 category='error')
162 160 return redirect(url('users_groups'))
163 161
164 162 def show(self, id, format='html'):
165 163 """GET /users_groups/id: Show a specific item"""
166 164 # url('users_group', id=ID)
167 165
168 166 def edit(self, id, format='html'):
169 167 """GET /users_groups/id/edit: Form to edit an existing item"""
170 168 # url('edit_users_group', id=ID)
171 169
172 170 c.users_group = self.sa.query(UsersGroup).get(id)
173 171 if not c.users_group:
174 172 return redirect(url('users_groups'))
175 173
176 174 c.users_group.permissions = {}
177 175 c.group_members = [(x.user_id, x.user.username) for x in
178 176 c.users_group.members]
179 177 c.available_members = [(x.user_id, x.username) for x in
180 178 self.sa.query(User).all()]
181 179 defaults = c.users_group.get_dict()
182 180 perm = Permission.get_by_key('hg.create.repository')
183 181 defaults.update({'create_repo_perm':
184 182 UsersGroupToPerm.has_perm(id, perm)})
185 183 return htmlfill.render(
186 184 render('admin/users_groups/users_group_edit.html'),
187 185 defaults=defaults,
188 186 encoding="UTF-8",
189 187 force_defaults=False
190 188 )
191 189
192 190 def update_perm(self, id):
193 191 """PUT /users_perm/id: Update an existing item"""
194 192 # url('users_group_perm', id=ID, method='put')
195 193
196 194 grant_perm = request.POST.get('create_repo_perm', False)
197 195
198 196 if grant_perm:
199 197 perm = Permission.get_by_key('hg.create.none')
200 198 UsersGroupToPerm.revoke_perm(id, perm)
201 199
202 200 perm = Permission.get_by_key('hg.create.repository')
203 201 UsersGroupToPerm.grant_perm(id, perm)
204 202 h.flash(_("Granted 'repository create' permission to user"),
205 203 category='success')
206 204
207 205 else:
208 206 perm = Permission.get_by_key('hg.create.repository')
209 207 UsersGroupToPerm.revoke_perm(id, perm)
210 208
211 209 perm = Permission.get_by_key('hg.create.none')
212 210 UsersGroupToPerm.grant_perm(id, perm)
213 211 h.flash(_("Revoked 'repository create' permission to user"),
214 212 category='success')
215 213
216 214 return redirect(url('edit_users_group', id=id))
@@ -1,803 +1,875 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.model.db
4 4 ~~~~~~~~~~~~~~~~~~
5 5
6 6 Database Models for RhodeCode
7 7
8 8 :created_on: Apr 08, 2010
9 9 :author: marcink
10 10 :copyright: (C) 2009-2011 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 from datetime import date
31 31
32 32 from sqlalchemy import *
33 33 from sqlalchemy.exc import DatabaseError
34 34 from sqlalchemy.orm import relationship, backref, joinedload, class_mapper
35 35 from sqlalchemy.orm.interfaces import MapperExtension
36 36
37 37 from beaker.cache import cache_region, region_invalidate
38 38
39 39 from vcs import get_backend
40 40 from vcs.utils.helpers import get_scm
41 41 from vcs.exceptions import RepositoryError, VCSError
42 42 from vcs.utils.lazy import LazyProperty
43 43 from vcs.nodes import FileNode
44 44
45 from rhodecode.lib.exceptions import UsersGroupsAssignedException
45 46 from rhodecode.lib import str2bool, json, safe_str
46 47 from rhodecode.model.meta import Base, Session
47 48 from rhodecode.model.caching_query import FromCache
48 49
49 50 log = logging.getLogger(__name__)
50 51
51 52 #==============================================================================
52 53 # BASE CLASSES
53 54 #==============================================================================
54 55
55 56 class ModelSerializer(json.JSONEncoder):
56 57 """
57 58 Simple Serializer for JSON,
58 59
59 60 usage::
60 61
61 62 to make object customized for serialization implement a __json__
62 63 method that will return a dict for serialization into json
63 64
64 65 example::
65 66
66 67 class Task(object):
67 68
68 69 def __init__(self, name, value):
69 70 self.name = name
70 71 self.value = value
71 72
72 73 def __json__(self):
73 74 return dict(name=self.name,
74 75 value=self.value)
75 76
76 77 """
77 78
78 79 def default(self, obj):
79 80
80 81 if hasattr(obj, '__json__'):
81 82 return obj.__json__()
82 83 else:
83 84 return json.JSONEncoder.default(self, obj)
84 85
85 86 class BaseModel(object):
86 87 """Base Model for all classess
87 88
88 89 """
89 90
90 91 @classmethod
91 92 def _get_keys(cls):
92 93 """return column names for this model """
93 94 return class_mapper(cls).c.keys()
94 95
95 96 def get_dict(self):
96 97 """return dict with keys and values corresponding
97 98 to this model data """
98 99
99 100 d = {}
100 101 for k in self._get_keys():
101 102 d[k] = getattr(self, k)
102 103 return d
103 104
104 105 def get_appstruct(self):
105 106 """return list with keys and values tupples corresponding
106 107 to this model data """
107 108
108 109 l = []
109 110 for k in self._get_keys():
110 111 l.append((k, getattr(self, k),))
111 112 return l
112 113
113 114 def populate_obj(self, populate_dict):
114 115 """populate model with data from given populate_dict"""
115 116
116 117 for k in self._get_keys():
117 118 if k in populate_dict:
118 119 setattr(self, k, populate_dict[k])
119 120
120 121 @classmethod
121 122 def query(cls):
122 123 return Session.query(cls)
123 124
124 125 @classmethod
125 126 def get(cls, id_):
126 127 return Session.query(cls).get(id_)
127 128
128 129
129 130 class RhodeCodeSettings(Base, BaseModel):
130 131 __tablename__ = 'rhodecode_settings'
131 132 __table_args__ = (UniqueConstraint('app_settings_name'), {'extend_existing':True})
132 133 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
133 134 app_settings_name = Column("app_settings_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
134 135 app_settings_value = Column("app_settings_value", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
135 136
136 137 def __init__(self, k='', v=''):
137 138 self.app_settings_name = k
138 139 self.app_settings_value = v
139 140
140 141 def __repr__(self):
141 142 return "<%s('%s:%s')>" % (self.__class__.__name__,
142 143 self.app_settings_name, self.app_settings_value)
143 144
144 145
145 146 @classmethod
146 147 def get_by_name(cls, ldap_key):
147 148 return Session.query(cls)\
148 149 .filter(cls.app_settings_name == ldap_key).scalar()
149 150
150 151 @classmethod
151 152 def get_app_settings(cls, cache=False):
152 153
153 154 ret = Session.query(cls)
154 155
155 156 if cache:
156 157 ret = ret.options(FromCache("sql_cache_short", "get_hg_settings"))
157 158
158 159 if not ret:
159 160 raise Exception('Could not get application settings !')
160 161 settings = {}
161 162 for each in ret:
162 163 settings['rhodecode_' + each.app_settings_name] = \
163 164 each.app_settings_value
164 165
165 166 return settings
166 167
167 168 @classmethod
168 169 def get_ldap_settings(cls, cache=False):
169 170 ret = Session.query(cls)\
170 171 .filter(cls.app_settings_name.startswith('ldap_'))\
171 172 .all()
172 173 fd = {}
173 174 for row in ret:
174 175 fd.update({row.app_settings_name:row.app_settings_value})
175 176
176 177 fd.update({'ldap_active':str2bool(fd.get('ldap_active'))})
177 178
178 179 return fd
179 180
180 181
181 182 class RhodeCodeUi(Base, BaseModel):
182 183 __tablename__ = 'rhodecode_ui'
183 184 __table_args__ = {'extend_existing':True}
184 185 ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
185 186 ui_section = Column("ui_section", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
186 187 ui_key = Column("ui_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
187 188 ui_value = Column("ui_value", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
188 189 ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True)
189 190
190 191
191 192 @classmethod
192 193 def get_by_key(cls, key):
193 194 return Session.query(cls).filter(cls.ui_key == key)
194 195
195 196
196 197 class User(Base, BaseModel):
197 198 __tablename__ = 'users'
198 199 __table_args__ = (UniqueConstraint('username'), UniqueConstraint('email'), {'extend_existing':True})
199 200 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
200 201 username = Column("username", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
201 202 password = Column("password", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
202 203 active = Column("active", Boolean(), nullable=True, unique=None, default=None)
203 204 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
204 205 name = Column("name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
205 206 lastname = Column("lastname", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
206 207 email = Column("email", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
207 208 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
208 209 ldap_dn = Column("ldap_dn", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
209 210 api_key = Column("api_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
210 211
211 212 user_log = relationship('UserLog', cascade='all')
212 213 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
213 214
214 215 repositories = relationship('Repository')
215 216 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
216 217 repo_to_perm = relationship('RepoToPerm', primaryjoin='RepoToPerm.user_id==User.user_id', cascade='all')
217 218
218 219 group_member = relationship('UsersGroupMember', cascade='all')
219 220
220 221 @property
221 222 def full_contact(self):
222 223 return '%s %s <%s>' % (self.name, self.lastname, self.email)
223 224
224 225 @property
225 226 def short_contact(self):
226 227 return '%s %s' % (self.name, self.lastname)
227 228
228 229 @property
229 230 def is_admin(self):
230 231 return self.admin
231 232
232 233 def __repr__(self):
233 234 try:
234 235 return "<%s('id:%s:%s')>" % (self.__class__.__name__,
235 236 self.user_id, self.username)
236 237 except:
237 238 return self.__class__.__name__
238 239
239 240 @classmethod
240 241 def by_username(cls, username, case_insensitive=False):
241 242 if case_insensitive:
242 243 return Session.query(cls).filter(cls.username.like(username)).one()
243 244 else:
244 245 return Session.query(cls).filter(cls.username == username).one()
245 246
246 247 @classmethod
247 248 def get_by_api_key(cls, api_key):
248 249 return Session.query(cls).filter(cls.api_key == api_key).one()
249 250
250 251
251 252 def update_lastlogin(self):
252 253 """Update user lastlogin"""
253 254
254 255 self.last_login = datetime.datetime.now()
255 256 Session.add(self)
256 257 Session.commit()
257 258 log.debug('updated user %s lastlogin', self.username)
258 259
259 260
260 261 class UserLog(Base, BaseModel):
261 262 __tablename__ = 'user_logs'
262 263 __table_args__ = {'extend_existing':True}
263 264 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
264 265 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
265 266 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
266 267 repository_name = Column("repository_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
267 268 user_ip = Column("user_ip", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
268 269 action = Column("action", UnicodeText(length=1200000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
269 270 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
270 271
271 272 @property
272 273 def action_as_day(self):
273 274 return date(*self.action_date.timetuple()[:3])
274 275
275 276 user = relationship('User')
276 277 repository = relationship('Repository')
277 278
278 279
279 280 class UsersGroup(Base, BaseModel):
280 281 __tablename__ = 'users_groups'
281 282 __table_args__ = {'extend_existing':True}
282 283
283 284 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
284 285 users_group_name = Column("users_group_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
285 286 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
286 287
287 288 members = relationship('UsersGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
288 289
289 290 def __repr__(self):
290 291 return '<userGroup(%s)>' % (self.users_group_name)
291 292
292 293 @classmethod
293 294 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
294 295 if case_insensitive:
295 296 gr = Session.query(cls)\
296 297 .filter(cls.users_group_name.ilike(group_name))
297 298 else:
298 299 gr = Session.query(UsersGroup)\
299 300 .filter(UsersGroup.users_group_name == group_name)
300 301 if cache:
301 302 gr = gr.options(FromCache("sql_cache_short",
302 303 "get_user_%s" % group_name))
303 304 return gr.scalar()
304 305
306
307 @classmethod
308 def get(cls, users_group_id, cache=False):
309 users_group = Session.query(cls)
310 if cache:
311 users_group = users_group.options(FromCache("sql_cache_short",
312 "get_users_group_%s" % users_group_id))
313 return users_group.get(users_group_id)
314
315 @classmethod
316 def create(cls, form_data):
317 try:
318 new_users_group = cls()
319 for k, v in form_data.items():
320 setattr(new_users_group, k, v)
321
322 Session.add(new_users_group)
323 Session.commit()
324 except:
325 log.error(traceback.format_exc())
326 Session.rollback()
327 raise
328
329 @classmethod
330 def update(cls, users_group_id, form_data):
331
332 try:
333 users_group = cls.get(users_group_id, cache=False)
334
335 for k, v in form_data.items():
336 if k == 'users_group_members':
337 users_group.members = []
338 Session.flush()
339 members_list = []
340 if v:
341 for u_id in set(v):
342 members_list.append(UsersGroupMember(
343 users_group_id,
344 u_id))
345 setattr(users_group, 'members', members_list)
346 setattr(users_group, k, v)
347
348 Session.add(users_group)
349 Session.commit()
350 except:
351 log.error(traceback.format_exc())
352 Session.rollback()
353 raise
354
355 @classmethod
356 def delete(cls, users_group_id):
357 try:
358
359 # check if this group is not assigned to repo
360 assigned_groups = UsersGroupRepoToPerm.query()\
361 .filter(UsersGroupRepoToPerm.users_group_id ==
362 users_group_id).all()
363
364 if assigned_groups:
365 raise UsersGroupsAssignedException('Group assigned to %s' %
366 assigned_groups)
367
368 users_group = cls.get(users_group_id, cache=False)
369 Session.delete(users_group)
370 Session.commit()
371 except:
372 log.error(traceback.format_exc())
373 Session.rollback()
374 raise
375
376
305 377 class UsersGroupMember(Base, BaseModel):
306 378 __tablename__ = 'users_groups_members'
307 379 __table_args__ = {'extend_existing':True}
308 380
309 381 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
310 382 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
311 383 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
312 384
313 385 user = relationship('User', lazy='joined')
314 386 users_group = relationship('UsersGroup')
315 387
316 388 def __init__(self, gr_id='', u_id=''):
317 389 self.users_group_id = gr_id
318 390 self.user_id = u_id
319 391
320 392 class Repository(Base, BaseModel):
321 393 __tablename__ = 'repositories'
322 394 __table_args__ = (UniqueConstraint('repo_name'), {'extend_existing':True},)
323 395
324 396 repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
325 397 repo_name = Column("repo_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
326 398 clone_uri = Column("clone_uri", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
327 399 repo_type = Column("repo_type", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default='hg')
328 400 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
329 401 private = Column("private", Boolean(), nullable=True, unique=None, default=None)
330 402 enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True)
331 403 enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True)
332 404 description = Column("description", String(length=10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
333 405 created_on = Column('created_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
334 406
335 407 fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None)
336 408 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None)
337 409
338 410
339 411 user = relationship('User')
340 412 fork = relationship('Repository', remote_side=repo_id)
341 413 group = relationship('Group')
342 414 repo_to_perm = relationship('RepoToPerm', cascade='all', order_by='RepoToPerm.repo_to_perm_id')
343 415 users_group_to_perm = relationship('UsersGroupRepoToPerm', cascade='all')
344 416 stats = relationship('Statistics', cascade='all', uselist=False)
345 417
346 418 followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id', cascade='all')
347 419
348 420 logs = relationship('UserLog', cascade='all')
349 421
350 422 def __repr__(self):
351 423 return "<%s('%s:%s')>" % (self.__class__.__name__,
352 424 self.repo_id, self.repo_name)
353 425
354 426 @classmethod
355 427 def by_repo_name(cls, repo_name):
356 428 q = Session.query(cls).filter(cls.repo_name == repo_name)
357 429
358 430 q = q.options(joinedload(Repository.fork))\
359 431 .options(joinedload(Repository.user))\
360 432 .options(joinedload(Repository.group))\
361 433
362 434 return q.one()
363 435
364 436 @classmethod
365 437 def get_repo_forks(cls, repo_id):
366 438 return Session.query(cls).filter(Repository.fork_id == repo_id)
367 439
368 440 @property
369 441 def just_name(self):
370 442 return self.repo_name.split(os.sep)[-1]
371 443
372 444 @property
373 445 def groups_with_parents(self):
374 446 groups = []
375 447 if self.group is None:
376 448 return groups
377 449
378 450 cur_gr = self.group
379 451 groups.insert(0, cur_gr)
380 452 while 1:
381 453 gr = getattr(cur_gr, 'parent_group', None)
382 454 cur_gr = cur_gr.parent_group
383 455 if gr is None:
384 456 break
385 457 groups.insert(0, gr)
386 458
387 459 return groups
388 460
389 461 @property
390 462 def groups_and_repo(self):
391 463 return self.groups_with_parents, self.just_name
392 464
393 465 @LazyProperty
394 466 def repo_path(self):
395 467 """
396 468 Returns base full path for that repository means where it actually
397 469 exists on a filesystem
398 470 """
399 471 q = Session.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key == '/')
400 472 q.options(FromCache("sql_cache_short", "repository_repo_path"))
401 473 return q.one().ui_value
402 474
403 475 @property
404 476 def repo_full_path(self):
405 477 p = [self.repo_path]
406 478 # we need to split the name by / since this is how we store the
407 479 # names in the database, but that eventually needs to be converted
408 480 # into a valid system path
409 481 p += self.repo_name.split('/')
410 482 return os.path.join(*p)
411 483
412 484 @property
413 485 def _ui(self):
414 486 """
415 487 Creates an db based ui object for this repository
416 488 """
417 489 from mercurial import ui
418 490 from mercurial import config
419 491 baseui = ui.ui()
420 492
421 493 #clean the baseui object
422 494 baseui._ocfg = config.config()
423 495 baseui._ucfg = config.config()
424 496 baseui._tcfg = config.config()
425 497
426 498
427 499 ret = Session.query(RhodeCodeUi)\
428 500 .options(FromCache("sql_cache_short",
429 501 "repository_repo_ui")).all()
430 502
431 503 hg_ui = ret
432 504 for ui_ in hg_ui:
433 505 if ui_.ui_active:
434 506 log.debug('settings ui from db[%s]%s:%s', ui_.ui_section,
435 507 ui_.ui_key, ui_.ui_value)
436 508 baseui.setconfig(ui_.ui_section, ui_.ui_key, ui_.ui_value)
437 509
438 510 return baseui
439 511
440 512 #==========================================================================
441 513 # SCM CACHE INSTANCE
442 514 #==========================================================================
443 515
444 516 @property
445 517 def invalidate(self):
446 518 """
447 519 Returns Invalidation object if this repo should be invalidated
448 520 None otherwise. `cache_active = False` means that this cache
449 521 state is not valid and needs to be invalidated
450 522 """
451 523 return Session.query(CacheInvalidation)\
452 524 .filter(CacheInvalidation.cache_key == self.repo_name)\
453 525 .filter(CacheInvalidation.cache_active == False)\
454 526 .scalar()
455 527
456 528 def set_invalidate(self):
457 529 """
458 530 set a cache for invalidation for this instance
459 531 """
460 532 inv = Session.query(CacheInvalidation)\
461 533 .filter(CacheInvalidation.cache_key == self.repo_name)\
462 534 .scalar()
463 535
464 536 if inv is None:
465 537 inv = CacheInvalidation(self.repo_name)
466 538 inv.cache_active = True
467 539 Session.add(inv)
468 540 Session.commit()
469 541
470 542 @property
471 543 def scm_instance(self):
472 544 return self.__get_instance()
473 545
474 546 @property
475 547 def scm_instance_cached(self):
476 548 @cache_region('long_term')
477 549 def _c(repo_name):
478 550 return self.__get_instance()
479 551
480 552 # TODO: remove this trick when beaker 1.6 is released
481 553 # and have fixed this issue with not supporting unicode keys
482 554 rn = safe_str(self.repo_name)
483 555
484 556 inv = self.invalidate
485 557 if inv is not None:
486 558 region_invalidate(_c, None, rn)
487 559 # update our cache
488 560 inv.cache_active = True
489 561 Session.add(inv)
490 562 Session.commit()
491 563
492 564 return _c(rn)
493 565
494 566 def __get_instance(self):
495 567
496 568 repo_full_path = self.repo_full_path
497 569
498 570 try:
499 571 alias = get_scm(repo_full_path)[0]
500 572 log.debug('Creating instance of %s repository', alias)
501 573 backend = get_backend(alias)
502 574 except VCSError:
503 575 log.error(traceback.format_exc())
504 576 log.error('Perhaps this repository is in db and not in '
505 577 'filesystem run rescan repositories with '
506 578 '"destroy old data " option from admin panel')
507 579 return
508 580
509 581 if alias == 'hg':
510 582
511 583 repo = backend(safe_str(repo_full_path), create=False,
512 584 baseui=self._ui)
513 585 #skip hidden web repository
514 586 if repo._get_hidden():
515 587 return
516 588 else:
517 589 repo = backend(repo_full_path, create=False)
518 590
519 591 return repo
520 592
521 593
522 594 class Group(Base, BaseModel):
523 595 __tablename__ = 'groups'
524 596 __table_args__ = (UniqueConstraint('group_name', 'group_parent_id'),
525 597 CheckConstraint('group_id != group_parent_id'), {'extend_existing':True},)
526 598 __mapper_args__ = {'order_by':'group_name'}
527 599
528 600 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
529 601 group_name = Column("group_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
530 602 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
531 603 group_description = Column("group_description", String(length=10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
532 604
533 605 parent_group = relationship('Group', remote_side=group_id)
534 606
535 607
536 608 def __init__(self, group_name='', parent_group=None):
537 609 self.group_name = group_name
538 610 self.parent_group = parent_group
539 611
540 612 def __repr__(self):
541 613 return "<%s('%s:%s')>" % (self.__class__.__name__, self.group_id,
542 614 self.group_name)
543 615
544 616 @classmethod
545 617 def url_sep(cls):
546 618 return '/'
547 619
548 620 @property
549 621 def parents(self):
550 622 parents_recursion_limit = 5
551 623 groups = []
552 624 if self.parent_group is None:
553 625 return groups
554 626 cur_gr = self.parent_group
555 627 groups.insert(0, cur_gr)
556 628 cnt = 0
557 629 while 1:
558 630 cnt += 1
559 631 gr = getattr(cur_gr, 'parent_group', None)
560 632 cur_gr = cur_gr.parent_group
561 633 if gr is None:
562 634 break
563 635 if cnt == parents_recursion_limit:
564 636 # this will prevent accidental infinit loops
565 637 log.error('group nested more than %s' %
566 638 parents_recursion_limit)
567 639 break
568 640
569 641 groups.insert(0, gr)
570 642 return groups
571 643
572 644 @property
573 645 def children(self):
574 646 return Session.query(Group).filter(Group.parent_group == self)
575 647
576 648 @property
577 649 def full_path(self):
578 650 return Group.url_sep().join([g.group_name for g in self.parents] +
579 651 [self.group_name])
580 652
581 653 @property
582 654 def repositories(self):
583 655 return Session.query(Repository).filter(Repository.group == self)
584 656
585 657 @property
586 658 def repositories_recursive_count(self):
587 659 cnt = self.repositories.count()
588 660
589 661 def children_count(group):
590 662 cnt = 0
591 663 for child in group.children:
592 664 cnt += child.repositories.count()
593 665 cnt += children_count(child)
594 666 return cnt
595 667
596 668 return cnt + children_count(self)
597 669
598 670 class Permission(Base, BaseModel):
599 671 __tablename__ = 'permissions'
600 672 __table_args__ = {'extend_existing':True}
601 673 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
602 674 permission_name = Column("permission_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
603 675 permission_longname = Column("permission_longname", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
604 676
605 677 def __repr__(self):
606 678 return "<%s('%s:%s')>" % (self.__class__.__name__,
607 679 self.permission_id, self.permission_name)
608 680
609 681 @classmethod
610 682 def get_by_key(cls, key):
611 683 return Session.query(cls).filter(cls.permission_name == key).scalar()
612 684
613 685 class RepoToPerm(Base, BaseModel):
614 686 __tablename__ = 'repo_to_perm'
615 687 __table_args__ = (UniqueConstraint('user_id', 'repository_id'), {'extend_existing':True})
616 688 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
617 689 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
618 690 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
619 691 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
620 692
621 693 user = relationship('User')
622 694 permission = relationship('Permission')
623 695 repository = relationship('Repository')
624 696
625 697 class UserToPerm(Base, BaseModel):
626 698 __tablename__ = 'user_to_perm'
627 699 __table_args__ = (UniqueConstraint('user_id', 'permission_id'), {'extend_existing':True})
628 700 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
629 701 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
630 702 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
631 703
632 704 user = relationship('User')
633 705 permission = relationship('Permission')
634 706
635 707 @classmethod
636 708 def has_perm(cls, user_id, perm):
637 709 if not isinstance(perm, Permission):
638 710 raise Exception('perm needs to be an instance of Permission class')
639 711
640 712 return Session.query(cls).filter(cls.user_id == user_id)\
641 713 .filter(cls.permission == perm).scalar() is not None
642 714
643 715 @classmethod
644 716 def grant_perm(cls, user_id, perm):
645 717 if not isinstance(perm, Permission):
646 718 raise Exception('perm needs to be an instance of Permission class')
647 719
648 720 new = cls()
649 721 new.user_id = user_id
650 722 new.permission = perm
651 723 try:
652 724 Session.add(new)
653 725 Session.commit()
654 726 except:
655 727 Session.rollback()
656 728
657 729
658 730 @classmethod
659 731 def revoke_perm(cls, user_id, perm):
660 732 if not isinstance(perm, Permission):
661 733 raise Exception('perm needs to be an instance of Permission class')
662 734
663 735 try:
664 736 Session.query(cls).filter(cls.user_id == user_id)\
665 737 .filter(cls.permission == perm).delete()
666 738 Session.commit()
667 739 except:
668 740 Session.rollback()
669 741
670 742 class UsersGroupRepoToPerm(Base, BaseModel):
671 743 __tablename__ = 'users_group_repo_to_perm'
672 744 __table_args__ = (UniqueConstraint('repository_id', 'users_group_id', 'permission_id'), {'extend_existing':True})
673 745 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
674 746 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
675 747 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
676 748 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
677 749
678 750 users_group = relationship('UsersGroup')
679 751 permission = relationship('Permission')
680 752 repository = relationship('Repository')
681 753
682 754 def __repr__(self):
683 755 return '<userGroup:%s => %s >' % (self.users_group, self.repository)
684 756
685 757 class UsersGroupToPerm(Base, BaseModel):
686 758 __tablename__ = 'users_group_to_perm'
687 759 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
688 760 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
689 761 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
690 762
691 763 users_group = relationship('UsersGroup')
692 764 permission = relationship('Permission')
693 765
694 766
695 767 @classmethod
696 768 def has_perm(cls, users_group_id, perm):
697 769 if not isinstance(perm, Permission):
698 770 raise Exception('perm needs to be an instance of Permission class')
699 771
700 772 return Session.query(cls).filter(cls.users_group_id ==
701 773 users_group_id)\
702 774 .filter(cls.permission == perm)\
703 775 .scalar() is not None
704 776
705 777 @classmethod
706 778 def grant_perm(cls, users_group_id, perm):
707 779 if not isinstance(perm, Permission):
708 780 raise Exception('perm needs to be an instance of Permission class')
709 781
710 782 new = cls()
711 783 new.users_group_id = users_group_id
712 784 new.permission = perm
713 785 try:
714 786 Session.add(new)
715 787 Session.commit()
716 788 except:
717 789 Session.rollback()
718 790
719 791
720 792 @classmethod
721 793 def revoke_perm(cls, users_group_id, perm):
722 794 if not isinstance(perm, Permission):
723 795 raise Exception('perm needs to be an instance of Permission class')
724 796
725 797 try:
726 798 Session.query(cls).filter(cls.users_group_id == users_group_id)\
727 799 .filter(cls.permission == perm).delete()
728 800 Session.commit()
729 801 except:
730 802 Session.rollback()
731 803
732 804
733 805 class GroupToPerm(Base, BaseModel):
734 806 __tablename__ = 'group_to_perm'
735 807 __table_args__ = (UniqueConstraint('group_id', 'permission_id'), {'extend_existing':True})
736 808
737 809 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
738 810 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
739 811 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
740 812 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
741 813
742 814 user = relationship('User')
743 815 permission = relationship('Permission')
744 816 group = relationship('Group')
745 817
746 818 class Statistics(Base, BaseModel):
747 819 __tablename__ = 'statistics'
748 820 __table_args__ = (UniqueConstraint('repository_id'), {'extend_existing':True})
749 821 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
750 822 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
751 823 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
752 824 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
753 825 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
754 826 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
755 827
756 828 repository = relationship('Repository', single_parent=True)
757 829
758 830 class UserFollowing(Base, BaseModel):
759 831 __tablename__ = 'user_followings'
760 832 __table_args__ = (UniqueConstraint('user_id', 'follows_repository_id'),
761 833 UniqueConstraint('user_id', 'follows_user_id')
762 834 , {'extend_existing':True})
763 835
764 836 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
765 837 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
766 838 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
767 839 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
768 840 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
769 841
770 842 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
771 843
772 844 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
773 845 follows_repository = relationship('Repository', order_by='Repository.repo_name')
774 846
775 847
776 848 @classmethod
777 849 def get_repo_followers(cls, repo_id):
778 850 return Session.query(cls).filter(cls.follows_repo_id == repo_id)
779 851
780 852 class CacheInvalidation(Base, BaseModel):
781 853 __tablename__ = 'cache_invalidation'
782 854 __table_args__ = (UniqueConstraint('cache_key'), {'extend_existing':True})
783 855 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
784 856 cache_key = Column("cache_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
785 857 cache_args = Column("cache_args", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
786 858 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
787 859
788 860
789 861 def __init__(self, cache_key, cache_args=''):
790 862 self.cache_key = cache_key
791 863 self.cache_args = cache_args
792 864 self.cache_active = False
793 865
794 866 def __repr__(self):
795 867 return "<%s('%s:%s')>" % (self.__class__.__name__,
796 868 self.cache_id, self.cache_key)
797 869
798 870 class DbMigrateVersion(Base, BaseModel):
799 871 __tablename__ = 'db_migrate_version'
800 872 __table_args__ = {'extend_existing':True}
801 873 repository_id = Column('repository_id', String(250), primary_key=True)
802 874 repository_path = Column('repository_path', Text)
803 875 version = Column('version', Integer)
@@ -1,179 +1,176 b''
1 1 ## -*- coding: utf-8 -*-
2 2
3 3 <%inherit file="/base/base.html"/>
4 4
5 5 <%def name="title()">
6 6 ${c.repo_name} ${_('Changelog')} - ${c.rhodecode_name}
7 7 </%def>
8 8
9 9 <%def name="breadcrumbs_links()">
10 10 ${h.link_to(u'Home',h.url('/'))}
11 11 &raquo;
12 12 ${h.link_to(c.repo_name,h.url('summary_home',repo_name=c.repo_name))}
13 13 &raquo;
14 14 ${_('Changelog')} - ${_('showing ')} ${c.size if c.size <= c.total_cs else c.total_cs} ${_('out of')} ${c.total_cs} ${_('revisions')}
15 15 </%def>
16 16
17 17 <%def name="page_nav()">
18 18 ${self.menu('changelog')}
19 19 </%def>
20 20
21 21 <%def name="main()">
22 22 <div class="box">
23 23 <!-- box / title -->
24 24 <div class="title">
25 25 ${self.breadcrumbs()}
26 26 </div>
27 27 <div class="table">
28 28 % if c.pagination:
29 29 <div id="graph">
30 30 <div id="graph_nodes">
31 31 <canvas id="graph_canvas"></canvas>
32 32 </div>
33 33 <div id="graph_content">
34 34 <div class="container_header">
35 35 ${h.form(h.url.current(),method='get')}
36 36 <div class="info_box">
37 37 ${h.submit('set',_('Show'),class_="ui-button-small")}
38 38 ${h.text('size',size=1,value=c.size)}
39 39 <span class="rev">${_('revisions')}</span>
40 40 </div>
41 41 ${h.end_form()}
42 42 <div id="rev_range_container" style="display:none"></div>
43 43 </div>
44 44
45 45 %for cnt,cs in enumerate(c.pagination):
46 46 <div id="chg_${cnt+1}" class="container">
47 47 <div class="left">
48 48 <div class="date">
49 49 ${h.checkbox(cs.short_id,class_="changeset_range")}
50 50 <span>${_('commit')} ${cs.revision}: ${h.short_id(cs.raw_id)}@${cs.date}</span>
51 51 </div>
52 52 <div class="author">
53 53 <div class="gravatar">
54 54 <img alt="gravatar" src="${h.gravatar_url(h.email(cs.author),20)}"/>
55 55 </div>
56 56 <span>${h.person(cs.author)}</span><br/>
57 57 <span><a href="mailto:${h.email_or_none(cs.author)}">${h.email_or_none(cs.author)}</a></span><br/>
58 58 </div>
59 59 <div class="message">${h.link_to(h.wrap_paragraphs(cs.message),h.url('changeset_home',repo_name=c.repo_name,revision=cs.raw_id))}</div>
60 60 </div>
61 61 <div class="right">
62 62 <div id="${cs.raw_id}_changes_info" class="changes">
63 <span id="${cs.raw_id}" class="changed_total tooltip"
64 title="${_('Affected number of files, click to show more details')}">
65 ${len(cs.affected_files)}
66 </span>
63 <span id="${cs.raw_id}" class="changed_total tooltip" title="${_('Affected number of files, click to show more details')}">${len(cs.affected_files)}</span>
67 64 </div>
68 65 %if len(cs.parents)>1:
69 66 <div class="merge">
70 67 ${_('merge')}<img alt="merge" src="${h.url('/images/icons/arrow_join.png')}"/>
71 68 </div>
72 69 %endif
73 70 %if cs.parents:
74 71 %for p_cs in reversed(cs.parents):
75 72 <div class="parent">${_('Parent')} ${p_cs.revision}: ${h.link_to(h.short_id(p_cs.raw_id),
76 73 h.url('changeset_home',repo_name=c.repo_name,revision=p_cs.raw_id),title=p_cs.message)}
77 74 </div>
78 75 %endfor
79 76 %else:
80 77 <div class="parent">${_('No parents')}</div>
81 78 %endif
82 79
83 80 <span class="logtags">
84 81 %if cs.branch:
85 82 <span class="branchtag" title="${'%s %s' % (_('branch'),cs.branch)}">
86 83 ${h.link_to(cs.branch,h.url('files_home',repo_name=c.repo_name,revision=cs.raw_id))}</span>
87 84 %endif
88 85 %for tag in cs.tags:
89 86 <span class="tagtag" title="${'%s %s' % (_('tag'),tag)}">
90 87 ${h.link_to(tag,h.url('files_home',repo_name=c.repo_name,revision=cs.raw_id))}</span>
91 88 %endfor
92 89 </span>
93 90 </div>
94 91 </div>
95 92
96 93 %endfor
97 94 <div class="pagination-wh pagination-left">
98 95 ${c.pagination.pager('$link_previous ~2~ $link_next')}
99 96 </div>
100 97 </div>
101 98 </div>
102 99
103 100 <script type="text/javascript" src="${h.url('/js/graph.js')}"></script>
104 101 <script type="text/javascript">
105 102 YAHOO.util.Event.onDOMReady(function(){
106 103
107 104 //Monitor range checkboxes and build a link to changesets
108 105 //ranges
109 106 var checkboxes = YUD.getElementsByClassName('changeset_range');
110 107 var url_tmpl = "${h.url('changeset_home',repo_name=c.repo_name,revision='__REVRANGE__')}";
111 108 YUE.on(checkboxes,'click',function(e){
112 109 var checked_checkboxes = [];
113 110 for (pos in checkboxes){
114 111 if(checkboxes[pos].checked){
115 112 checked_checkboxes.push(checkboxes[pos]);
116 113 }
117 114 }
118 115 if(checked_checkboxes.length>1){
119 116 var rev_end = checked_checkboxes[0].name;
120 117 var rev_start = checked_checkboxes[checked_checkboxes.length-1].name;
121 118
122 119 var url = url_tmpl.replace('__REVRANGE__',
123 120 rev_start+'...'+rev_end);
124 121
125 122 var link = "<a href="+url+">${_('Show selected changes __S -> __E')}</a>"
126 123 link = link.replace('__S',rev_start);
127 124 link = link.replace('__E',rev_end);
128 125 YUD.get('rev_range_container').innerHTML = link;
129 126 YUD.setStyle('rev_range_container','display','');
130 127 }
131 128 else{
132 129 YUD.setStyle('rev_range_container','display','none');
133 130
134 131 }
135 132 });
136 133
137 134 //Fetch changeset details
138 135 YUE.on(YUD.getElementsByClassName('changed_total'),'click',function(e){
139 136 var id = e.currentTarget.id
140 137 var url = "${h.url('changelog_details',repo_name=c.repo_name,cs='__CS__')}"
141 138 var url = url.replace('__CS__',id);
142 139 ypjax(url,id+'_changes_info',function(){tooltip_activate()});
143 140 });
144 141
145 142
146 143 function set_canvas(heads) {
147 144 var c = document.getElementById('graph_nodes');
148 145 var t = document.getElementById('graph_content');
149 146 canvas = document.getElementById('graph_canvas');
150 147 var div_h = t.clientHeight;
151 148 c.style.height=div_h+'px';
152 149 canvas.setAttribute('height',div_h);
153 150 c.style.height=max_w+'px';
154 151 canvas.setAttribute('width',max_w);
155 152 };
156 153 var heads = 1;
157 154 var max_heads = 0;
158 155 var jsdata = ${c.jsdata|n};
159 156
160 157 for( var i=0;i<jsdata.length;i++){
161 158 var m = Math.max.apply(Math, jsdata[i][1]);
162 159 if (m>max_heads){
163 160 max_heads = m;
164 161 }
165 162 }
166 163 var max_w = Math.max(100,max_heads*25);
167 164 set_canvas(max_w);
168 165
169 166 var r = new BranchRenderer();
170 167 r.render(jsdata,max_w);
171 168
172 169 });
173 170 </script>
174 171 %else:
175 172 ${_('There are no changes yet')}
176 173 %endif
177 174 </div>
178 175 </div>
179 176 </%def> No newline at end of file
@@ -1,70 +1,95 b''
1 1 from rhodecode.tests import *
2 from rhodecode.model.db import UsersGroup
2 3
3 4 TEST_USERS_GROUP = 'admins_test'
4 5
5 6 class TestAdminUsersGroupsController(TestController):
6 7
7 8 def test_index(self):
8 9 response = self.app.get(url('users_groups'))
9 10 # Test response...
10 11
11 12 def test_index_as_xml(self):
12 13 response = self.app.get(url('formatted_users_groups', format='xml'))
13 14
14 15 def test_create(self):
15 16 self.log_user()
16 17 users_group_name = TEST_USERS_GROUP
17 18 response = self.app.post(url('users_groups'),
18 19 {'users_group_name':users_group_name,
19 20 'active':True})
20 21 response.follow()
21 22
22 23 self.checkSessionFlash(response,
23 24 'created users group %s' % TEST_USERS_GROUP)
24 25
25 26
26 27
27 28
28 29
29 30 def test_new(self):
30 31 response = self.app.get(url('new_users_group'))
31 32
32 33 def test_new_as_xml(self):
33 34 response = self.app.get(url('formatted_new_users_group', format='xml'))
34 35
35 36 def test_update(self):
36 37 response = self.app.put(url('users_group', id=1))
37 38
38 39 def test_update_browser_fakeout(self):
39 response = self.app.post(url('users_group', id=1), params=dict(_method='put'))
40 response = self.app.post(url('users_group', id=1),
41 params=dict(_method='put'))
40 42
41 43 def test_delete(self):
42 response = self.app.delete(url('users_group', id=1))
44 self.log_user()
45 users_group_name = TEST_USERS_GROUP + 'another'
46 response = self.app.post(url('users_groups'),
47 {'users_group_name':users_group_name,
48 'active':True})
49 response.follow()
50
51 self.checkSessionFlash(response,
52 'created users group %s' % users_group_name)
53
54
55 gr = self.sa.query(UsersGroup)\
56 .filter(UsersGroup.users_group_name ==
57 users_group_name).one()
58
59 response = self.app.delete(url('users_group', id=gr.users_group_id))
60
61 gr = self.sa.query(UsersGroup)\
62 .filter(UsersGroup.users_group_name ==
63 users_group_name).scalar()
64
65 self.assertEqual(gr, None)
66
43 67
44 68 def test_delete_browser_fakeout(self):
45 response = self.app.post(url('users_group', id=1), params=dict(_method='delete'))
69 response = self.app.post(url('users_group', id=1),
70 params=dict(_method='delete'))
46 71
47 72 def test_show(self):
48 73 response = self.app.get(url('users_group', id=1))
49 74
50 75 def test_show_as_xml(self):
51 76 response = self.app.get(url('formatted_users_group', id=1, format='xml'))
52 77
53 78 def test_edit(self):
54 79 response = self.app.get(url('edit_users_group', id=1))
55 80
56 81 def test_edit_as_xml(self):
57 82 response = self.app.get(url('formatted_edit_users_group', id=1, format='xml'))
58 83
59 84 def test_assign_members(self):
60 85 pass
61 86
62 87 def test_add_create_permission(self):
63 88 pass
64 89
65 90 def test_revoke_members(self):
66 91 pass
67 92
68 93
69 94
70 95
@@ -1,41 +1,61 b''
1 1 from rhodecode.tests import *
2 2
3 3 class TestChangelogController(TestController):
4 4
5 5 def test_index_hg(self):
6 6 self.log_user()
7 response = self.app.get(url(controller='changelog', action='index', repo_name=HG_REPO))
7 response = self.app.get(url(controller='changelog', action='index',
8 repo_name=HG_REPO))
8 9
9 assert """<div id="chg_20" class="container">""" in response.body, 'wrong info about number of changes'
10 assert """<input class="changeset_range" id="5e204e7583b9" name="5e204e7583b9" type="checkbox" value="1" />""" in response.body, 'no checkbox for this commit'
11 assert """<span>commit 154: 5e204e7583b9@2010-08-10 01:18:46</span>""" in response.body , 'no info on this commit'
12 assert """Small update at simplevcs app""" in response.body, 'missing info about commit message'
10 self.assertTrue("""<div id="chg_20" class="container">"""
11 in response.body)
12 self.assertTrue("""<input class="changeset_range" id="5e204e7583b9" """
13 """name="5e204e7583b9" type="checkbox" value="1" />"""
14 in response.body)
15 self.assertTrue("""<span>commit 154: 5e204e7583b9@2010-08-10 """
16 """01:18:46</span>""" in response.body)
17 self.assertTrue("""Small update at simplevcs app""" in response.body)
13 18
14 assert """<span class="removed tooltip" title="<b>removed</b>: No Files">0</span>""" in response.body, 'wrong info about removed nodes'
15 assert """<span class="changed tooltip" title="<b>changed</b>: <br/> vcs/backends/hg.py<br/> vcs/web/simplevcs/models.py">2</span>""" in response.body, 'wrong info about changed nodes'
16 assert """<span class="added tooltip" title="<b>added</b>: <br/> vcs/web/simplevcs/managers.py">1</span>""" in response.body, 'wrong info about added nodes'
19
20 self.assertTrue("""<span id="5e204e7583b9c8e7b93a020bd036564b1e"""
21 """731dae" class="changed_total tooltip" """
22 """title="Affected number of files, click to """
23 """show more details">3</span>""" in response.body)
17 24
18 25 #pagination
19 26
20 response = self.app.get(url(controller='changelog', action='index', repo_name=HG_REPO), {'page':1})
21 response = self.app.get(url(controller='changelog', action='index', repo_name=HG_REPO), {'page':2})
22 response = self.app.get(url(controller='changelog', action='index', repo_name=HG_REPO), {'page':3})
23 response = self.app.get(url(controller='changelog', action='index', repo_name=HG_REPO), {'page':4})
24 response = self.app.get(url(controller='changelog', action='index', repo_name=HG_REPO), {'page':5})
25 response = self.app.get(url(controller='changelog', action='index', repo_name=HG_REPO), {'page':6})
27 response = self.app.get(url(controller='changelog', action='index',
28 repo_name=HG_REPO), {'page':1})
29 response = self.app.get(url(controller='changelog', action='index',
30 repo_name=HG_REPO), {'page':2})
31 response = self.app.get(url(controller='changelog', action='index',
32 repo_name=HG_REPO), {'page':3})
33 response = self.app.get(url(controller='changelog', action='index',
34 repo_name=HG_REPO), {'page':4})
35 response = self.app.get(url(controller='changelog', action='index',
36 repo_name=HG_REPO), {'page':5})
37 response = self.app.get(url(controller='changelog', action='index',
38 repo_name=HG_REPO), {'page':6})
26 39
27 40
28 41 # Test response after pagination...
29 print response.body
30 assert """<input class="changeset_range" id="46ad32a4f974" name="46ad32a4f974" type="checkbox" value="1" />""" in response.body, 'no checkbox for this commit'
31 assert """<span>commit 64: 46ad32a4f974@2010-04-20 00:33:21</span>"""in response.body, 'wrong info about commit 64'
32 assert """<span class="removed tooltip" title="<b>removed</b>: <br/> docs/api.rst">1</span>"""in response.body, 'wrong info about number of removed'
33 assert """<span class="changed tooltip" title="<b>changed</b>: <br/> .hgignore<br/> README.rst<br/> docs/conf.py<br/> docs/index.rst<br/> setup.py<br/> tests/test_hg.py<br/> tests/test_nodes.py<br/> vcs/__init__.py<br/> vcs/backends/__init__.py<br/> vcs/backends/base.py<br/> vcs/backends/hg.py<br/> vcs/nodes.py<br/> vcs/utils/__init__.py">13</span>"""in response.body, 'wrong info about number of changes'
34 assert """<span class="added tooltip" title="<b>added</b>: <br/> docs/api/backends/hg.rst<br/> docs/api/backends/index.rst<br/> docs/api/index.rst<br/> docs/api/nodes.rst<br/> docs/api/web/index.rst<br/> docs/api/web/simplevcs.rst<br/> docs/installation.rst<br/> docs/quickstart.rst<br/> setup.cfg<br/> vcs/utils/baseui_config.py<br/> vcs/utils/web.py<br/> vcs/web/__init__.py<br/> vcs/web/exceptions.py<br/> vcs/web/simplevcs/__init__.py<br/> vcs/web/simplevcs/exceptions.py<br/> vcs/web/simplevcs/middleware.py<br/> vcs/web/simplevcs/models.py<br/> vcs/web/simplevcs/settings.py<br/> vcs/web/simplevcs/utils.py<br/> vcs/web/simplevcs/views.py">20</span>"""in response.body, 'wrong info about number of added'
35 assert """<div class="message"><a href="/%s/changeset/46ad32a4f974e45472a898c6b0acb600320579b1">Merge with 2e6a2bf9356ca56df08807f4ad86d480da72a8f4</a></div>""" % HG_REPO in response.body, 'wrong info about commit 64 is a merge'
42 self.assertTrue("""<input class="changeset_range" id="46ad32a4f974" """
43 """name="46ad32a4f974" type="checkbox" value="1" />"""
44 in response.body)
45 self.assertTrue("""<span>commit 64: 46ad32a4f974@2010-04-20"""
46 """ 00:33:21</span>"""in response.body)
47
48 self.assertTrue("""<span id="46ad32a4f974e45472a898c6b0acb600320"""
49 """579b1" class="changed_total tooltip" """
50 """title="Affected number of files, click to """
51 """show more details">21</span>"""in response.body)
52 self.assertTrue("""<div class="message"><a href="/%s/changeset/"""
53 """46ad32a4f974e45472a898c6b0acb600320579b1">"""
54 """Merge with 2e6a2bf9356ca56df08807f4ad86d48"""
55 """0da72a8f4</a></div>""" % HG_REPO in response.body)
36 56
37 57
38 58
39 59 #def test_index_git(self):
40 60 # self.log_user()
41 61 # response = self.app.get(url(controller='changelog', action='index', repo_name=GIT_REPO))
1 NO CONTENT: file was removed
General Comments 0
You need to be logged in to leave comments. Login now