##// END OF EJS Templates
Improved testing scenarios. Made test env creator...
marcink -
r473:6b934c96 celery
parent child Browse files
Show More
@@ -1,74 +1,77 b''
1 1 """Pylons environment configuration"""
2 2 from mako.lookup import TemplateLookup
3 3 from pylons.configuration import PylonsConfig
4 4 from pylons.error import handle_mako_error
5 5 from pylons_app.config.routing import make_map
6 6 from pylons_app.lib.auth import set_available_permissions, set_base_path
7 7 from pylons_app.lib.utils import repo2db_mapper, make_ui, set_hg_app_config
8 8 from pylons_app.model import init_model
9 9 from pylons_app.model.hg_model import _get_repos_cached_initial
10 10 from sqlalchemy import engine_from_config
11 11 import logging
12 12 import os
13 13 import pylons_app.lib.app_globals as app_globals
14 14 import pylons_app.lib.helpers
15 15
16 16 log = logging.getLogger(__name__)
17 17
18 18 def load_environment(global_conf, app_conf, initial=False):
19 19 """Configure the Pylons environment via the ``pylons.config``
20 20 object
21 21 """
22 22 config = PylonsConfig()
23 23
24 24 # Pylons paths
25 25 root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
26 26 paths = dict(root=root,
27 27 controllers=os.path.join(root, 'controllers'),
28 28 static_files=os.path.join(root, 'public'),
29 29 templates=[os.path.join(root, 'templates')])
30 30
31 31 # Initialize config with the basic options
32 32 config.init_app(global_conf, app_conf, package='pylons_app', paths=paths)
33 33
34 34 config['routes.map'] = make_map(config)
35 35 config['pylons.app_globals'] = app_globals.Globals(config)
36 36 config['pylons.h'] = pylons_app.lib.helpers
37 37
38 38 # Setup cache object as early as possible
39 39 import pylons
40 40 pylons.cache._push_object(config['pylons.app_globals'].cache)
41 41
42 42 # Create the Mako TemplateLookup, with the default auto-escaping
43 43 config['pylons.app_globals'].mako_lookup = TemplateLookup(
44 44 directories=paths['templates'],
45 45 error_handler=handle_mako_error,
46 46 module_directory=os.path.join(app_conf['cache_dir'], 'templates'),
47 47 input_encoding='utf-8', default_filters=['escape'],
48 48 imports=['from webhelpers.html import escape'])
49 49
50 50 #sets the c attribute access when don't existing attribute are accessed
51 51 config['pylons.strict_tmpl_context'] = True
52 test = os.path.split(config['__file__'])[-1] == 'tests.ini'
52 test = os.path.split(config['__file__'])[-1] == 'test.ini'
53 if test:
54 from pylons_app.lib.utils import make_test_env
55 make_test_env()
53 56 #MULTIPLE DB configs
54 57 # Setup the SQLAlchemy database engine
55 58 if config['debug'] and not test:
56 59 #use query time debugging.
57 60 from pylons_app.lib.timerproxy import TimerProxy
58 61 sa_engine_db1 = engine_from_config(config, 'sqlalchemy.db1.',
59 62 proxy=TimerProxy())
60 63 else:
61 64 sa_engine_db1 = engine_from_config(config, 'sqlalchemy.db1.')
62 65
63 66 init_model(sa_engine_db1)
64 67 #init baseui
65 68 config['pylons.app_globals'].baseui = make_ui('db')
66 69
67 70 repo2db_mapper(_get_repos_cached_initial(config['pylons.app_globals'], initial))
68 71 set_available_permissions(config)
69 72 set_base_path(config)
70 73 set_hg_app_config(config)
71 74 # CONFIGURATION OPTIONS HERE (note: all config options will override
72 75 # any Pylons config options)
73 76
74 77 return config
@@ -1,293 +1,293 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 July 14, 2010
22 22 settings controller for pylons
23 23 @author: marcink
24 24 """
25 25 from formencode import htmlfill
26 26 from pylons import request, session, tmpl_context as c, url, app_globals as g, \
27 27 config
28 28 from pylons.controllers.util import abort, redirect
29 29 from pylons.i18n.translation import _
30 30 from pylons_app.lib import helpers as h
31 31 from pylons_app.lib.auth import LoginRequired, HasPermissionAllDecorator, \
32 32 HasPermissionAnyDecorator
33 33 from pylons_app.lib.base import BaseController, render
34 34 from pylons_app.lib.utils import repo2db_mapper, invalidate_cache, \
35 35 set_hg_app_config, get_hg_settings, get_hg_ui_settings, make_ui
36 36 from pylons_app.model.db import User, UserLog, HgAppSettings, HgAppUi
37 37 from pylons_app.model.forms import UserForm, ApplicationSettingsForm, \
38 38 ApplicationUiSettingsForm
39 39 from pylons_app.model.hg_model import HgModel
40 40 from pylons_app.model.user_model import UserModel
41 41 from pylons_app.lib.celerylib import tasks,run_task
42 42 import formencode
43 43 import logging
44 44 import traceback
45 45
46 46 log = logging.getLogger(__name__)
47 47
48 48
49 49 class SettingsController(BaseController):
50 50 """REST Controller styled on the Atom Publishing Protocol"""
51 51 # To properly map this controller, ensure your config/routing.py
52 52 # file has a resource setup:
53 53 # map.resource('setting', 'settings', controller='admin/settings',
54 54 # path_prefix='/admin', name_prefix='admin_')
55 55
56 56
57 57 @LoginRequired()
58 58 def __before__(self):
59 59 c.admin_user = session.get('admin_user')
60 60 c.admin_username = session.get('admin_username')
61 61 super(SettingsController, self).__before__()
62 62
63 63
64 64 @HasPermissionAllDecorator('hg.admin')
65 65 def index(self, format='html'):
66 66 """GET /admin/settings: All items in the collection"""
67 67 # url('admin_settings')
68 68
69 69 defaults = get_hg_settings()
70 70 defaults.update(get_hg_ui_settings())
71 71 return htmlfill.render(
72 72 render('admin/settings/settings.html'),
73 73 defaults=defaults,
74 74 encoding="UTF-8",
75 75 force_defaults=False
76 76 )
77 77
78 78 @HasPermissionAllDecorator('hg.admin')
79 79 def create(self):
80 80 """POST /admin/settings: Create a new item"""
81 81 # url('admin_settings')
82 82
83 83 @HasPermissionAllDecorator('hg.admin')
84 84 def new(self, format='html'):
85 85 """GET /admin/settings/new: Form to create a new item"""
86 86 # url('admin_new_setting')
87 87
88 88 @HasPermissionAllDecorator('hg.admin')
89 89 def update(self, setting_id):
90 90 """PUT /admin/settings/setting_id: Update an existing item"""
91 91 # Forms posted to this method should contain a hidden field:
92 92 # <input type="hidden" name="_method" value="PUT" />
93 93 # Or using helpers:
94 94 # h.form(url('admin_setting', setting_id=ID),
95 95 # method='put')
96 96 # url('admin_setting', setting_id=ID)
97 97 if setting_id == 'mapping':
98 98 rm_obsolete = request.POST.get('destroy', False)
99 99 log.debug('Rescanning directories with destroy=%s', rm_obsolete)
100 100
101 101 initial = HgModel.repo_scan(g.paths[0][0], g.paths[0][1], g.baseui)
102 102 repo2db_mapper(initial, rm_obsolete)
103 103 invalidate_cache('cached_repo_list')
104 104 h.flash(_('Repositories sucessfully rescanned'), category='success')
105 105
106 106 if setting_id == 'whoosh':
107 107 repo_location = get_hg_ui_settings()['paths_root_path']
108 108 full_index = request.POST.get('full_index',False)
109 task = run_task(tasks.whoosh_index,True,repo_location,full_index)
109 task = run_task(tasks.whoosh_index,repo_location,full_index)
110 110
111 111 h.flash(_('Whoosh reindex task scheduled'), category='success')
112 112 if setting_id == 'global':
113 113
114 114 application_form = ApplicationSettingsForm()()
115 115 try:
116 116 form_result = application_form.to_python(dict(request.POST))
117 117
118 118 try:
119 119 hgsettings1 = self.sa.query(HgAppSettings)\
120 120 .filter(HgAppSettings.app_settings_name == 'title').one()
121 121 hgsettings1.app_settings_value = form_result['hg_app_title']
122 122
123 123 hgsettings2 = self.sa.query(HgAppSettings)\
124 124 .filter(HgAppSettings.app_settings_name == 'realm').one()
125 125 hgsettings2.app_settings_value = form_result['hg_app_realm']
126 126
127 127
128 128 self.sa.add(hgsettings1)
129 129 self.sa.add(hgsettings2)
130 130 self.sa.commit()
131 131 set_hg_app_config(config)
132 132 h.flash(_('Updated application settings'),
133 133 category='success')
134 134
135 135 except:
136 136 log.error(traceback.format_exc())
137 137 h.flash(_('error occured during updating application settings'),
138 138 category='error')
139 139
140 140 self.sa.rollback()
141 141
142 142
143 143 except formencode.Invalid as errors:
144 144 return htmlfill.render(
145 145 render('admin/settings/settings.html'),
146 146 defaults=errors.value,
147 147 errors=errors.error_dict or {},
148 148 prefix_error=False,
149 149 encoding="UTF-8")
150 150
151 151 if setting_id == 'mercurial':
152 152 application_form = ApplicationUiSettingsForm()()
153 153 try:
154 154 form_result = application_form.to_python(dict(request.POST))
155 155
156 156 try:
157 157
158 158 hgsettings1 = self.sa.query(HgAppUi)\
159 159 .filter(HgAppUi.ui_key == 'push_ssl').one()
160 160 hgsettings1.ui_value = form_result['web_push_ssl']
161 161
162 162 hgsettings2 = self.sa.query(HgAppUi)\
163 163 .filter(HgAppUi.ui_key == '/').one()
164 164 hgsettings2.ui_value = form_result['paths_root_path']
165 165
166 166
167 167 #HOOKS
168 168 hgsettings3 = self.sa.query(HgAppUi)\
169 169 .filter(HgAppUi.ui_key == 'changegroup.update').one()
170 170 hgsettings3.ui_active = bool(form_result['hooks_changegroup_update'])
171 171
172 172 hgsettings4 = self.sa.query(HgAppUi)\
173 173 .filter(HgAppUi.ui_key == 'changegroup.repo_size').one()
174 174 hgsettings4.ui_active = bool(form_result['hooks_changegroup_repo_size'])
175 175
176 176
177 177
178 178
179 179 self.sa.add(hgsettings1)
180 180 self.sa.add(hgsettings2)
181 181 self.sa.add(hgsettings3)
182 182 self.sa.add(hgsettings4)
183 183 self.sa.commit()
184 184
185 185 h.flash(_('Updated mercurial settings'),
186 186 category='success')
187 187
188 188 except:
189 189 log.error(traceback.format_exc())
190 190 h.flash(_('error occured during updating application settings'),
191 191 category='error')
192 192
193 193 self.sa.rollback()
194 194
195 195
196 196 except formencode.Invalid as errors:
197 197 return htmlfill.render(
198 198 render('admin/settings/settings.html'),
199 199 defaults=errors.value,
200 200 errors=errors.error_dict or {},
201 201 prefix_error=False,
202 202 encoding="UTF-8")
203 203
204 204
205 205
206 206 return redirect(url('admin_settings'))
207 207
208 208 @HasPermissionAllDecorator('hg.admin')
209 209 def delete(self, setting_id):
210 210 """DELETE /admin/settings/setting_id: Delete an existing item"""
211 211 # Forms posted to this method should contain a hidden field:
212 212 # <input type="hidden" name="_method" value="DELETE" />
213 213 # Or using helpers:
214 214 # h.form(url('admin_setting', setting_id=ID),
215 215 # method='delete')
216 216 # url('admin_setting', setting_id=ID)
217 217
218 218 @HasPermissionAllDecorator('hg.admin')
219 219 def show(self, setting_id, format='html'):
220 220 """GET /admin/settings/setting_id: Show a specific item"""
221 221 # url('admin_setting', setting_id=ID)
222 222
223 223 @HasPermissionAllDecorator('hg.admin')
224 224 def edit(self, setting_id, format='html'):
225 225 """GET /admin/settings/setting_id/edit: Form to edit an existing item"""
226 226 # url('admin_edit_setting', setting_id=ID)
227 227
228 228
229 229 def my_account(self):
230 230 """
231 231 GET /_admin/my_account Displays info about my account
232 232 """
233 233 # url('admin_settings_my_account')
234 234 c.user = self.sa.query(User).get(c.hg_app_user.user_id)
235 235 c.user_repos = []
236 236 for repo in c.cached_repo_list.values():
237 237 if repo.dbrepo.user.username == c.user.username:
238 238 c.user_repos.append(repo)
239 239
240 240 if c.user.username == 'default':
241 241 h.flash(_("You can't edit this user since it's"
242 242 " crucial for entire application"), category='warning')
243 243 return redirect(url('users'))
244 244
245 245 defaults = c.user.__dict__
246 246 return htmlfill.render(
247 247 render('admin/users/user_edit_my_account.html'),
248 248 defaults=defaults,
249 249 encoding="UTF-8",
250 250 force_defaults=False
251 251 )
252 252
253 253 def my_account_update(self):
254 254 """PUT /_admin/my_account_update: Update an existing item"""
255 255 # Forms posted to this method should contain a hidden field:
256 256 # <input type="hidden" name="_method" value="PUT" />
257 257 # Or using helpers:
258 258 # h.form(url('admin_settings_my_account_update'),
259 259 # method='put')
260 260 # url('admin_settings_my_account_update', id=ID)
261 261 user_model = UserModel()
262 262 uid = c.hg_app_user.user_id
263 263 _form = UserForm(edit=True, old_data={'user_id':uid})()
264 264 form_result = {}
265 265 try:
266 266 form_result = _form.to_python(dict(request.POST))
267 267 user_model.update_my_account(uid, form_result)
268 268 h.flash(_('Your account was updated succesfully'),
269 269 category='success')
270 270
271 271 except formencode.Invalid as errors:
272 272 #c.user = self.sa.query(User).get(c.hg_app_user.user_id)
273 273 return htmlfill.render(
274 274 render('admin/users/user_edit_my_account.html'),
275 275 defaults=errors.value,
276 276 errors=errors.error_dict or {},
277 277 prefix_error=False,
278 278 encoding="UTF-8")
279 279 except Exception:
280 280 log.error(traceback.format_exc())
281 281 h.flash(_('error occured during update of user %s') \
282 282 % form_result.get('username'), category='error')
283 283
284 284 return redirect(url('my_account'))
285 285
286 286 @HasPermissionAnyDecorator('hg.admin', 'hg.create.repository')
287 287 def create_repository(self):
288 288 """GET /_admin/create_repository: Form to create a new item"""
289 289 new_repo = request.GET.get('repo', '')
290 290 c.new_repo = h.repo_name_slug(new_repo)
291 291
292 292 return render('admin/repos/repo_add_create_repository.html')
293 293
@@ -1,70 +1,70 b''
1 1 #!/usr/bin/env python
2 2 # encoding: utf-8
3 3 # summary 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 April 18, 2010
22 22 summary controller for pylons
23 23 @author: marcink
24 24 """
25 25 from pylons import tmpl_context as c, request,url
26 26 from pylons_app.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
27 27 from pylons_app.lib.base import BaseController, render
28 28 from pylons_app.lib.utils import OrderedDict
29 29 from pylons_app.model.hg_model import HgModel
30 30 from webhelpers.paginate import Page
31 31 from pylons_app.lib.celerylib import run_task
32 32 from pylons_app.lib.celerylib.tasks import get_commits_stats
33 33 import logging
34 34
35 35 log = logging.getLogger(__name__)
36 36
37 37 class SummaryController(BaseController):
38 38
39 39 @LoginRequired()
40 40 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
41 41 'repository.admin')
42 42 def __before__(self):
43 43 super(SummaryController, self).__before__()
44 44
45 45 def index(self):
46 46 hg_model = HgModel()
47 47 c.repo_info = hg_model.get_repo(c.repo_name)
48 48 c.repo_changesets = Page(list(c.repo_info[:10]), page=1, items_per_page=20)
49 49 e = request.environ
50 50 uri = u'%(protocol)s://%(user)s@%(host)s/%(repo_name)s' % {
51 51 'protocol': e.get('wsgi.url_scheme'),
52 52 'user':str(c.hg_app_user.username),
53 53 'host':e.get('HTTP_HOST'),
54 54 'repo_name':c.repo_name, }
55 55 c.clone_repo_url = uri
56 56 c.repo_tags = OrderedDict()
57 57 for name, hash in c.repo_info.tags.items()[:10]:
58 58 c.repo_tags[name] = c.repo_info.get_changeset(hash)
59 59
60 60 c.repo_branches = OrderedDict()
61 61 for name, hash in c.repo_info.branches.items()[:10]:
62 62 c.repo_branches[name] = c.repo_info.get_changeset(hash)
63 63
64 task = run_task(get_commits_stats,False,c.repo_info.name)
64 task = run_task(get_commits_stats,c.repo_info.name)
65 65 c.ts_min = task.result[0]
66 66 c.ts_max = task.result[1]
67 67 c.commit_data = task.result[2]
68 68
69 69 return render('summary/summary.html')
70 70
@@ -1,364 +1,432 b''
1 1 #!/usr/bin/env python
2 2 # encoding: utf-8
3 3 # Utilities for hg app
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 import shutil
19 20
20 21 """
21 22 Created on April 18, 2010
22 23 Utilities for hg app
23 24 @author: marcink
24 25 """
25 26 from beaker.cache import cache_region
26 27 from mercurial import ui, config, hg
27 28 from mercurial.error import RepoError
28 29 from pylons_app.model import meta
29 30 from pylons_app.model.db import Repository, User, HgAppUi, HgAppSettings
30 31 from vcs.backends.base import BaseChangeset
31 32 from vcs.utils.lazy import LazyProperty
32 33 import logging
33 34 import os
35 from os.path import dirname as dn, join as jn
36 import tarfile
34 37 log = logging.getLogger(__name__)
35 38
36 39
37 40 def get_repo_slug(request):
38 41 return request.environ['pylons.routes_dict'].get('repo_name')
39 42
40 43 def is_mercurial(environ):
41 44 """
42 45 Returns True if request's target is mercurial server - header
43 46 ``HTTP_ACCEPT`` of such request would start with ``application/mercurial``.
44 47 """
45 48 http_accept = environ.get('HTTP_ACCEPT')
46 49 if http_accept and http_accept.startswith('application/mercurial'):
47 50 return True
48 51 return False
49 52
50 53 def check_repo_dir(paths):
51 54 repos_path = paths[0][1].split('/')
52 55 if repos_path[-1] in ['*', '**']:
53 56 repos_path = repos_path[:-1]
54 57 if repos_path[0] != '/':
55 58 repos_path[0] = '/'
56 59 if not os.path.isdir(os.path.join(*repos_path)):
57 60 raise Exception('Not a valid repository in %s' % paths[0][1])
58 61
59 62 def check_repo_fast(repo_name, base_path):
60 63 if os.path.isdir(os.path.join(base_path, repo_name)):return False
61 64 return True
62 65
63 66 def check_repo(repo_name, base_path, verify=True):
64 67
65 68 repo_path = os.path.join(base_path, repo_name)
66 69
67 70 try:
68 71 if not check_repo_fast(repo_name, base_path):
69 72 return False
70 73 r = hg.repository(ui.ui(), repo_path)
71 74 if verify:
72 75 hg.verify(r)
73 76 #here we hnow that repo exists it was verified
74 77 log.info('%s repo is already created', repo_name)
75 78 return False
76 79 except RepoError:
77 80 #it means that there is no valid repo there...
78 81 log.info('%s repo is free for creation', repo_name)
79 82 return True
80 83
81 84 def ask_ok(prompt, retries=4, complaint='Yes or no, please!'):
82 85 while True:
83 86 ok = raw_input(prompt)
84 87 if ok in ('y', 'ye', 'yes'): return True
85 88 if ok in ('n', 'no', 'nop', 'nope'): return False
86 89 retries = retries - 1
87 90 if retries < 0: raise IOError
88 91 print complaint
89 92
90 93 @cache_region('super_short_term', 'cached_hg_ui')
91 94 def get_hg_ui_cached():
92 95 try:
93 96 sa = meta.Session
94 97 ret = sa.query(HgAppUi).all()
95 98 finally:
96 99 meta.Session.remove()
97 100 return ret
98 101
99 102
100 103 def get_hg_settings():
101 104 try:
102 105 sa = meta.Session
103 106 ret = sa.query(HgAppSettings).all()
104 107 finally:
105 108 meta.Session.remove()
106 109
107 110 if not ret:
108 111 raise Exception('Could not get application settings !')
109 112 settings = {}
110 113 for each in ret:
111 114 settings['hg_app_' + each.app_settings_name] = each.app_settings_value
112 115
113 116 return settings
114 117
115 118 def get_hg_ui_settings():
116 119 try:
117 120 sa = meta.Session
118 121 ret = sa.query(HgAppUi).all()
119 122 finally:
120 123 meta.Session.remove()
121 124
122 125 if not ret:
123 126 raise Exception('Could not get application ui settings !')
124 127 settings = {}
125 128 for each in ret:
126 129 k = each.ui_key
127 130 v = each.ui_value
128 131 if k == '/':
129 132 k = 'root_path'
130 133
131 134 if k.find('.') != -1:
132 135 k = k.replace('.', '_')
133 136
134 137 if each.ui_section == 'hooks':
135 138 v = each.ui_active
136 139
137 140 settings[each.ui_section + '_' + k] = v
138 141
139 142 return settings
140 143
141 144 #propagated from mercurial documentation
142 145 ui_sections = ['alias', 'auth',
143 146 'decode/encode', 'defaults',
144 147 'diff', 'email',
145 148 'extensions', 'format',
146 149 'merge-patterns', 'merge-tools',
147 150 'hooks', 'http_proxy',
148 151 'smtp', 'patch',
149 152 'paths', 'profiling',
150 153 'server', 'trusted',
151 154 'ui', 'web', ]
152 155
153 156 def make_ui(read_from='file', path=None, checkpaths=True):
154 157 """
155 158 A function that will read python rc files or database
156 159 and make an mercurial ui object from read options
157 160
158 161 @param path: path to mercurial config file
159 162 @param checkpaths: check the path
160 163 @param read_from: read from 'file' or 'db'
161 164 """
162 165
163 166 baseui = ui.ui()
164 167
165 168 if read_from == 'file':
166 169 if not os.path.isfile(path):
167 170 log.warning('Unable to read config file %s' % path)
168 171 return False
169 172 log.debug('reading hgrc from %s', path)
170 173 cfg = config.config()
171 174 cfg.read(path)
172 175 for section in ui_sections:
173 176 for k, v in cfg.items(section):
174 177 baseui.setconfig(section, k, v)
175 178 log.debug('settings ui from file[%s]%s:%s', section, k, v)
176 179 if checkpaths:check_repo_dir(cfg.items('paths'))
177 180
178 181
179 182 elif read_from == 'db':
180 183 hg_ui = get_hg_ui_cached()
181 184 for ui_ in hg_ui:
182 185 if ui_.ui_active:
183 186 log.debug('settings ui from db[%s]%s:%s', ui_.ui_section, ui_.ui_key, ui_.ui_value)
184 187 baseui.setconfig(ui_.ui_section, ui_.ui_key, ui_.ui_value)
185 188
186 189
187 190 return baseui
188 191
189 192
190 193 def set_hg_app_config(config):
191 194 hgsettings = get_hg_settings()
192 195
193 196 for k, v in hgsettings.items():
194 197 config[k] = v
195 198
196 199 def invalidate_cache(name, *args):
197 200 """Invalidates given name cache"""
198 201
199 202 from beaker.cache import region_invalidate
200 203 log.info('INVALIDATING CACHE FOR %s', name)
201 204
202 205 """propagate our arguments to make sure invalidation works. First
203 206 argument has to be the name of cached func name give to cache decorator
204 207 without that the invalidation would not work"""
205 208 tmp = [name]
206 209 tmp.extend(args)
207 210 args = tuple(tmp)
208 211
209 212 if name == 'cached_repo_list':
210 213 from pylons_app.model.hg_model import _get_repos_cached
211 214 region_invalidate(_get_repos_cached, None, *args)
212 215
213 216 if name == 'full_changelog':
214 217 from pylons_app.model.hg_model import _full_changelog_cached
215 218 region_invalidate(_full_changelog_cached, None, *args)
216 219
217 220 class EmptyChangeset(BaseChangeset):
218 221
219 222 revision = -1
220 223 message = ''
221 224
222 225 @LazyProperty
223 226 def raw_id(self):
224 227 """
225 228 Returns raw string identifing this changeset, useful for web
226 229 representation.
227 230 """
228 231 return '0' * 12
229 232
230 233
231 234 def repo2db_mapper(initial_repo_list, remove_obsolete=False):
232 235 """
233 236 maps all found repositories into db
234 237 """
235 238 from pylons_app.model.repo_model import RepoModel
236 239
237 240 sa = meta.Session
238 241 user = sa.query(User).filter(User.admin == True).first()
239 242
240 243 rm = RepoModel()
241 244
242 245 for name, repo in initial_repo_list.items():
243 246 if not sa.query(Repository).filter(Repository.repo_name == name).scalar():
244 247 log.info('repository %s not found creating default', name)
245 248
246 249 form_data = {
247 250 'repo_name':name,
248 251 'description':repo.description if repo.description != 'unknown' else \
249 252 'auto description for %s' % name,
250 253 'private':False
251 254 }
252 255 rm.create(form_data, user, just_db=True)
253 256
254 257
255 258 if remove_obsolete:
256 259 #remove from database those repositories that are not in the filesystem
257 260 for repo in sa.query(Repository).all():
258 261 if repo.repo_name not in initial_repo_list.keys():
259 262 sa.delete(repo)
260 263 sa.commit()
261 264
262 265
263 266 meta.Session.remove()
264 267
265 268 from UserDict import DictMixin
266 269
267 270 class OrderedDict(dict, DictMixin):
268 271
269 272 def __init__(self, *args, **kwds):
270 273 if len(args) > 1:
271 274 raise TypeError('expected at most 1 arguments, got %d' % len(args))
272 275 try:
273 276 self.__end
274 277 except AttributeError:
275 278 self.clear()
276 279 self.update(*args, **kwds)
277 280
278 281 def clear(self):
279 282 self.__end = end = []
280 283 end += [None, end, end] # sentinel node for doubly linked list
281 284 self.__map = {} # key --> [key, prev, next]
282 285 dict.clear(self)
283 286
284 287 def __setitem__(self, key, value):
285 288 if key not in self:
286 289 end = self.__end
287 290 curr = end[1]
288 291 curr[2] = end[1] = self.__map[key] = [key, curr, end]
289 292 dict.__setitem__(self, key, value)
290 293
291 294 def __delitem__(self, key):
292 295 dict.__delitem__(self, key)
293 296 key, prev, next = self.__map.pop(key)
294 297 prev[2] = next
295 298 next[1] = prev
296 299
297 300 def __iter__(self):
298 301 end = self.__end
299 302 curr = end[2]
300 303 while curr is not end:
301 304 yield curr[0]
302 305 curr = curr[2]
303 306
304 307 def __reversed__(self):
305 308 end = self.__end
306 309 curr = end[1]
307 310 while curr is not end:
308 311 yield curr[0]
309 312 curr = curr[1]
310 313
311 314 def popitem(self, last=True):
312 315 if not self:
313 316 raise KeyError('dictionary is empty')
314 317 if last:
315 318 key = reversed(self).next()
316 319 else:
317 320 key = iter(self).next()
318 321 value = self.pop(key)
319 322 return key, value
320 323
321 324 def __reduce__(self):
322 325 items = [[k, self[k]] for k in self]
323 326 tmp = self.__map, self.__end
324 327 del self.__map, self.__end
325 328 inst_dict = vars(self).copy()
326 329 self.__map, self.__end = tmp
327 330 if inst_dict:
328 331 return (self.__class__, (items,), inst_dict)
329 332 return self.__class__, (items,)
330 333
331 334 def keys(self):
332 335 return list(self)
333 336
334 337 setdefault = DictMixin.setdefault
335 338 update = DictMixin.update
336 339 pop = DictMixin.pop
337 340 values = DictMixin.values
338 341 items = DictMixin.items
339 342 iterkeys = DictMixin.iterkeys
340 343 itervalues = DictMixin.itervalues
341 344 iteritems = DictMixin.iteritems
342 345
343 346 def __repr__(self):
344 347 if not self:
345 348 return '%s()' % (self.__class__.__name__,)
346 349 return '%s(%r)' % (self.__class__.__name__, self.items())
347 350
348 351 def copy(self):
349 352 return self.__class__(self)
350 353
351 354 @classmethod
352 355 def fromkeys(cls, iterable, value=None):
353 356 d = cls()
354 357 for key in iterable:
355 358 d[key] = value
356 359 return d
357 360
358 361 def __eq__(self, other):
359 362 if isinstance(other, OrderedDict):
360 363 return len(self) == len(other) and self.items() == other.items()
361 364 return dict.__eq__(self, other)
362 365
363 366 def __ne__(self, other):
364 367 return not self == other
368
369 def make_test_env():
370 """Makes a fresh database from base64+zlib dump and
371 install test repository into tmp dir
372 """
373 new_db_dump = """
374 eJztXN1vE8sVn9nxR+wAIXDpFiiXjSAXfEOc2ElwQkVLPjYf5NNOAklUydrYG3tv1t5ldx0nuUJV\noL
375 cPrVr1X7jSfUJ96nMfK1Xty23VqlWlPlRIlahUXbXqFUL0pTNjx5614xAoKEDmJ3t2zpkzM2fO\neHe+
376 zno+PqU5qrRmWDnFkXqAB0AIbkkSAKANf8+BKprwFzI0G28ECXQ+PufFEYT+Tehz6L/oaSnK\nwcFxGP
377 igFQfHjuMg4CehH7UA9Af0Y2ShWdSPLmOSg+N9x7U9eKf9PiC2nIWm4mTtri4nZ3Z9DE/5\nfOD0+RZY
378 VFdXFVstWHoXPOPFvDbKU3TdKCbNgp39GLZ5MPtKW5WtWKmstqFmtqVtzZRWt6NQRFjk\ngkhESJ6kbe
379 trim6rcFTAdcfuwqxhrNuprJLPqBnLKJhhSzWNpK1tq+aWkzXyN8wt3cjbScU0w7q2\nGqbyVSHYAXE5
380 kSv15RTMtOKo2YxUikjf+SgKg4Dc/38C6Dn6Gn2FnqDH6K+Y5ODgeGfhRRD6/ST0\n+Ujo9ZLQ4yEhQi
381 QUBBJCeFy4BLywHaCfCEXM+AJHOWpx39sMrux4IbzQ3gMc1XaSlpop6IoVvRxV\nLke6L4/cmx7vjedG
382 4qmVmXvTW5nl7PDaSmFEXR6ejC+YVrpnsNi1fn17fHldj06p6YH84tzaGKBF\n5ZWcSq66Uorn8Iih5W
383 /ZBolqejhl5O57mkEPqf6sOFCq3lRsu2hYaayHrTplJeJD/Uu3p7u3Er19\nS4sb26PmemQiE54vLKfn
384 I8Wx2/Nd+XurmbH4TOpupHdk25I/sYbmCgDQstK0oHLdpWGmc1U3MqR6\nbICF123RHb/QDNpIm1rFnk
385 HaJiWd0/Llpgzq41lzIJMrjMXi2/JmdyGxMDKnjs1FR9WMcduMb3TZ\nfZuZTXVs1uiS53NxY9yan4Vw
386 PDNICqEl3dKNlKJnDdshbYh2R7o7uwc6I1EpGr3RHbvREwn3D/T3\nd/fuBFAzaHdpUu7csi6Tw4ou94
387 zOLt3JxTNZo7g8muvV1Lg6sNj/SX4dD7srqenpfCJ6d3g5vKRM\njq/Ob3VHIXgJXaKx8PWBvoHrvfdg
388 MzhPVDl/vgek1TWloO927tbUdsqeNzfurK5Frq+v5NbHZ1bG\nCnZxdnxxbGStmOudnwub6+rQYNxZku
389 Wh28Ph9Nos2C3EfblVvhJlyPjvRY+Z8f91dzUHB8fhYf/x\nv3T/PwL47v87+iX6I45ycHC8dWhFV6Br
390 7ukVUQ/cYzroOYnaXZLoBGqD1TmW0IzOw/IUAJL9v6Dg\nA+jP6Ofo+yiBelFA+IvwC2EFMzmOCBJBD/
391 huMZsJ41+MZjuqFVYKjpFUUo62oThqosyV8mpRKtg4\nUtScrJTNdCqmSeNGwZFIFqmcRTPydwIeMPwp
392 W2ZOyRcU/SVLLWViym1v8oDOLrbcvJGvFpbWbGVV\nV9NhvweEZCyWslRcWVnINGzNMawtiXJxaRX5kM
393 8D+rqq8lZFtjaX+i2vB1zoxKL0dhrPSHSmj6u3\nFCzV4cH6fbuavSTFFEJp3KCUatsdqEa4aGkOqyel
394 y8IhwQM6BhhhrE2akSVkWfQKxKJ9jGhN8/NG\nWZCM/0H0q5r9P/Q79FvM5ODgeOtBZvLBIAkDARI2Nb
395 3E/h/O7wdDAAzBj+Cy8IXwpfAc/eZlat9R\noF+8eBE+bHXIgzSbIQcTyYJWiQjDCXlwQZYWBoemZKnC
396 lq4GAwUtqaWliZkFeUxOSDOzC9LM4tTU\nNYmm2GqKPqEX5KWFMmtd3WLJDUUvqCyDjhKqNDQ7OyUPzh
397 DmXGJiejCxLE3Ky9JVWl2IsBdnJuKL\nMssZHpeHJymjXMjEjHS1+5oUCYWCoYjgE+WLEGj5tLpp39Px
398 MzlJhjtKJytNSkYqUfRgHPlFUYQ/\nMKhZyPhm08DjMgdlUVPgSENj4DSyN1hp6u6Er8Kob3hplGEYrg
399 J2dxsrDLrZ6EpO6kYGlzCCdV2Y\nmJbrjVlS2G1Ohlc2aJ012TSqozuJLYpoiK0f8vjEm2Ij61MLJiP0
400 4g15XywapRffzpTPL166BB8k\naQeZqpXTbBv/4Gwm6nd1FpNAuqxKNuo4RsLdf1W+buQzrjSXkV1VuO
401 zjTgmG+vw+ceJSo5Yzmicj\nDNFE7n8BfQnQ33DAwcHxLqMFLxHEs47mkIGYrKM+xAsBMYZXBnquvLDC
402 D4Wsmne0FF3/kPm/gL6m\n8//DVp6Dg+PNo3b+7wOPAHgEH8F/CFfRT9GvD1u/vbFzv8kvdnTAhxF2nW
403 GrjqPlM3YNGdxrzbGb\nSOZuLN1o9uaScc3RXCnuVYhr+lZTi2sCd+C08iz4ZsAnxjtesAapZIrUMJpv
404 Bl8me7SGcfxBqtkv\ntrfDzwLU+pWdJU212fgJl93ZFGJ06qPWwNg0rWLkuuVPwxm2RfcS2YVOWrVTlm
405 a61o6uXimr4bJ4\npfp67r6So7MJeWJshhRcWf1ICXlUTsgzw/L87vpuj4XRrubsOjN2zCdOtjfqJNac
406 yQhLtcSOHzhj\nlKVOlsb/fwL0FAccHBzvLQJIhHRpIJAYXRPQ8R+i3wP84eDgeNfRCX3gAoRjGyk7Sc
407 78BUDPZdlJ\n0ZphSbvJZPyH6D8Afzg4ON5/HEMX4O7tD0v3/3OAPxwcHEcG1f0/hJ4A9Az9C184ODje
408 Q/gQ+WcP\nKPgEevX5IL0GyPiP0Fdl/7/D1pKDg+PNYe/3f+j4/wSP/88OWz8ODo43Ab+H3O0CKl19Qu
409 kaoPN/\nD/gcgM+FD4W7ws8OW886PNg+UTp4jlX8aJOOQR0a2XhrnVftbkrFubZM7+dkewA/zgYS9a6x
410 1erq\nXWRr0thDZLdfJ3uU7PI+rXcMfYWT6Bq33WtSrVNprGW/Y2VXUyIsdSp28sAZoyx1+kGulXqTfx
411 aq\ndrduZOxK5Ex9RxN2pZcx8So9XEozKw4D1Vdn6v0RFLdfeolM0r/U2d9buqRbvekZ/iv0IpulqrYr
412 \nl9sRo+rBEAyR+x8/ADg4OI4gyPyf3/8cHEcTJf+fpwB/ODg4jgSaoBfQ/QB+/s/BcSRR3f+H6Bng\n
413 e/8cHEcHpf1/CI+jHwEP3AToLtx8e9/9e//w8Hun6bHGDz+tvE+3uwfOxsW69+nYYw2WfjPHGtX9\n5A
414 MdfNQo9P+eS7youNdyVuJq4ot2zRsdnLgLCYYip/b7w5jKqUX51IREv4F/FJ7YBy96ja963sJS\n34yd
415 OXDGKEud/R8efZUt\n
416 """
417 newdb = open('test.db','wb')
418 newdb.write(new_db_dump.decode('base64').decode('zlib'))
419 newdb.close()
420
421
422 #PART TWO make test repo
423 if os.path.isdir('/tmp/vcs_test'):
424 shutil.rmtree('/tmp/vcs_test')
425
426 cur_dir = dn(dn(os.path.abspath(__file__)))
427 tar = tarfile.open(jn(cur_dir,'tests',"vcs_test.tar.gz"))
428 tar.extractall('/tmp')
429 tar.close()
430
431
432 No newline at end of file
@@ -1,30 +1,23 b''
1 1 """The application's model objects"""
2 2 import logging
3 import sqlalchemy as sa
4 from sqlalchemy import orm
5 3 from pylons_app.model import meta
6 from pylons_app.model.meta import Session
7 4 log = logging.getLogger(__name__)
8 5
9 # Add these two imports:
10 import datetime
11 from sqlalchemy import schema, types
12
13 6 def init_model(engine):
14 7 """Call me before using any of the tables or classes in the model"""
15 8 log.info("INITIALIZING DB MODELS")
16 9 meta.Base.metadata.bind = engine
17 10 #meta.Base2.metadata.bind = engine2
18 11
19 12 #THIS IS A TEST FOR EXECUTING SCRIPT AND LOAD PYLONS APPLICATION GLOBALS
20 13 #from paste.deploy import appconfig
21 14 #from pylons import config
22 15 #from sqlalchemy import engine_from_config
23 16 #from pylons_app.config.environment import load_environment
24 17 #
25 18 #conf = appconfig('config:development.ini', relative_to = './../../')
26 19 #load_environment(conf.global_conf, conf.local_conf)
27 20 #
28 21 #engine = engine_from_config(config, 'sqlalchemy.')
29 22 #init_model(engine)
30 23 # DO SOMETHING
@@ -1,125 +1,125 b''
1 1 from pylons_app.model.meta import Base
2 2 from sqlalchemy import *
3 3 from sqlalchemy.orm import relation, backref
4 4 from sqlalchemy.orm.session import Session
5 5 from vcs.utils.lazy import LazyProperty
6 6 import logging
7 7
8 8 log = logging.getLogger(__name__)
9 9
10 10 class HgAppSettings(Base):
11 11 __tablename__ = 'hg_app_settings'
12 12 __table_args__ = (UniqueConstraint('app_settings_name'), {'useexisting':True})
13 13 app_settings_id = Column("app_settings_id", INTEGER(), nullable=False, unique=True, default=None, primary_key=True)
14 14 app_settings_name = Column("app_settings_name", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
15 15 app_settings_value = Column("app_settings_value", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
16 16
17 17 class HgAppUi(Base):
18 18 __tablename__ = 'hg_app_ui'
19 19 __table_args__ = {'useexisting':True}
20 20 ui_id = Column("ui_id", INTEGER(), nullable=False, unique=True, default=None, primary_key=True)
21 21 ui_section = Column("ui_section", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
22 22 ui_key = Column("ui_key", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
23 23 ui_value = Column("ui_value", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
24 24 ui_active = Column("ui_active", BOOLEAN(), nullable=True, unique=None, default=True)
25 25
26 26
27 27 class User(Base):
28 28 __tablename__ = 'users'
29 __table_args__ = (UniqueConstraint('username'), {'useexisting':True})
29 __table_args__ = (UniqueConstraint('username'), UniqueConstraint('email'), {'useexisting':True})
30 30 user_id = Column("user_id", INTEGER(), nullable=False, unique=True, default=None, primary_key=True)
31 31 username = Column("username", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
32 32 password = Column("password", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
33 33 active = Column("active", BOOLEAN(), nullable=True, unique=None, default=None)
34 34 admin = Column("admin", BOOLEAN(), nullable=True, unique=None, default=False)
35 35 name = Column("name", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
36 36 lastname = Column("lastname", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
37 37 email = Column("email", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
38 38 last_login = Column("last_login", DATETIME(timezone=False), nullable=True, unique=None, default=None)
39 39
40 40 user_log = relation('UserLog')
41 41 user_perms = relation('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id")
42 42
43 43 @LazyProperty
44 44 def full_contact(self):
45 45 return '%s %s <%s>' % (self.name, self.lastname, self.email)
46 46
47 47 def __repr__(self):
48 48 return "<User('id:%s:%s')>" % (self.user_id, self.username)
49 49
50 50 def update_lastlogin(self):
51 51 """Update user lastlogin"""
52 52 import datetime
53 53
54 54 try:
55 55 session = Session.object_session(self)
56 56 self.last_login = datetime.datetime.now()
57 57 session.add(self)
58 58 session.commit()
59 log.debug('updated user %s lastlogin',self.username)
59 log.debug('updated user %s lastlogin', self.username)
60 60 except Exception:
61 61 session.rollback()
62 62
63 63
64 64 class UserLog(Base):
65 65 __tablename__ = 'user_logs'
66 66 __table_args__ = {'useexisting':True}
67 67 user_log_id = Column("user_log_id", INTEGER(), nullable=False, unique=True, default=None, primary_key=True)
68 68 user_id = Column("user_id", INTEGER(), ForeignKey(u'users.user_id'), nullable=False, unique=None, default=None)
69 69 user_ip = Column("user_ip", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
70 70 repository = Column("repository", TEXT(length=None, convert_unicode=False, assert_unicode=None), ForeignKey(u'repositories.repo_name'), nullable=False, unique=None, default=None)
71 71 action = Column("action", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
72 72 action_date = Column("action_date", DATETIME(timezone=False), nullable=True, unique=None, default=None)
73 73
74 74 user = relation('User')
75 75
76 76 class Repository(Base):
77 77 __tablename__ = 'repositories'
78 78 __table_args__ = (UniqueConstraint('repo_name'), {'useexisting':True},)
79 79 repo_id = Column("repo_id", INTEGER(), nullable=False, unique=True, default=None, primary_key=True)
80 80 repo_name = Column("repo_name", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
81 81 user_id = Column("user_id", INTEGER(), ForeignKey(u'users.user_id'), nullable=False, unique=False, default=None)
82 82 private = Column("private", BOOLEAN(), nullable=True, unique=None, default=None)
83 83 description = Column("description", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
84 84
85 85 user = relation('User')
86 86 repo_to_perm = relation('RepoToPerm', cascade='all')
87 87
88 88 def __repr__(self):
89 89 return "<Repository('id:%s:%s')>" % (self.repo_id, self.repo_name)
90 90
91 91 class Permission(Base):
92 92 __tablename__ = 'permissions'
93 93 __table_args__ = {'useexisting':True}
94 94 permission_id = Column("permission_id", INTEGER(), nullable=False, unique=True, default=None, primary_key=True)
95 95 permission_name = Column("permission_name", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
96 96 permission_longname = Column("permission_longname", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
97 97
98 98 def __repr__(self):
99 99 return "<Permission('%s:%s')>" % (self.permission_id, self.permission_name)
100 100
101 101 class RepoToPerm(Base):
102 102 __tablename__ = 'repo_to_perm'
103 103 __table_args__ = (UniqueConstraint('user_id', 'repository_id'), {'useexisting':True})
104 104 repo_to_perm_id = Column("repo_to_perm_id", INTEGER(), nullable=False, unique=True, default=None, primary_key=True)
105 105 user_id = Column("user_id", INTEGER(), ForeignKey(u'users.user_id'), nullable=False, unique=None, default=None)
106 106 permission_id = Column("permission_id", INTEGER(), ForeignKey(u'permissions.permission_id'), nullable=False, unique=None, default=None)
107 107 repository_id = Column("repository_id", INTEGER(), ForeignKey(u'repositories.repo_id'), nullable=False, unique=None, default=None)
108 108
109 109 user = relation('User')
110 110 permission = relation('Permission')
111 111 repository = relation('Repository')
112 112
113 113 class UserToPerm(Base):
114 114 __tablename__ = 'user_to_perm'
115 115 __table_args__ = (UniqueConstraint('user_id', 'permission_id'), {'useexisting':True})
116 116 user_to_perm_id = Column("user_to_perm_id", INTEGER(), nullable=False, unique=True, default=None, primary_key=True)
117 117 user_id = Column("user_id", INTEGER(), ForeignKey(u'users.user_id'), nullable=False, unique=None, default=None)
118 118 permission_id = Column("permission_id", INTEGER(), ForeignKey(u'permissions.permission_id'), nullable=False, unique=None, default=None)
119 119
120 120 user = relation('User')
121 121 permission = relation('Permission')
122 122
123 123
124 124
125 125
@@ -1,174 +1,174 b''
1 1 #!/usr/bin/env python
2 2 # encoding: utf-8
3 3 # Model for hg app
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 April 9, 2010
22 22 Model for hg app
23 23 @author: marcink
24 24 """
25 25 from beaker.cache import cache_region
26 26 from mercurial import ui
27 27 from mercurial.hgweb.hgwebdir_mod import findrepos
28 28 from pylons.i18n.translation import _
29 29 from pylons_app.lib.auth import HasRepoPermissionAny
30 30 from pylons_app.model import meta
31 31 from pylons_app.model.db import Repository, User
32 32 from pylons_app.lib import helpers as h
33 33 from vcs.exceptions import RepositoryError, VCSError
34 34 import logging
35 35 import os
36 36 import sys
37 37 log = logging.getLogger(__name__)
38 38
39 39 try:
40 40 from vcs.backends.hg import MercurialRepository
41 41 except ImportError:
42 42 sys.stderr.write('You have to import vcs module')
43 43 raise Exception('Unable to import vcs')
44 44
45 45 def _get_repos_cached_initial(app_globals, initial):
46 46 """
47 47 return cached dict with repos
48 48 """
49 49 g = app_globals
50 50 return HgModel.repo_scan(g.paths[0][0], g.paths[0][1], g.baseui, initial)
51 51
52 52 @cache_region('long_term', 'cached_repo_list')
53 53 def _get_repos_cached():
54 54 """
55 55 return cached dict with repos
56 56 """
57 57 log.info('getting all repositories list')
58 58 from pylons import app_globals as g
59 59 return HgModel.repo_scan(g.paths[0][0], g.paths[0][1], g.baseui)
60 60
61 61 @cache_region('super_short_term', 'cached_repos_switcher_list')
62 62 def _get_repos_switcher_cached(cached_repo_list):
63 63 repos_lst = []
64 64 for repo in sorted(x.name.lower() for x in cached_repo_list.values()):
65 65 if HasRepoPermissionAny('repository.write', 'repository.read', 'repository.admin')(repo, 'main page check'):
66 66 repos_lst.append(repo)
67 67
68 68 return repos_lst
69 69
70 70 @cache_region('long_term', 'full_changelog')
71 71 def _full_changelog_cached(repo_name):
72 72 log.info('getting full changelog for %s', repo_name)
73 73 return list(reversed(list(HgModel().get_repo(repo_name))))
74 74
75 75 class HgModel(object):
76 76 """
77 77 Mercurial Model
78 78 """
79 79
80 80 def __init__(self):
81 81 """
82 82 Constructor
83 83 """
84 84
85 85 @staticmethod
86 86 def repo_scan(repos_prefix, repos_path, baseui, initial=False):
87 87 """
88 88 Listing of repositories in given path. This path should not be a
89 89 repository itself. Return a dictionary of repository objects
90 90 :param repos_path: path to directory it could take syntax with
91 91 * or ** for deep recursive displaying repositories
92 92 """
93 93 sa = meta.Session()
94 94 def check_repo_dir(path):
95 95 """
96 96 Checks the repository
97 97 :param path:
98 98 """
99 99 repos_path = path.split('/')
100 100 if repos_path[-1] in ['*', '**']:
101 101 repos_path = repos_path[:-1]
102 102 if repos_path[0] != '/':
103 103 repos_path[0] = '/'
104 104 if not os.path.isdir(os.path.join(*repos_path)):
105 raise RepositoryError('Not a valid repository in %s' % path[0][1])
105 raise RepositoryError('Not a valid repository in %s' % path)
106 106 if not repos_path.endswith('*'):
107 107 raise VCSError('You need to specify * or ** at the end of path '
108 108 'for recursive scanning')
109 109
110 110 check_repo_dir(repos_path)
111 111 log.info('scanning for repositories in %s', repos_path)
112 112 repos = findrepos([(repos_prefix, repos_path)])
113 113 if not isinstance(baseui, ui.ui):
114 114 baseui = ui.ui()
115 115
116 116 repos_list = {}
117 117 for name, path in repos:
118 118 try:
119 119 #name = name.split('/')[-1]
120 120 if repos_list.has_key(name):
121 121 raise RepositoryError('Duplicate repository name %s found in'
122 122 ' %s' % (name, path))
123 123 else:
124 124
125 125 repos_list[name] = MercurialRepository(path, baseui=baseui)
126 126 repos_list[name].name = name
127 127
128 128 dbrepo = None
129 129 if not initial:
130 130 dbrepo = sa.query(Repository)\
131 131 .filter(Repository.repo_name == name).scalar()
132 132
133 133 if dbrepo:
134 134 log.info('Adding db instance to cached list')
135 135 repos_list[name].dbrepo = dbrepo
136 136 repos_list[name].description = dbrepo.description
137 137 if dbrepo.user:
138 138 repos_list[name].contact = dbrepo.user.full_contact
139 139 else:
140 140 repos_list[name].contact = sa.query(User)\
141 141 .filter(User.admin == True).first().full_contact
142 142 except OSError:
143 143 continue
144 144 meta.Session.remove()
145 145 return repos_list
146 146
147 147 def get_repos(self):
148 148 for name, repo in _get_repos_cached().items():
149 149 if repo._get_hidden():
150 150 #skip hidden web repository
151 151 continue
152 152
153 153 last_change = repo.last_change
154 154 tip = h.get_changeset_safe(repo, 'tip')
155 155
156 156 tmp_d = {}
157 157 tmp_d['name'] = repo.name
158 158 tmp_d['name_sort'] = tmp_d['name'].lower()
159 159 tmp_d['description'] = repo.description
160 160 tmp_d['description_sort'] = tmp_d['description']
161 161 tmp_d['last_change'] = last_change
162 162 tmp_d['last_change_sort'] = last_change[1] - last_change[0]
163 163 tmp_d['tip'] = tip.raw_id
164 164 tmp_d['tip_sort'] = tip.revision
165 165 tmp_d['rev'] = tip.revision
166 166 tmp_d['contact'] = repo.contact
167 167 tmp_d['contact_sort'] = tmp_d['contact']
168 168 tmp_d['repo_archives'] = list(repo._get_archives())
169 169 tmp_d['last_msg'] = tip.message
170 170 tmp_d['repo'] = repo
171 171 yield tmp_d
172 172
173 173 def get_repo(self, repo_name):
174 174 return _get_repos_cached()[repo_name]
@@ -1,45 +1,49 b''
1 1 """Pylons application test package
2 2
3 3 This package assumes the Pylons environment is already loaded, such as
4 4 when this script is imported from the `nosetests --with-pylons=test.ini`
5 5 command.
6 6
7 7 This module initializes the application via ``websetup`` (`paster
8 8 setup-app`) and provides the base testing objects.
9 9 """
10 10 from unittest import TestCase
11 11
12 12 from paste.deploy import loadapp
13 13 from paste.script.appinstall import SetupCommand
14 14 from pylons import config, url
15 15 from routes.util import URLGenerator
16 16 from webtest import TestApp
17 17 import os
18 18 from pylons_app.model import meta
19 import logging
20 log = logging.getLogger(__name__)
21
19 22 import pylons.test
20 23
21 24 __all__ = ['environ', 'url', 'TestController']
22 25
23 26 # Invoke websetup with the current config file
24 SetupCommand('setup-app').run([pylons.test.pylonsapp.config['__file__']])
27 #SetupCommand('setup-app').run([pylons.test.pylonsapp.config['__file__']])
25 28
26 29 environ = {}
27 30
28 31 class TestController(TestCase):
29 32
30 33 def __init__(self, *args, **kwargs):
31 34 wsgiapp = pylons.test.pylonsapp
32 35 config = wsgiapp.config
33 36 self.app = TestApp(wsgiapp)
34 37 url._push_object(URLGenerator(config['routes.map'], environ))
35 38 self.sa = meta.Session
36 39 TestCase.__init__(self, *args, **kwargs)
37 40
38 41
39 42 def log_user(self):
40 43 response = self.app.post(url(controller='login', action='index'),
41 44 {'username':'test_admin',
42 45 'password':'test'})
43 46 assert response.status == '302 Found', 'Wrong response code from login got %s' % response.status
44 47 assert response.session['hg_app_user'].username == 'test_admin', 'wrong logged in user'
45 return response.follow() No newline at end of file
48 return response.follow()
49 No newline at end of file
@@ -1,7 +1,9 b''
1 1 from pylons_app.tests import *
2 2
3 3 class TestAdminController(TestController):
4 4
5 5 def test_index(self):
6 self.log_user()
6 7 response = self.app.get(url(controller='admin/admin', action='index'))
8 assert 'Admin dashboard - journal' in response.body,'No proper title in dashboard'
7 9 # Test response...
@@ -1,7 +1,8 b''
1 1 from pylons_app.tests import *
2 2
3 3 class TestBranchesController(TestController):
4 4
5 5 def test_index(self):
6 self.log_user()
6 7 response = self.app.get(url(controller='branches', action='index',repo_name='vcs_test'))
7 8 # Test response...
@@ -1,7 +1,8 b''
1 1 from pylons_app.tests import *
2 2
3 3 class TestChangelogController(TestController):
4 4
5 5 def test_index(self):
6 self.log_user()
6 7 response = self.app.get(url(controller='changelog', action='index',repo_name='vcs_test'))
7 8 # Test response...
@@ -1,13 +1,15 b''
1 1 from pylons_app.tests import *
2 2
3 3 class TestFeedController(TestController):
4 4
5 5 def test_rss(self):
6 self.log_user()
6 7 response = self.app.get(url(controller='feed', action='rss',
7 8 repo_name='vcs_test'))
8 9 # Test response...
9 10
10 11 def test_atom(self):
12 self.log_user()
11 13 response = self.app.get(url(controller='feed', action='atom',
12 14 repo_name='vcs_test'))
13 15 # Test response... No newline at end of file
@@ -1,10 +1,11 b''
1 1 from pylons_app.tests import *
2 2
3 3 class TestFilesController(TestController):
4 4
5 5 def test_index(self):
6 self.log_user()
6 7 response = self.app.get(url(controller='files', action='index',
7 8 repo_name='vcs_test',
8 9 revision='tip',
9 10 f_path='/'))
10 11 # Test response...
@@ -1,111 +1,139 b''
1 1 from pylons_app.tests import *
2 2 from pylons_app.model.db import User
3 3 from pylons_app.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':'test'})
17 17 assert response.status == '302 Found', 'Wrong response code from login got %s' % response.status
18 18 assert response.session['hg_app_user'].username == 'test_admin', 'wrong logged in user'
19 19 response = response.follow()
20 20 assert 'auto description for vcs_test' in response.body
21 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':'test'})
26 26 assert response.status == '302 Found', 'Wrong response code from login got %s' % response.status
27 27 assert response.session['hg_app_user'].username == 'test_regular', 'wrong logged in user'
28 28 response = response.follow()
29 29 assert 'auto description for vcs_test' in response.body
30 30 assert '<a title="Admin" href="/_admin">' not in response.body
31 31
32 32 def test_login_ok_came_from(self):
33 33 test_came_from = '/_admin/users'
34 34 response = self.app.post(url(controller='login', action='index', came_from=test_came_from),
35 35 {'username':'test_admin',
36 36 'password':'test'})
37 37 assert response.status == '302 Found', 'Wrong response code from came from redirection'
38 38 response = response.follow()
39 39
40 40 assert response.status == '200 OK', 'Wrong response from login page got %s' % response.status
41 41 assert 'Users administration' in response.body, 'No proper title in response'
42 42
43 43
44 44 def test_login_wrong(self):
45 45 response = self.app.post(url(controller='login', action='index'),
46 46 {'username':'error',
47 47 'password':'test'})
48 48 assert response.status == '200 OK', 'Wrong response from login page'
49 49
50 50 assert 'invalid user name' in response.body, 'No error username message in response'
51 51 assert 'invalid password' in response.body, 'No error password message in response'
52 52
53 53
54 54 def test_register(self):
55 55 response = self.app.get(url(controller='login', action='register'))
56 56 assert 'Sign Up to hg-app' in response.body, 'wrong page for user registration'
57 57
58 58 def test_register_err_same_username(self):
59 59 response = self.app.post(url(controller='login', action='register'),
60 60 {'username':'test_admin',
61 61 'password':'test',
62 62 'email':'goodmail@domain.com',
63 63 'name':'test',
64 64 'lastname':'test'})
65 65
66 66 assert response.status == '200 OK', 'Wrong response from register page got %s' % response.status
67 67 assert 'This username already exists' in response.body
68 68
69 69 def test_register_err_wrong_data(self):
70 70 response = self.app.post(url(controller='login', action='register'),
71 71 {'username':'xs',
72 72 'password':'',
73 73 'email':'goodmailm',
74 74 'name':'test',
75 75 'lastname':'test'})
76 76
77 77 assert response.status == '200 OK', 'Wrong response from register page got %s' % response.status
78 78 assert 'An email address must contain a single @' in response.body
79 79 assert 'Enter a value 3 characters long or more' in response.body
80 80 assert 'Please enter a value<' in response.body
81 81
82 82
83 83
84 84 def test_register_ok(self):
85 username = 'test_regular2'
85 username = 'test_regular4'
86 86 password = 'qweqwe'
87 email = 'goodmail@mail.com'
87 email = 'marcin@somemail.com'
88 88 name = 'testname'
89 89 lastname = 'testlastname'
90 90
91 91 response = self.app.post(url(controller='login', action='register'),
92 92 {'username':username,
93 93 'password':password,
94 94 'email':email,
95 95 'name':name,
96 96 'lastname':lastname})
97
97 print response.body
98 98 assert response.status == '302 Found', 'Wrong response from register page got %s' % response.status
99 assert 'You have successfully registered into hg-app' in response.session['flash'][0], 'No flash message about user registration'
99 100
100 ret = self.sa.query(User).filter(User.username == 'test_regular2').one()
101 ret = self.sa.query(User).filter(User.username == 'test_regular4').one()
101 102 assert ret.username == username , 'field mismatch %s %s' % (ret.username, username)
102 103 assert check_password(password,ret.password) == True , 'password mismatch'
103 104 assert ret.email == email , 'field mismatch %s %s' % (ret.email, email)
104 105 assert ret.name == name , 'field mismatch %s %s' % (ret.name, name)
105 106 assert ret.lastname == lastname , 'field mismatch %s %s' % (ret.lastname, lastname)
106 107
107 108
109 def test_forgot_password_wrong_mail(self):
110 response = self.app.post(url(controller='login', action='password_reset'),
111 {'email':'marcin@wrongmail.org',})
112
113 assert "That e-mail address doesn't exist" in response.body,'Missing error message about wrong email'
114
115 def test_forgot_password(self):
116 response = self.app.get(url(controller='login', action='password_reset'))
117 assert response.status == '200 OK', 'Wrong response from login page got %s' % response.status
118
119 username = 'test_password_reset_1'
120 password = 'qweqwe'
121 email = 'marcin@python-works.com'
122 name = 'passwd'
123 lastname = 'reset'
124
125 response = self.app.post(url(controller='login', action='register'),
126 {'username':username,
127 'password':password,
128 'email':email,
129 'name':name,
130 'lastname':lastname})
131 #register new user for email test
132 response = self.app.post(url(controller='login', action='password_reset'),
133 {'email':email,})
134 print response.session['flash']
135 assert 'You have successfully registered into hg-app' in response.session['flash'][0], 'No flash message about user registration'
136 assert 'Your new password was sent' in response.session['flash'][1], 'No flash message about password reset'
108 137
109 138
110 139
111
@@ -1,8 +1,9 b''
1 1 from pylons_app.tests import *
2 2
3 3 class TestSettingsController(TestController):
4 4
5 5 def test_index(self):
6 self.log_user()
6 7 response = self.app.get(url(controller='settings', action='index',
7 8 repo_name='vcs_test'))
8 9 # Test response...
@@ -1,7 +1,8 b''
1 1 from pylons_app.tests import *
2 2
3 3 class TestShortlogController(TestController):
4 4
5 5 def test_index(self):
6 self.log_user()
6 7 response = self.app.get(url(controller='shortlog', action='index',repo_name='vcs_test'))
7 8 # Test response...
@@ -1,7 +1,8 b''
1 1 from pylons_app.tests import *
2 2
3 3 class TestSummaryController(TestController):
4 4
5 5 def test_index(self):
6 self.log_user()
6 7 response = self.app.get(url(controller='summary', action='index',repo_name='vcs_test'))
7 8 # Test response...
@@ -1,7 +1,8 b''
1 1 from pylons_app.tests import *
2 2
3 3 class TestTagsController(TestController):
4 4
5 5 def test_index(self):
6 self.log_user()
6 7 response = self.app.get(url(controller='tags', action='index',repo_name='vcs_test'))
7 8 # Test response...
@@ -1,47 +1,45 b''
1 1 """Setup the pylons_app application"""
2 2
3 from os.path import dirname as dn, join as jn
3 from os.path import dirname as dn
4 4 from pylons_app.config.environment import load_environment
5 5 from pylons_app.lib.db_manage import DbManage
6 import datetime
7 from time import mktime
8 6 import logging
9 7 import os
10 8 import sys
11 import tarfile
12 9
13 10 log = logging.getLogger(__name__)
14 11
15 12 ROOT = dn(dn(os.path.realpath(__file__)))
16 13 sys.path.append(ROOT)
17 14
15
18 16 def setup_app(command, conf, vars):
19 17 """Place any commands to setup pylons_app here"""
20 18 log_sql = True
21 19 tests = False
20 REPO_TEST_PATH = None
22 21
23 22 dbname = os.path.split(conf['sqlalchemy.db1.url'])[-1]
24 filename = os.path.split(conf.filename)[-1]
25
26 if filename == 'tests.ini':
27 uniq_suffix = str(int(mktime(datetime.datetime.now().timetuple())))
28 REPO_TEST_PATH = '/tmp/hg_app_test_%s' % uniq_suffix
29
30 if not os.path.isdir(REPO_TEST_PATH):
31 os.mkdir(REPO_TEST_PATH)
32 cur_dir = dn(os.path.abspath(__file__))
33 tar = tarfile.open(jn(cur_dir,'tests',"vcs_test.tar.gz"))
34 tar.extractall(REPO_TEST_PATH)
35 tar.close()
36
37 tests = True
23 # filename = os.path.split(conf.filename)[-1]
24 # if filename == 'test.ini':
25 # uniq_suffix = str(int(mktime(datetime.datetime.now().timetuple())))
26 # REPO_TEST_PATH = '/tmp/hg_app_test_%s' % uniq_suffix
27 #
28 # if not os.path.isdir(REPO_TEST_PATH):
29 # os.mkdir(REPO_TEST_PATH)
30 # cur_dir = dn(os.path.abspath(__file__))
31 # tar = tarfile.open(jn(cur_dir,'tests',"vcs_test.tar.gz"))
32 # tar.extractall(REPO_TEST_PATH)
33 # tar.close()
34 #
35 # tests = True
38 36
39 37 dbmanage = DbManage(log_sql, dbname, tests)
40 38 dbmanage.create_tables(override=True)
41 39 dbmanage.config_prompt(REPO_TEST_PATH)
42 40 dbmanage.create_default_user()
43 41 dbmanage.admin_prompt()
44 42 dbmanage.create_permissions()
45 43 dbmanage.populate_default_permissions()
46 44 load_environment(conf.global_conf, conf.local_conf, initial=True)
47 45
@@ -1,34 +1,34 b''
1 1 [egg_info]
2 2 tag_build = dev
3 3 tag_svn_revision = true
4 4
5 5 [easy_install]
6 6 find_links = http://www.pylonshq.com/download/
7 7
8 8 [nosetests]
9 9 verbose=True
10 10 verbosity=2
11 with-pylons=tests.ini
11 with-pylons=test.ini
12 12 detailed-errors=1
13 13
14 14 # Babel configuration
15 15 [compile_catalog]
16 16 domain = pylons_app
17 17 directory = pylons_app/i18n
18 18 statistics = true
19 19
20 20 [extract_messages]
21 21 add_comments = TRANSLATORS:
22 22 output_file = pylons_app/i18n/pylons_app.pot
23 23 width = 80
24 24
25 25 [init_catalog]
26 26 domain = pylons_app
27 27 input_file = pylons_app/i18n/pylons_app.pot
28 28 output_dir = pylons_app/i18n
29 29
30 30 [update_catalog]
31 31 domain = pylons_app
32 32 input_file = pylons_app/i18n/pylons_app.pot
33 33 output_dir = pylons_app/i18n
34 34 previous = true
1 NO CONTENT: file renamed from tests.ini to test.ini
General Comments 0
You need to be logged in to leave comments. Login now