##// END OF EJS Templates
extended repo creation by repo type. fixed fork creation to maintain repo type.
marcink -
r659:758f64f3 beta
parent child Browse files
Show More
@@ -1,175 +1,175 b''
1 1 #!/usr/bin/env python
2 2 # encoding: utf-8
3 3 # settings controller for pylons
4 4 # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
5 5 #
6 6 # This program is free software; you can redistribute it and/or
7 7 # modify it under the terms of the GNU General Public License
8 8 # as published by the Free Software Foundation; version 2
9 9 # of the License or (at your opinion) any later version of the license.
10 10 #
11 11 # This program is distributed in the hope that it will be useful,
12 12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 14 # GNU General Public License for more details.
15 15 #
16 16 # You should have received a copy of the GNU General Public License
17 17 # along with this program; if not, write to the Free Software
18 18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19 19 # MA 02110-1301, USA.
20 20 """
21 21 Created on June 30, 2010
22 22 settings controller for pylons
23 23 @author: marcink
24 24 """
25 25 from formencode import htmlfill
26 26 from pylons import tmpl_context as c, request, url
27 27 from pylons.controllers.util import redirect
28 28 from pylons.i18n.translation import _
29 29 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAllDecorator
30 30 from rhodecode.lib.base import BaseController, render
31 31 from rhodecode.lib.utils import invalidate_cache, action_logger
32 32 from rhodecode.model.forms import RepoSettingsForm, RepoForkForm
33 33 from rhodecode.model.repo import RepoModel
34 34 import formencode
35 35 import logging
36 36 import rhodecode.lib.helpers as h
37 37 import traceback
38 38
39 39 log = logging.getLogger(__name__)
40 40
41 41 class SettingsController(BaseController):
42 42
43 43 @LoginRequired()
44 44 @HasRepoPermissionAllDecorator('repository.admin')
45 45 def __before__(self):
46 46 super(SettingsController, self).__before__()
47 47
48 48 def index(self, repo_name):
49 49 repo_model = RepoModel()
50 50 c.repo_info = repo = repo_model.get(repo_name)
51 51 if not repo:
52 52 h.flash(_('%s repository is not mapped to db perhaps'
53 53 ' it was created or renamed from the filesystem'
54 54 ' please run the application again'
55 55 ' in order to rescan repositories') % repo_name,
56 56 category='error')
57 57
58 58 return redirect(url('home'))
59 59 defaults = c.repo_info.__dict__
60 60 defaults.update({'user':c.repo_info.user.username})
61 61 c.users_array = repo_model.get_users_js()
62 62
63 63 for p in c.repo_info.repo_to_perm:
64 64 defaults.update({'perm_%s' % p.user.username:
65 65 p.permission.permission_name})
66 66
67 67 return htmlfill.render(
68 68 render('settings/repo_settings.html'),
69 69 defaults=defaults,
70 70 encoding="UTF-8",
71 71 force_defaults=False
72 72 )
73 73
74 74 def update(self, repo_name):
75 75 repo_model = RepoModel()
76 76 changed_name = repo_name
77 77 _form = RepoSettingsForm(edit=True, old_data={'repo_name':repo_name})()
78 78 try:
79 79 form_result = _form.to_python(dict(request.POST))
80 80 repo_model.update(repo_name, form_result)
81 81 invalidate_cache('cached_repo_list')
82 82 h.flash(_('Repository %s updated successfully' % repo_name),
83 83 category='success')
84 84 changed_name = form_result['repo_name']
85 85 except formencode.Invalid, errors:
86 86 c.repo_info = repo_model.get(repo_name)
87 87 c.users_array = repo_model.get_users_js()
88 88 errors.value.update({'user':c.repo_info.user.username})
89 89 return htmlfill.render(
90 90 render('settings/repo_settings.html'),
91 91 defaults=errors.value,
92 92 errors=errors.error_dict or {},
93 93 prefix_error=False,
94 94 encoding="UTF-8")
95 95 except Exception:
96 96 log.error(traceback.format_exc())
97 97 h.flash(_('error occured during update of repository %s') \
98 98 % repo_name, category='error')
99 99
100 100 return redirect(url('repo_settings_home', repo_name=changed_name))
101 101
102 102
103 103
104 104 def delete(self, repo_name):
105 105 """DELETE /repos/repo_name: Delete an existing item"""
106 106 # Forms posted to this method should contain a hidden field:
107 107 # <input type="hidden" name="_method" value="DELETE" />
108 108 # Or using helpers:
109 109 # h.form(url('repo_settings_delete', repo_name=ID),
110 110 # method='delete')
111 111 # url('repo_settings_delete', repo_name=ID)
112 112
113 113 repo_model = RepoModel()
114 114 repo = repo_model.get(repo_name)
115 115 if not repo:
116 116 h.flash(_('%s repository is not mapped to db perhaps'
117 117 ' it was moved or renamed from the filesystem'
118 118 ' please run the application again'
119 119 ' in order to rescan repositories') % repo_name,
120 120 category='error')
121 121
122 122 return redirect(url('home'))
123 123 try:
124 124 action_logger(self.rhodecode_user, 'user_deleted_repo',
125 125 repo_name, '', self.sa)
126 126 repo_model.delete(repo)
127 127 invalidate_cache('cached_repo_list')
128 128 h.flash(_('deleted repository %s') % repo_name, category='success')
129 129 except Exception:
130 130 h.flash(_('An error occurred during deletion of %s') % repo_name,
131 131 category='error')
132 132
133 133 return redirect(url('home'))
134 134
135 135 def fork(self, repo_name):
136 136 repo_model = RepoModel()
137 137 c.repo_info = repo = repo_model.get(repo_name)
138 138 if not repo:
139 139 h.flash(_('%s repository is not mapped to db perhaps'
140 140 ' it was created or renamed from the filesystem'
141 141 ' please run the application again'
142 142 ' in order to rescan repositories') % repo_name,
143 143 category='error')
144 144
145 145 return redirect(url('home'))
146 146
147 147 return render('settings/repo_fork.html')
148 148
149 149
150 150
151 151 def fork_create(self, repo_name):
152 152 repo_model = RepoModel()
153 153 c.repo_info = repo_model.get(repo_name)
154 _form = RepoForkForm()()
154 _form = RepoForkForm(old_data={'repo_type':c.repo_info.repo_type})()
155 155 form_result = {}
156 156 try:
157 157 form_result = _form.to_python(dict(request.POST))
158 158 form_result.update({'repo_name':repo_name})
159 159 repo_model.create_fork(form_result, c.rhodecode_user)
160 160 h.flash(_('fork %s repository as %s task added') \
161 161 % (repo_name, form_result['fork_name']),
162 162 category='success')
163 163 action_logger(self.rhodecode_user, 'user_forked_repo',
164 164 repo_name, '', self.sa)
165 165 except formencode.Invalid, errors:
166 166 c.new_repo = errors.value['fork_name']
167 167 r = render('settings/repo_fork.html')
168 168
169 169 return htmlfill.render(
170 170 r,
171 171 defaults=errors.value,
172 172 errors=errors.error_dict or {},
173 173 prefix_error=False,
174 174 encoding="UTF-8")
175 175 return redirect(url('home'))
@@ -1,46 +1,46 b''
1 1 """The base Controller API
2 2
3 3 Provides the BaseController class for subclassing.
4 4 """
5 5 from pylons import config, tmpl_context as c, request, session
6 6 from pylons.controllers import WSGIController
7 7 from pylons.templating import render_mako as render
8 8 from rhodecode import __version__
9 9 from rhodecode.lib import auth
10 10 from rhodecode.lib.utils import get_repo_slug
11 11 from rhodecode.model import meta
12 12 from rhodecode.model.hg import _get_repos_cached, \
13 13 _get_repos_switcher_cached
14
14 from vcs import BACKENDS
15 15 class BaseController(WSGIController):
16
16
17 17 def __before__(self):
18 18 c.rhodecode_version = __version__
19 19 c.rhodecode_name = config['rhodecode_title']
20 20 c.repo_name = get_repo_slug(request)
21 21 c.cached_repo_list = _get_repos_cached()
22 22 c.repo_switcher_list = _get_repos_switcher_cached(c.cached_repo_list)
23
23 c.backends = BACKENDS.keys()
24 24 if c.repo_name:
25 25 cached_repo = c.cached_repo_list.get(c.repo_name)
26
26
27 27 if cached_repo:
28 28 c.repository_tags = cached_repo.tags
29 29 c.repository_branches = cached_repo.branches
30 30 else:
31 31 c.repository_tags = {}
32 32 c.repository_branches = {}
33
33
34 34 self.sa = meta.Session()
35
35
36 36 def __call__(self, environ, start_response):
37 37 """Invoke the Controller"""
38 38 # WSGIController.__call__ dispatches to the Controller method
39 39 # the request is routed to. This routing information is
40 40 # available in environ['pylons.routes_dict']
41 41 try:
42 42 #putting this here makes sure that we update permissions every time
43 43 self.rhodecode_user = c.rhodecode_user = auth.get_user(session)
44 44 return WSGIController.__call__(self, environ, start_response)
45 45 finally:
46 46 meta.Session.remove()
@@ -1,334 +1,336 b''
1 1 from celery.decorators import task
2 2
3 3 from operator import itemgetter
4 4 from pylons.i18n.translation import _
5 5 from rhodecode.lib.celerylib import run_task, locked_task
6 6 from rhodecode.lib.helpers import person
7 7 from rhodecode.lib.smtp_mailer import SmtpMailer
8 8 from rhodecode.lib.utils import OrderedDict
9 9 from time import mktime
10 10 from vcs.backends.hg import MercurialRepository
11 11 from vcs.backends.git import GitRepository
12 12 import os
13 13 import traceback
14 14 from vcs.backends import get_repo
15 15 from vcs.utils.helpers import get_scm
16 16
17 17 try:
18 18 import json
19 19 except ImportError:
20 20 #python 2.5 compatibility
21 21 import simplejson as json
22 22
23 23 try:
24 24 from celeryconfig import PYLONS_CONFIG as config
25 25 celery_on = True
26 26 except ImportError:
27 27 #if celeryconfig is not present let's just load our pylons
28 28 #config instead
29 29 from pylons import config
30 30 celery_on = False
31 31
32 32
33 33 __all__ = ['whoosh_index', 'get_commits_stats',
34 34 'reset_user_password', 'send_email']
35 35
36 36 def get_session():
37 37 if celery_on:
38 38 from sqlalchemy import engine_from_config
39 39 from sqlalchemy.orm import sessionmaker, scoped_session
40 40 engine = engine_from_config(dict(config.items('app:main')), 'sqlalchemy.db1.')
41 41 sa = scoped_session(sessionmaker(bind=engine))
42 42 else:
43 43 #If we don't use celery reuse our current application Session
44 44 from rhodecode.model.meta import Session
45 45 sa = Session()
46 46
47 47 return sa
48 48
49 49 def get_hg_settings():
50 50 from rhodecode.model.db import RhodeCodeSettings
51 51 sa = get_session()
52 52 ret = sa.query(RhodeCodeSettings).all()
53 53
54 54 if not ret:
55 55 raise Exception('Could not get application settings !')
56 56 settings = {}
57 57 for each in ret:
58 58 settings['rhodecode_' + each.app_settings_name] = each.app_settings_value
59 59
60 60 return settings
61 61
62 62 def get_hg_ui_settings():
63 63 from rhodecode.model.db import RhodeCodeUi
64 64 sa = get_session()
65 65 ret = sa.query(RhodeCodeUi).all()
66 66
67 67 if not ret:
68 68 raise Exception('Could not get application ui settings !')
69 69 settings = {}
70 70 for each in ret:
71 71 k = each.ui_key
72 72 v = each.ui_value
73 73 if k == '/':
74 74 k = 'root_path'
75 75
76 76 if k.find('.') != -1:
77 77 k = k.replace('.', '_')
78 78
79 79 if each.ui_section == 'hooks':
80 80 v = each.ui_active
81 81
82 82 settings[each.ui_section + '_' + k] = v
83 83
84 84 return settings
85 85
86 86 @task
87 87 @locked_task
88 88 def whoosh_index(repo_location, full_index):
89 89 log = whoosh_index.get_logger()
90 90 from rhodecode.lib.indexers.daemon import WhooshIndexingDaemon
91 91 WhooshIndexingDaemon(repo_location=repo_location).run(full_index=full_index)
92 92
93 93 @task
94 94 @locked_task
95 95 def get_commits_stats(repo_name, ts_min_y, ts_max_y):
96 96 from rhodecode.model.db import Statistics, Repository
97 97 log = get_commits_stats.get_logger()
98 98 author_key_cleaner = lambda k: person(k).replace('"', "") #for js data compatibilty
99 99
100 100 commits_by_day_author_aggregate = {}
101 101 commits_by_day_aggregate = {}
102 102 repos_path = get_hg_ui_settings()['paths_root_path']
103 103 p = os.path.join(repos_path, repo_name)
104 104 repo = get_repo(p)
105 105
106 106 skip_date_limit = True
107 107 parse_limit = 250 #limit for single task changeset parsing optimal for
108 108 last_rev = 0
109 109 last_cs = None
110 110 timegetter = itemgetter('time')
111 111
112 112 sa = get_session()
113 113
114 114 dbrepo = sa.query(Repository)\
115 115 .filter(Repository.repo_name == repo_name).scalar()
116 116 cur_stats = sa.query(Statistics)\
117 117 .filter(Statistics.repository == dbrepo).scalar()
118 118 if cur_stats:
119 119 last_rev = cur_stats.stat_on_revision
120 120 if not repo.revisions:
121 121 return True
122 122
123 123 if last_rev == repo.revisions[-1] and len(repo.revisions) > 1:
124 124 #pass silently without any work if we're not on first revision or current
125 125 #state of parsing revision(from db marker) is the last revision
126 126 return True
127 127
128 128 if cur_stats:
129 129 commits_by_day_aggregate = OrderedDict(
130 130 json.loads(
131 131 cur_stats.commit_activity_combined))
132 132 commits_by_day_author_aggregate = json.loads(cur_stats.commit_activity)
133 133
134 134 log.debug('starting parsing %s', parse_limit)
135 135 lmktime = mktime
136 136
137 137 for cnt, rev in enumerate(repo.revisions[last_rev:]):
138 138 last_cs = cs = repo.get_changeset(rev)
139 139 k = '%s-%s-%s' % (cs.date.timetuple()[0], cs.date.timetuple()[1],
140 140 cs.date.timetuple()[2])
141 141 timetupple = [int(x) for x in k.split('-')]
142 142 timetupple.extend([0 for _ in xrange(6)])
143 143 k = lmktime(timetupple)
144 144 if commits_by_day_author_aggregate.has_key(author_key_cleaner(cs.author)):
145 145 try:
146 146 l = [timegetter(x) for x in commits_by_day_author_aggregate\
147 147 [author_key_cleaner(cs.author)]['data']]
148 148 time_pos = l.index(k)
149 149 except ValueError:
150 150 time_pos = False
151 151
152 152 if time_pos >= 0 and time_pos is not False:
153 153
154 154 datadict = commits_by_day_author_aggregate\
155 155 [author_key_cleaner(cs.author)]['data'][time_pos]
156 156
157 157 datadict["commits"] += 1
158 158 datadict["added"] += len(cs.added)
159 159 datadict["changed"] += len(cs.changed)
160 160 datadict["removed"] += len(cs.removed)
161 161
162 162 else:
163 163 if k >= ts_min_y and k <= ts_max_y or skip_date_limit:
164 164
165 165 datadict = {"time":k,
166 166 "commits":1,
167 167 "added":len(cs.added),
168 168 "changed":len(cs.changed),
169 169 "removed":len(cs.removed),
170 170 }
171 171 commits_by_day_author_aggregate\
172 172 [author_key_cleaner(cs.author)]['data'].append(datadict)
173 173
174 174 else:
175 175 if k >= ts_min_y and k <= ts_max_y or skip_date_limit:
176 176 commits_by_day_author_aggregate[author_key_cleaner(cs.author)] = {
177 177 "label":author_key_cleaner(cs.author),
178 178 "data":[{"time":k,
179 179 "commits":1,
180 180 "added":len(cs.added),
181 181 "changed":len(cs.changed),
182 182 "removed":len(cs.removed),
183 183 }],
184 184 "schema":["commits"],
185 185 }
186 186
187 187 #gather all data by day
188 188 if commits_by_day_aggregate.has_key(k):
189 189 commits_by_day_aggregate[k] += 1
190 190 else:
191 191 commits_by_day_aggregate[k] = 1
192 192
193 193 if cnt >= parse_limit:
194 194 #don't fetch to much data since we can freeze application
195 195 break
196 196 overview_data = []
197 197 for k, v in commits_by_day_aggregate.items():
198 198 overview_data.append([k, v])
199 199 overview_data = sorted(overview_data, key=itemgetter(0))
200 200 if not commits_by_day_author_aggregate:
201 201 commits_by_day_author_aggregate[author_key_cleaner(repo.contact)] = {
202 202 "label":author_key_cleaner(repo.contact),
203 203 "data":[0, 1],
204 204 "schema":["commits"],
205 205 }
206 206
207 207 stats = cur_stats if cur_stats else Statistics()
208 208 stats.commit_activity = json.dumps(commits_by_day_author_aggregate)
209 209 stats.commit_activity_combined = json.dumps(overview_data)
210 210
211 211 log.debug('last revison %s', last_rev)
212 212 leftovers = len(repo.revisions[last_rev:])
213 213 log.debug('revisions to parse %s', leftovers)
214 214
215 215 if last_rev == 0 or leftovers < parse_limit:
216 216 stats.languages = json.dumps(__get_codes_stats(repo_name))
217 217
218 218 stats.repository = dbrepo
219 219 stats.stat_on_revision = last_cs.revision
220 220
221 221 try:
222 222 sa.add(stats)
223 223 sa.commit()
224 224 except:
225 225 log.error(traceback.format_exc())
226 226 sa.rollback()
227 227 return False
228 228 if len(repo.revisions) > 1:
229 229 run_task(get_commits_stats, repo_name, ts_min_y, ts_max_y)
230 230
231 231 return True
232 232
233 233 @task
234 234 def reset_user_password(user_email):
235 235 log = reset_user_password.get_logger()
236 236 from rhodecode.lib import auth
237 237 from rhodecode.model.db import User
238 238
239 239 try:
240 240 try:
241 241 sa = get_session()
242 242 user = sa.query(User).filter(User.email == user_email).scalar()
243 243 new_passwd = auth.PasswordGenerator().gen_password(8,
244 244 auth.PasswordGenerator.ALPHABETS_BIG_SMALL)
245 245 if user:
246 246 user.password = auth.get_crypt_password(new_passwd)
247 247 sa.add(user)
248 248 sa.commit()
249 249 log.info('change password for %s', user_email)
250 250 if new_passwd is None:
251 251 raise Exception('unable to generate new password')
252 252
253 253 except:
254 254 log.error(traceback.format_exc())
255 255 sa.rollback()
256 256
257 257 run_task(send_email, user_email,
258 258 "Your new rhodecode password",
259 259 'Your new rhodecode password:%s' % (new_passwd))
260 260 log.info('send new password mail to %s', user_email)
261 261
262 262
263 263 except:
264 264 log.error('Failed to update user password')
265 265 log.error(traceback.format_exc())
266 266 return True
267 267
268 268 @task
269 269 def send_email(recipients, subject, body):
270 270 log = send_email.get_logger()
271 271 email_config = dict(config.items('DEFAULT'))
272 272 mail_from = email_config.get('app_email_from')
273 273 user = email_config.get('smtp_username')
274 274 passwd = email_config.get('smtp_password')
275 275 mail_server = email_config.get('smtp_server')
276 276 mail_port = email_config.get('smtp_port')
277 277 tls = email_config.get('smtp_use_tls')
278 278 ssl = False
279 279
280 280 try:
281 281 m = SmtpMailer(mail_from, user, passwd, mail_server,
282 282 mail_port, ssl, tls)
283 283 m.send(recipients, subject, body)
284 284 except:
285 285 log.error('Mail sending failed')
286 286 log.error(traceback.format_exc())
287 287 return False
288 288 return True
289 289
290 290 @task
291 291 def create_repo_fork(form_data, cur_user):
292 import os
293 292 from rhodecode.model.repo import RepoModel
294
293 from vcs import get_backend
294 log = create_repo_fork.get_logger()
295 295 repo_model = RepoModel(get_session())
296 296 repo_model.create(form_data, cur_user, just_db=True, fork=True)
297
298 repos_path = get_hg_ui_settings()['paths_root_path'].replace('*', '')
299 repo_path = os.path.join(repos_path, form_data['repo_name'])
297 repo_name = form_data['repo_name']
298 repos_path = get_hg_ui_settings()['paths_root_path']
299 repo_path = os.path.join(repos_path, repo_name)
300 300 repo_fork_path = os.path.join(repos_path, form_data['fork_name'])
301 alias = form_data['repo_type']
301 302
302 MercurialRepository(str(repo_fork_path), True, clone_url=str(repo_path))
303
303 log.info('creating repo fork %s as %s', repo_name, repo_path)
304 backend = get_backend(alias)
305 backend(str(repo_fork_path), create=True, src_url=str(repo_path))
304 306
305 307 def __get_codes_stats(repo_name):
306 308 LANGUAGES_EXTENSIONS = ['action', 'adp', 'ashx', 'asmx',
307 309 'aspx', 'asx', 'axd', 'c', 'cfg', 'cfm', 'cpp', 'cs', 'diff', 'do', 'el',
308 310 'erl', 'h', 'java', 'js', 'jsp', 'jspx', 'lisp', 'lua', 'm', 'mako', 'ml',
309 311 'pas', 'patch', 'php', 'php3', 'php4', 'phtml', 'pm', 'py', 'rb', 'rst',
310 312 's', 'sh', 'tpl', 'txt', 'vim', 'wss', 'xhtml', 'xml', 'xsl', 'xslt', 'yaws']
311 313
312 314
313 315 repos_path = get_hg_ui_settings()['paths_root_path']
314 316 p = os.path.join(repos_path, repo_name)
315 317 repo = get_repo(p)
316 318 tip = repo.get_changeset()
317 319 code_stats = {}
318 320
319 321 def aggregate(cs):
320 322 for f in cs[2]:
321 323 k = f.mimetype
322 324 if f.extension in LANGUAGES_EXTENSIONS:
323 325 if code_stats.has_key(k):
324 326 code_stats[k] += 1
325 327 else:
326 328 code_stats[k] = 1
327 329
328 330 map(aggregate, tip.walk('/'))
329 331
330 332 return code_stats or {}
331 333
332 334
333 335
334 336
@@ -1,537 +1,538 b''
1 1 #!/usr/bin/env python
2 2 # encoding: utf-8
3 3 # Utilities for RhodeCode
4 4 # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
5 5 # This program is free software; you can redistribute it and/or
6 6 # modify it under the terms of the GNU General Public License
7 7 # as published by the Free Software Foundation; version 2
8 8 # of the License or (at your opinion) any later version of the license.
9 9 #
10 10 # This program is distributed in the hope that it will be useful,
11 11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 13 # GNU General Public License for more details.
14 14 #
15 15 # You should have received a copy of the GNU General Public License
16 16 # along with this program; if not, write to the Free Software
17 17 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
18 18 # MA 02110-1301, USA.
19 19 """
20 20 Created on April 18, 2010
21 21 Utilities for RhodeCode
22 22 @author: marcink
23 23 """
24 24
25 25 from UserDict import DictMixin
26 26 from mercurial import ui, config, hg
27 27 from mercurial.error import RepoError
28 28 from rhodecode.model import meta
29 29 from rhodecode.model.caching_query import FromCache
30 30 from rhodecode.model.db import Repository, User, RhodeCodeUi, RhodeCodeSettings, \
31 31 UserLog
32 32 from rhodecode.model.repo import RepoModel
33 33 from rhodecode.model.user import UserModel
34 34 from vcs.backends.base import BaseChangeset
35 35 from vcs.backends.git import GitRepository
36 36 from vcs.backends.hg import MercurialRepository
37 37 from vcs.utils.lazy import LazyProperty
38 38 import traceback
39 39 import datetime
40 40 import logging
41 41 import os
42 42
43 43 log = logging.getLogger(__name__)
44 44
45 45
46 46 def get_repo_slug(request):
47 47 return request.environ['pylons.routes_dict'].get('repo_name')
48 48
49 49 def is_mercurial(environ):
50 50 """
51 51 Returns True if request's target is mercurial server - header
52 52 ``HTTP_ACCEPT`` of such request would start with ``application/mercurial``.
53 53 """
54 54 http_accept = environ.get('HTTP_ACCEPT')
55 55 if http_accept and http_accept.startswith('application/mercurial'):
56 56 return True
57 57 return False
58 58
59 59 def is_git(environ):
60 60 """
61 61 Returns True if request's target is git server. ``HTTP_USER_AGENT`` would
62 62 then have git client version given.
63 63
64 64 :param environ:
65 65 """
66 66 http_user_agent = environ.get('HTTP_USER_AGENT')
67 67 if http_user_agent.startswith('git'):
68 68 return True
69 69 return False
70 70
71 71 def action_logger(user, action, repo, ipaddr, sa=None):
72 72 """
73 73 Action logger for various action made by users
74 74 """
75 75
76 76 if not sa:
77 77 sa = meta.Session()
78 78
79 79 try:
80 80 if hasattr(user, 'user_id'):
81 81 user_obj = user
82 82 elif isinstance(user, basestring):
83 83 user_obj = UserModel(sa).get_by_username(user, cache=False)
84 84 else:
85 85 raise Exception('You have to provide user object or username')
86 86
87 87 repo_name = repo.lstrip('/')
88 88 user_log = UserLog()
89 89 user_log.user_id = user_obj.user_id
90 90 user_log.action = action
91 91 user_log.repository_name = repo_name
92 92 user_log.repository = RepoModel(sa).get(repo_name, cache=False)
93 93 user_log.action_date = datetime.datetime.now()
94 94 user_log.user_ip = ipaddr
95 95 sa.add(user_log)
96 96 sa.commit()
97 97
98 98 log.info('Adding user %s, action %s on %s',
99 99 user_obj.username, action, repo)
100 100 except:
101 101 log.error(traceback.format_exc())
102 102 sa.rollback()
103 103
104 104 def get_repos(path, recursive=False, initial=False):
105 105 """
106 106 Scans given path for repos and return (name,(type,path)) tuple
107 107 :param prefix:
108 108 :param path:
109 109 :param recursive:
110 110 :param initial:
111 111 """
112 112 from vcs.utils.helpers import get_scm
113 113 from vcs.exceptions import VCSError
114 114
115 115 try:
116 116 scm = get_scm(path)
117 117 except:
118 118 pass
119 119 else:
120 120 raise Exception('The given path %s should not be a repository got %s',
121 121 path, scm)
122 122
123 123 for dirpath in os.listdir(path):
124 124 try:
125 125 yield dirpath, get_scm(os.path.join(path, dirpath))
126 126 except VCSError:
127 127 pass
128 128
129 129 if __name__ == '__main__':
130 130 get_repos('', '/home/marcink/workspace-python')
131 131
132 132
133 133 def check_repo_fast(repo_name, base_path):
134 134 if os.path.isdir(os.path.join(base_path, repo_name)):return False
135 135 return True
136 136
137 137 def check_repo(repo_name, base_path, verify=True):
138 138
139 139 repo_path = os.path.join(base_path, repo_name)
140 140
141 141 try:
142 142 if not check_repo_fast(repo_name, base_path):
143 143 return False
144 144 r = hg.repository(ui.ui(), repo_path)
145 145 if verify:
146 146 hg.verify(r)
147 147 #here we hnow that repo exists it was verified
148 148 log.info('%s repo is already created', repo_name)
149 149 return False
150 150 except RepoError:
151 151 #it means that there is no valid repo there...
152 152 log.info('%s repo is free for creation', repo_name)
153 153 return True
154 154
155 155 def ask_ok(prompt, retries=4, complaint='Yes or no, please!'):
156 156 while True:
157 157 ok = raw_input(prompt)
158 158 if ok in ('y', 'ye', 'yes'): return True
159 159 if ok in ('n', 'no', 'nop', 'nope'): return False
160 160 retries = retries - 1
161 161 if retries < 0: raise IOError
162 162 print complaint
163 163
164 164 def get_hg_ui_cached():
165 165 try:
166 166 sa = meta.Session
167 167 ret = sa.query(RhodeCodeUi)\
168 168 .options(FromCache("sql_cache_short", "get_hg_ui_settings"))\
169 169 .all()
170 170 except:
171 171 pass
172 172 finally:
173 173 meta.Session.remove()
174 174 return ret
175 175
176 176
177 177 def get_hg_settings():
178 178 try:
179 179 sa = meta.Session()
180 180 ret = sa.query(RhodeCodeSettings)\
181 181 .options(FromCache("sql_cache_short", "get_hg_settings"))\
182 182 .all()
183 183 except:
184 184 pass
185 185 finally:
186 186 meta.Session.remove()
187 187
188 188 if not ret:
189 189 raise Exception('Could not get application settings !')
190 190 settings = {}
191 191 for each in ret:
192 192 settings['rhodecode_' + each.app_settings_name] = each.app_settings_value
193 193
194 194 return settings
195 195
196 196 def get_hg_ui_settings():
197 197 try:
198 198 sa = meta.Session()
199 199 ret = sa.query(RhodeCodeUi).all()
200 200 except:
201 201 pass
202 202 finally:
203 203 meta.Session.remove()
204 204
205 205 if not ret:
206 206 raise Exception('Could not get application ui settings !')
207 207 settings = {}
208 208 for each in ret:
209 209 k = each.ui_key
210 210 v = each.ui_value
211 211 if k == '/':
212 212 k = 'root_path'
213 213
214 214 if k.find('.') != -1:
215 215 k = k.replace('.', '_')
216 216
217 217 if each.ui_section == 'hooks':
218 218 v = each.ui_active
219 219
220 220 settings[each.ui_section + '_' + k] = v
221 221
222 222 return settings
223 223
224 224 #propagated from mercurial documentation
225 225 ui_sections = ['alias', 'auth',
226 226 'decode/encode', 'defaults',
227 227 'diff', 'email',
228 228 'extensions', 'format',
229 229 'merge-patterns', 'merge-tools',
230 230 'hooks', 'http_proxy',
231 231 'smtp', 'patch',
232 232 'paths', 'profiling',
233 233 'server', 'trusted',
234 234 'ui', 'web', ]
235 235
236 236 def make_ui(read_from='file', path=None, checkpaths=True):
237 237 """
238 238 A function that will read python rc files or database
239 239 and make an mercurial ui object from read options
240 240
241 241 :param path: path to mercurial config file
242 242 :param checkpaths: check the path
243 243 :param read_from: read from 'file' or 'db'
244 244 """
245 245
246 246 baseui = ui.ui()
247 247
248 248 if read_from == 'file':
249 249 if not os.path.isfile(path):
250 250 log.warning('Unable to read config file %s' % path)
251 251 return False
252 252 log.debug('reading hgrc from %s', path)
253 253 cfg = config.config()
254 254 cfg.read(path)
255 255 for section in ui_sections:
256 256 for k, v in cfg.items(section):
257 257 baseui.setconfig(section, k, v)
258 258 log.debug('settings ui from file[%s]%s:%s', section, k, v)
259 259
260 260 elif read_from == 'db':
261 261 hg_ui = get_hg_ui_cached()
262 262 for ui_ in hg_ui:
263 263 if ui_.ui_active:
264 264 log.debug('settings ui from db[%s]%s:%s', ui_.ui_section, ui_.ui_key, ui_.ui_value)
265 265 baseui.setconfig(ui_.ui_section, ui_.ui_key, ui_.ui_value)
266 266
267 267
268 268 return baseui
269 269
270 270
271 271 def set_rhodecode_config(config):
272 272 hgsettings = get_hg_settings()
273 273
274 274 for k, v in hgsettings.items():
275 275 config[k] = v
276 276
277 277 def invalidate_cache(name, *args):
278 278 """Invalidates given name cache"""
279 279
280 280 from beaker.cache import region_invalidate
281 281 log.info('INVALIDATING CACHE FOR %s', name)
282 282
283 283 """propagate our arguments to make sure invalidation works. First
284 284 argument has to be the name of cached func name give to cache decorator
285 285 without that the invalidation would not work"""
286 286 tmp = [name]
287 287 tmp.extend(args)
288 288 args = tuple(tmp)
289 289
290 290 if name == 'cached_repo_list':
291 291 from rhodecode.model.hg import _get_repos_cached
292 292 region_invalidate(_get_repos_cached, None, *args)
293 293
294 294 if name == 'full_changelog':
295 295 from rhodecode.model.hg import _full_changelog_cached
296 296 region_invalidate(_full_changelog_cached, None, *args)
297 297
298 298 class EmptyChangeset(BaseChangeset):
299 299 """
300 300 An dummy empty changeset. It's possible to pass hash when creating
301 301 an EmptyChangeset
302 302 """
303 303
304 304 def __init__(self, cs='0' * 40):
305 305 self._empty_cs = cs
306 306 self.revision = -1
307 307 self.message = ''
308 308 self.author = ''
309 309 self.date = ''
310 310
311 311 @LazyProperty
312 312 def raw_id(self):
313 313 """
314 314 Returns raw string identifying this changeset, useful for web
315 315 representation.
316 316 """
317 317 return self._empty_cs
318 318
319 319 @LazyProperty
320 320 def short_id(self):
321 321 return self.raw_id[:12]
322 322
323 323 def get_file_changeset(self, path):
324 324 return self
325 325
326 326 def get_file_content(self, path):
327 327 return u''
328 328
329 329 def get_file_size(self, path):
330 330 return 0
331 331
332 332 def repo2db_mapper(initial_repo_list, remove_obsolete=False):
333 333 """
334 334 maps all found repositories into db
335 335 """
336 336
337 337 sa = meta.Session()
338 338 rm = RepoModel(sa)
339 339 user = sa.query(User).filter(User.admin == True).first()
340 340
341 341 for name, repo in initial_repo_list.items():
342 342 if not rm.get(name, cache=False):
343 343 log.info('repository %s not found creating default', name)
344 344
345 345 form_data = {
346 346 'repo_name':name,
347 347 'repo_type':repo.alias,
348 'description':repo.description if repo.description != 'unknown' else \
349 'auto description for %s' % name,
348 'description':repo.description \
349 if repo.description != 'unknown' else \
350 '%s repository' % name,
350 351 'private':False
351 352 }
352 353 rm.create(form_data, user, just_db=True)
353 354
354 355
355 356 if remove_obsolete:
356 357 #remove from database those repositories that are not in the filesystem
357 358 for repo in sa.query(Repository).all():
358 359 if repo.repo_name not in initial_repo_list.keys():
359 360 sa.delete(repo)
360 361 sa.commit()
361 362
362 363
363 364 meta.Session.remove()
364 365
365 366
366 367 class OrderedDict(dict, DictMixin):
367 368
368 369 def __init__(self, *args, **kwds):
369 370 if len(args) > 1:
370 371 raise TypeError('expected at most 1 arguments, got %d' % len(args))
371 372 try:
372 373 self.__end
373 374 except AttributeError:
374 375 self.clear()
375 376 self.update(*args, **kwds)
376 377
377 378 def clear(self):
378 379 self.__end = end = []
379 380 end += [None, end, end] # sentinel node for doubly linked list
380 381 self.__map = {} # key --> [key, prev, next]
381 382 dict.clear(self)
382 383
383 384 def __setitem__(self, key, value):
384 385 if key not in self:
385 386 end = self.__end
386 387 curr = end[1]
387 388 curr[2] = end[1] = self.__map[key] = [key, curr, end]
388 389 dict.__setitem__(self, key, value)
389 390
390 391 def __delitem__(self, key):
391 392 dict.__delitem__(self, key)
392 393 key, prev, next = self.__map.pop(key)
393 394 prev[2] = next
394 395 next[1] = prev
395 396
396 397 def __iter__(self):
397 398 end = self.__end
398 399 curr = end[2]
399 400 while curr is not end:
400 401 yield curr[0]
401 402 curr = curr[2]
402 403
403 404 def __reversed__(self):
404 405 end = self.__end
405 406 curr = end[1]
406 407 while curr is not end:
407 408 yield curr[0]
408 409 curr = curr[1]
409 410
410 411 def popitem(self, last=True):
411 412 if not self:
412 413 raise KeyError('dictionary is empty')
413 414 if last:
414 415 key = reversed(self).next()
415 416 else:
416 417 key = iter(self).next()
417 418 value = self.pop(key)
418 419 return key, value
419 420
420 421 def __reduce__(self):
421 422 items = [[k, self[k]] for k in self]
422 423 tmp = self.__map, self.__end
423 424 del self.__map, self.__end
424 425 inst_dict = vars(self).copy()
425 426 self.__map, self.__end = tmp
426 427 if inst_dict:
427 428 return (self.__class__, (items,), inst_dict)
428 429 return self.__class__, (items,)
429 430
430 431 def keys(self):
431 432 return list(self)
432 433
433 434 setdefault = DictMixin.setdefault
434 435 update = DictMixin.update
435 436 pop = DictMixin.pop
436 437 values = DictMixin.values
437 438 items = DictMixin.items
438 439 iterkeys = DictMixin.iterkeys
439 440 itervalues = DictMixin.itervalues
440 441 iteritems = DictMixin.iteritems
441 442
442 443 def __repr__(self):
443 444 if not self:
444 445 return '%s()' % (self.__class__.__name__,)
445 446 return '%s(%r)' % (self.__class__.__name__, self.items())
446 447
447 448 def copy(self):
448 449 return self.__class__(self)
449 450
450 451 @classmethod
451 452 def fromkeys(cls, iterable, value=None):
452 453 d = cls()
453 454 for key in iterable:
454 455 d[key] = value
455 456 return d
456 457
457 458 def __eq__(self, other):
458 459 if isinstance(other, OrderedDict):
459 460 return len(self) == len(other) and self.items() == other.items()
460 461 return dict.__eq__(self, other)
461 462
462 463 def __ne__(self, other):
463 464 return not self == other
464 465
465 466
466 467 #===============================================================================
467 468 # TEST FUNCTIONS AND CREATORS
468 469 #===============================================================================
469 470 def create_test_index(repo_location, full_index):
470 471 """Makes default test index
471 472 :param repo_location:
472 473 :param full_index:
473 474 """
474 475 from rhodecode.lib.indexers.daemon import WhooshIndexingDaemon
475 476 from rhodecode.lib.pidlock import DaemonLock, LockHeld
476 477 from rhodecode.lib.indexers import IDX_LOCATION
477 478 import shutil
478 479
479 480 if os.path.exists(IDX_LOCATION):
480 481 shutil.rmtree(IDX_LOCATION)
481 482
482 483 try:
483 484 l = DaemonLock()
484 485 WhooshIndexingDaemon(repo_location=repo_location)\
485 486 .run(full_index=full_index)
486 487 l.release()
487 488 except LockHeld:
488 489 pass
489 490
490 491 def create_test_env(repos_test_path, config):
491 492 """Makes a fresh database and
492 493 install test repository into tmp dir
493 494 """
494 495 from rhodecode.lib.db_manage import DbManage
495 496 import tarfile
496 497 import shutil
497 498 from os.path import dirname as dn, join as jn, abspath
498 499
499 500 log = logging.getLogger('TestEnvCreator')
500 501 # create logger
501 502 log.setLevel(logging.DEBUG)
502 503 log.propagate = True
503 504 # create console handler and set level to debug
504 505 ch = logging.StreamHandler()
505 506 ch.setLevel(logging.DEBUG)
506 507
507 508 # create formatter
508 509 formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
509 510
510 511 # add formatter to ch
511 512 ch.setFormatter(formatter)
512 513
513 514 # add ch to logger
514 515 log.addHandler(ch)
515 516
516 517 #PART ONE create db
517 518 dbname = config['sqlalchemy.db1.url'].split('/')[-1]
518 519 log.debug('making test db %s', dbname)
519 520
520 521 dbmanage = DbManage(log_sql=True, dbname=dbname, root=config['here'],
521 522 tests=True)
522 523 dbmanage.create_tables(override=True)
523 524 dbmanage.config_prompt(repos_test_path)
524 525 dbmanage.create_default_user()
525 526 dbmanage.admin_prompt()
526 527 dbmanage.create_permissions()
527 528 dbmanage.populate_default_permissions()
528 529
529 530 #PART TWO make test repo
530 531 log.debug('making test vcs repo')
531 532 if os.path.isdir('/tmp/vcs_test'):
532 533 shutil.rmtree('/tmp/vcs_test')
533 534
534 535 cur_dir = dn(dn(abspath(__file__)))
535 536 tar = tarfile.open(jn(cur_dir, 'tests', "vcs_test.tar.gz"))
536 537 tar.extractall('/tmp')
537 538 tar.close()
@@ -1,353 +1,363 b''
1 1 """ this is forms validation classes
2 2 http://formencode.org/module-formencode.validators.html
3 3 for list off all availible validators
4 4
5 5 we can create our own validators
6 6
7 7 The table below outlines the options which can be used in a schema in addition to the validators themselves
8 8 pre_validators [] These validators will be applied before the schema
9 9 chained_validators [] These validators will be applied after the schema
10 10 allow_extra_fields False If True, then it is not an error when keys that aren't associated with a validator are present
11 11 filter_extra_fields False If True, then keys that aren't associated with a validator are removed
12 12 if_key_missing NoDefault If this is given, then any keys that aren't available but are expected will be replaced with this value (and then validated). This does not override a present .if_missing attribute on validators. NoDefault is a special FormEncode class to mean that no default values has been specified and therefore missing keys shouldn't take a default value.
13 13 ignore_key_missing False If True, then missing keys will be missing in the result, if the validator doesn't have .if_missing on it already
14 14
15 15
16 16 <name> = formencode.validators.<name of validator>
17 17 <name> must equal form name
18 18 list=[1,2,3,4,5]
19 19 for SELECT use formencode.All(OneOf(list), Int())
20 20
21 21 """
22 22 from formencode import All
23 23 from formencode.validators import UnicodeString, OneOf, Int, Number, Regex, \
24 24 Email, Bool, StringBoolean
25 25 from pylons import session
26 26 from pylons.i18n.translation import _
27 27 from rhodecode.lib.auth import check_password, get_crypt_password
28 28 from rhodecode.model import meta
29 29 from rhodecode.model.user import UserModel
30 30 from rhodecode.model.repo import RepoModel
31 31 from rhodecode.model.db import User
32 32 from webhelpers.pylonslib.secure_form import authentication_token
33 from vcs import BACKENDS
33 34 import formencode
34 35 import logging
35 36 import os
36 37 import rhodecode.lib.helpers as h
37 38
38 39 log = logging.getLogger(__name__)
39 40
40 41 #this is needed to translate the messages using _() in validators
41 42 class State_obj(object):
42 43 _ = staticmethod(_)
43 44
44 45 #===============================================================================
45 46 # VALIDATORS
46 47 #===============================================================================
47 48 class ValidAuthToken(formencode.validators.FancyValidator):
48 49 messages = {'invalid_token':_('Token mismatch')}
49 50
50 51 def validate_python(self, value, state):
51 52
52 53 if value != authentication_token():
53 54 raise formencode.Invalid(self.message('invalid_token', state,
54 55 search_number=value), value, state)
55 56
56 57 def ValidUsername(edit, old_data):
57 58 class _ValidUsername(formencode.validators.FancyValidator):
58 59
59 60 def validate_python(self, value, state):
60 61 if value in ['default', 'new_user']:
61 62 raise formencode.Invalid(_('Invalid username'), value, state)
62 63 #check if user is unique
63 64 old_un = None
64 65 if edit:
65 66 old_un = UserModel().get(old_data.get('user_id')).username
66 67
67 68 if old_un != value or not edit:
68 69 if UserModel().get_by_username(value, cache=False):
69 70 raise formencode.Invalid(_('This username already exists') ,
70 71 value, state)
71 72
72 73 return _ValidUsername
73 74
74 75 class ValidPassword(formencode.validators.FancyValidator):
75 76
76 77 def to_python(self, value, state):
77 78 if value:
78 79 return get_crypt_password(value)
79 80
80 81 class ValidAuth(formencode.validators.FancyValidator):
81 82 messages = {
82 83 'invalid_password':_('invalid password'),
83 84 'invalid_login':_('invalid user name'),
84 85 'disabled_account':_('Your acccount is disabled')
85 86
86 87 }
87 88 #error mapping
88 89 e_dict = {'username':messages['invalid_login'],
89 90 'password':messages['invalid_password']}
90 91 e_dict_disable = {'username':messages['disabled_account']}
91 92
92 93 def validate_python(self, value, state):
93 94 password = value['password']
94 95 username = value['username']
95 96 user = UserModel().get_by_username(username)
96 97 if user is None:
97 98 raise formencode.Invalid(self.message('invalid_password',
98 99 state=State_obj), value, state,
99 100 error_dict=self.e_dict)
100 101 if user:
101 102 if user.active:
102 103 if user.username == username and check_password(password,
103 104 user.password):
104 105 return value
105 106 else:
106 107 log.warning('user %s not authenticated', username)
107 108 raise formencode.Invalid(self.message('invalid_password',
108 109 state=State_obj), value, state,
109 110 error_dict=self.e_dict)
110 111 else:
111 112 log.warning('user %s is disabled', username)
112 113 raise formencode.Invalid(self.message('disabled_account',
113 114 state=State_obj),
114 115 value, state,
115 116 error_dict=self.e_dict_disable)
116 117
117 118 class ValidRepoUser(formencode.validators.FancyValidator):
118 119
119 120 def to_python(self, value, state):
120 121 sa = meta.Session()
121 122 try:
122 123 self.user_db = sa.query(User)\
123 124 .filter(User.active == True)\
124 125 .filter(User.username == value).one()
125 126 except Exception:
126 127 raise formencode.Invalid(_('This username is not valid'),
127 128 value, state)
128 129 finally:
129 130 meta.Session.remove()
130 131
131 132 return self.user_db.user_id
132 133
133 134 def ValidRepoName(edit, old_data):
134 135 class _ValidRepoName(formencode.validators.FancyValidator):
135 136
136 137 def to_python(self, value, state):
137 138 slug = h.repo_name_slug(value)
138 139 if slug in ['_admin']:
139 140 raise formencode.Invalid(_('This repository name is disallowed'),
140 141 value, state)
141 142 if old_data.get('repo_name') != value or not edit:
142 143 if RepoModel().get(slug, cache=False):
143 144 raise formencode.Invalid(_('This repository already exists') ,
144 145 value, state)
145 146 return slug
146 147
147 148
148 149 return _ValidRepoName
149 150
151 def ValidForkType(old_data):
152 class _ValidForkType(formencode.validators.FancyValidator):
153
154 def to_python(self, value, state):
155 if old_data['repo_type'] != value:
156 raise formencode.Invalid(_('Fork have to be the same type as original'), value, state)
157 return value
158 return _ValidForkType
159
150 160 class ValidPerms(formencode.validators.FancyValidator):
151 161 messages = {'perm_new_user_name':_('This username is not valid')}
152 162
153 163 def to_python(self, value, state):
154 164 perms_update = []
155 165 perms_new = []
156 166 #build a list of permission to update and new permission to create
157 167 for k, v in value.items():
158 168 if k.startswith('perm_'):
159 169 if k.startswith('perm_new_user'):
160 170 new_perm = value.get('perm_new_user', False)
161 171 new_user = value.get('perm_new_user_name', False)
162 172 if new_user and new_perm:
163 173 if (new_user, new_perm) not in perms_new:
164 174 perms_new.append((new_user, new_perm))
165 175 else:
166 176 usr = k[5:]
167 177 if usr == 'default':
168 178 if value['private']:
169 179 #set none for default when updating to private repo
170 180 v = 'repository.none'
171 181 perms_update.append((usr, v))
172 182 value['perms_updates'] = perms_update
173 183 value['perms_new'] = perms_new
174 184 sa = meta.Session
175 185 for k, v in perms_new:
176 186 try:
177 187 self.user_db = sa.query(User)\
178 188 .filter(User.active == True)\
179 189 .filter(User.username == k).one()
180 190 except Exception:
181 191 msg = self.message('perm_new_user_name',
182 192 state=State_obj)
183 193 raise formencode.Invalid(msg, value, state, error_dict={'perm_new_user_name':msg})
184 194 return value
185 195
186 196 class ValidSettings(formencode.validators.FancyValidator):
187 197
188 198 def to_python(self, value, state):
189 199 #settings form can't edit user
190 200 if value.has_key('user'):
191 201 del['value']['user']
192 202
193 203 return value
194 204
195 205 class ValidPath(formencode.validators.FancyValidator):
196 206 def to_python(self, value, state):
197 207
198 208 if not os.path.isdir(value):
199 209 msg = _('This is not a valid path')
200 210 raise formencode.Invalid(msg, value, state,
201 211 error_dict={'paths_root_path':msg})
202 212 return value
203 213
204 214 def UniqSystemEmail(old_data):
205 215 class _UniqSystemEmail(formencode.validators.FancyValidator):
206 216 def to_python(self, value, state):
207 217 if old_data.get('email') != value:
208 218 sa = meta.Session()
209 219 try:
210 220 user = sa.query(User).filter(User.email == value).scalar()
211 221 if user:
212 222 raise formencode.Invalid(_("That e-mail address is already taken") ,
213 223 value, state)
214 224 finally:
215 225 meta.Session.remove()
216 226
217 227 return value
218 228
219 229 return _UniqSystemEmail
220 230
221 231 class ValidSystemEmail(formencode.validators.FancyValidator):
222 232 def to_python(self, value, state):
223 233 sa = meta.Session
224 234 try:
225 235 user = sa.query(User).filter(User.email == value).scalar()
226 236 if user is None:
227 237 raise formencode.Invalid(_("That e-mail address doesn't exist.") ,
228 238 value, state)
229 239 finally:
230 240 meta.Session.remove()
231 241
232 242 return value
233 243
234 244 #===============================================================================
235 245 # FORMS
236 246 #===============================================================================
237 247 class LoginForm(formencode.Schema):
238 248 allow_extra_fields = True
239 249 filter_extra_fields = True
240 250 username = UnicodeString(
241 251 strip=True,
242 252 min=1,
243 253 not_empty=True,
244 254 messages={
245 255 'empty':_('Please enter a login'),
246 256 'tooShort':_('Enter a value %(min)i characters long or more')}
247 257 )
248 258
249 259 password = UnicodeString(
250 260 strip=True,
251 261 min=6,
252 262 not_empty=True,
253 263 messages={
254 264 'empty':_('Please enter a password'),
255 265 'tooShort':_('Enter %(min)i characters or more')}
256 266 )
257 267
258 268
259 269 #chained validators have access to all data
260 270 chained_validators = [ValidAuth]
261 271
262 272 def UserForm(edit=False, old_data={}):
263 273 class _UserForm(formencode.Schema):
264 274 allow_extra_fields = True
265 275 filter_extra_fields = True
266 276 username = All(UnicodeString(strip=True, min=1, not_empty=True), ValidUsername(edit, old_data))
267 277 if edit:
268 278 new_password = All(UnicodeString(strip=True, min=6, not_empty=False), ValidPassword)
269 279 admin = StringBoolean(if_missing=False)
270 280 else:
271 281 password = All(UnicodeString(strip=True, min=6, not_empty=True), ValidPassword)
272 282 active = StringBoolean(if_missing=False)
273 283 name = UnicodeString(strip=True, min=1, not_empty=True)
274 284 lastname = UnicodeString(strip=True, min=1, not_empty=True)
275 285 email = All(Email(not_empty=True), UniqSystemEmail(old_data))
276 286
277 287 return _UserForm
278 288
279 289 RegisterForm = UserForm
280 290
281 291 def PasswordResetForm():
282 292 class _PasswordResetForm(formencode.Schema):
283 293 allow_extra_fields = True
284 294 filter_extra_fields = True
285 295 email = All(ValidSystemEmail(), Email(not_empty=True))
286 296 return _PasswordResetForm
287 297
288 298 def RepoForm(edit=False, old_data={}):
289 299 class _RepoForm(formencode.Schema):
290 300 allow_extra_fields = True
291 301 filter_extra_fields = False
292 302 repo_name = All(UnicodeString(strip=True, min=1, not_empty=True), ValidRepoName(edit, old_data))
293 303 description = UnicodeString(strip=True, min=1, not_empty=True)
294 304 private = StringBoolean(if_missing=False)
295
305 repo_type = OneOf(BACKENDS.keys())
296 306 if edit:
297 307 user = All(Int(not_empty=True), ValidRepoUser)
298 308
299 309 chained_validators = [ValidPerms]
300 310 return _RepoForm
301 311
302 312 def RepoForkForm(edit=False, old_data={}):
303 313 class _RepoForkForm(formencode.Schema):
304 314 allow_extra_fields = True
305 315 filter_extra_fields = False
306 316 fork_name = All(UnicodeString(strip=True, min=1, not_empty=True), ValidRepoName(edit, old_data))
307 317 description = UnicodeString(strip=True, min=1, not_empty=True)
308 318 private = StringBoolean(if_missing=False)
309
319 repo_type = All(ValidForkType(old_data), OneOf(BACKENDS.keys()))
310 320 return _RepoForkForm
311 321
312 322 def RepoSettingsForm(edit=False, old_data={}):
313 323 class _RepoForm(formencode.Schema):
314 324 allow_extra_fields = True
315 325 filter_extra_fields = False
316 326 repo_name = All(UnicodeString(strip=True, min=1, not_empty=True), ValidRepoName(edit, old_data))
317 327 description = UnicodeString(strip=True, min=1, not_empty=True)
318 328 private = StringBoolean(if_missing=False)
319 329
320 330 chained_validators = [ValidPerms, ValidSettings]
321 331 return _RepoForm
322 332
323 333
324 334 def ApplicationSettingsForm():
325 335 class _ApplicationSettingsForm(formencode.Schema):
326 336 allow_extra_fields = True
327 337 filter_extra_fields = False
328 338 rhodecode_title = UnicodeString(strip=True, min=1, not_empty=True)
329 339 rhodecode_realm = UnicodeString(strip=True, min=1, not_empty=True)
330 340
331 341 return _ApplicationSettingsForm
332 342
333 343 def ApplicationUiSettingsForm():
334 344 class _ApplicationUiSettingsForm(formencode.Schema):
335 345 allow_extra_fields = True
336 346 filter_extra_fields = False
337 347 web_push_ssl = OneOf(['true', 'false'], if_missing='false')
338 348 paths_root_path = All(ValidPath(), UnicodeString(strip=True, min=1, not_empty=True))
339 349 hooks_changegroup_update = OneOf(['True', 'False'], if_missing=False)
340 350 hooks_changegroup_repo_size = OneOf(['True', 'False'], if_missing=False)
341 351
342 352 return _ApplicationUiSettingsForm
343 353
344 354 def DefaultPermissionsForm(perms_choices, register_choices, create_choices):
345 355 class _DefaultPermissionsForm(formencode.Schema):
346 356 allow_extra_fields = True
347 357 filter_extra_fields = True
348 358 overwrite_default = OneOf(['true', 'false'], if_missing='false')
349 359 default_perm = OneOf(perms_choices)
350 360 default_register = OneOf(register_choices)
351 361 default_create = OneOf(create_choices)
352 362
353 363 return _DefaultPermissionsForm
@@ -1,210 +1,210 b''
1 1 #!/usr/bin/env python
2 2 # encoding: utf-8
3 3 # model for handling repositories actions
4 4 # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
5 5 # This program is free software; you can redistribute it and/or
6 6 # modify it under the terms of the GNU General Public License
7 7 # as published by the Free Software Foundation; version 2
8 8 # of the License or (at your opinion) any later version of the license.
9 9 #
10 10 # This program is distributed in the hope that it will be useful,
11 11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 13 # GNU General Public License for more details.
14 14 #
15 15 # You should have received a copy of the GNU General Public License
16 16 # along with this program; if not, write to the Free Software
17 17 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
18 18 # MA 02110-1301, USA.
19 19 """
20 20 Created on Jun 5, 2010
21 21 model for handling repositories actions
22 22 :author: marcink
23 23 """
24
24 from vcs.backends import get_repo, get_backend
25 25 from datetime import datetime
26 26 from pylons import app_globals as g
27 27 from rhodecode.model.db import Repository, RepoToPerm, User, Permission
28 28 from rhodecode.model.meta import Session
29 29 from rhodecode.model.user import UserModel
30 30 from rhodecode.model.caching_query import FromCache
31 31 import logging
32 32 import os
33 33 import shutil
34 34 import traceback
35 35 log = logging.getLogger(__name__)
36 36
37 37 class RepoModel(object):
38 38
39 39 def __init__(self, sa=None):
40 40 if not sa:
41 41 self.sa = Session()
42 42 else:
43 43 self.sa = sa
44 44
45 45 def get(self, repo_id, cache=False):
46 46 repo = self.sa.query(Repository)\
47 47 .filter(Repository.repo_name == repo_id)
48 48
49 49 if cache:
50 50 repo = repo.options(FromCache("sql_cache_short",
51 51 "get_repo_%s" % repo))
52 52 return repo.scalar()
53 53
54 54 def get_users_js(self):
55 55
56 56 users = self.sa.query(User).filter(User.active == True).all()
57 57 u_tmpl = '''{id:%s, fname:"%s", lname:"%s", nname:"%s"},'''
58 58 users_array = '[%s];' % '\n'.join([u_tmpl % (u.user_id, u.name,
59 59 u.lastname, u.username)
60 60 for u in users])
61 61 return users_array
62 62
63 63
64 64 def update(self, repo_name, form_data):
65 65 try:
66 66
67 67 #update permissions
68 68 for username, perm in form_data['perms_updates']:
69 69 r2p = self.sa.query(RepoToPerm)\
70 70 .filter(RepoToPerm.user == UserModel(self.sa).get_by_username(username, cache=False))\
71 71 .filter(RepoToPerm.repository == self.get(repo_name))\
72 72 .one()
73 73
74 74 r2p.permission_id = self.sa.query(Permission).filter(
75 75 Permission.permission_name ==
76 76 perm).one().permission_id
77 77 self.sa.add(r2p)
78 78
79 79 #set new permissions
80 80 for username, perm in form_data['perms_new']:
81 81 r2p = RepoToPerm()
82 82 r2p.repository = self.get(repo_name)
83 83 r2p.user = UserModel(self.sa).get_by_username(username, cache=False)
84 84
85 85 r2p.permission_id = self.sa.query(Permission).filter(
86 86 Permission.permission_name == perm)\
87 87 .one().permission_id
88 88 self.sa.add(r2p)
89 89
90 90 #update current repo
91 91 cur_repo = self.get(repo_name, cache=False)
92 92
93 93 for k, v in form_data.items():
94 94 if k == 'user':
95 95 cur_repo.user_id = v
96 96 else:
97 97 setattr(cur_repo, k, v)
98 98
99 99 self.sa.add(cur_repo)
100 100
101 101 if repo_name != form_data['repo_name']:
102 102 #rename our data
103 103 self.__rename_repo(repo_name, form_data['repo_name'])
104 104
105 105 self.sa.commit()
106 106 except:
107 107 log.error(traceback.format_exc())
108 108 self.sa.rollback()
109 109 raise
110 110
111 111 def create(self, form_data, cur_user, just_db=False, fork=False):
112 112 try:
113 113 if fork:
114 114 #force str since hg doesn't go with unicode
115 115 repo_name = str(form_data['fork_name'])
116 116 org_name = str(form_data['repo_name'])
117 117
118 118 else:
119 119 org_name = repo_name = str(form_data['repo_name'])
120 120 new_repo = Repository()
121 121 for k, v in form_data.items():
122 122 if k == 'repo_name':
123 123 v = repo_name
124 124 setattr(new_repo, k, v)
125 125
126 126 if fork:
127 127 parent_repo = self.sa.query(Repository)\
128 128 .filter(Repository.repo_name == org_name).scalar()
129 129 new_repo.fork = parent_repo
130 130
131 131 new_repo.user_id = cur_user.user_id
132 132 self.sa.add(new_repo)
133 133
134 134 #create default permission
135 135 repo_to_perm = RepoToPerm()
136 136 default = 'repository.read'
137 137 for p in UserModel(self.sa).get_by_username('default', cache=False).user_perms:
138 138 if p.permission.permission_name.startswith('repository.'):
139 139 default = p.permission.permission_name
140 140 break
141 141
142 142 default_perm = 'repository.none' if form_data['private'] else default
143 143
144 144 repo_to_perm.permission_id = self.sa.query(Permission)\
145 145 .filter(Permission.permission_name == default_perm)\
146 146 .one().permission_id
147 147
148 148 repo_to_perm.repository_id = new_repo.repo_id
149 149 repo_to_perm.user_id = UserModel(self.sa).get_by_username('default', cache=False).user_id
150 150
151 151 self.sa.add(repo_to_perm)
152 152 self.sa.commit()
153 153 if not just_db:
154 self.__create_repo(repo_name)
154 self.__create_repo(repo_name, form_data['repo_type'])
155 155 except:
156 156 log.error(traceback.format_exc())
157 157 self.sa.rollback()
158 158 raise
159 159
160 160 def create_fork(self, form_data, cur_user):
161 161 from rhodecode.lib.celerylib import tasks, run_task
162 162 run_task(tasks.create_repo_fork, form_data, cur_user)
163 163
164 164 def delete(self, repo):
165 165 try:
166 166 self.sa.delete(repo)
167 167 self.sa.commit()
168 168 self.__delete_repo(repo.repo_name)
169 169 except:
170 170 log.error(traceback.format_exc())
171 171 self.sa.rollback()
172 172 raise
173 173
174 174 def delete_perm_user(self, form_data, repo_name):
175 175 try:
176 176 self.sa.query(RepoToPerm)\
177 177 .filter(RepoToPerm.repository == self.get(repo_name))\
178 178 .filter(RepoToPerm.user_id == form_data['user_id']).delete()
179 179 self.sa.commit()
180 180 except:
181 181 log.error(traceback.format_exc())
182 182 self.sa.rollback()
183 183 raise
184 184
185 def __create_repo(self, repo_name):
185 def __create_repo(self, repo_name, alias):
186 186 from rhodecode.lib.utils import check_repo
187 187 repo_path = os.path.join(g.base_path, repo_name)
188 188 if check_repo(repo_name, g.base_path):
189 189 log.info('creating repo %s in %s', repo_name, repo_path)
190 from vcs.backends.hg import MercurialRepository
191 MercurialRepository(repo_path, create=True)
190 backend = get_backend(alias)
191 backend(repo_path, create=True)
192 192
193 193 def __rename_repo(self, old, new):
194 194 log.info('renaming repo from %s to %s', old, new)
195 195
196 196 old_path = os.path.join(g.base_path, old)
197 197 new_path = os.path.join(g.base_path, new)
198 198 if os.path.isdir(new_path):
199 199 raise Exception('Was trying to rename to already existing dir %s',
200 200 new_path)
201 201 shutil.move(old_path, new_path)
202 202
203 203 def __delete_repo(self, name):
204 204 rm_path = os.path.join(g.base_path, name)
205 205 log.info("Removing %s", rm_path)
206 206 #disable hg
207 207 shutil.move(os.path.join(rm_path, '.hg'), os.path.join(rm_path, 'rm__.hg'))
208 208 #disable repo
209 209 shutil.move(rm_path, os.path.join(g.base_path, 'rm__%s__%s' \
210 210 % (datetime.today(), name)))
@@ -1,60 +1,68 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <%inherit file="/base/base.html"/>
3 3
4 4 <%def name="title()">
5 5 ${_('Add repository')} - ${c.rhodecode_name}
6 6 </%def>
7 7
8 8 <%def name="breadcrumbs_links()">
9 9 ${h.link_to(_('Admin'),h.url('admin_home'))}
10 10 &raquo;
11 11 ${h.link_to(_('Repositories'),h.url('repos'))}
12 12 &raquo;
13 13 ${_('add new')}
14 14 </%def>
15 15
16 16 <%def name="page_nav()">
17 17 ${self.menu('admin')}
18 18 </%def>
19 19 <%def name="main()">
20 20 <div class="box">
21 21 <!-- box / title -->
22 22 <div class="title">
23 23 ${self.breadcrumbs()}
24 24 </div>
25 25 ${h.form(url('repos'))}
26 26 <div class="form">
27 27 <!-- fields -->
28 28 <div class="fields">
29 29 <div class="field">
30 30 <div class="label">
31 31 <label for="repo_name">${_('Name')}:</label>
32 32 </div>
33 33 <div class="input">
34 34 ${h.text('repo_name',c.new_repo,class_="small")}
35 35 </div>
36 36 </div>
37 37 <div class="field">
38 <div class="label">
39 <label for="repo_type">${_('Type')}:</label>
40 </div>
41 <div class="input">
42 ${h.select('repo_type','hg',c.backends,class_="small")}
43 </div>
44 </div>
45 <div class="field">
38 46 <div class="label label-textarea">
39 47 <label for="description">${_('Description')}:</label>
40 48 </div>
41 49 <div class="textarea text-area editor">
42 50 ${h.textarea('description',cols=23,rows=5)}
43 51 </div>
44 52 </div>
45 53 <div class="field">
46 54 <div class="label label-checkbox">
47 55 <label for="private">${_('Private')}:</label>
48 56 </div>
49 57 <div class="checkboxes">
50 58 ${h.checkbox('private',value="True")}
51 59 </div>
52 60 </div>
53 61 <div class="buttons">
54 62 ${h.submit('add','add',class_="ui-button ui-widget ui-state-default ui-corner-all")}
55 63 </div>
56 64 </div>
57 65 </div>
58 66 ${h.end_form()}
59 67 </div>
60 68 </%def>
@@ -1,57 +1,65 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <%inherit file="/base/base.html"/>
3 3
4 4 <%def name="title()">
5 5 ${_('Add repository')} - ${c.rhodecode_name}
6 6 </%def>
7 7
8 8 <%def name="breadcrumbs_links()">
9 9 ${_('add new repository')}
10 10 </%def>
11 11
12 12 <%def name="page_nav()">
13 13 ${self.menu('admin')}
14 14 </%def>
15 15 <%def name="main()">
16 16 <div class="box">
17 17 <!-- box / title -->
18 18 <div class="title">
19 19 ${self.breadcrumbs()}
20 20 </div>
21 21 ${h.form(url('repos'))}
22 22 <div class="form">
23 23 <!-- fields -->
24 24 <div class="fields">
25 25 <div class="field">
26 26 <div class="label">
27 27 <label for="repo_name">${_('Name')}:</label>
28 28 </div>
29 29 <div class="input">
30 30 ${h.text('repo_name',c.new_repo,class_="small")}
31 31 ${h.hidden('user_created','True')}
32 32 </div>
33 33 </div>
34 34 <div class="field">
35 <div class="label">
36 <label for="repo_type">${_('Type')}:</label>
37 </div>
38 <div class="input">
39 ${h.select('repo_type','hg',c.backends,class_="small")}
40 </div>
41 </div>
42 <div class="field">
35 43 <div class="label label-textarea">
36 44 <label for="description">${_('Description')}:</label>
37 45 </div>
38 46 <div class="textarea text-area editor">
39 47 ${h.textarea('description',cols=23,rows=5)}
40 48 </div>
41 49 </div>
42 50 <div class="field">
43 51 <div class="label label-checkbox">
44 52 <label for="private">${_('Private')}:</label>
45 53 </div>
46 54 <div class="checkboxes">
47 55 ${h.checkbox('private',value="True")}
48 56 </div>
49 57 </div>
50 58 <div class="buttons">
51 59 ${h.submit('add','add',class_="ui-button ui-widget ui-state-default ui-corner-all")}
52 60 </div>
53 61 </div>
54 62 </div>
55 63 ${h.end_form()}
56 64 </div>
57 65 </%def>
@@ -1,275 +1,282 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <%inherit file="/base/base.html"/>
3 3
4 4 <%def name="title()">
5 5 ${_('Edit repository')} ${c.repo_info.repo_name} - ${c.rhodecode_name}
6 6 </%def>
7 7
8 8 <%def name="breadcrumbs_links()">
9 9 ${h.link_to(_('Admin'),h.url('admin_home'))}
10 10 &raquo;
11 11 ${h.link_to(_('Repositories'),h.url('repos'))}
12 12 &raquo;
13 13 ${_('edit')} "${c.repo_name}"
14 14 </%def>
15 15
16 16 <%def name="page_nav()">
17 17 ${self.menu('admin')}
18 18 </%def>
19 19
20 20 <%def name="main()">
21 21 <div class="box">
22 22 <!-- box / title -->
23 23 <div class="title">
24 24 ${self.breadcrumbs()}
25 25 </div>
26 26 ${h.form(url('repo', repo_name=c.repo_info.repo_name),method='put')}
27 27 <div class="form">
28 28 <!-- fields -->
29 29 <div class="fields">
30 30 <div class="field">
31 31 <div class="label">
32 32 <label for="repo_name">${_('Name')}:</label>
33 33 </div>
34 34 <div class="input input-medium">
35 35 ${h.text('repo_name',class_="small")}
36 36 </div>
37 37 </div>
38
38 <div class="field">
39 <div class="label">
40 <label for="repo_type">${_('Type')}:</label>
41 </div>
42 <div class="input">
43 ${h.select('repo_type','hg',c.backends,class_="small")}
44 </div>
45 </div>
39 46 <div class="field">
40 47 <div class="label label-textarea">
41 48 <label for="description">${_('Description')}:</label>
42 49 </div>
43 50 <div class="textarea text-area editor">
44 51 ${h.textarea('description',cols=23,rows=5)}
45 52 </div>
46 53 </div>
47 54
48 55 <div class="field">
49 56 <div class="label label-checkbox">
50 57 <label for="private">${_('Private')}:</label>
51 58 </div>
52 59 <div class="checkboxes">
53 60 ${h.checkbox('private',value="True")}
54 61 </div>
55 62 </div>
56 63
57 64 <div class="field">
58 65 <div class="label">
59 66 <label for="user">${_('Owner')}:</label>
60 67 </div>
61 68 <div class="input input-small ac">
62 69 <div class="perm_ac">
63 70 ${h.text('user',class_='yui-ac-input')}
64 71 <div id="owner_container"></div>
65 72 </div>
66 73 </div>
67 74 </div>
68 75
69 76 <div class="field">
70 77 <div class="label">
71 78 <label for="input">${_('Permissions')}:</label>
72 79 </div>
73 80 <div class="input">
74 81 <table id="permissions_manage">
75 82 <tr>
76 83 <td>${_('none')}</td>
77 84 <td>${_('read')}</td>
78 85 <td>${_('write')}</td>
79 86 <td>${_('admin')}</td>
80 87 <td>${_('user')}</td>
81 88 <td></td>
82 89 </tr>
83 90
84 91 %for r2p in c.repo_info.repo_to_perm:
85 92 %if r2p.user.username =='default' and c.repo_info.private:
86 93 <tr>
87 94 <td colspan="4">
88 95 <span class="private_repo_msg">
89 96 ${_('private repository')}
90 97 </span>
91 98 </td>
92 99 <td class="private_repo_msg">${r2p.user.username}</td>
93 100 </tr>
94 101 %else:
95 102 <tr id="id${id(r2p.user.username)}">
96 103 <td>${h.radio('perm_%s' % r2p.user.username,'repository.none')}</td>
97 104 <td>${h.radio('perm_%s' % r2p.user.username,'repository.read')}</td>
98 105 <td>${h.radio('perm_%s' % r2p.user.username,'repository.write')}</td>
99 106 <td>${h.radio('perm_%s' % r2p.user.username,'repository.admin')}</td>
100 107 <td>${r2p.user.username}</td>
101 108 <td>
102 109 %if r2p.user.username !='default':
103 110 <span class="delete_icon action_button" onclick="ajaxAction(${r2p.user.user_id},'${'id%s'%id(r2p.user.username)}')">
104 111 <script type="text/javascript">
105 112 function ajaxAction(user_id,field_id){
106 113 var sUrl = "${h.url('delete_repo_user',repo_name=c.repo_name)}";
107 114 var callback = { success:function(o){
108 115 var tr = YAHOO.util.Dom.get(String(field_id));
109 116 tr.parentNode.removeChild(tr);},failure:function(o){
110 117 alert("${_('Failed to remove user')}");},};
111 118 var postData = '_method=delete&user_id='+user_id;
112 119 var request = YAHOO.util.Connect.asyncRequest('POST', sUrl, callback, postData);};
113 120 </script>
114 121 </span>
115 122 %endif
116 123 </td>
117 124 </tr>
118 125 %endif
119 126 %endfor
120 127
121 128 <tr id="add_perm_input">
122 129 <td>${h.radio('perm_new_user','repository.none')}</td>
123 130 <td>${h.radio('perm_new_user','repository.read')}</td>
124 131 <td>${h.radio('perm_new_user','repository.write')}</td>
125 132 <td>${h.radio('perm_new_user','repository.admin')}</td>
126 133 <td class='ac'>
127 134 <div class="perm_ac" id="perm_ac">
128 135 ${h.text('perm_new_user_name',class_='yui-ac-input')}
129 136 <div id="perm_container"></div>
130 137 </div>
131 138 </td>
132 139 <td></td>
133 140 </tr>
134 141 <tr>
135 142 <td colspan="6">
136 143 <span id="add_perm" class="add_icon" style="cursor: pointer;">
137 144 ${_('Add another user')}
138 145 </span>
139 146 </td>
140 147 </tr>
141 148 </table>
142 149 </div>
143 150
144 151 <div class="buttons">
145 152 ${h.submit('save','save',class_="ui-button ui-widget ui-state-default ui-corner-all")}
146 153 </div>
147 154 </div>
148 155 </div>
149 156 </div>
150 157 ${h.end_form()}
151 158 <script type="text/javascript">
152 159 YAHOO.util.Event.onDOMReady(function(){
153 160 var D = YAHOO.util.Dom;
154 161 if(!D.hasClass('perm_new_user_name','error')){
155 162 D.setStyle('add_perm_input','display','none');
156 163 }
157 164 YAHOO.util.Event.addListener('add_perm','click',function(){
158 165 D.setStyle('add_perm_input','display','');
159 166 D.setStyle('add_perm','opacity','0.6');
160 167 D.setStyle('add_perm','cursor','default');
161 168 });
162 169 });
163 170 </script>
164 171 <script type="text/javascript">
165 172 YAHOO.example.FnMultipleFields = function(){
166 173 var myContacts = ${c.users_array|n}
167 174
168 175 // Define a custom search function for the DataSource
169 176 var matchNames = function(sQuery) {
170 177 // Case insensitive matching
171 178 var query = sQuery.toLowerCase(),
172 179 contact,
173 180 i=0,
174 181 l=myContacts.length,
175 182 matches = [];
176 183
177 184 // Match against each name of each contact
178 185 for(; i<l; i++) {
179 186 contact = myContacts[i];
180 187 if((contact.fname.toLowerCase().indexOf(query) > -1) ||
181 188 (contact.lname.toLowerCase().indexOf(query) > -1) ||
182 189 (contact.nname && (contact.nname.toLowerCase().indexOf(query) > -1))) {
183 190 matches[matches.length] = contact;
184 191 }
185 192 }
186 193
187 194 return matches;
188 195 };
189 196
190 197 // Use a FunctionDataSource
191 198 var oDS = new YAHOO.util.FunctionDataSource(matchNames);
192 199 oDS.responseSchema = {
193 200 fields: ["id", "fname", "lname", "nname"]
194 201 }
195 202
196 203 // Instantiate AutoComplete for perms
197 204 var oAC_perms = new YAHOO.widget.AutoComplete("perm_new_user_name", "perm_container", oDS);
198 205 oAC_perms.useShadow = false;
199 206 oAC_perms.resultTypeList = false;
200 207
201 208 // Instantiate AutoComplete for owner
202 209 var oAC_owner = new YAHOO.widget.AutoComplete("user", "owner_container", oDS);
203 210 oAC_owner.useShadow = false;
204 211 oAC_owner.resultTypeList = false;
205 212
206 213
207 214 // Custom formatter to highlight the matching letters
208 215 var custom_formatter = function(oResultData, sQuery, sResultMatch) {
209 216 var query = sQuery.toLowerCase(),
210 217 fname = oResultData.fname,
211 218 lname = oResultData.lname,
212 219 nname = oResultData.nname || "", // Guard against null value
213 220 query = sQuery.toLowerCase(),
214 221 fnameMatchIndex = fname.toLowerCase().indexOf(query),
215 222 lnameMatchIndex = lname.toLowerCase().indexOf(query),
216 223 nnameMatchIndex = nname.toLowerCase().indexOf(query),
217 224 displayfname, displaylname, displaynname;
218 225
219 226 if(fnameMatchIndex > -1) {
220 227 displayfname = highlightMatch(fname, query, fnameMatchIndex);
221 228 }
222 229 else {
223 230 displayfname = fname;
224 231 }
225 232
226 233 if(lnameMatchIndex > -1) {
227 234 displaylname = highlightMatch(lname, query, lnameMatchIndex);
228 235 }
229 236 else {
230 237 displaylname = lname;
231 238 }
232 239
233 240 if(nnameMatchIndex > -1) {
234 241 displaynname = "(" + highlightMatch(nname, query, nnameMatchIndex) + ")";
235 242 }
236 243 else {
237 244 displaynname = nname ? "(" + nname + ")" : "";
238 245 }
239 246
240 247 return displayfname + " " + displaylname + " " + displaynname;
241 248
242 249 };
243 250 oAC_perms.formatResult = custom_formatter;
244 251 oAC_owner.formatResult = custom_formatter;
245 252
246 253 // Helper function for the formatter
247 254 var highlightMatch = function(full, snippet, matchindex) {
248 255 return full.substring(0, matchindex) +
249 256 "<span class='match'>" +
250 257 full.substr(matchindex, snippet.length) +
251 258 "</span>" +
252 259 full.substring(matchindex + snippet.length);
253 260 };
254 261
255 262 var myHandler = function(sType, aArgs) {
256 263 var myAC = aArgs[0]; // reference back to the AC instance
257 264 var elLI = aArgs[1]; // reference to the selected LI element
258 265 var oData = aArgs[2]; // object literal of selected item's result data
259 266 myAC.getInputEl().value = oData.nname;
260 267 };
261 268
262 269 oAC_perms.itemSelectEvent.subscribe(myHandler);
263 270 oAC_owner.itemSelectEvent.subscribe(myHandler);
264 271
265 272 return {
266 273 oDS: oDS,
267 274 oAC_perms: oAC_perms,
268 275 oAC_owner: oAC_owner,
269 276 };
270 277 }();
271 278
272 279 </script>
273 280
274 281 </div>
275 282 </%def> No newline at end of file
@@ -1,58 +1,59 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <%inherit file="/base/base.html"/>
3 3
4 4 <%def name="title()">
5 5 ${c.repo_name} ${_('Fork')} - ${c.rhodecode_name}
6 6 </%def>
7 7
8 8 <%def name="breadcrumbs_links()">
9 9 ${h.link_to(c.repo_info.repo_name,h.url('summary_home',repo_name=c.repo_info.repo_name))}
10 10 &raquo;
11 11 ${_('fork')}
12 12 </%def>
13 13
14 14 <%def name="page_nav()">
15 15 ${self.menu('')}
16 16 </%def>
17 17 <%def name="main()">
18 18 <div class="box">
19 19 <!-- box / title -->
20 20 <div class="title">
21 21 ${self.breadcrumbs()}
22 22 </div>
23 23 ${h.form(url('repo_fork_create_home',repo_name=c.repo_info.repo_name))}
24 24 <div class="form">
25 25 <!-- fields -->
26 26 <div class="fields">
27 27 <div class="field">
28 28 <div class="label">
29 29 <label for="repo_name">${_('Fork name')}:</label>
30 30 </div>
31 31 <div class="input">
32 32 ${h.text('fork_name',class_="small")}
33 ${h.hidden('repo_type',c.repo_info.repo_type)}
33 34 </div>
34 35 </div>
35 36 <div class="field">
36 37 <div class="label label-textarea">
37 38 <label for="description">${_('Description')}:</label>
38 39 </div>
39 40 <div class="textarea text-area editor">
40 41 ${h.textarea('description',cols=23,rows=5)}
41 42 </div>
42 43 </div>
43 44 <div class="field">
44 45 <div class="label label-checkbox">
45 46 <label for="private">${_('Private')}:</label>
46 47 </div>
47 48 <div class="checkboxes">
48 49 ${h.checkbox('private',value="True")}
49 50 </div>
50 51 </div>
51 52 <div class="buttons">
52 53 ${h.submit('','fork this repository',class_="ui-button ui-widget ui-state-default ui-corner-all")}
53 54 </div>
54 55 </div>
55 56 </div>
56 57 ${h.end_form()}
57 58 </div>
58 59 </%def>
@@ -1,147 +1,147 b''
1 1 from rhodecode.tests import *
2 2 from rhodecode.model.db import User
3 3 from rhodecode.lib.auth import check_password
4 4
5 5
6 6 class TestLoginController(TestController):
7 7
8 8 def test_index(self):
9 9 response = self.app.get(url(controller='login', action='index'))
10 10 assert response.status == '200 OK', 'Wrong response from login page got %s' % response.status
11 11 # Test response...
12 12
13 13 def test_login_admin_ok(self):
14 14 response = self.app.post(url(controller='login', action='index'),
15 15 {'username':'test_admin',
16 16 'password':'test12'})
17 17 assert response.status == '302 Found', 'Wrong response code from login got %s' % response.status
18 18 assert response.session['rhodecode_user'].username == 'test_admin', 'wrong logged in user'
19 19 response = response.follow()
20 assert 'auto description for vcs_test' in response.body
21
20 assert 'vcs_test repository' in response.body
21
22 22 def test_login_regular_ok(self):
23 23 response = self.app.post(url(controller='login', action='index'),
24 24 {'username':'test_regular',
25 25 'password':'test12'})
26 26 print response
27 27 assert response.status == '302 Found', 'Wrong response code from login got %s' % response.status
28 28 assert response.session['rhodecode_user'].username == 'test_regular', 'wrong logged in user'
29 29 response = response.follow()
30 assert 'auto description for vcs_test' in response.body
30 assert 'vcs_test repository' in response.body
31 31 assert '<a title="Admin" href="/_admin">' not in response.body
32
32
33 33 def test_login_ok_came_from(self):
34 34 test_came_from = '/_admin/users'
35 35 response = self.app.post(url(controller='login', action='index', came_from=test_came_from),
36 36 {'username':'test_admin',
37 37 'password':'test12'})
38 38 assert response.status == '302 Found', 'Wrong response code from came from redirection'
39 39 response = response.follow()
40
40
41 41 assert response.status == '200 OK', 'Wrong response from login page got %s' % response.status
42 42 assert 'Users administration' in response.body, 'No proper title in response'
43
44
43
44
45 45 def test_login_short_password(self):
46 46 response = self.app.post(url(controller='login', action='index'),
47 47 {'username':'error',
48 48 'password':'test'})
49 49 assert response.status == '200 OK', 'Wrong response from login page'
50 50 print response.body
51 51 assert 'Enter 6 characters or more' in response.body, 'No error password message in response'
52 52
53 53 def test_login_wrong_username_password(self):
54 54 response = self.app.post(url(controller='login', action='index'),
55 55 {'username':'error',
56 56 'password':'test12'})
57 57 assert response.status == '200 OK', 'Wrong response from login page'
58
58
59 59 assert 'invalid user name' in response.body, 'No error username message in response'
60 60 assert 'invalid password' in response.body, 'No error password message in response'
61
62
61
62
63 63 def test_register(self):
64 64 response = self.app.get(url(controller='login', action='register'))
65 65 assert 'Sign Up to rhodecode' in response.body, 'wrong page for user registration'
66
66
67 67 def test_register_err_same_username(self):
68 68 response = self.app.post(url(controller='login', action='register'),
69 69 {'username':'test_admin',
70 70 'password':'test',
71 71 'email':'goodmail@domain.com',
72 72 'name':'test',
73 73 'lastname':'test'})
74
74
75 75 assert response.status == '200 OK', 'Wrong response from register page got %s' % response.status
76 assert 'This username already exists' in response.body
77
76 assert 'This username already exists' in response.body
77
78 78 def test_register_err_wrong_data(self):
79 79 response = self.app.post(url(controller='login', action='register'),
80 80 {'username':'xs',
81 81 'password':'',
82 82 'email':'goodmailm',
83 83 'name':'test',
84 84 'lastname':'test'})
85
85
86 86 assert response.status == '200 OK', 'Wrong response from register page got %s' % response.status
87 87 assert 'An email address must contain a single @' in response.body
88 88 assert 'Please enter a value' in response.body
89
90
91
89
90
91
92 92 def test_register_ok(self):
93 93 username = 'test_regular4'
94 94 password = 'qweqwe'
95 95 email = 'marcin@test.com'
96 96 name = 'testname'
97 97 lastname = 'testlastname'
98
98
99 99 response = self.app.post(url(controller='login', action='register'),
100 100 {'username':username,
101 101 'password':password,
102 102 'email':email,
103 103 'name':name,
104 104 'lastname':lastname})
105 105 print response.body
106 assert response.status == '302 Found', 'Wrong response from register page got %s' % response.status
106 assert response.status == '302 Found', 'Wrong response from register page got %s' % response.status
107 107 assert 'You have successfully registered into rhodecode' in response.session['flash'][0], 'No flash message about user registration'
108
108
109 109 ret = self.sa.query(User).filter(User.username == 'test_regular4').one()
110 110 assert ret.username == username , 'field mismatch %s %s' % (ret.username, username)
111 111 assert check_password(password, ret.password) == True , 'password mismatch'
112 112 assert ret.email == email , 'field mismatch %s %s' % (ret.email, email)
113 113 assert ret.name == name , 'field mismatch %s %s' % (ret.name, name)
114 114 assert ret.lastname == lastname , 'field mismatch %s %s' % (ret.lastname, lastname)
115
116
117 def test_forgot_password_wrong_mail(self):
115
116
117 def test_forgot_password_wrong_mail(self):
118 118 response = self.app.post(url(controller='login', action='password_reset'),
119 119 {'email':'marcin@wrongmail.org', })
120
120
121 121 assert "That e-mail address doesn't exist" in response.body, 'Missing error message about wrong email'
122
122
123 123 def test_forgot_password(self):
124 124 response = self.app.get(url(controller='login', action='password_reset'))
125 125 assert response.status == '200 OK', 'Wrong response from login page got %s' % response.status
126 126
127 127 username = 'test_password_reset_1'
128 128 password = 'qweqwe'
129 129 email = 'marcin@python-works.com'
130 130 name = 'passwd'
131 131 lastname = 'reset'
132
132
133 133 response = self.app.post(url(controller='login', action='register'),
134 134 {'username':username,
135 135 'password':password,
136 136 'email':email,
137 137 'name':name,
138 'lastname':lastname})
138 'lastname':lastname})
139 139 #register new user for email test
140 140 response = self.app.post(url(controller='login', action='password_reset'),
141 141 {'email':email, })
142 142 print response.session['flash']
143 143 assert 'You have successfully registered into rhodecode' in response.session['flash'][0], 'No flash message about user registration'
144 144 assert 'Your new password was sent' in response.session['flash'][1], 'No flash message about password reset'
145
146
147
145
146
147
General Comments 0
You need to be logged in to leave comments. Login now