##// 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 """Pylons environment configuration"""
1 """Pylons environment configuration"""
2 from mako.lookup import TemplateLookup
2 from mako.lookup import TemplateLookup
3 from pylons.configuration import PylonsConfig
3 from pylons.configuration import PylonsConfig
4 from pylons.error import handle_mako_error
4 from pylons.error import handle_mako_error
5 from pylons_app.config.routing import make_map
5 from pylons_app.config.routing import make_map
6 from pylons_app.lib.auth import set_available_permissions, set_base_path
6 from pylons_app.lib.auth import set_available_permissions, set_base_path
7 from pylons_app.lib.utils import repo2db_mapper, make_ui, set_hg_app_config
7 from pylons_app.lib.utils import repo2db_mapper, make_ui, set_hg_app_config
8 from pylons_app.model import init_model
8 from pylons_app.model import init_model
9 from pylons_app.model.hg_model import _get_repos_cached_initial
9 from pylons_app.model.hg_model import _get_repos_cached_initial
10 from sqlalchemy import engine_from_config
10 from sqlalchemy import engine_from_config
11 import logging
11 import logging
12 import os
12 import os
13 import pylons_app.lib.app_globals as app_globals
13 import pylons_app.lib.app_globals as app_globals
14 import pylons_app.lib.helpers
14 import pylons_app.lib.helpers
15
15
16 log = logging.getLogger(__name__)
16 log = logging.getLogger(__name__)
17
17
18 def load_environment(global_conf, app_conf, initial=False):
18 def load_environment(global_conf, app_conf, initial=False):
19 """Configure the Pylons environment via the ``pylons.config``
19 """Configure the Pylons environment via the ``pylons.config``
20 object
20 object
21 """
21 """
22 config = PylonsConfig()
22 config = PylonsConfig()
23
23
24 # Pylons paths
24 # Pylons paths
25 root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
25 root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
26 paths = dict(root=root,
26 paths = dict(root=root,
27 controllers=os.path.join(root, 'controllers'),
27 controllers=os.path.join(root, 'controllers'),
28 static_files=os.path.join(root, 'public'),
28 static_files=os.path.join(root, 'public'),
29 templates=[os.path.join(root, 'templates')])
29 templates=[os.path.join(root, 'templates')])
30
30
31 # Initialize config with the basic options
31 # Initialize config with the basic options
32 config.init_app(global_conf, app_conf, package='pylons_app', paths=paths)
32 config.init_app(global_conf, app_conf, package='pylons_app', paths=paths)
33
33
34 config['routes.map'] = make_map(config)
34 config['routes.map'] = make_map(config)
35 config['pylons.app_globals'] = app_globals.Globals(config)
35 config['pylons.app_globals'] = app_globals.Globals(config)
36 config['pylons.h'] = pylons_app.lib.helpers
36 config['pylons.h'] = pylons_app.lib.helpers
37
37
38 # Setup cache object as early as possible
38 # Setup cache object as early as possible
39 import pylons
39 import pylons
40 pylons.cache._push_object(config['pylons.app_globals'].cache)
40 pylons.cache._push_object(config['pylons.app_globals'].cache)
41
41
42 # Create the Mako TemplateLookup, with the default auto-escaping
42 # Create the Mako TemplateLookup, with the default auto-escaping
43 config['pylons.app_globals'].mako_lookup = TemplateLookup(
43 config['pylons.app_globals'].mako_lookup = TemplateLookup(
44 directories=paths['templates'],
44 directories=paths['templates'],
45 error_handler=handle_mako_error,
45 error_handler=handle_mako_error,
46 module_directory=os.path.join(app_conf['cache_dir'], 'templates'),
46 module_directory=os.path.join(app_conf['cache_dir'], 'templates'),
47 input_encoding='utf-8', default_filters=['escape'],
47 input_encoding='utf-8', default_filters=['escape'],
48 imports=['from webhelpers.html import escape'])
48 imports=['from webhelpers.html import escape'])
49
49
50 #sets the c attribute access when don't existing attribute are accessed
50 #sets the c attribute access when don't existing attribute are accessed
51 config['pylons.strict_tmpl_context'] = True
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 #MULTIPLE DB configs
56 #MULTIPLE DB configs
54 # Setup the SQLAlchemy database engine
57 # Setup the SQLAlchemy database engine
55 if config['debug'] and not test:
58 if config['debug'] and not test:
56 #use query time debugging.
59 #use query time debugging.
57 from pylons_app.lib.timerproxy import TimerProxy
60 from pylons_app.lib.timerproxy import TimerProxy
58 sa_engine_db1 = engine_from_config(config, 'sqlalchemy.db1.',
61 sa_engine_db1 = engine_from_config(config, 'sqlalchemy.db1.',
59 proxy=TimerProxy())
62 proxy=TimerProxy())
60 else:
63 else:
61 sa_engine_db1 = engine_from_config(config, 'sqlalchemy.db1.')
64 sa_engine_db1 = engine_from_config(config, 'sqlalchemy.db1.')
62
65
63 init_model(sa_engine_db1)
66 init_model(sa_engine_db1)
64 #init baseui
67 #init baseui
65 config['pylons.app_globals'].baseui = make_ui('db')
68 config['pylons.app_globals'].baseui = make_ui('db')
66
69
67 repo2db_mapper(_get_repos_cached_initial(config['pylons.app_globals'], initial))
70 repo2db_mapper(_get_repos_cached_initial(config['pylons.app_globals'], initial))
68 set_available_permissions(config)
71 set_available_permissions(config)
69 set_base_path(config)
72 set_base_path(config)
70 set_hg_app_config(config)
73 set_hg_app_config(config)
71 # CONFIGURATION OPTIONS HERE (note: all config options will override
74 # CONFIGURATION OPTIONS HERE (note: all config options will override
72 # any Pylons config options)
75 # any Pylons config options)
73
76
74 return config
77 return config
@@ -1,293 +1,293 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 # encoding: utf-8
2 # encoding: utf-8
3 # settings controller for pylons
3 # settings controller for pylons
4 # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
4 # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
5 #
5 #
6 # This program is free software; you can redistribute it and/or
6 # This program is free software; you can redistribute it and/or
7 # modify it under the terms of the GNU General Public License
7 # modify it under the terms of the GNU General Public License
8 # as published by the Free Software Foundation; version 2
8 # as published by the Free Software Foundation; version 2
9 # of the License or (at your opinion) any later version of the license.
9 # of the License or (at your opinion) any later version of the license.
10 #
10 #
11 # This program is distributed in the hope that it will be useful,
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
14 # GNU General Public License for more details.
15 #
15 #
16 # You should have received a copy of the GNU General Public License
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19 # MA 02110-1301, USA.
19 # MA 02110-1301, USA.
20 """
20 """
21 Created on July 14, 2010
21 Created on July 14, 2010
22 settings controller for pylons
22 settings controller for pylons
23 @author: marcink
23 @author: marcink
24 """
24 """
25 from formencode import htmlfill
25 from formencode import htmlfill
26 from pylons import request, session, tmpl_context as c, url, app_globals as g, \
26 from pylons import request, session, tmpl_context as c, url, app_globals as g, \
27 config
27 config
28 from pylons.controllers.util import abort, redirect
28 from pylons.controllers.util import abort, redirect
29 from pylons.i18n.translation import _
29 from pylons.i18n.translation import _
30 from pylons_app.lib import helpers as h
30 from pylons_app.lib import helpers as h
31 from pylons_app.lib.auth import LoginRequired, HasPermissionAllDecorator, \
31 from pylons_app.lib.auth import LoginRequired, HasPermissionAllDecorator, \
32 HasPermissionAnyDecorator
32 HasPermissionAnyDecorator
33 from pylons_app.lib.base import BaseController, render
33 from pylons_app.lib.base import BaseController, render
34 from pylons_app.lib.utils import repo2db_mapper, invalidate_cache, \
34 from pylons_app.lib.utils import repo2db_mapper, invalidate_cache, \
35 set_hg_app_config, get_hg_settings, get_hg_ui_settings, make_ui
35 set_hg_app_config, get_hg_settings, get_hg_ui_settings, make_ui
36 from pylons_app.model.db import User, UserLog, HgAppSettings, HgAppUi
36 from pylons_app.model.db import User, UserLog, HgAppSettings, HgAppUi
37 from pylons_app.model.forms import UserForm, ApplicationSettingsForm, \
37 from pylons_app.model.forms import UserForm, ApplicationSettingsForm, \
38 ApplicationUiSettingsForm
38 ApplicationUiSettingsForm
39 from pylons_app.model.hg_model import HgModel
39 from pylons_app.model.hg_model import HgModel
40 from pylons_app.model.user_model import UserModel
40 from pylons_app.model.user_model import UserModel
41 from pylons_app.lib.celerylib import tasks,run_task
41 from pylons_app.lib.celerylib import tasks,run_task
42 import formencode
42 import formencode
43 import logging
43 import logging
44 import traceback
44 import traceback
45
45
46 log = logging.getLogger(__name__)
46 log = logging.getLogger(__name__)
47
47
48
48
49 class SettingsController(BaseController):
49 class SettingsController(BaseController):
50 """REST Controller styled on the Atom Publishing Protocol"""
50 """REST Controller styled on the Atom Publishing Protocol"""
51 # To properly map this controller, ensure your config/routing.py
51 # To properly map this controller, ensure your config/routing.py
52 # file has a resource setup:
52 # file has a resource setup:
53 # map.resource('setting', 'settings', controller='admin/settings',
53 # map.resource('setting', 'settings', controller='admin/settings',
54 # path_prefix='/admin', name_prefix='admin_')
54 # path_prefix='/admin', name_prefix='admin_')
55
55
56
56
57 @LoginRequired()
57 @LoginRequired()
58 def __before__(self):
58 def __before__(self):
59 c.admin_user = session.get('admin_user')
59 c.admin_user = session.get('admin_user')
60 c.admin_username = session.get('admin_username')
60 c.admin_username = session.get('admin_username')
61 super(SettingsController, self).__before__()
61 super(SettingsController, self).__before__()
62
62
63
63
64 @HasPermissionAllDecorator('hg.admin')
64 @HasPermissionAllDecorator('hg.admin')
65 def index(self, format='html'):
65 def index(self, format='html'):
66 """GET /admin/settings: All items in the collection"""
66 """GET /admin/settings: All items in the collection"""
67 # url('admin_settings')
67 # url('admin_settings')
68
68
69 defaults = get_hg_settings()
69 defaults = get_hg_settings()
70 defaults.update(get_hg_ui_settings())
70 defaults.update(get_hg_ui_settings())
71 return htmlfill.render(
71 return htmlfill.render(
72 render('admin/settings/settings.html'),
72 render('admin/settings/settings.html'),
73 defaults=defaults,
73 defaults=defaults,
74 encoding="UTF-8",
74 encoding="UTF-8",
75 force_defaults=False
75 force_defaults=False
76 )
76 )
77
77
78 @HasPermissionAllDecorator('hg.admin')
78 @HasPermissionAllDecorator('hg.admin')
79 def create(self):
79 def create(self):
80 """POST /admin/settings: Create a new item"""
80 """POST /admin/settings: Create a new item"""
81 # url('admin_settings')
81 # url('admin_settings')
82
82
83 @HasPermissionAllDecorator('hg.admin')
83 @HasPermissionAllDecorator('hg.admin')
84 def new(self, format='html'):
84 def new(self, format='html'):
85 """GET /admin/settings/new: Form to create a new item"""
85 """GET /admin/settings/new: Form to create a new item"""
86 # url('admin_new_setting')
86 # url('admin_new_setting')
87
87
88 @HasPermissionAllDecorator('hg.admin')
88 @HasPermissionAllDecorator('hg.admin')
89 def update(self, setting_id):
89 def update(self, setting_id):
90 """PUT /admin/settings/setting_id: Update an existing item"""
90 """PUT /admin/settings/setting_id: Update an existing item"""
91 # Forms posted to this method should contain a hidden field:
91 # Forms posted to this method should contain a hidden field:
92 # <input type="hidden" name="_method" value="PUT" />
92 # <input type="hidden" name="_method" value="PUT" />
93 # Or using helpers:
93 # Or using helpers:
94 # h.form(url('admin_setting', setting_id=ID),
94 # h.form(url('admin_setting', setting_id=ID),
95 # method='put')
95 # method='put')
96 # url('admin_setting', setting_id=ID)
96 # url('admin_setting', setting_id=ID)
97 if setting_id == 'mapping':
97 if setting_id == 'mapping':
98 rm_obsolete = request.POST.get('destroy', False)
98 rm_obsolete = request.POST.get('destroy', False)
99 log.debug('Rescanning directories with destroy=%s', rm_obsolete)
99 log.debug('Rescanning directories with destroy=%s', rm_obsolete)
100
100
101 initial = HgModel.repo_scan(g.paths[0][0], g.paths[0][1], g.baseui)
101 initial = HgModel.repo_scan(g.paths[0][0], g.paths[0][1], g.baseui)
102 repo2db_mapper(initial, rm_obsolete)
102 repo2db_mapper(initial, rm_obsolete)
103 invalidate_cache('cached_repo_list')
103 invalidate_cache('cached_repo_list')
104 h.flash(_('Repositories sucessfully rescanned'), category='success')
104 h.flash(_('Repositories sucessfully rescanned'), category='success')
105
105
106 if setting_id == 'whoosh':
106 if setting_id == 'whoosh':
107 repo_location = get_hg_ui_settings()['paths_root_path']
107 repo_location = get_hg_ui_settings()['paths_root_path']
108 full_index = request.POST.get('full_index',False)
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 h.flash(_('Whoosh reindex task scheduled'), category='success')
111 h.flash(_('Whoosh reindex task scheduled'), category='success')
112 if setting_id == 'global':
112 if setting_id == 'global':
113
113
114 application_form = ApplicationSettingsForm()()
114 application_form = ApplicationSettingsForm()()
115 try:
115 try:
116 form_result = application_form.to_python(dict(request.POST))
116 form_result = application_form.to_python(dict(request.POST))
117
117
118 try:
118 try:
119 hgsettings1 = self.sa.query(HgAppSettings)\
119 hgsettings1 = self.sa.query(HgAppSettings)\
120 .filter(HgAppSettings.app_settings_name == 'title').one()
120 .filter(HgAppSettings.app_settings_name == 'title').one()
121 hgsettings1.app_settings_value = form_result['hg_app_title']
121 hgsettings1.app_settings_value = form_result['hg_app_title']
122
122
123 hgsettings2 = self.sa.query(HgAppSettings)\
123 hgsettings2 = self.sa.query(HgAppSettings)\
124 .filter(HgAppSettings.app_settings_name == 'realm').one()
124 .filter(HgAppSettings.app_settings_name == 'realm').one()
125 hgsettings2.app_settings_value = form_result['hg_app_realm']
125 hgsettings2.app_settings_value = form_result['hg_app_realm']
126
126
127
127
128 self.sa.add(hgsettings1)
128 self.sa.add(hgsettings1)
129 self.sa.add(hgsettings2)
129 self.sa.add(hgsettings2)
130 self.sa.commit()
130 self.sa.commit()
131 set_hg_app_config(config)
131 set_hg_app_config(config)
132 h.flash(_('Updated application settings'),
132 h.flash(_('Updated application settings'),
133 category='success')
133 category='success')
134
134
135 except:
135 except:
136 log.error(traceback.format_exc())
136 log.error(traceback.format_exc())
137 h.flash(_('error occured during updating application settings'),
137 h.flash(_('error occured during updating application settings'),
138 category='error')
138 category='error')
139
139
140 self.sa.rollback()
140 self.sa.rollback()
141
141
142
142
143 except formencode.Invalid as errors:
143 except formencode.Invalid as errors:
144 return htmlfill.render(
144 return htmlfill.render(
145 render('admin/settings/settings.html'),
145 render('admin/settings/settings.html'),
146 defaults=errors.value,
146 defaults=errors.value,
147 errors=errors.error_dict or {},
147 errors=errors.error_dict or {},
148 prefix_error=False,
148 prefix_error=False,
149 encoding="UTF-8")
149 encoding="UTF-8")
150
150
151 if setting_id == 'mercurial':
151 if setting_id == 'mercurial':
152 application_form = ApplicationUiSettingsForm()()
152 application_form = ApplicationUiSettingsForm()()
153 try:
153 try:
154 form_result = application_form.to_python(dict(request.POST))
154 form_result = application_form.to_python(dict(request.POST))
155
155
156 try:
156 try:
157
157
158 hgsettings1 = self.sa.query(HgAppUi)\
158 hgsettings1 = self.sa.query(HgAppUi)\
159 .filter(HgAppUi.ui_key == 'push_ssl').one()
159 .filter(HgAppUi.ui_key == 'push_ssl').one()
160 hgsettings1.ui_value = form_result['web_push_ssl']
160 hgsettings1.ui_value = form_result['web_push_ssl']
161
161
162 hgsettings2 = self.sa.query(HgAppUi)\
162 hgsettings2 = self.sa.query(HgAppUi)\
163 .filter(HgAppUi.ui_key == '/').one()
163 .filter(HgAppUi.ui_key == '/').one()
164 hgsettings2.ui_value = form_result['paths_root_path']
164 hgsettings2.ui_value = form_result['paths_root_path']
165
165
166
166
167 #HOOKS
167 #HOOKS
168 hgsettings3 = self.sa.query(HgAppUi)\
168 hgsettings3 = self.sa.query(HgAppUi)\
169 .filter(HgAppUi.ui_key == 'changegroup.update').one()
169 .filter(HgAppUi.ui_key == 'changegroup.update').one()
170 hgsettings3.ui_active = bool(form_result['hooks_changegroup_update'])
170 hgsettings3.ui_active = bool(form_result['hooks_changegroup_update'])
171
171
172 hgsettings4 = self.sa.query(HgAppUi)\
172 hgsettings4 = self.sa.query(HgAppUi)\
173 .filter(HgAppUi.ui_key == 'changegroup.repo_size').one()
173 .filter(HgAppUi.ui_key == 'changegroup.repo_size').one()
174 hgsettings4.ui_active = bool(form_result['hooks_changegroup_repo_size'])
174 hgsettings4.ui_active = bool(form_result['hooks_changegroup_repo_size'])
175
175
176
176
177
177
178
178
179 self.sa.add(hgsettings1)
179 self.sa.add(hgsettings1)
180 self.sa.add(hgsettings2)
180 self.sa.add(hgsettings2)
181 self.sa.add(hgsettings3)
181 self.sa.add(hgsettings3)
182 self.sa.add(hgsettings4)
182 self.sa.add(hgsettings4)
183 self.sa.commit()
183 self.sa.commit()
184
184
185 h.flash(_('Updated mercurial settings'),
185 h.flash(_('Updated mercurial settings'),
186 category='success')
186 category='success')
187
187
188 except:
188 except:
189 log.error(traceback.format_exc())
189 log.error(traceback.format_exc())
190 h.flash(_('error occured during updating application settings'),
190 h.flash(_('error occured during updating application settings'),
191 category='error')
191 category='error')
192
192
193 self.sa.rollback()
193 self.sa.rollback()
194
194
195
195
196 except formencode.Invalid as errors:
196 except formencode.Invalid as errors:
197 return htmlfill.render(
197 return htmlfill.render(
198 render('admin/settings/settings.html'),
198 render('admin/settings/settings.html'),
199 defaults=errors.value,
199 defaults=errors.value,
200 errors=errors.error_dict or {},
200 errors=errors.error_dict or {},
201 prefix_error=False,
201 prefix_error=False,
202 encoding="UTF-8")
202 encoding="UTF-8")
203
203
204
204
205
205
206 return redirect(url('admin_settings'))
206 return redirect(url('admin_settings'))
207
207
208 @HasPermissionAllDecorator('hg.admin')
208 @HasPermissionAllDecorator('hg.admin')
209 def delete(self, setting_id):
209 def delete(self, setting_id):
210 """DELETE /admin/settings/setting_id: Delete an existing item"""
210 """DELETE /admin/settings/setting_id: Delete an existing item"""
211 # Forms posted to this method should contain a hidden field:
211 # Forms posted to this method should contain a hidden field:
212 # <input type="hidden" name="_method" value="DELETE" />
212 # <input type="hidden" name="_method" value="DELETE" />
213 # Or using helpers:
213 # Or using helpers:
214 # h.form(url('admin_setting', setting_id=ID),
214 # h.form(url('admin_setting', setting_id=ID),
215 # method='delete')
215 # method='delete')
216 # url('admin_setting', setting_id=ID)
216 # url('admin_setting', setting_id=ID)
217
217
218 @HasPermissionAllDecorator('hg.admin')
218 @HasPermissionAllDecorator('hg.admin')
219 def show(self, setting_id, format='html'):
219 def show(self, setting_id, format='html'):
220 """GET /admin/settings/setting_id: Show a specific item"""
220 """GET /admin/settings/setting_id: Show a specific item"""
221 # url('admin_setting', setting_id=ID)
221 # url('admin_setting', setting_id=ID)
222
222
223 @HasPermissionAllDecorator('hg.admin')
223 @HasPermissionAllDecorator('hg.admin')
224 def edit(self, setting_id, format='html'):
224 def edit(self, setting_id, format='html'):
225 """GET /admin/settings/setting_id/edit: Form to edit an existing item"""
225 """GET /admin/settings/setting_id/edit: Form to edit an existing item"""
226 # url('admin_edit_setting', setting_id=ID)
226 # url('admin_edit_setting', setting_id=ID)
227
227
228
228
229 def my_account(self):
229 def my_account(self):
230 """
230 """
231 GET /_admin/my_account Displays info about my account
231 GET /_admin/my_account Displays info about my account
232 """
232 """
233 # url('admin_settings_my_account')
233 # url('admin_settings_my_account')
234 c.user = self.sa.query(User).get(c.hg_app_user.user_id)
234 c.user = self.sa.query(User).get(c.hg_app_user.user_id)
235 c.user_repos = []
235 c.user_repos = []
236 for repo in c.cached_repo_list.values():
236 for repo in c.cached_repo_list.values():
237 if repo.dbrepo.user.username == c.user.username:
237 if repo.dbrepo.user.username == c.user.username:
238 c.user_repos.append(repo)
238 c.user_repos.append(repo)
239
239
240 if c.user.username == 'default':
240 if c.user.username == 'default':
241 h.flash(_("You can't edit this user since it's"
241 h.flash(_("You can't edit this user since it's"
242 " crucial for entire application"), category='warning')
242 " crucial for entire application"), category='warning')
243 return redirect(url('users'))
243 return redirect(url('users'))
244
244
245 defaults = c.user.__dict__
245 defaults = c.user.__dict__
246 return htmlfill.render(
246 return htmlfill.render(
247 render('admin/users/user_edit_my_account.html'),
247 render('admin/users/user_edit_my_account.html'),
248 defaults=defaults,
248 defaults=defaults,
249 encoding="UTF-8",
249 encoding="UTF-8",
250 force_defaults=False
250 force_defaults=False
251 )
251 )
252
252
253 def my_account_update(self):
253 def my_account_update(self):
254 """PUT /_admin/my_account_update: Update an existing item"""
254 """PUT /_admin/my_account_update: Update an existing item"""
255 # Forms posted to this method should contain a hidden field:
255 # Forms posted to this method should contain a hidden field:
256 # <input type="hidden" name="_method" value="PUT" />
256 # <input type="hidden" name="_method" value="PUT" />
257 # Or using helpers:
257 # Or using helpers:
258 # h.form(url('admin_settings_my_account_update'),
258 # h.form(url('admin_settings_my_account_update'),
259 # method='put')
259 # method='put')
260 # url('admin_settings_my_account_update', id=ID)
260 # url('admin_settings_my_account_update', id=ID)
261 user_model = UserModel()
261 user_model = UserModel()
262 uid = c.hg_app_user.user_id
262 uid = c.hg_app_user.user_id
263 _form = UserForm(edit=True, old_data={'user_id':uid})()
263 _form = UserForm(edit=True, old_data={'user_id':uid})()
264 form_result = {}
264 form_result = {}
265 try:
265 try:
266 form_result = _form.to_python(dict(request.POST))
266 form_result = _form.to_python(dict(request.POST))
267 user_model.update_my_account(uid, form_result)
267 user_model.update_my_account(uid, form_result)
268 h.flash(_('Your account was updated succesfully'),
268 h.flash(_('Your account was updated succesfully'),
269 category='success')
269 category='success')
270
270
271 except formencode.Invalid as errors:
271 except formencode.Invalid as errors:
272 #c.user = self.sa.query(User).get(c.hg_app_user.user_id)
272 #c.user = self.sa.query(User).get(c.hg_app_user.user_id)
273 return htmlfill.render(
273 return htmlfill.render(
274 render('admin/users/user_edit_my_account.html'),
274 render('admin/users/user_edit_my_account.html'),
275 defaults=errors.value,
275 defaults=errors.value,
276 errors=errors.error_dict or {},
276 errors=errors.error_dict or {},
277 prefix_error=False,
277 prefix_error=False,
278 encoding="UTF-8")
278 encoding="UTF-8")
279 except Exception:
279 except Exception:
280 log.error(traceback.format_exc())
280 log.error(traceback.format_exc())
281 h.flash(_('error occured during update of user %s') \
281 h.flash(_('error occured during update of user %s') \
282 % form_result.get('username'), category='error')
282 % form_result.get('username'), category='error')
283
283
284 return redirect(url('my_account'))
284 return redirect(url('my_account'))
285
285
286 @HasPermissionAnyDecorator('hg.admin', 'hg.create.repository')
286 @HasPermissionAnyDecorator('hg.admin', 'hg.create.repository')
287 def create_repository(self):
287 def create_repository(self):
288 """GET /_admin/create_repository: Form to create a new item"""
288 """GET /_admin/create_repository: Form to create a new item"""
289 new_repo = request.GET.get('repo', '')
289 new_repo = request.GET.get('repo', '')
290 c.new_repo = h.repo_name_slug(new_repo)
290 c.new_repo = h.repo_name_slug(new_repo)
291
291
292 return render('admin/repos/repo_add_create_repository.html')
292 return render('admin/repos/repo_add_create_repository.html')
293
293
@@ -1,70 +1,70 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 # encoding: utf-8
2 # encoding: utf-8
3 # summary controller for pylons
3 # summary controller for pylons
4 # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
4 # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
5 #
5 #
6 # This program is free software; you can redistribute it and/or
6 # This program is free software; you can redistribute it and/or
7 # modify it under the terms of the GNU General Public License
7 # modify it under the terms of the GNU General Public License
8 # as published by the Free Software Foundation; version 2
8 # as published by the Free Software Foundation; version 2
9 # of the License or (at your opinion) any later version of the license.
9 # of the License or (at your opinion) any later version of the license.
10 #
10 #
11 # This program is distributed in the hope that it will be useful,
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
14 # GNU General Public License for more details.
15 #
15 #
16 # You should have received a copy of the GNU General Public License
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19 # MA 02110-1301, USA.
19 # MA 02110-1301, USA.
20 """
20 """
21 Created on April 18, 2010
21 Created on April 18, 2010
22 summary controller for pylons
22 summary controller for pylons
23 @author: marcink
23 @author: marcink
24 """
24 """
25 from pylons import tmpl_context as c, request,url
25 from pylons import tmpl_context as c, request,url
26 from pylons_app.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
26 from pylons_app.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
27 from pylons_app.lib.base import BaseController, render
27 from pylons_app.lib.base import BaseController, render
28 from pylons_app.lib.utils import OrderedDict
28 from pylons_app.lib.utils import OrderedDict
29 from pylons_app.model.hg_model import HgModel
29 from pylons_app.model.hg_model import HgModel
30 from webhelpers.paginate import Page
30 from webhelpers.paginate import Page
31 from pylons_app.lib.celerylib import run_task
31 from pylons_app.lib.celerylib import run_task
32 from pylons_app.lib.celerylib.tasks import get_commits_stats
32 from pylons_app.lib.celerylib.tasks import get_commits_stats
33 import logging
33 import logging
34
34
35 log = logging.getLogger(__name__)
35 log = logging.getLogger(__name__)
36
36
37 class SummaryController(BaseController):
37 class SummaryController(BaseController):
38
38
39 @LoginRequired()
39 @LoginRequired()
40 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
40 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
41 'repository.admin')
41 'repository.admin')
42 def __before__(self):
42 def __before__(self):
43 super(SummaryController, self).__before__()
43 super(SummaryController, self).__before__()
44
44
45 def index(self):
45 def index(self):
46 hg_model = HgModel()
46 hg_model = HgModel()
47 c.repo_info = hg_model.get_repo(c.repo_name)
47 c.repo_info = hg_model.get_repo(c.repo_name)
48 c.repo_changesets = Page(list(c.repo_info[:10]), page=1, items_per_page=20)
48 c.repo_changesets = Page(list(c.repo_info[:10]), page=1, items_per_page=20)
49 e = request.environ
49 e = request.environ
50 uri = u'%(protocol)s://%(user)s@%(host)s/%(repo_name)s' % {
50 uri = u'%(protocol)s://%(user)s@%(host)s/%(repo_name)s' % {
51 'protocol': e.get('wsgi.url_scheme'),
51 'protocol': e.get('wsgi.url_scheme'),
52 'user':str(c.hg_app_user.username),
52 'user':str(c.hg_app_user.username),
53 'host':e.get('HTTP_HOST'),
53 'host':e.get('HTTP_HOST'),
54 'repo_name':c.repo_name, }
54 'repo_name':c.repo_name, }
55 c.clone_repo_url = uri
55 c.clone_repo_url = uri
56 c.repo_tags = OrderedDict()
56 c.repo_tags = OrderedDict()
57 for name, hash in c.repo_info.tags.items()[:10]:
57 for name, hash in c.repo_info.tags.items()[:10]:
58 c.repo_tags[name] = c.repo_info.get_changeset(hash)
58 c.repo_tags[name] = c.repo_info.get_changeset(hash)
59
59
60 c.repo_branches = OrderedDict()
60 c.repo_branches = OrderedDict()
61 for name, hash in c.repo_info.branches.items()[:10]:
61 for name, hash in c.repo_info.branches.items()[:10]:
62 c.repo_branches[name] = c.repo_info.get_changeset(hash)
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 c.ts_min = task.result[0]
65 c.ts_min = task.result[0]
66 c.ts_max = task.result[1]
66 c.ts_max = task.result[1]
67 c.commit_data = task.result[2]
67 c.commit_data = task.result[2]
68
68
69 return render('summary/summary.html')
69 return render('summary/summary.html')
70
70
@@ -1,364 +1,432 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 # encoding: utf-8
2 # encoding: utf-8
3 # Utilities for hg app
3 # Utilities for hg app
4 # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
4 # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
5 # This program is free software; you can redistribute it and/or
5 # This program is free software; you can redistribute it and/or
6 # modify it under the terms of the GNU General Public License
6 # modify it under the terms of the GNU General Public License
7 # as published by the Free Software Foundation; version 2
7 # as published by the Free Software Foundation; version 2
8 # of the License or (at your opinion) any later version of the license.
8 # of the License or (at your opinion) any later version of the license.
9 #
9 #
10 # This program is distributed in the hope that it will be useful,
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # GNU General Public License for more details.
13 # GNU General Public License for more details.
14 #
14 #
15 # You should have received a copy of the GNU General Public License
15 # You should have received a copy of the GNU General Public License
16 # along with this program; if not, write to the Free Software
16 # along with this program; if not, write to the Free Software
17 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
17 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
18 # MA 02110-1301, USA.
18 # MA 02110-1301, USA.
19 import shutil
19
20
20 """
21 """
21 Created on April 18, 2010
22 Created on April 18, 2010
22 Utilities for hg app
23 Utilities for hg app
23 @author: marcink
24 @author: marcink
24 """
25 """
25 from beaker.cache import cache_region
26 from beaker.cache import cache_region
26 from mercurial import ui, config, hg
27 from mercurial import ui, config, hg
27 from mercurial.error import RepoError
28 from mercurial.error import RepoError
28 from pylons_app.model import meta
29 from pylons_app.model import meta
29 from pylons_app.model.db import Repository, User, HgAppUi, HgAppSettings
30 from pylons_app.model.db import Repository, User, HgAppUi, HgAppSettings
30 from vcs.backends.base import BaseChangeset
31 from vcs.backends.base import BaseChangeset
31 from vcs.utils.lazy import LazyProperty
32 from vcs.utils.lazy import LazyProperty
32 import logging
33 import logging
33 import os
34 import os
35 from os.path import dirname as dn, join as jn
36 import tarfile
34 log = logging.getLogger(__name__)
37 log = logging.getLogger(__name__)
35
38
36
39
37 def get_repo_slug(request):
40 def get_repo_slug(request):
38 return request.environ['pylons.routes_dict'].get('repo_name')
41 return request.environ['pylons.routes_dict'].get('repo_name')
39
42
40 def is_mercurial(environ):
43 def is_mercurial(environ):
41 """
44 """
42 Returns True if request's target is mercurial server - header
45 Returns True if request's target is mercurial server - header
43 ``HTTP_ACCEPT`` of such request would start with ``application/mercurial``.
46 ``HTTP_ACCEPT`` of such request would start with ``application/mercurial``.
44 """
47 """
45 http_accept = environ.get('HTTP_ACCEPT')
48 http_accept = environ.get('HTTP_ACCEPT')
46 if http_accept and http_accept.startswith('application/mercurial'):
49 if http_accept and http_accept.startswith('application/mercurial'):
47 return True
50 return True
48 return False
51 return False
49
52
50 def check_repo_dir(paths):
53 def check_repo_dir(paths):
51 repos_path = paths[0][1].split('/')
54 repos_path = paths[0][1].split('/')
52 if repos_path[-1] in ['*', '**']:
55 if repos_path[-1] in ['*', '**']:
53 repos_path = repos_path[:-1]
56 repos_path = repos_path[:-1]
54 if repos_path[0] != '/':
57 if repos_path[0] != '/':
55 repos_path[0] = '/'
58 repos_path[0] = '/'
56 if not os.path.isdir(os.path.join(*repos_path)):
59 if not os.path.isdir(os.path.join(*repos_path)):
57 raise Exception('Not a valid repository in %s' % paths[0][1])
60 raise Exception('Not a valid repository in %s' % paths[0][1])
58
61
59 def check_repo_fast(repo_name, base_path):
62 def check_repo_fast(repo_name, base_path):
60 if os.path.isdir(os.path.join(base_path, repo_name)):return False
63 if os.path.isdir(os.path.join(base_path, repo_name)):return False
61 return True
64 return True
62
65
63 def check_repo(repo_name, base_path, verify=True):
66 def check_repo(repo_name, base_path, verify=True):
64
67
65 repo_path = os.path.join(base_path, repo_name)
68 repo_path = os.path.join(base_path, repo_name)
66
69
67 try:
70 try:
68 if not check_repo_fast(repo_name, base_path):
71 if not check_repo_fast(repo_name, base_path):
69 return False
72 return False
70 r = hg.repository(ui.ui(), repo_path)
73 r = hg.repository(ui.ui(), repo_path)
71 if verify:
74 if verify:
72 hg.verify(r)
75 hg.verify(r)
73 #here we hnow that repo exists it was verified
76 #here we hnow that repo exists it was verified
74 log.info('%s repo is already created', repo_name)
77 log.info('%s repo is already created', repo_name)
75 return False
78 return False
76 except RepoError:
79 except RepoError:
77 #it means that there is no valid repo there...
80 #it means that there is no valid repo there...
78 log.info('%s repo is free for creation', repo_name)
81 log.info('%s repo is free for creation', repo_name)
79 return True
82 return True
80
83
81 def ask_ok(prompt, retries=4, complaint='Yes or no, please!'):
84 def ask_ok(prompt, retries=4, complaint='Yes or no, please!'):
82 while True:
85 while True:
83 ok = raw_input(prompt)
86 ok = raw_input(prompt)
84 if ok in ('y', 'ye', 'yes'): return True
87 if ok in ('y', 'ye', 'yes'): return True
85 if ok in ('n', 'no', 'nop', 'nope'): return False
88 if ok in ('n', 'no', 'nop', 'nope'): return False
86 retries = retries - 1
89 retries = retries - 1
87 if retries < 0: raise IOError
90 if retries < 0: raise IOError
88 print complaint
91 print complaint
89
92
90 @cache_region('super_short_term', 'cached_hg_ui')
93 @cache_region('super_short_term', 'cached_hg_ui')
91 def get_hg_ui_cached():
94 def get_hg_ui_cached():
92 try:
95 try:
93 sa = meta.Session
96 sa = meta.Session
94 ret = sa.query(HgAppUi).all()
97 ret = sa.query(HgAppUi).all()
95 finally:
98 finally:
96 meta.Session.remove()
99 meta.Session.remove()
97 return ret
100 return ret
98
101
99
102
100 def get_hg_settings():
103 def get_hg_settings():
101 try:
104 try:
102 sa = meta.Session
105 sa = meta.Session
103 ret = sa.query(HgAppSettings).all()
106 ret = sa.query(HgAppSettings).all()
104 finally:
107 finally:
105 meta.Session.remove()
108 meta.Session.remove()
106
109
107 if not ret:
110 if not ret:
108 raise Exception('Could not get application settings !')
111 raise Exception('Could not get application settings !')
109 settings = {}
112 settings = {}
110 for each in ret:
113 for each in ret:
111 settings['hg_app_' + each.app_settings_name] = each.app_settings_value
114 settings['hg_app_' + each.app_settings_name] = each.app_settings_value
112
115
113 return settings
116 return settings
114
117
115 def get_hg_ui_settings():
118 def get_hg_ui_settings():
116 try:
119 try:
117 sa = meta.Session
120 sa = meta.Session
118 ret = sa.query(HgAppUi).all()
121 ret = sa.query(HgAppUi).all()
119 finally:
122 finally:
120 meta.Session.remove()
123 meta.Session.remove()
121
124
122 if not ret:
125 if not ret:
123 raise Exception('Could not get application ui settings !')
126 raise Exception('Could not get application ui settings !')
124 settings = {}
127 settings = {}
125 for each in ret:
128 for each in ret:
126 k = each.ui_key
129 k = each.ui_key
127 v = each.ui_value
130 v = each.ui_value
128 if k == '/':
131 if k == '/':
129 k = 'root_path'
132 k = 'root_path'
130
133
131 if k.find('.') != -1:
134 if k.find('.') != -1:
132 k = k.replace('.', '_')
135 k = k.replace('.', '_')
133
136
134 if each.ui_section == 'hooks':
137 if each.ui_section == 'hooks':
135 v = each.ui_active
138 v = each.ui_active
136
139
137 settings[each.ui_section + '_' + k] = v
140 settings[each.ui_section + '_' + k] = v
138
141
139 return settings
142 return settings
140
143
141 #propagated from mercurial documentation
144 #propagated from mercurial documentation
142 ui_sections = ['alias', 'auth',
145 ui_sections = ['alias', 'auth',
143 'decode/encode', 'defaults',
146 'decode/encode', 'defaults',
144 'diff', 'email',
147 'diff', 'email',
145 'extensions', 'format',
148 'extensions', 'format',
146 'merge-patterns', 'merge-tools',
149 'merge-patterns', 'merge-tools',
147 'hooks', 'http_proxy',
150 'hooks', 'http_proxy',
148 'smtp', 'patch',
151 'smtp', 'patch',
149 'paths', 'profiling',
152 'paths', 'profiling',
150 'server', 'trusted',
153 'server', 'trusted',
151 'ui', 'web', ]
154 'ui', 'web', ]
152
155
153 def make_ui(read_from='file', path=None, checkpaths=True):
156 def make_ui(read_from='file', path=None, checkpaths=True):
154 """
157 """
155 A function that will read python rc files or database
158 A function that will read python rc files or database
156 and make an mercurial ui object from read options
159 and make an mercurial ui object from read options
157
160
158 @param path: path to mercurial config file
161 @param path: path to mercurial config file
159 @param checkpaths: check the path
162 @param checkpaths: check the path
160 @param read_from: read from 'file' or 'db'
163 @param read_from: read from 'file' or 'db'
161 """
164 """
162
165
163 baseui = ui.ui()
166 baseui = ui.ui()
164
167
165 if read_from == 'file':
168 if read_from == 'file':
166 if not os.path.isfile(path):
169 if not os.path.isfile(path):
167 log.warning('Unable to read config file %s' % path)
170 log.warning('Unable to read config file %s' % path)
168 return False
171 return False
169 log.debug('reading hgrc from %s', path)
172 log.debug('reading hgrc from %s', path)
170 cfg = config.config()
173 cfg = config.config()
171 cfg.read(path)
174 cfg.read(path)
172 for section in ui_sections:
175 for section in ui_sections:
173 for k, v in cfg.items(section):
176 for k, v in cfg.items(section):
174 baseui.setconfig(section, k, v)
177 baseui.setconfig(section, k, v)
175 log.debug('settings ui from file[%s]%s:%s', section, k, v)
178 log.debug('settings ui from file[%s]%s:%s', section, k, v)
176 if checkpaths:check_repo_dir(cfg.items('paths'))
179 if checkpaths:check_repo_dir(cfg.items('paths'))
177
180
178
181
179 elif read_from == 'db':
182 elif read_from == 'db':
180 hg_ui = get_hg_ui_cached()
183 hg_ui = get_hg_ui_cached()
181 for ui_ in hg_ui:
184 for ui_ in hg_ui:
182 if ui_.ui_active:
185 if ui_.ui_active:
183 log.debug('settings ui from db[%s]%s:%s', ui_.ui_section, ui_.ui_key, ui_.ui_value)
186 log.debug('settings ui from db[%s]%s:%s', ui_.ui_section, ui_.ui_key, ui_.ui_value)
184 baseui.setconfig(ui_.ui_section, ui_.ui_key, ui_.ui_value)
187 baseui.setconfig(ui_.ui_section, ui_.ui_key, ui_.ui_value)
185
188
186
189
187 return baseui
190 return baseui
188
191
189
192
190 def set_hg_app_config(config):
193 def set_hg_app_config(config):
191 hgsettings = get_hg_settings()
194 hgsettings = get_hg_settings()
192
195
193 for k, v in hgsettings.items():
196 for k, v in hgsettings.items():
194 config[k] = v
197 config[k] = v
195
198
196 def invalidate_cache(name, *args):
199 def invalidate_cache(name, *args):
197 """Invalidates given name cache"""
200 """Invalidates given name cache"""
198
201
199 from beaker.cache import region_invalidate
202 from beaker.cache import region_invalidate
200 log.info('INVALIDATING CACHE FOR %s', name)
203 log.info('INVALIDATING CACHE FOR %s', name)
201
204
202 """propagate our arguments to make sure invalidation works. First
205 """propagate our arguments to make sure invalidation works. First
203 argument has to be the name of cached func name give to cache decorator
206 argument has to be the name of cached func name give to cache decorator
204 without that the invalidation would not work"""
207 without that the invalidation would not work"""
205 tmp = [name]
208 tmp = [name]
206 tmp.extend(args)
209 tmp.extend(args)
207 args = tuple(tmp)
210 args = tuple(tmp)
208
211
209 if name == 'cached_repo_list':
212 if name == 'cached_repo_list':
210 from pylons_app.model.hg_model import _get_repos_cached
213 from pylons_app.model.hg_model import _get_repos_cached
211 region_invalidate(_get_repos_cached, None, *args)
214 region_invalidate(_get_repos_cached, None, *args)
212
215
213 if name == 'full_changelog':
216 if name == 'full_changelog':
214 from pylons_app.model.hg_model import _full_changelog_cached
217 from pylons_app.model.hg_model import _full_changelog_cached
215 region_invalidate(_full_changelog_cached, None, *args)
218 region_invalidate(_full_changelog_cached, None, *args)
216
219
217 class EmptyChangeset(BaseChangeset):
220 class EmptyChangeset(BaseChangeset):
218
221
219 revision = -1
222 revision = -1
220 message = ''
223 message = ''
221
224
222 @LazyProperty
225 @LazyProperty
223 def raw_id(self):
226 def raw_id(self):
224 """
227 """
225 Returns raw string identifing this changeset, useful for web
228 Returns raw string identifing this changeset, useful for web
226 representation.
229 representation.
227 """
230 """
228 return '0' * 12
231 return '0' * 12
229
232
230
233
231 def repo2db_mapper(initial_repo_list, remove_obsolete=False):
234 def repo2db_mapper(initial_repo_list, remove_obsolete=False):
232 """
235 """
233 maps all found repositories into db
236 maps all found repositories into db
234 """
237 """
235 from pylons_app.model.repo_model import RepoModel
238 from pylons_app.model.repo_model import RepoModel
236
239
237 sa = meta.Session
240 sa = meta.Session
238 user = sa.query(User).filter(User.admin == True).first()
241 user = sa.query(User).filter(User.admin == True).first()
239
242
240 rm = RepoModel()
243 rm = RepoModel()
241
244
242 for name, repo in initial_repo_list.items():
245 for name, repo in initial_repo_list.items():
243 if not sa.query(Repository).filter(Repository.repo_name == name).scalar():
246 if not sa.query(Repository).filter(Repository.repo_name == name).scalar():
244 log.info('repository %s not found creating default', name)
247 log.info('repository %s not found creating default', name)
245
248
246 form_data = {
249 form_data = {
247 'repo_name':name,
250 'repo_name':name,
248 'description':repo.description if repo.description != 'unknown' else \
251 'description':repo.description if repo.description != 'unknown' else \
249 'auto description for %s' % name,
252 'auto description for %s' % name,
250 'private':False
253 'private':False
251 }
254 }
252 rm.create(form_data, user, just_db=True)
255 rm.create(form_data, user, just_db=True)
253
256
254
257
255 if remove_obsolete:
258 if remove_obsolete:
256 #remove from database those repositories that are not in the filesystem
259 #remove from database those repositories that are not in the filesystem
257 for repo in sa.query(Repository).all():
260 for repo in sa.query(Repository).all():
258 if repo.repo_name not in initial_repo_list.keys():
261 if repo.repo_name not in initial_repo_list.keys():
259 sa.delete(repo)
262 sa.delete(repo)
260 sa.commit()
263 sa.commit()
261
264
262
265
263 meta.Session.remove()
266 meta.Session.remove()
264
267
265 from UserDict import DictMixin
268 from UserDict import DictMixin
266
269
267 class OrderedDict(dict, DictMixin):
270 class OrderedDict(dict, DictMixin):
268
271
269 def __init__(self, *args, **kwds):
272 def __init__(self, *args, **kwds):
270 if len(args) > 1:
273 if len(args) > 1:
271 raise TypeError('expected at most 1 arguments, got %d' % len(args))
274 raise TypeError('expected at most 1 arguments, got %d' % len(args))
272 try:
275 try:
273 self.__end
276 self.__end
274 except AttributeError:
277 except AttributeError:
275 self.clear()
278 self.clear()
276 self.update(*args, **kwds)
279 self.update(*args, **kwds)
277
280
278 def clear(self):
281 def clear(self):
279 self.__end = end = []
282 self.__end = end = []
280 end += [None, end, end] # sentinel node for doubly linked list
283 end += [None, end, end] # sentinel node for doubly linked list
281 self.__map = {} # key --> [key, prev, next]
284 self.__map = {} # key --> [key, prev, next]
282 dict.clear(self)
285 dict.clear(self)
283
286
284 def __setitem__(self, key, value):
287 def __setitem__(self, key, value):
285 if key not in self:
288 if key not in self:
286 end = self.__end
289 end = self.__end
287 curr = end[1]
290 curr = end[1]
288 curr[2] = end[1] = self.__map[key] = [key, curr, end]
291 curr[2] = end[1] = self.__map[key] = [key, curr, end]
289 dict.__setitem__(self, key, value)
292 dict.__setitem__(self, key, value)
290
293
291 def __delitem__(self, key):
294 def __delitem__(self, key):
292 dict.__delitem__(self, key)
295 dict.__delitem__(self, key)
293 key, prev, next = self.__map.pop(key)
296 key, prev, next = self.__map.pop(key)
294 prev[2] = next
297 prev[2] = next
295 next[1] = prev
298 next[1] = prev
296
299
297 def __iter__(self):
300 def __iter__(self):
298 end = self.__end
301 end = self.__end
299 curr = end[2]
302 curr = end[2]
300 while curr is not end:
303 while curr is not end:
301 yield curr[0]
304 yield curr[0]
302 curr = curr[2]
305 curr = curr[2]
303
306
304 def __reversed__(self):
307 def __reversed__(self):
305 end = self.__end
308 end = self.__end
306 curr = end[1]
309 curr = end[1]
307 while curr is not end:
310 while curr is not end:
308 yield curr[0]
311 yield curr[0]
309 curr = curr[1]
312 curr = curr[1]
310
313
311 def popitem(self, last=True):
314 def popitem(self, last=True):
312 if not self:
315 if not self:
313 raise KeyError('dictionary is empty')
316 raise KeyError('dictionary is empty')
314 if last:
317 if last:
315 key = reversed(self).next()
318 key = reversed(self).next()
316 else:
319 else:
317 key = iter(self).next()
320 key = iter(self).next()
318 value = self.pop(key)
321 value = self.pop(key)
319 return key, value
322 return key, value
320
323
321 def __reduce__(self):
324 def __reduce__(self):
322 items = [[k, self[k]] for k in self]
325 items = [[k, self[k]] for k in self]
323 tmp = self.__map, self.__end
326 tmp = self.__map, self.__end
324 del self.__map, self.__end
327 del self.__map, self.__end
325 inst_dict = vars(self).copy()
328 inst_dict = vars(self).copy()
326 self.__map, self.__end = tmp
329 self.__map, self.__end = tmp
327 if inst_dict:
330 if inst_dict:
328 return (self.__class__, (items,), inst_dict)
331 return (self.__class__, (items,), inst_dict)
329 return self.__class__, (items,)
332 return self.__class__, (items,)
330
333
331 def keys(self):
334 def keys(self):
332 return list(self)
335 return list(self)
333
336
334 setdefault = DictMixin.setdefault
337 setdefault = DictMixin.setdefault
335 update = DictMixin.update
338 update = DictMixin.update
336 pop = DictMixin.pop
339 pop = DictMixin.pop
337 values = DictMixin.values
340 values = DictMixin.values
338 items = DictMixin.items
341 items = DictMixin.items
339 iterkeys = DictMixin.iterkeys
342 iterkeys = DictMixin.iterkeys
340 itervalues = DictMixin.itervalues
343 itervalues = DictMixin.itervalues
341 iteritems = DictMixin.iteritems
344 iteritems = DictMixin.iteritems
342
345
343 def __repr__(self):
346 def __repr__(self):
344 if not self:
347 if not self:
345 return '%s()' % (self.__class__.__name__,)
348 return '%s()' % (self.__class__.__name__,)
346 return '%s(%r)' % (self.__class__.__name__, self.items())
349 return '%s(%r)' % (self.__class__.__name__, self.items())
347
350
348 def copy(self):
351 def copy(self):
349 return self.__class__(self)
352 return self.__class__(self)
350
353
351 @classmethod
354 @classmethod
352 def fromkeys(cls, iterable, value=None):
355 def fromkeys(cls, iterable, value=None):
353 d = cls()
356 d = cls()
354 for key in iterable:
357 for key in iterable:
355 d[key] = value
358 d[key] = value
356 return d
359 return d
357
360
358 def __eq__(self, other):
361 def __eq__(self, other):
359 if isinstance(other, OrderedDict):
362 if isinstance(other, OrderedDict):
360 return len(self) == len(other) and self.items() == other.items()
363 return len(self) == len(other) and self.items() == other.items()
361 return dict.__eq__(self, other)
364 return dict.__eq__(self, other)
362
365
363 def __ne__(self, other):
366 def __ne__(self, other):
364 return not self == other
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 """The application's model objects"""
1 """The application's model objects"""
2 import logging
2 import logging
3 import sqlalchemy as sa
4 from sqlalchemy import orm
5 from pylons_app.model import meta
3 from pylons_app.model import meta
6 from pylons_app.model.meta import Session
7 log = logging.getLogger(__name__)
4 log = logging.getLogger(__name__)
8
5
9 # Add these two imports:
10 import datetime
11 from sqlalchemy import schema, types
12
13 def init_model(engine):
6 def init_model(engine):
14 """Call me before using any of the tables or classes in the model"""
7 """Call me before using any of the tables or classes in the model"""
15 log.info("INITIALIZING DB MODELS")
8 log.info("INITIALIZING DB MODELS")
16 meta.Base.metadata.bind = engine
9 meta.Base.metadata.bind = engine
17 #meta.Base2.metadata.bind = engine2
10 #meta.Base2.metadata.bind = engine2
18
11
19 #THIS IS A TEST FOR EXECUTING SCRIPT AND LOAD PYLONS APPLICATION GLOBALS
12 #THIS IS A TEST FOR EXECUTING SCRIPT AND LOAD PYLONS APPLICATION GLOBALS
20 #from paste.deploy import appconfig
13 #from paste.deploy import appconfig
21 #from pylons import config
14 #from pylons import config
22 #from sqlalchemy import engine_from_config
15 #from sqlalchemy import engine_from_config
23 #from pylons_app.config.environment import load_environment
16 #from pylons_app.config.environment import load_environment
24 #
17 #
25 #conf = appconfig('config:development.ini', relative_to = './../../')
18 #conf = appconfig('config:development.ini', relative_to = './../../')
26 #load_environment(conf.global_conf, conf.local_conf)
19 #load_environment(conf.global_conf, conf.local_conf)
27 #
20 #
28 #engine = engine_from_config(config, 'sqlalchemy.')
21 #engine = engine_from_config(config, 'sqlalchemy.')
29 #init_model(engine)
22 #init_model(engine)
30 # DO SOMETHING
23 # DO SOMETHING
@@ -1,125 +1,125 b''
1 from pylons_app.model.meta import Base
1 from pylons_app.model.meta import Base
2 from sqlalchemy import *
2 from sqlalchemy import *
3 from sqlalchemy.orm import relation, backref
3 from sqlalchemy.orm import relation, backref
4 from sqlalchemy.orm.session import Session
4 from sqlalchemy.orm.session import Session
5 from vcs.utils.lazy import LazyProperty
5 from vcs.utils.lazy import LazyProperty
6 import logging
6 import logging
7
7
8 log = logging.getLogger(__name__)
8 log = logging.getLogger(__name__)
9
9
10 class HgAppSettings(Base):
10 class HgAppSettings(Base):
11 __tablename__ = 'hg_app_settings'
11 __tablename__ = 'hg_app_settings'
12 __table_args__ = (UniqueConstraint('app_settings_name'), {'useexisting':True})
12 __table_args__ = (UniqueConstraint('app_settings_name'), {'useexisting':True})
13 app_settings_id = Column("app_settings_id", INTEGER(), nullable=False, unique=True, default=None, primary_key=True)
13 app_settings_id = Column("app_settings_id", INTEGER(), nullable=False, unique=True, default=None, primary_key=True)
14 app_settings_name = Column("app_settings_name", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
14 app_settings_name = Column("app_settings_name", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
15 app_settings_value = Column("app_settings_value", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
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 class HgAppUi(Base):
17 class HgAppUi(Base):
18 __tablename__ = 'hg_app_ui'
18 __tablename__ = 'hg_app_ui'
19 __table_args__ = {'useexisting':True}
19 __table_args__ = {'useexisting':True}
20 ui_id = Column("ui_id", INTEGER(), nullable=False, unique=True, default=None, primary_key=True)
20 ui_id = Column("ui_id", INTEGER(), nullable=False, unique=True, default=None, primary_key=True)
21 ui_section = Column("ui_section", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
21 ui_section = Column("ui_section", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
22 ui_key = Column("ui_key", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
22 ui_key = Column("ui_key", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
23 ui_value = Column("ui_value", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
23 ui_value = Column("ui_value", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
24 ui_active = Column("ui_active", BOOLEAN(), nullable=True, unique=None, default=True)
24 ui_active = Column("ui_active", BOOLEAN(), nullable=True, unique=None, default=True)
25
25
26
26
27 class User(Base):
27 class User(Base):
28 __tablename__ = 'users'
28 __tablename__ = 'users'
29 __table_args__ = (UniqueConstraint('username'), {'useexisting':True})
29 __table_args__ = (UniqueConstraint('username'), UniqueConstraint('email'), {'useexisting':True})
30 user_id = Column("user_id", INTEGER(), nullable=False, unique=True, default=None, primary_key=True)
30 user_id = Column("user_id", INTEGER(), nullable=False, unique=True, default=None, primary_key=True)
31 username = Column("username", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
31 username = Column("username", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
32 password = Column("password", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
32 password = Column("password", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
33 active = Column("active", BOOLEAN(), nullable=True, unique=None, default=None)
33 active = Column("active", BOOLEAN(), nullable=True, unique=None, default=None)
34 admin = Column("admin", BOOLEAN(), nullable=True, unique=None, default=False)
34 admin = Column("admin", BOOLEAN(), nullable=True, unique=None, default=False)
35 name = Column("name", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
35 name = Column("name", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
36 lastname = Column("lastname", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
36 lastname = Column("lastname", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
37 email = Column("email", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
37 email = Column("email", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
38 last_login = Column("last_login", DATETIME(timezone=False), nullable=True, unique=None, default=None)
38 last_login = Column("last_login", DATETIME(timezone=False), nullable=True, unique=None, default=None)
39
39
40 user_log = relation('UserLog')
40 user_log = relation('UserLog')
41 user_perms = relation('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id")
41 user_perms = relation('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id")
42
42
43 @LazyProperty
43 @LazyProperty
44 def full_contact(self):
44 def full_contact(self):
45 return '%s %s <%s>' % (self.name, self.lastname, self.email)
45 return '%s %s <%s>' % (self.name, self.lastname, self.email)
46
46
47 def __repr__(self):
47 def __repr__(self):
48 return "<User('id:%s:%s')>" % (self.user_id, self.username)
48 return "<User('id:%s:%s')>" % (self.user_id, self.username)
49
49
50 def update_lastlogin(self):
50 def update_lastlogin(self):
51 """Update user lastlogin"""
51 """Update user lastlogin"""
52 import datetime
52 import datetime
53
53
54 try:
54 try:
55 session = Session.object_session(self)
55 session = Session.object_session(self)
56 self.last_login = datetime.datetime.now()
56 self.last_login = datetime.datetime.now()
57 session.add(self)
57 session.add(self)
58 session.commit()
58 session.commit()
59 log.debug('updated user %s lastlogin',self.username)
59 log.debug('updated user %s lastlogin', self.username)
60 except Exception:
60 except Exception:
61 session.rollback()
61 session.rollback()
62
62
63
63
64 class UserLog(Base):
64 class UserLog(Base):
65 __tablename__ = 'user_logs'
65 __tablename__ = 'user_logs'
66 __table_args__ = {'useexisting':True}
66 __table_args__ = {'useexisting':True}
67 user_log_id = Column("user_log_id", INTEGER(), nullable=False, unique=True, default=None, primary_key=True)
67 user_log_id = Column("user_log_id", INTEGER(), nullable=False, unique=True, default=None, primary_key=True)
68 user_id = Column("user_id", INTEGER(), ForeignKey(u'users.user_id'), nullable=False, unique=None, default=None)
68 user_id = Column("user_id", INTEGER(), ForeignKey(u'users.user_id'), nullable=False, unique=None, default=None)
69 user_ip = Column("user_ip", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
69 user_ip = Column("user_ip", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
70 repository = Column("repository", TEXT(length=None, convert_unicode=False, assert_unicode=None), ForeignKey(u'repositories.repo_name'), nullable=False, unique=None, default=None)
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 action = Column("action", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
71 action = Column("action", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
72 action_date = Column("action_date", DATETIME(timezone=False), nullable=True, unique=None, default=None)
72 action_date = Column("action_date", DATETIME(timezone=False), nullable=True, unique=None, default=None)
73
73
74 user = relation('User')
74 user = relation('User')
75
75
76 class Repository(Base):
76 class Repository(Base):
77 __tablename__ = 'repositories'
77 __tablename__ = 'repositories'
78 __table_args__ = (UniqueConstraint('repo_name'), {'useexisting':True},)
78 __table_args__ = (UniqueConstraint('repo_name'), {'useexisting':True},)
79 repo_id = Column("repo_id", INTEGER(), nullable=False, unique=True, default=None, primary_key=True)
79 repo_id = Column("repo_id", INTEGER(), nullable=False, unique=True, default=None, primary_key=True)
80 repo_name = Column("repo_name", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
80 repo_name = Column("repo_name", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
81 user_id = Column("user_id", INTEGER(), ForeignKey(u'users.user_id'), nullable=False, unique=False, default=None)
81 user_id = Column("user_id", INTEGER(), ForeignKey(u'users.user_id'), nullable=False, unique=False, default=None)
82 private = Column("private", BOOLEAN(), nullable=True, unique=None, default=None)
82 private = Column("private", BOOLEAN(), nullable=True, unique=None, default=None)
83 description = Column("description", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
83 description = Column("description", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
84
84
85 user = relation('User')
85 user = relation('User')
86 repo_to_perm = relation('RepoToPerm', cascade='all')
86 repo_to_perm = relation('RepoToPerm', cascade='all')
87
87
88 def __repr__(self):
88 def __repr__(self):
89 return "<Repository('id:%s:%s')>" % (self.repo_id, self.repo_name)
89 return "<Repository('id:%s:%s')>" % (self.repo_id, self.repo_name)
90
90
91 class Permission(Base):
91 class Permission(Base):
92 __tablename__ = 'permissions'
92 __tablename__ = 'permissions'
93 __table_args__ = {'useexisting':True}
93 __table_args__ = {'useexisting':True}
94 permission_id = Column("permission_id", INTEGER(), nullable=False, unique=True, default=None, primary_key=True)
94 permission_id = Column("permission_id", INTEGER(), nullable=False, unique=True, default=None, primary_key=True)
95 permission_name = Column("permission_name", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
95 permission_name = Column("permission_name", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
96 permission_longname = Column("permission_longname", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
96 permission_longname = Column("permission_longname", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
97
97
98 def __repr__(self):
98 def __repr__(self):
99 return "<Permission('%s:%s')>" % (self.permission_id, self.permission_name)
99 return "<Permission('%s:%s')>" % (self.permission_id, self.permission_name)
100
100
101 class RepoToPerm(Base):
101 class RepoToPerm(Base):
102 __tablename__ = 'repo_to_perm'
102 __tablename__ = 'repo_to_perm'
103 __table_args__ = (UniqueConstraint('user_id', 'repository_id'), {'useexisting':True})
103 __table_args__ = (UniqueConstraint('user_id', 'repository_id'), {'useexisting':True})
104 repo_to_perm_id = Column("repo_to_perm_id", INTEGER(), nullable=False, unique=True, default=None, primary_key=True)
104 repo_to_perm_id = Column("repo_to_perm_id", INTEGER(), nullable=False, unique=True, default=None, primary_key=True)
105 user_id = Column("user_id", INTEGER(), ForeignKey(u'users.user_id'), nullable=False, unique=None, default=None)
105 user_id = Column("user_id", INTEGER(), ForeignKey(u'users.user_id'), nullable=False, unique=None, default=None)
106 permission_id = Column("permission_id", INTEGER(), ForeignKey(u'permissions.permission_id'), nullable=False, unique=None, default=None)
106 permission_id = Column("permission_id", INTEGER(), ForeignKey(u'permissions.permission_id'), nullable=False, unique=None, default=None)
107 repository_id = Column("repository_id", INTEGER(), ForeignKey(u'repositories.repo_id'), nullable=False, unique=None, default=None)
107 repository_id = Column("repository_id", INTEGER(), ForeignKey(u'repositories.repo_id'), nullable=False, unique=None, default=None)
108
108
109 user = relation('User')
109 user = relation('User')
110 permission = relation('Permission')
110 permission = relation('Permission')
111 repository = relation('Repository')
111 repository = relation('Repository')
112
112
113 class UserToPerm(Base):
113 class UserToPerm(Base):
114 __tablename__ = 'user_to_perm'
114 __tablename__ = 'user_to_perm'
115 __table_args__ = (UniqueConstraint('user_id', 'permission_id'), {'useexisting':True})
115 __table_args__ = (UniqueConstraint('user_id', 'permission_id'), {'useexisting':True})
116 user_to_perm_id = Column("user_to_perm_id", INTEGER(), nullable=False, unique=True, default=None, primary_key=True)
116 user_to_perm_id = Column("user_to_perm_id", INTEGER(), nullable=False, unique=True, default=None, primary_key=True)
117 user_id = Column("user_id", INTEGER(), ForeignKey(u'users.user_id'), nullable=False, unique=None, default=None)
117 user_id = Column("user_id", INTEGER(), ForeignKey(u'users.user_id'), nullable=False, unique=None, default=None)
118 permission_id = Column("permission_id", INTEGER(), ForeignKey(u'permissions.permission_id'), nullable=False, unique=None, default=None)
118 permission_id = Column("permission_id", INTEGER(), ForeignKey(u'permissions.permission_id'), nullable=False, unique=None, default=None)
119
119
120 user = relation('User')
120 user = relation('User')
121 permission = relation('Permission')
121 permission = relation('Permission')
122
122
123
123
124
124
125
125
@@ -1,174 +1,174 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 # encoding: utf-8
2 # encoding: utf-8
3 # Model for hg app
3 # Model for hg app
4 # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
4 # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
5 #
5 #
6 # This program is free software; you can redistribute it and/or
6 # This program is free software; you can redistribute it and/or
7 # modify it under the terms of the GNU General Public License
7 # modify it under the terms of the GNU General Public License
8 # as published by the Free Software Foundation; version 2
8 # as published by the Free Software Foundation; version 2
9 # of the License or (at your opinion) any later version of the license.
9 # of the License or (at your opinion) any later version of the license.
10 #
10 #
11 # This program is distributed in the hope that it will be useful,
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
14 # GNU General Public License for more details.
15 #
15 #
16 # You should have received a copy of the GNU General Public License
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19 # MA 02110-1301, USA.
19 # MA 02110-1301, USA.
20 """
20 """
21 Created on April 9, 2010
21 Created on April 9, 2010
22 Model for hg app
22 Model for hg app
23 @author: marcink
23 @author: marcink
24 """
24 """
25 from beaker.cache import cache_region
25 from beaker.cache import cache_region
26 from mercurial import ui
26 from mercurial import ui
27 from mercurial.hgweb.hgwebdir_mod import findrepos
27 from mercurial.hgweb.hgwebdir_mod import findrepos
28 from pylons.i18n.translation import _
28 from pylons.i18n.translation import _
29 from pylons_app.lib.auth import HasRepoPermissionAny
29 from pylons_app.lib.auth import HasRepoPermissionAny
30 from pylons_app.model import meta
30 from pylons_app.model import meta
31 from pylons_app.model.db import Repository, User
31 from pylons_app.model.db import Repository, User
32 from pylons_app.lib import helpers as h
32 from pylons_app.lib import helpers as h
33 from vcs.exceptions import RepositoryError, VCSError
33 from vcs.exceptions import RepositoryError, VCSError
34 import logging
34 import logging
35 import os
35 import os
36 import sys
36 import sys
37 log = logging.getLogger(__name__)
37 log = logging.getLogger(__name__)
38
38
39 try:
39 try:
40 from vcs.backends.hg import MercurialRepository
40 from vcs.backends.hg import MercurialRepository
41 except ImportError:
41 except ImportError:
42 sys.stderr.write('You have to import vcs module')
42 sys.stderr.write('You have to import vcs module')
43 raise Exception('Unable to import vcs')
43 raise Exception('Unable to import vcs')
44
44
45 def _get_repos_cached_initial(app_globals, initial):
45 def _get_repos_cached_initial(app_globals, initial):
46 """
46 """
47 return cached dict with repos
47 return cached dict with repos
48 """
48 """
49 g = app_globals
49 g = app_globals
50 return HgModel.repo_scan(g.paths[0][0], g.paths[0][1], g.baseui, initial)
50 return HgModel.repo_scan(g.paths[0][0], g.paths[0][1], g.baseui, initial)
51
51
52 @cache_region('long_term', 'cached_repo_list')
52 @cache_region('long_term', 'cached_repo_list')
53 def _get_repos_cached():
53 def _get_repos_cached():
54 """
54 """
55 return cached dict with repos
55 return cached dict with repos
56 """
56 """
57 log.info('getting all repositories list')
57 log.info('getting all repositories list')
58 from pylons import app_globals as g
58 from pylons import app_globals as g
59 return HgModel.repo_scan(g.paths[0][0], g.paths[0][1], g.baseui)
59 return HgModel.repo_scan(g.paths[0][0], g.paths[0][1], g.baseui)
60
60
61 @cache_region('super_short_term', 'cached_repos_switcher_list')
61 @cache_region('super_short_term', 'cached_repos_switcher_list')
62 def _get_repos_switcher_cached(cached_repo_list):
62 def _get_repos_switcher_cached(cached_repo_list):
63 repos_lst = []
63 repos_lst = []
64 for repo in sorted(x.name.lower() for x in cached_repo_list.values()):
64 for repo in sorted(x.name.lower() for x in cached_repo_list.values()):
65 if HasRepoPermissionAny('repository.write', 'repository.read', 'repository.admin')(repo, 'main page check'):
65 if HasRepoPermissionAny('repository.write', 'repository.read', 'repository.admin')(repo, 'main page check'):
66 repos_lst.append(repo)
66 repos_lst.append(repo)
67
67
68 return repos_lst
68 return repos_lst
69
69
70 @cache_region('long_term', 'full_changelog')
70 @cache_region('long_term', 'full_changelog')
71 def _full_changelog_cached(repo_name):
71 def _full_changelog_cached(repo_name):
72 log.info('getting full changelog for %s', repo_name)
72 log.info('getting full changelog for %s', repo_name)
73 return list(reversed(list(HgModel().get_repo(repo_name))))
73 return list(reversed(list(HgModel().get_repo(repo_name))))
74
74
75 class HgModel(object):
75 class HgModel(object):
76 """
76 """
77 Mercurial Model
77 Mercurial Model
78 """
78 """
79
79
80 def __init__(self):
80 def __init__(self):
81 """
81 """
82 Constructor
82 Constructor
83 """
83 """
84
84
85 @staticmethod
85 @staticmethod
86 def repo_scan(repos_prefix, repos_path, baseui, initial=False):
86 def repo_scan(repos_prefix, repos_path, baseui, initial=False):
87 """
87 """
88 Listing of repositories in given path. This path should not be a
88 Listing of repositories in given path. This path should not be a
89 repository itself. Return a dictionary of repository objects
89 repository itself. Return a dictionary of repository objects
90 :param repos_path: path to directory it could take syntax with
90 :param repos_path: path to directory it could take syntax with
91 * or ** for deep recursive displaying repositories
91 * or ** for deep recursive displaying repositories
92 """
92 """
93 sa = meta.Session()
93 sa = meta.Session()
94 def check_repo_dir(path):
94 def check_repo_dir(path):
95 """
95 """
96 Checks the repository
96 Checks the repository
97 :param path:
97 :param path:
98 """
98 """
99 repos_path = path.split('/')
99 repos_path = path.split('/')
100 if repos_path[-1] in ['*', '**']:
100 if repos_path[-1] in ['*', '**']:
101 repos_path = repos_path[:-1]
101 repos_path = repos_path[:-1]
102 if repos_path[0] != '/':
102 if repos_path[0] != '/':
103 repos_path[0] = '/'
103 repos_path[0] = '/'
104 if not os.path.isdir(os.path.join(*repos_path)):
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 if not repos_path.endswith('*'):
106 if not repos_path.endswith('*'):
107 raise VCSError('You need to specify * or ** at the end of path '
107 raise VCSError('You need to specify * or ** at the end of path '
108 'for recursive scanning')
108 'for recursive scanning')
109
109
110 check_repo_dir(repos_path)
110 check_repo_dir(repos_path)
111 log.info('scanning for repositories in %s', repos_path)
111 log.info('scanning for repositories in %s', repos_path)
112 repos = findrepos([(repos_prefix, repos_path)])
112 repos = findrepos([(repos_prefix, repos_path)])
113 if not isinstance(baseui, ui.ui):
113 if not isinstance(baseui, ui.ui):
114 baseui = ui.ui()
114 baseui = ui.ui()
115
115
116 repos_list = {}
116 repos_list = {}
117 for name, path in repos:
117 for name, path in repos:
118 try:
118 try:
119 #name = name.split('/')[-1]
119 #name = name.split('/')[-1]
120 if repos_list.has_key(name):
120 if repos_list.has_key(name):
121 raise RepositoryError('Duplicate repository name %s found in'
121 raise RepositoryError('Duplicate repository name %s found in'
122 ' %s' % (name, path))
122 ' %s' % (name, path))
123 else:
123 else:
124
124
125 repos_list[name] = MercurialRepository(path, baseui=baseui)
125 repos_list[name] = MercurialRepository(path, baseui=baseui)
126 repos_list[name].name = name
126 repos_list[name].name = name
127
127
128 dbrepo = None
128 dbrepo = None
129 if not initial:
129 if not initial:
130 dbrepo = sa.query(Repository)\
130 dbrepo = sa.query(Repository)\
131 .filter(Repository.repo_name == name).scalar()
131 .filter(Repository.repo_name == name).scalar()
132
132
133 if dbrepo:
133 if dbrepo:
134 log.info('Adding db instance to cached list')
134 log.info('Adding db instance to cached list')
135 repos_list[name].dbrepo = dbrepo
135 repos_list[name].dbrepo = dbrepo
136 repos_list[name].description = dbrepo.description
136 repos_list[name].description = dbrepo.description
137 if dbrepo.user:
137 if dbrepo.user:
138 repos_list[name].contact = dbrepo.user.full_contact
138 repos_list[name].contact = dbrepo.user.full_contact
139 else:
139 else:
140 repos_list[name].contact = sa.query(User)\
140 repos_list[name].contact = sa.query(User)\
141 .filter(User.admin == True).first().full_contact
141 .filter(User.admin == True).first().full_contact
142 except OSError:
142 except OSError:
143 continue
143 continue
144 meta.Session.remove()
144 meta.Session.remove()
145 return repos_list
145 return repos_list
146
146
147 def get_repos(self):
147 def get_repos(self):
148 for name, repo in _get_repos_cached().items():
148 for name, repo in _get_repos_cached().items():
149 if repo._get_hidden():
149 if repo._get_hidden():
150 #skip hidden web repository
150 #skip hidden web repository
151 continue
151 continue
152
152
153 last_change = repo.last_change
153 last_change = repo.last_change
154 tip = h.get_changeset_safe(repo, 'tip')
154 tip = h.get_changeset_safe(repo, 'tip')
155
155
156 tmp_d = {}
156 tmp_d = {}
157 tmp_d['name'] = repo.name
157 tmp_d['name'] = repo.name
158 tmp_d['name_sort'] = tmp_d['name'].lower()
158 tmp_d['name_sort'] = tmp_d['name'].lower()
159 tmp_d['description'] = repo.description
159 tmp_d['description'] = repo.description
160 tmp_d['description_sort'] = tmp_d['description']
160 tmp_d['description_sort'] = tmp_d['description']
161 tmp_d['last_change'] = last_change
161 tmp_d['last_change'] = last_change
162 tmp_d['last_change_sort'] = last_change[1] - last_change[0]
162 tmp_d['last_change_sort'] = last_change[1] - last_change[0]
163 tmp_d['tip'] = tip.raw_id
163 tmp_d['tip'] = tip.raw_id
164 tmp_d['tip_sort'] = tip.revision
164 tmp_d['tip_sort'] = tip.revision
165 tmp_d['rev'] = tip.revision
165 tmp_d['rev'] = tip.revision
166 tmp_d['contact'] = repo.contact
166 tmp_d['contact'] = repo.contact
167 tmp_d['contact_sort'] = tmp_d['contact']
167 tmp_d['contact_sort'] = tmp_d['contact']
168 tmp_d['repo_archives'] = list(repo._get_archives())
168 tmp_d['repo_archives'] = list(repo._get_archives())
169 tmp_d['last_msg'] = tip.message
169 tmp_d['last_msg'] = tip.message
170 tmp_d['repo'] = repo
170 tmp_d['repo'] = repo
171 yield tmp_d
171 yield tmp_d
172
172
173 def get_repo(self, repo_name):
173 def get_repo(self, repo_name):
174 return _get_repos_cached()[repo_name]
174 return _get_repos_cached()[repo_name]
@@ -1,45 +1,49 b''
1 """Pylons application test package
1 """Pylons application test package
2
2
3 This package assumes the Pylons environment is already loaded, such as
3 This package assumes the Pylons environment is already loaded, such as
4 when this script is imported from the `nosetests --with-pylons=test.ini`
4 when this script is imported from the `nosetests --with-pylons=test.ini`
5 command.
5 command.
6
6
7 This module initializes the application via ``websetup`` (`paster
7 This module initializes the application via ``websetup`` (`paster
8 setup-app`) and provides the base testing objects.
8 setup-app`) and provides the base testing objects.
9 """
9 """
10 from unittest import TestCase
10 from unittest import TestCase
11
11
12 from paste.deploy import loadapp
12 from paste.deploy import loadapp
13 from paste.script.appinstall import SetupCommand
13 from paste.script.appinstall import SetupCommand
14 from pylons import config, url
14 from pylons import config, url
15 from routes.util import URLGenerator
15 from routes.util import URLGenerator
16 from webtest import TestApp
16 from webtest import TestApp
17 import os
17 import os
18 from pylons_app.model import meta
18 from pylons_app.model import meta
19 import logging
20 log = logging.getLogger(__name__)
21
19 import pylons.test
22 import pylons.test
20
23
21 __all__ = ['environ', 'url', 'TestController']
24 __all__ = ['environ', 'url', 'TestController']
22
25
23 # Invoke websetup with the current config file
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 environ = {}
29 environ = {}
27
30
28 class TestController(TestCase):
31 class TestController(TestCase):
29
32
30 def __init__(self, *args, **kwargs):
33 def __init__(self, *args, **kwargs):
31 wsgiapp = pylons.test.pylonsapp
34 wsgiapp = pylons.test.pylonsapp
32 config = wsgiapp.config
35 config = wsgiapp.config
33 self.app = TestApp(wsgiapp)
36 self.app = TestApp(wsgiapp)
34 url._push_object(URLGenerator(config['routes.map'], environ))
37 url._push_object(URLGenerator(config['routes.map'], environ))
35 self.sa = meta.Session
38 self.sa = meta.Session
36 TestCase.__init__(self, *args, **kwargs)
39 TestCase.__init__(self, *args, **kwargs)
37
40
38
41
39 def log_user(self):
42 def log_user(self):
40 response = self.app.post(url(controller='login', action='index'),
43 response = self.app.post(url(controller='login', action='index'),
41 {'username':'test_admin',
44 {'username':'test_admin',
42 'password':'test'})
45 'password':'test'})
43 assert response.status == '302 Found', 'Wrong response code from login got %s' % response.status
46 assert response.status == '302 Found', 'Wrong response code from login got %s' % response.status
44 assert response.session['hg_app_user'].username == 'test_admin', 'wrong logged in user'
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 from pylons_app.tests import *
1 from pylons_app.tests import *
2
2
3 class TestAdminController(TestController):
3 class TestAdminController(TestController):
4
4
5 def test_index(self):
5 def test_index(self):
6 self.log_user()
6 response = self.app.get(url(controller='admin/admin', action='index'))
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 # Test response...
9 # Test response...
@@ -1,7 +1,8 b''
1 from pylons_app.tests import *
1 from pylons_app.tests import *
2
2
3 class TestBranchesController(TestController):
3 class TestBranchesController(TestController):
4
4
5 def test_index(self):
5 def test_index(self):
6 self.log_user()
6 response = self.app.get(url(controller='branches', action='index',repo_name='vcs_test'))
7 response = self.app.get(url(controller='branches', action='index',repo_name='vcs_test'))
7 # Test response...
8 # Test response...
@@ -1,7 +1,8 b''
1 from pylons_app.tests import *
1 from pylons_app.tests import *
2
2
3 class TestChangelogController(TestController):
3 class TestChangelogController(TestController):
4
4
5 def test_index(self):
5 def test_index(self):
6 self.log_user()
6 response = self.app.get(url(controller='changelog', action='index',repo_name='vcs_test'))
7 response = self.app.get(url(controller='changelog', action='index',repo_name='vcs_test'))
7 # Test response...
8 # Test response...
@@ -1,13 +1,15 b''
1 from pylons_app.tests import *
1 from pylons_app.tests import *
2
2
3 class TestFeedController(TestController):
3 class TestFeedController(TestController):
4
4
5 def test_rss(self):
5 def test_rss(self):
6 self.log_user()
6 response = self.app.get(url(controller='feed', action='rss',
7 response = self.app.get(url(controller='feed', action='rss',
7 repo_name='vcs_test'))
8 repo_name='vcs_test'))
8 # Test response...
9 # Test response...
9
10
10 def test_atom(self):
11 def test_atom(self):
12 self.log_user()
11 response = self.app.get(url(controller='feed', action='atom',
13 response = self.app.get(url(controller='feed', action='atom',
12 repo_name='vcs_test'))
14 repo_name='vcs_test'))
13 # Test response... No newline at end of file
15 # Test response...
@@ -1,10 +1,11 b''
1 from pylons_app.tests import *
1 from pylons_app.tests import *
2
2
3 class TestFilesController(TestController):
3 class TestFilesController(TestController):
4
4
5 def test_index(self):
5 def test_index(self):
6 self.log_user()
6 response = self.app.get(url(controller='files', action='index',
7 response = self.app.get(url(controller='files', action='index',
7 repo_name='vcs_test',
8 repo_name='vcs_test',
8 revision='tip',
9 revision='tip',
9 f_path='/'))
10 f_path='/'))
10 # Test response...
11 # Test response...
@@ -1,111 +1,139 b''
1 from pylons_app.tests import *
1 from pylons_app.tests import *
2 from pylons_app.model.db import User
2 from pylons_app.model.db import User
3 from pylons_app.lib.auth import check_password
3 from pylons_app.lib.auth import check_password
4
4
5
5
6 class TestLoginController(TestController):
6 class TestLoginController(TestController):
7
7
8 def test_index(self):
8 def test_index(self):
9 response = self.app.get(url(controller='login', action='index'))
9 response = self.app.get(url(controller='login', action='index'))
10 assert response.status == '200 OK', 'Wrong response from login page got %s' % response.status
10 assert response.status == '200 OK', 'Wrong response from login page got %s' % response.status
11 # Test response...
11 # Test response...
12
12
13 def test_login_admin_ok(self):
13 def test_login_admin_ok(self):
14 response = self.app.post(url(controller='login', action='index'),
14 response = self.app.post(url(controller='login', action='index'),
15 {'username':'test_admin',
15 {'username':'test_admin',
16 'password':'test'})
16 'password':'test'})
17 assert response.status == '302 Found', 'Wrong response code from login got %s' % response.status
17 assert response.status == '302 Found', 'Wrong response code from login got %s' % response.status
18 assert response.session['hg_app_user'].username == 'test_admin', 'wrong logged in user'
18 assert response.session['hg_app_user'].username == 'test_admin', 'wrong logged in user'
19 response = response.follow()
19 response = response.follow()
20 assert 'auto description for vcs_test' in response.body
20 assert 'auto description for vcs_test' in response.body
21
21
22 def test_login_regular_ok(self):
22 def test_login_regular_ok(self):
23 response = self.app.post(url(controller='login', action='index'),
23 response = self.app.post(url(controller='login', action='index'),
24 {'username':'test_regular',
24 {'username':'test_regular',
25 'password':'test'})
25 'password':'test'})
26 assert response.status == '302 Found', 'Wrong response code from login got %s' % response.status
26 assert response.status == '302 Found', 'Wrong response code from login got %s' % response.status
27 assert response.session['hg_app_user'].username == 'test_regular', 'wrong logged in user'
27 assert response.session['hg_app_user'].username == 'test_regular', 'wrong logged in user'
28 response = response.follow()
28 response = response.follow()
29 assert 'auto description for vcs_test' in response.body
29 assert 'auto description for vcs_test' in response.body
30 assert '<a title="Admin" href="/_admin">' not in response.body
30 assert '<a title="Admin" href="/_admin">' not in response.body
31
31
32 def test_login_ok_came_from(self):
32 def test_login_ok_came_from(self):
33 test_came_from = '/_admin/users'
33 test_came_from = '/_admin/users'
34 response = self.app.post(url(controller='login', action='index', came_from=test_came_from),
34 response = self.app.post(url(controller='login', action='index', came_from=test_came_from),
35 {'username':'test_admin',
35 {'username':'test_admin',
36 'password':'test'})
36 'password':'test'})
37 assert response.status == '302 Found', 'Wrong response code from came from redirection'
37 assert response.status == '302 Found', 'Wrong response code from came from redirection'
38 response = response.follow()
38 response = response.follow()
39
39
40 assert response.status == '200 OK', 'Wrong response from login page got %s' % response.status
40 assert response.status == '200 OK', 'Wrong response from login page got %s' % response.status
41 assert 'Users administration' in response.body, 'No proper title in response'
41 assert 'Users administration' in response.body, 'No proper title in response'
42
42
43
43
44 def test_login_wrong(self):
44 def test_login_wrong(self):
45 response = self.app.post(url(controller='login', action='index'),
45 response = self.app.post(url(controller='login', action='index'),
46 {'username':'error',
46 {'username':'error',
47 'password':'test'})
47 'password':'test'})
48 assert response.status == '200 OK', 'Wrong response from login page'
48 assert response.status == '200 OK', 'Wrong response from login page'
49
49
50 assert 'invalid user name' in response.body, 'No error username message in response'
50 assert 'invalid user name' in response.body, 'No error username message in response'
51 assert 'invalid password' in response.body, 'No error password message in response'
51 assert 'invalid password' in response.body, 'No error password message in response'
52
52
53
53
54 def test_register(self):
54 def test_register(self):
55 response = self.app.get(url(controller='login', action='register'))
55 response = self.app.get(url(controller='login', action='register'))
56 assert 'Sign Up to hg-app' in response.body, 'wrong page for user registration'
56 assert 'Sign Up to hg-app' in response.body, 'wrong page for user registration'
57
57
58 def test_register_err_same_username(self):
58 def test_register_err_same_username(self):
59 response = self.app.post(url(controller='login', action='register'),
59 response = self.app.post(url(controller='login', action='register'),
60 {'username':'test_admin',
60 {'username':'test_admin',
61 'password':'test',
61 'password':'test',
62 'email':'goodmail@domain.com',
62 'email':'goodmail@domain.com',
63 'name':'test',
63 'name':'test',
64 'lastname':'test'})
64 'lastname':'test'})
65
65
66 assert response.status == '200 OK', 'Wrong response from register page got %s' % response.status
66 assert response.status == '200 OK', 'Wrong response from register page got %s' % response.status
67 assert 'This username already exists' in response.body
67 assert 'This username already exists' in response.body
68
68
69 def test_register_err_wrong_data(self):
69 def test_register_err_wrong_data(self):
70 response = self.app.post(url(controller='login', action='register'),
70 response = self.app.post(url(controller='login', action='register'),
71 {'username':'xs',
71 {'username':'xs',
72 'password':'',
72 'password':'',
73 'email':'goodmailm',
73 'email':'goodmailm',
74 'name':'test',
74 'name':'test',
75 'lastname':'test'})
75 'lastname':'test'})
76
76
77 assert response.status == '200 OK', 'Wrong response from register page got %s' % response.status
77 assert response.status == '200 OK', 'Wrong response from register page got %s' % response.status
78 assert 'An email address must contain a single @' in response.body
78 assert 'An email address must contain a single @' in response.body
79 assert 'Enter a value 3 characters long or more' in response.body
79 assert 'Enter a value 3 characters long or more' in response.body
80 assert 'Please enter a value<' in response.body
80 assert 'Please enter a value<' in response.body
81
81
82
82
83
83
84 def test_register_ok(self):
84 def test_register_ok(self):
85 username = 'test_regular2'
85 username = 'test_regular4'
86 password = 'qweqwe'
86 password = 'qweqwe'
87 email = 'goodmail@mail.com'
87 email = 'marcin@somemail.com'
88 name = 'testname'
88 name = 'testname'
89 lastname = 'testlastname'
89 lastname = 'testlastname'
90
90
91 response = self.app.post(url(controller='login', action='register'),
91 response = self.app.post(url(controller='login', action='register'),
92 {'username':username,
92 {'username':username,
93 'password':password,
93 'password':password,
94 'email':email,
94 'email':email,
95 'name':name,
95 'name':name,
96 'lastname':lastname})
96 'lastname':lastname})
97
97 print response.body
98 assert response.status == '302 Found', 'Wrong response from register page got %s' % response.status
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 assert ret.username == username , 'field mismatch %s %s' % (ret.username, username)
102 assert ret.username == username , 'field mismatch %s %s' % (ret.username, username)
102 assert check_password(password,ret.password) == True , 'password mismatch'
103 assert check_password(password,ret.password) == True , 'password mismatch'
103 assert ret.email == email , 'field mismatch %s %s' % (ret.email, email)
104 assert ret.email == email , 'field mismatch %s %s' % (ret.email, email)
104 assert ret.name == name , 'field mismatch %s %s' % (ret.name, name)
105 assert ret.name == name , 'field mismatch %s %s' % (ret.name, name)
105 assert ret.lastname == lastname , 'field mismatch %s %s' % (ret.lastname, lastname)
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 from pylons_app.tests import *
1 from pylons_app.tests import *
2
2
3 class TestSettingsController(TestController):
3 class TestSettingsController(TestController):
4
4
5 def test_index(self):
5 def test_index(self):
6 self.log_user()
6 response = self.app.get(url(controller='settings', action='index',
7 response = self.app.get(url(controller='settings', action='index',
7 repo_name='vcs_test'))
8 repo_name='vcs_test'))
8 # Test response...
9 # Test response...
@@ -1,7 +1,8 b''
1 from pylons_app.tests import *
1 from pylons_app.tests import *
2
2
3 class TestShortlogController(TestController):
3 class TestShortlogController(TestController):
4
4
5 def test_index(self):
5 def test_index(self):
6 self.log_user()
6 response = self.app.get(url(controller='shortlog', action='index',repo_name='vcs_test'))
7 response = self.app.get(url(controller='shortlog', action='index',repo_name='vcs_test'))
7 # Test response...
8 # Test response...
@@ -1,7 +1,8 b''
1 from pylons_app.tests import *
1 from pylons_app.tests import *
2
2
3 class TestSummaryController(TestController):
3 class TestSummaryController(TestController):
4
4
5 def test_index(self):
5 def test_index(self):
6 self.log_user()
6 response = self.app.get(url(controller='summary', action='index',repo_name='vcs_test'))
7 response = self.app.get(url(controller='summary', action='index',repo_name='vcs_test'))
7 # Test response...
8 # Test response...
@@ -1,7 +1,8 b''
1 from pylons_app.tests import *
1 from pylons_app.tests import *
2
2
3 class TestTagsController(TestController):
3 class TestTagsController(TestController):
4
4
5 def test_index(self):
5 def test_index(self):
6 self.log_user()
6 response = self.app.get(url(controller='tags', action='index',repo_name='vcs_test'))
7 response = self.app.get(url(controller='tags', action='index',repo_name='vcs_test'))
7 # Test response...
8 # Test response...
@@ -1,47 +1,45 b''
1 """Setup the pylons_app application"""
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 from pylons_app.config.environment import load_environment
4 from pylons_app.config.environment import load_environment
5 from pylons_app.lib.db_manage import DbManage
5 from pylons_app.lib.db_manage import DbManage
6 import datetime
7 from time import mktime
8 import logging
6 import logging
9 import os
7 import os
10 import sys
8 import sys
11 import tarfile
12
9
13 log = logging.getLogger(__name__)
10 log = logging.getLogger(__name__)
14
11
15 ROOT = dn(dn(os.path.realpath(__file__)))
12 ROOT = dn(dn(os.path.realpath(__file__)))
16 sys.path.append(ROOT)
13 sys.path.append(ROOT)
17
14
15
18 def setup_app(command, conf, vars):
16 def setup_app(command, conf, vars):
19 """Place any commands to setup pylons_app here"""
17 """Place any commands to setup pylons_app here"""
20 log_sql = True
18 log_sql = True
21 tests = False
19 tests = False
20 REPO_TEST_PATH = None
22
21
23 dbname = os.path.split(conf['sqlalchemy.db1.url'])[-1]
22 dbname = os.path.split(conf['sqlalchemy.db1.url'])[-1]
24 filename = os.path.split(conf.filename)[-1]
23 # filename = os.path.split(conf.filename)[-1]
25
24 # if filename == 'test.ini':
26 if filename == 'tests.ini':
25 # uniq_suffix = str(int(mktime(datetime.datetime.now().timetuple())))
27 uniq_suffix = str(int(mktime(datetime.datetime.now().timetuple())))
26 # REPO_TEST_PATH = '/tmp/hg_app_test_%s' % uniq_suffix
28 REPO_TEST_PATH = '/tmp/hg_app_test_%s' % uniq_suffix
27 #
29
28 # if not os.path.isdir(REPO_TEST_PATH):
30 if not os.path.isdir(REPO_TEST_PATH):
29 # os.mkdir(REPO_TEST_PATH)
31 os.mkdir(REPO_TEST_PATH)
30 # cur_dir = dn(os.path.abspath(__file__))
32 cur_dir = dn(os.path.abspath(__file__))
31 # tar = tarfile.open(jn(cur_dir,'tests',"vcs_test.tar.gz"))
33 tar = tarfile.open(jn(cur_dir,'tests',"vcs_test.tar.gz"))
32 # tar.extractall(REPO_TEST_PATH)
34 tar.extractall(REPO_TEST_PATH)
33 # tar.close()
35 tar.close()
34 #
36
35 # tests = True
37 tests = True
38
36
39 dbmanage = DbManage(log_sql, dbname, tests)
37 dbmanage = DbManage(log_sql, dbname, tests)
40 dbmanage.create_tables(override=True)
38 dbmanage.create_tables(override=True)
41 dbmanage.config_prompt(REPO_TEST_PATH)
39 dbmanage.config_prompt(REPO_TEST_PATH)
42 dbmanage.create_default_user()
40 dbmanage.create_default_user()
43 dbmanage.admin_prompt()
41 dbmanage.admin_prompt()
44 dbmanage.create_permissions()
42 dbmanage.create_permissions()
45 dbmanage.populate_default_permissions()
43 dbmanage.populate_default_permissions()
46 load_environment(conf.global_conf, conf.local_conf, initial=True)
44 load_environment(conf.global_conf, conf.local_conf, initial=True)
47
45
@@ -1,34 +1,34 b''
1 [egg_info]
1 [egg_info]
2 tag_build = dev
2 tag_build = dev
3 tag_svn_revision = true
3 tag_svn_revision = true
4
4
5 [easy_install]
5 [easy_install]
6 find_links = http://www.pylonshq.com/download/
6 find_links = http://www.pylonshq.com/download/
7
7
8 [nosetests]
8 [nosetests]
9 verbose=True
9 verbose=True
10 verbosity=2
10 verbosity=2
11 with-pylons=tests.ini
11 with-pylons=test.ini
12 detailed-errors=1
12 detailed-errors=1
13
13
14 # Babel configuration
14 # Babel configuration
15 [compile_catalog]
15 [compile_catalog]
16 domain = pylons_app
16 domain = pylons_app
17 directory = pylons_app/i18n
17 directory = pylons_app/i18n
18 statistics = true
18 statistics = true
19
19
20 [extract_messages]
20 [extract_messages]
21 add_comments = TRANSLATORS:
21 add_comments = TRANSLATORS:
22 output_file = pylons_app/i18n/pylons_app.pot
22 output_file = pylons_app/i18n/pylons_app.pot
23 width = 80
23 width = 80
24
24
25 [init_catalog]
25 [init_catalog]
26 domain = pylons_app
26 domain = pylons_app
27 input_file = pylons_app/i18n/pylons_app.pot
27 input_file = pylons_app/i18n/pylons_app.pot
28 output_dir = pylons_app/i18n
28 output_dir = pylons_app/i18n
29
29
30 [update_catalog]
30 [update_catalog]
31 domain = pylons_app
31 domain = pylons_app
32 input_file = pylons_app/i18n/pylons_app.pot
32 input_file = pylons_app/i18n/pylons_app.pot
33 output_dir = pylons_app/i18n
33 output_dir = pylons_app/i18n
34 previous = true
34 previous = true
1 NO CONTENT: file renamed from tests.ini to test.ini
NO CONTENT: file renamed from tests.ini to test.ini
General Comments 0
You need to be logged in to leave comments. Login now