Show More
@@ -0,0 +1,106 b'' | |||||
|
1 | #!/usr/bin/env python | |||
|
2 | # encoding: utf-8 | |||
|
3 | # model for handling repositories actions | |||
|
4 | # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com> | |||
|
5 | # This program is free software; you can redistribute it and/or | |||
|
6 | # modify it under the terms of the GNU General Public License | |||
|
7 | # as published by the Free Software Foundation; version 2 | |||
|
8 | # of the License or (at your opinion) any later version of the license. | |||
|
9 | # | |||
|
10 | # This program is distributed in the hope that it will be useful, | |||
|
11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
|
12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
|
13 | # GNU General Public License for more details. | |||
|
14 | # | |||
|
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 | |||
|
17 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, | |||
|
18 | # MA 02110-1301, USA. | |||
|
19 | ||||
|
20 | """ | |||
|
21 | Created on Jun 5, 2010 | |||
|
22 | model for handling repositories actions | |||
|
23 | @author: marcink | |||
|
24 | """ | |||
|
25 | from pylons_app.model.meta import Session | |||
|
26 | from pylons_app.model.db import Repository | |||
|
27 | import shutil | |||
|
28 | import os | |||
|
29 | from datetime import datetime | |||
|
30 | from pylons_app.lib.utils import check_repo | |||
|
31 | from pylons import app_globals as g | |||
|
32 | import logging | |||
|
33 | log = logging.getLogger(__name__) | |||
|
34 | ||||
|
35 | class RepoModel(object): | |||
|
36 | ||||
|
37 | def __init__(self): | |||
|
38 | self.sa = Session() | |||
|
39 | ||||
|
40 | def get(self, id): | |||
|
41 | return self.sa.query(Repository).get(id) | |||
|
42 | ||||
|
43 | ||||
|
44 | def update(self, id, form_data): | |||
|
45 | try: | |||
|
46 | if id != form_data['repo_name']: | |||
|
47 | self.__rename_repo(id, form_data['repo_name']) | |||
|
48 | cur_repo = self.sa.query(Repository).get(id) | |||
|
49 | for k, v in form_data.items(): | |||
|
50 | if k == 'user': | |||
|
51 | cur_repo.user_id = v | |||
|
52 | else: | |||
|
53 | setattr(cur_repo, k, v) | |||
|
54 | ||||
|
55 | self.sa.add(cur_repo) | |||
|
56 | self.sa.commit() | |||
|
57 | except Exception as e: | |||
|
58 | log.error(e) | |||
|
59 | self.sa.rollback() | |||
|
60 | raise | |||
|
61 | ||||
|
62 | def create(self, form_data, cur_user): | |||
|
63 | try: | |||
|
64 | new_repo = Repository() | |||
|
65 | for k, v in form_data.items(): | |||
|
66 | setattr(new_repo, k, v) | |||
|
67 | ||||
|
68 | new_repo.user_id = cur_user.user_id | |||
|
69 | self.sa.add(new_repo) | |||
|
70 | self.sa.commit() | |||
|
71 | self.__create_repo(form_data['repo_name']) | |||
|
72 | except Exception as e: | |||
|
73 | log.error(e) | |||
|
74 | self.sa.rollback() | |||
|
75 | raise | |||
|
76 | ||||
|
77 | def delete(self, repo): | |||
|
78 | try: | |||
|
79 | self.sa.delete(repo) | |||
|
80 | self.sa.commit() | |||
|
81 | self.__delete_repo(repo.repo_name) | |||
|
82 | except Exception as e: | |||
|
83 | log.error(e) | |||
|
84 | self.sa.rollback() | |||
|
85 | raise | |||
|
86 | ||||
|
87 | def __create_repo(self, repo_name): | |||
|
88 | repo_path = os.path.join(g.base_path, repo_name) | |||
|
89 | if check_repo(repo_name, g.base_path): | |||
|
90 | log.info('creating repo %s in %s', repo_name, repo_path) | |||
|
91 | from vcs.backends.hg import MercurialRepository | |||
|
92 | MercurialRepository(repo_path, create=True) | |||
|
93 | ||||
|
94 | def __rename_repo(self, old, new): | |||
|
95 | log.info('renaming repoo from %s to %s', old, new) | |||
|
96 | old_path = os.path.join(g.base_path, old) | |||
|
97 | new_path = os.path.join(g.base_path, new) | |||
|
98 | shutil.move(old_path, new_path) | |||
|
99 | ||||
|
100 | def __delete_repo(self, name): | |||
|
101 | rm_path = os.path.join(g.base_path, name) | |||
|
102 | log.info("Removing %s", rm_path) | |||
|
103 | #disable hg | |||
|
104 | shutil.move(os.path.join(rm_path, '.hg'), os.path.join(rm_path, 'rm__.hg')) | |||
|
105 | #disable repo | |||
|
106 | shutil.move(rm_path, os.path.join(g.base_path, 'rm__%s-%s' % (datetime.today(), id))) |
@@ -6,6 +6,7 b' from pylons_app.config.routing import ma' | |||||
6 | from pylons_app.lib.auth import set_available_permissions |
|
6 | from pylons_app.lib.auth import set_available_permissions | |
7 | from pylons_app.lib.utils import repo2db_mapper |
|
7 | from pylons_app.lib.utils import repo2db_mapper | |
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 sqlalchemy import engine_from_config |
|
10 | from sqlalchemy import engine_from_config | |
10 | import logging |
|
11 | import logging | |
11 | import os |
|
12 | import os | |
@@ -60,7 +61,7 b' def load_environment(global_conf, app_co' | |||||
60 | sa_engine_db1 = engine_from_config(config, 'sqlalchemy.db1.') |
|
61 | sa_engine_db1 = engine_from_config(config, 'sqlalchemy.db1.') | |
61 |
|
62 | |||
62 | init_model(sa_engine_db1) |
|
63 | init_model(sa_engine_db1) | |
63 | repo2db_mapper() |
|
64 | repo2db_mapper(_get_repos_cached_initial(config['pylons.app_globals'])) | |
64 | set_available_permissions(config) |
|
65 | set_available_permissions(config) | |
65 | # CONFIGURATION OPTIONS HERE (note: all config options will override |
|
66 | # CONFIGURATION OPTIONS HERE (note: all config options will override | |
66 | # any Pylons config options) |
|
67 | # any Pylons config options) |
@@ -2,7 +2,6 b'' | |||||
2 | # encoding: utf-8 |
|
2 | # encoding: utf-8 | |
3 | # repos controller for pylons |
|
3 | # repos 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 |
|
||||
6 | # This program is free software; you can redistribute it and/or |
|
5 | # This program is free software; you can redistribute it and/or | |
7 | # modify it under the terms of the GNU General Public License |
|
6 | # modify it under the terms of the GNU General Public License | |
8 | # as published by the Free Software Foundation; version 2 |
|
7 | # as published by the Free Software Foundation; version 2 | |
@@ -22,20 +21,25 b' Created on April 7, 2010' | |||||
22 | admin controller for pylons |
|
21 | admin controller for pylons | |
23 | @author: marcink |
|
22 | @author: marcink | |
24 | """ |
|
23 | """ | |
25 | import logging |
|
24 | from operator import itemgetter | |
26 | from pylons import request, response, session, tmpl_context as c, url, \ |
|
25 | from pylons import request, response, session, tmpl_context as c, url, \ | |
27 | app_globals as g |
|
26 | app_globals as g | |
28 | from pylons.controllers.util import abort, redirect |
|
27 | from pylons.controllers.util import abort, redirect | |
29 | from pylons_app.lib.auth import LoginRequired |
|
|||
30 | from pylons.i18n.translation import _ |
|
28 | from pylons.i18n.translation import _ | |
31 | from pylons_app.lib import helpers as h |
|
29 | from pylons_app.lib import helpers as h | |
|
30 | from pylons_app.lib.auth import LoginRequired | |||
32 | from pylons_app.lib.base import BaseController, render |
|
31 | from pylons_app.lib.base import BaseController, render | |
33 |
from pylons_app.lib. |
|
32 | from pylons_app.lib.utils import invalidate_cache | |
34 | from pylons_app.lib.utils import check_repo, invalidate_cache |
|
33 | from pylons_app.model.repo_model import RepoModel | |
35 | from pylons_app.model.hg_model import HgModel |
|
34 | from pylons_app.model.hg_model import HgModel | |
|
35 | from pylons_app.model.forms import RepoForm | |||
|
36 | from pylons_app.model.meta import Session | |||
|
37 | from datetime import datetime | |||
|
38 | import formencode | |||
|
39 | from formencode import htmlfill | |||
|
40 | import logging | |||
36 | import os |
|
41 | import os | |
37 | import shutil |
|
42 | import shutil | |
38 | from operator import itemgetter |
|
|||
39 | log = logging.getLogger(__name__) |
|
43 | log = logging.getLogger(__name__) | |
40 |
|
44 | |||
41 | class ReposController(BaseController): |
|
45 | class ReposController(BaseController): | |
@@ -59,30 +63,33 b' class ReposController(BaseController):' | |||||
59 | def create(self): |
|
63 | def create(self): | |
60 | """POST /repos: Create a new item""" |
|
64 | """POST /repos: Create a new item""" | |
61 | # url('repos') |
|
65 | # url('repos') | |
62 | name = request.POST.get('name') |
|
66 | repo_model = RepoModel() | |
63 |
|
67 | _form = RepoForm()() | ||
64 | try: |
|
68 | try: | |
65 | self._create_repo(name) |
|
69 | form_result = _form.to_python(dict(request.POST)) | |
66 | #clear our cached list for refresh with new repo |
|
70 | repo_model.create(form_result, c.hg_app_user) | |
67 | invalidate_cache('cached_repo_list') |
|
71 | invalidate_cache('cached_repo_list') | |
68 |
h.flash(_('created repository %s') % name, |
|
72 | h.flash(_('created repository %s') % form_result['repo_name'], | |
69 | except Exception as e: |
|
73 | category='success') | |
70 | log.error(e) |
|
74 | ||
71 |
|
75 | except formencode.Invalid as errors: | ||
|
76 | c.form_errors = errors.error_dict | |||
|
77 | c.new_repo = errors.value['repo_name'] | |||
|
78 | return htmlfill.render( | |||
|
79 | render('admin/repos/repo_add.html'), | |||
|
80 | defaults=errors.value, | |||
|
81 | encoding="UTF-8") | |||
|
82 | ||||
|
83 | except Exception: | |||
|
84 | h.flash(_('error occured during creation of repository %s') \ | |||
|
85 | % form_result['repo_name'], category='error') | |||
|
86 | ||||
72 | return redirect('repos') |
|
87 | return redirect('repos') | |
73 |
|
||||
74 | def _create_repo(self, repo_name): |
|
|||
75 | repo_path = os.path.join(g.base_path, repo_name) |
|
|||
76 | if check_repo(repo_name, g.base_path): |
|
|||
77 | log.info('creating repo %s in %s', repo_name, repo_path) |
|
|||
78 | from vcs.backends.hg import MercurialRepository |
|
|||
79 | MercurialRepository(repo_path, create=True) |
|
|||
80 |
|
||||
81 |
|
88 | |||
82 | def new(self, format='html'): |
|
89 | def new(self, format='html'): | |
83 | """GET /repos/new: Form to create a new item""" |
|
90 | """GET /repos/new: Form to create a new item""" | |
84 | new_repo = request.GET.get('repo', '') |
|
91 | new_repo = request.GET.get('repo', '') | |
85 |
c.new_repo = |
|
92 | c.new_repo = h.repo_name_slug(new_repo) | |
86 |
|
93 | |||
87 | return render('admin/repos/repo_add.html') |
|
94 | return render('admin/repos/repo_add.html') | |
88 |
|
95 | |||
@@ -94,7 +101,26 b' class ReposController(BaseController):' | |||||
94 | # h.form(url('repo', id=ID), |
|
101 | # h.form(url('repo', id=ID), | |
95 | # method='put') |
|
102 | # method='put') | |
96 | # url('repo', id=ID) |
|
103 | # url('repo', id=ID) | |
97 |
|
104 | repo_model = RepoModel() | ||
|
105 | _form = RepoForm(edit=True)() | |||
|
106 | try: | |||
|
107 | form_result = _form.to_python(dict(request.POST)) | |||
|
108 | repo_model.update(id, form_result) | |||
|
109 | invalidate_cache('cached_repo_list') | |||
|
110 | h.flash(_('Repository updated succesfully'), category='success') | |||
|
111 | ||||
|
112 | except formencode.Invalid as errors: | |||
|
113 | c.repo_info = repo_model.get(id) | |||
|
114 | c.form_errors = errors.error_dict | |||
|
115 | return htmlfill.render( | |||
|
116 | render('admin/repos/repo_edit.html'), | |||
|
117 | defaults=errors.value, | |||
|
118 | encoding="UTF-8") | |||
|
119 | except Exception: | |||
|
120 | h.flash(_('error occured during update of repository %s') \ | |||
|
121 | % form_result['repo_name'], category='error') | |||
|
122 | return redirect(url('repos')) | |||
|
123 | ||||
98 | def delete(self, id): |
|
124 | def delete(self, id): | |
99 | """DELETE /repos/id: Delete an existing item""" |
|
125 | """DELETE /repos/id: Delete an existing item""" | |
100 | # Forms posted to this method should contain a hidden field: |
|
126 | # Forms posted to this method should contain a hidden field: | |
@@ -103,19 +129,25 b' class ReposController(BaseController):' | |||||
103 | # h.form(url('repo', id=ID), |
|
129 | # h.form(url('repo', id=ID), | |
104 | # method='delete') |
|
130 | # method='delete') | |
105 | # url('repo', id=ID) |
|
131 | # url('repo', id=ID) | |
106 | from datetime import datetime |
|
132 | ||
107 | path = g.paths[0][1].replace('*', '') |
|
133 | repo_model = RepoModel() | |
108 | rm_path = os.path.join(path, id) |
|
134 | repo = repo_model.get(id) | |
109 | log.info("Removing %s", rm_path) |
|
135 | if not repo: | |
110 | shutil.move(os.path.join(rm_path, '.hg'), os.path.join(rm_path, 'rm__.hg')) |
|
136 | h.flash(_('%s repository is not mapped to db perhaps' | |
111 | shutil.move(rm_path, os.path.join(path, 'rm__%s-%s' % (datetime.today(), id))) |
|
137 | ' it was moved or renamed please run the application again' | |
|
138 | ' in order to rescan repositories') % id, category='error') | |||
112 |
|
139 | |||
113 | #clear our cached list for refresh with new repo |
|
140 | return redirect(url('repos')) | |
114 | invalidate_cache('cached_repo_list') |
|
141 | try: | |
115 | h.flash(_('deleted repository %s') % rm_path, category='success') |
|
142 | repo_model.delete(repo) | |
|
143 | invalidate_cache('cached_repo_list') | |||
|
144 | h.flash(_('deleted repository %s') % id, category='success') | |||
|
145 | except Exception: | |||
|
146 | h.flash(_('An error occured during deletion of %s') % id, | |||
|
147 | category='error') | |||
|
148 | ||||
116 | return redirect(url('repos')) |
|
149 | return redirect(url('repos')) | |
117 |
|
150 | |||
118 |
|
||||
119 | def show(self, id, format='html'): |
|
151 | def show(self, id, format='html'): | |
120 | """GET /repos/id: Show a specific item""" |
|
152 | """GET /repos/id: Show a specific item""" | |
121 | # url('repo', id=ID) |
|
153 | # url('repo', id=ID) | |
@@ -123,5 +155,13 b' class ReposController(BaseController):' | |||||
123 | def edit(self, id, format='html'): |
|
155 | def edit(self, id, format='html'): | |
124 | """GET /repos/id/edit: Form to edit an existing item""" |
|
156 | """GET /repos/id/edit: Form to edit an existing item""" | |
125 | # url('edit_repo', id=ID) |
|
157 | # url('edit_repo', id=ID) | |
126 | c.new_repo = id |
|
158 | repo_model = RepoModel() | |
127 | return render('admin/repos/repo_edit.html') |
|
159 | c.repo_info = repo_model.get(id) | |
|
160 | defaults = c.repo_info.__dict__ | |||
|
161 | defaults.update({'user':c.repo_info.user.username}) | |||
|
162 | return htmlfill.render( | |||
|
163 | render('admin/repos/repo_edit.html'), | |||
|
164 | defaults=defaults, | |||
|
165 | encoding="UTF-8", | |||
|
166 | force_defaults=False | |||
|
167 | ) |
@@ -23,7 +23,6 b' users controller for pylons' | |||||
23 | @author: marcink |
|
23 | @author: marcink | |
24 | """ |
|
24 | """ | |
25 | import logging |
|
25 | import logging | |
26 | from formencode import htmlfill |
|
|||
27 | from pylons import request, session, tmpl_context as c, url |
|
26 | from pylons import request, session, tmpl_context as c, url | |
28 | from pylons.controllers.util import abort, redirect |
|
27 | from pylons.controllers.util import abort, redirect | |
29 | from pylons.i18n.translation import _ |
|
28 | from pylons.i18n.translation import _ | |
@@ -34,6 +33,7 b' from pylons_app.model.db import User, Us' | |||||
34 | from pylons_app.model.forms import UserForm |
|
33 | from pylons_app.model.forms import UserForm | |
35 | from pylons_app.model.user_model import UserModel |
|
34 | from pylons_app.model.user_model import UserModel | |
36 | import formencode |
|
35 | import formencode | |
|
36 | from formencode import htmlfill | |||
37 |
|
37 | |||
38 | log = logging.getLogger(__name__) |
|
38 | log = logging.getLogger(__name__) | |
39 |
|
39 | |||
@@ -65,15 +65,18 b' class UsersController(BaseController):' | |||||
65 | try: |
|
65 | try: | |
66 | form_result = login_form.to_python(dict(request.POST)) |
|
66 | form_result = login_form.to_python(dict(request.POST)) | |
67 | user_model.create(form_result) |
|
67 | user_model.create(form_result) | |
68 |
h.flash(_('created user %s') % form_result['username'], |
|
68 | h.flash(_('created user %s') % form_result['username'], | |
69 | return redirect(url('users')) |
|
69 | category='success') | |
70 |
|
||||
71 | except formencode.Invalid as errors: |
|
70 | except formencode.Invalid as errors: | |
72 | c.form_errors = errors.error_dict |
|
71 | c.form_errors = errors.error_dict | |
73 | return htmlfill.render( |
|
72 | return htmlfill.render( | |
74 | render('admin/users/user_add.html'), |
|
73 | render('admin/users/user_add.html'), | |
75 | defaults=errors.value, |
|
74 | defaults=errors.value, | |
76 | encoding="UTF-8") |
|
75 | encoding="UTF-8") | |
|
76 | except Exception: | |||
|
77 | h.flash(_('error occured during creation of user %s') \ | |||
|
78 | % form_result['username'], category='error') | |||
|
79 | return redirect(url('users')) | |||
77 |
|
80 | |||
78 | def new(self, format='html'): |
|
81 | def new(self, format='html'): | |
79 | """GET /users/new: Form to create a new item""" |
|
82 | """GET /users/new: Form to create a new item""" | |
@@ -89,12 +92,11 b' class UsersController(BaseController):' | |||||
89 | # method='put') |
|
92 | # method='put') | |
90 | # url('user', id=ID) |
|
93 | # url('user', id=ID) | |
91 | user_model = UserModel() |
|
94 | user_model = UserModel() | |
92 |
|
|
95 | _form = UserForm(edit=True)() | |
93 | try: |
|
96 | try: | |
94 |
form_result = |
|
97 | form_result = _form.to_python(dict(request.POST)) | |
95 | user_model.update(id, form_result) |
|
98 | user_model.update(id, form_result) | |
96 | h.flash(_('User updated succesfully'), category='success') |
|
99 | h.flash(_('User updated succesfully'), category='success') | |
97 | return redirect(url('users')) |
|
|||
98 |
|
100 | |||
99 | except formencode.Invalid as errors: |
|
101 | except formencode.Invalid as errors: | |
100 | c.user = user_model.get_user(id) |
|
102 | c.user = user_model.get_user(id) | |
@@ -103,7 +105,12 b' class UsersController(BaseController):' | |||||
103 | render('admin/users/user_edit.html'), |
|
105 | render('admin/users/user_edit.html'), | |
104 | defaults=errors.value, |
|
106 | defaults=errors.value, | |
105 | encoding="UTF-8") |
|
107 | encoding="UTF-8") | |
106 |
|
108 | except Exception: | ||
|
109 | h.flash(_('error occured during update of user %s') \ | |||
|
110 | % form_result['username'], category='error') | |||
|
111 | ||||
|
112 | return redirect(url('users')) | |||
|
113 | ||||
107 | def delete(self, id): |
|
114 | def delete(self, id): | |
108 | """DELETE /users/id: Delete an existing item""" |
|
115 | """DELETE /users/id: Delete an existing item""" | |
109 | # Forms posted to this method should contain a hidden field: |
|
116 | # Forms posted to this method should contain a hidden field: | |
@@ -112,13 +119,14 b' class UsersController(BaseController):' | |||||
112 | # h.form(url('user', id=ID), |
|
119 | # h.form(url('user', id=ID), | |
113 | # method='delete') |
|
120 | # method='delete') | |
114 | # url('user', id=ID) |
|
121 | # url('user', id=ID) | |
|
122 | user_model = UserModel() | |||
115 | try: |
|
123 | try: | |
116 | self.sa.delete(self.sa.query(User).get(id)) |
|
124 | user_model.delete(id) | |
117 | self.sa.commit() |
|
|||
118 | h.flash(_('sucessfully deleted user'), category='success') |
|
125 | h.flash(_('sucessfully deleted user'), category='success') | |
119 | except: |
|
126 | except Exception: | |
120 | self.sa.rollback() |
|
127 | h.flash(_('An error occured during deletion of user'), | |
121 | raise |
|
128 | category='error') | |
|
129 | ||||
122 | return redirect(url('users')) |
|
130 | return redirect(url('users')) | |
123 |
|
131 | |||
124 | def show(self, id, format='html'): |
|
132 | def show(self, id, format='html'): |
@@ -65,6 +65,7 b' class AuthUser(object):' | |||||
65 | A simple object that handles a mercurial username for authentication |
|
65 | A simple object that handles a mercurial username for authentication | |
66 | """ |
|
66 | """ | |
67 | username = 'None' |
|
67 | username = 'None' | |
|
68 | user_id = None | |||
68 | is_authenticated = False |
|
69 | is_authenticated = False | |
69 | is_admin = False |
|
70 | is_admin = False | |
70 | permissions = set() |
|
71 | permissions = set() |
@@ -114,11 +114,9 b' def repo_name_slug(value):' | |||||
114 | Return slug of name of repository |
|
114 | Return slug of name of repository | |
115 | """ |
|
115 | """ | |
116 | slug = urlify(value) |
|
116 | slug = urlify(value) | |
117 | for c in """=[]\;',/~!@#$%^&*()+{}|:""": |
|
117 | for c in """=[]\;'"<>,/~!@#$%^&*()+{}|:""": | |
118 | slug = slug.replace(c, '-') |
|
118 | slug = slug.replace(c, '-') | |
119 | print slug |
|
|||
120 | slug = recursive_replace(slug, '-') |
|
119 | slug = recursive_replace(slug, '-') | |
121 | print slug |
|
|||
122 | return slug |
|
120 | return slug | |
123 |
|
121 | |||
124 | files_breadcrumbs = _FilesBreadCrumbs() |
|
122 | files_breadcrumbs = _FilesBreadCrumbs() |
@@ -28,6 +28,7 b' import os' | |||||
28 | import logging |
|
28 | import logging | |
29 | from mercurial import ui, config, hg |
|
29 | from mercurial import ui, config, hg | |
30 | from mercurial.error import RepoError |
|
30 | from mercurial.error import RepoError | |
|
31 | from pylons_app.model.db import Repository, User | |||
31 | log = logging.getLogger(__name__) |
|
32 | log = logging.getLogger(__name__) | |
32 |
|
33 | |||
33 |
|
34 | |||
@@ -152,9 +153,28 b' class EmptyChangeset(BaseChangeset):' | |||||
152 | return '0' * 12 |
|
153 | return '0' * 12 | |
153 |
|
154 | |||
154 |
|
155 | |||
155 | def repo2db_mapper(): |
|
156 | def repo2db_mapper(initial_repo_list): | |
|
157 | """ | |||
|
158 | maps all found repositories into db | |||
156 |
|
|
159 | """ | |
157 | scann all dirs for .hgdbid |
|
160 | from pylons_app.model.meta import Session | |
158 | if some dir doesn't have one generate one. |
|
161 | sa = Session() | |
159 | """ |
|
162 | user = sa.query(User).filter(User.admin == True).first() | |
160 | pass |
|
163 | for name, repo in initial_repo_list.items(): | |
|
164 | if not sa.query(Repository).get(name): | |||
|
165 | log.info('%s not found creating default', name) | |||
|
166 | try: | |||
|
167 | ||||
|
168 | new_repo = Repository() | |||
|
169 | new_repo.repo_name = name | |||
|
170 | desc = repo.description if repo.description != 'unknown' else \ | |||
|
171 | 'auto description for %s' % name | |||
|
172 | new_repo.description = desc | |||
|
173 | new_repo.user_id = user.user_id | |||
|
174 | new_repo.private = False | |||
|
175 | sa.add(new_repo) | |||
|
176 | sa.commit() | |||
|
177 | except: | |||
|
178 | sa.rollback() | |||
|
179 | raise | |||
|
180 |
@@ -1,11 +1,12 b'' | |||||
1 | from pylons_app.model.meta import Base |
|
1 | from pylons_app.model.meta import Base | |
2 | from sqlalchemy.orm import relation, backref |
|
2 | from sqlalchemy.orm import relation, backref | |
3 | from sqlalchemy import * |
|
3 | from sqlalchemy import * | |
|
4 | from vcs.utils.lazy import LazyProperty | |||
4 |
|
5 | |||
5 | class User(Base): |
|
6 | class User(Base): | |
6 | __tablename__ = 'users' |
|
7 | __tablename__ = 'users' | |
7 | __table_args__ = {'useexisting':True} |
|
8 | __table_args__ = {'useexisting':True} | |
8 |
user_id = Column("user_id", INTEGER(), nullable=False, unique=True, default=None, primary_key= |
|
9 | user_id = Column("user_id", INTEGER(), nullable=False, unique=True, default=None, primary_key=True) | |
9 | username = Column("username", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) |
|
10 | username = Column("username", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) | |
10 | password = Column("password", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) |
|
11 | password = Column("password", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) | |
11 | active = Column("active", BOOLEAN(), nullable=True, unique=None, default=None) |
|
12 | active = Column("active", BOOLEAN(), nullable=True, unique=None, default=None) | |
@@ -17,6 +18,10 b' class User(Base):' | |||||
17 |
|
18 | |||
18 | user_log = relation('UserLog') |
|
19 | user_log = relation('UserLog') | |
19 |
|
20 | |||
|
21 | @LazyProperty | |||
|
22 | def full_contact(self): | |||
|
23 | return '%s %s <%s>' % (self.name, self.lastname, self.email) | |||
|
24 | ||||
20 | def __repr__(self): |
|
25 | def __repr__(self): | |
21 | return "<User('%s:%s')>" % (self.user_id, self.username) |
|
26 | return "<User('%s:%s')>" % (self.user_id, self.username) | |
22 |
|
27 | |||
@@ -24,7 +29,7 b' class UserLog(Base):' | |||||
24 | __tablename__ = 'user_logs' |
|
29 | __tablename__ = 'user_logs' | |
25 | __table_args__ = {'useexisting':True} |
|
30 | __table_args__ = {'useexisting':True} | |
26 | user_log_id = Column("user_log_id", INTEGER(), nullable=False, unique=True, default=None, primary_key=1) |
|
31 | user_log_id = Column("user_log_id", INTEGER(), nullable=False, unique=True, default=None, primary_key=1) | |
27 |
user_id = Column("user_id", INTEGER(), ForeignKey(u'users.user_id'), nullable= |
|
32 | user_id = Column("user_id", INTEGER(), ForeignKey(u'users.user_id'), nullable=False, unique=None, default=None) | |
28 | repository = Column("repository", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) |
|
33 | repository = Column("repository", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) | |
29 | action = Column("action", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) |
|
34 | action = Column("action", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) | |
30 | action_date = Column("action_date", DATETIME(timezone=False), nullable=True, unique=None, default=None) |
|
35 | action_date = Column("action_date", DATETIME(timezone=False), nullable=True, unique=None, default=None) | |
@@ -33,14 +38,12 b' class UserLog(Base):' | |||||
33 |
|
38 | |||
34 | class Repository(Base): |
|
39 | class Repository(Base): | |
35 | __tablename__ = 'repositories' |
|
40 | __tablename__ = 'repositories' | |
36 |
repo_ |
|
41 | repo_name = Column("repo_name", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None, primary_key=True) | |
37 | repo_name = Column("repo_name", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) |
|
42 | user_id = Column("user_id", INTEGER(), ForeignKey(u'users.user_id'), nullable=False, unique=False, default=None) | |
38 | user_id = Column("user_id", INTEGER(), ForeignKey(u'users.user_id'), nullable=True, unique=None, default=None) |
|
|||
39 | private = Column("private", BOOLEAN(), nullable=True, unique=None, default=None) |
|
43 | private = Column("private", BOOLEAN(), nullable=True, unique=None, default=None) | |
40 |
|
44 | description = Column("description", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) | ||
41 | user = relation('User') |
|
45 | user = relation('User') | |
42 |
|
46 | |||
43 |
|
||||
44 | class Permission(Base): |
|
47 | class Permission(Base): | |
45 | __tablename__ = 'permissions' |
|
48 | __tablename__ = 'permissions' | |
46 | __table_args__ = {'useexisting':True} |
|
49 | __table_args__ = {'useexisting':True} |
@@ -25,8 +25,9 b' from formencode.validators import Unicod' | |||||
25 | from pylons import session |
|
25 | from pylons import session | |
26 | from pylons.i18n.translation import _ |
|
26 | from pylons.i18n.translation import _ | |
27 | from pylons_app.lib.auth import get_crypt_password |
|
27 | from pylons_app.lib.auth import get_crypt_password | |
|
28 | import pylons_app.lib.helpers as h | |||
28 | from pylons_app.model import meta |
|
29 | from pylons_app.model import meta | |
29 | from pylons_app.model.db import User |
|
30 | from pylons_app.model.db import User, Repository | |
30 | from sqlalchemy.exc import OperationalError |
|
31 | from sqlalchemy.exc import OperationalError | |
31 | from sqlalchemy.orm.exc import NoResultFound, MultipleResultsFound |
|
32 | from sqlalchemy.orm.exc import NoResultFound, MultipleResultsFound | |
32 | from webhelpers.pylonslib.secure_form import authentication_token |
|
33 | from webhelpers.pylonslib.secure_form import authentication_token | |
@@ -93,6 +94,7 b' class ValidAuth(formencode.validators.Fa' | |||||
93 | auth_user.username = username |
|
94 | auth_user.username = username | |
94 | auth_user.is_authenticated = True |
|
95 | auth_user.is_authenticated = True | |
95 | auth_user.is_admin = user.admin |
|
96 | auth_user.is_admin = user.admin | |
|
97 | auth_user.user_id = user.user_id | |||
96 | session['hg_app_user'] = auth_user |
|
98 | session['hg_app_user'] = auth_user | |
97 | session.save() |
|
99 | session.save() | |
98 | log.info('user %s is now authenticated', username) |
|
100 | log.info('user %s is now authenticated', username) | |
@@ -119,7 +121,30 b' class ValidAuth(formencode.validators.Fa' | |||||
119 | error_dict=self.e_dict_disable) |
|
121 | error_dict=self.e_dict_disable) | |
120 |
|
122 | |||
121 |
|
123 | |||
122 |
|
124 | class ValidRepoUser(formencode.validators.FancyValidator): | ||
|
125 | ||||
|
126 | def to_python(self, value, state): | |||
|
127 | sa = meta.Session | |||
|
128 | try: | |||
|
129 | self.user_db = sa.query(User).filter(User.username == value).one() | |||
|
130 | except Exception: | |||
|
131 | raise formencode.Invalid(_('This username is not valid'), | |||
|
132 | value, state) | |||
|
133 | return self.user_db.user_id | |||
|
134 | ||||
|
135 | def ValidRepoName(edit=False): | |||
|
136 | class _ValidRepoName(formencode.validators.FancyValidator): | |||
|
137 | ||||
|
138 | def to_python(self, value, state): | |||
|
139 | slug = h.repo_name_slug(value) | |||
|
140 | ||||
|
141 | sa = meta.Session | |||
|
142 | if sa.query(Repository).get(slug) and not edit: | |||
|
143 | raise formencode.Invalid(_('This repository already exists'), | |||
|
144 | value, state) | |||
|
145 | ||||
|
146 | return slug | |||
|
147 | return _ValidRepoName | |||
123 | #=============================================================================== |
|
148 | #=============================================================================== | |
124 | # FORMS |
|
149 | # FORMS | |
125 | #=============================================================================== |
|
150 | #=============================================================================== | |
@@ -163,3 +188,16 b' def UserForm(edit=False):' | |||||
163 | email = Email(not_empty=True) |
|
188 | email = Email(not_empty=True) | |
164 |
|
189 | |||
165 | return _UserForm |
|
190 | return _UserForm | |
|
191 | ||||
|
192 | def RepoForm(edit=False): | |||
|
193 | class _RepoForm(formencode.Schema): | |||
|
194 | allow_extra_fields = True | |||
|
195 | filter_extra_fields = True | |||
|
196 | repo_name = All(UnicodeString(strip=True, min=1, not_empty=True), ValidRepoName(edit)) | |||
|
197 | description = UnicodeString(strip=True, min=3, not_empty=True) | |||
|
198 | private = StringBoolean(if_missing=False) | |||
|
199 | ||||
|
200 | if edit: | |||
|
201 | user = All(Int(not_empty=True), ValidRepoUser) | |||
|
202 | ||||
|
203 | return _RepoForm |
@@ -27,8 +27,9 b' Model for hg app' | |||||
27 | from beaker.cache import cache_region |
|
27 | from beaker.cache import cache_region | |
28 | from mercurial import ui |
|
28 | from mercurial import ui | |
29 | from mercurial.hgweb.hgwebdir_mod import findrepos |
|
29 | from mercurial.hgweb.hgwebdir_mod import findrepos | |
30 | from pylons import app_globals as g |
|
|||
31 | from vcs.exceptions import RepositoryError, VCSError |
|
30 | from vcs.exceptions import RepositoryError, VCSError | |
|
31 | from pylons_app.model.meta import Session | |||
|
32 | from pylons_app.model.db import Repository | |||
32 | import logging |
|
33 | import logging | |
33 | import os |
|
34 | import os | |
34 | import sys |
|
35 | import sys | |
@@ -40,12 +41,19 b' except ImportError:' | |||||
40 | sys.stderr.write('You have to import vcs module') |
|
41 | sys.stderr.write('You have to import vcs module') | |
41 | raise Exception('Unable to import vcs') |
|
42 | raise Exception('Unable to import vcs') | |
42 |
|
43 | |||
|
44 | def _get_repos_cached_initial(app_globals): | |||
|
45 | """ | |||
|
46 | return cached dict with repos | |||
|
47 | """ | |||
|
48 | g = app_globals | |||
|
49 | return HgModel.repo_scan(g.paths[0][0], g.paths[0][1], g.baseui) | |||
43 |
|
50 | |||
44 | @cache_region('long_term', 'cached_repo_list') |
|
51 | @cache_region('long_term', 'cached_repo_list') | |
45 | def _get_repos_cached(): |
|
52 | def _get_repos_cached(): | |
46 | """ |
|
53 | """ | |
47 | return cached dict with repos |
|
54 | return cached dict with repos | |
48 | """ |
|
55 | """ | |
|
56 | from pylons import app_globals as g | |||
49 | return HgModel.repo_scan(g.paths[0][0], g.paths[0][1], g.baseui) |
|
57 | return HgModel.repo_scan(g.paths[0][0], g.paths[0][1], g.baseui) | |
50 |
|
58 | |||
51 | @cache_region('long_term', 'full_changelog') |
|
59 | @cache_region('long_term', 'full_changelog') | |
@@ -62,7 +70,6 b' class HgModel(object):' | |||||
62 | """ |
|
70 | """ | |
63 | Constructor |
|
71 | Constructor | |
64 | """ |
|
72 | """ | |
65 | pass |
|
|||
66 |
|
73 | |||
67 | @staticmethod |
|
74 | @staticmethod | |
68 | def repo_scan(repos_prefix, repos_path, baseui): |
|
75 | def repo_scan(repos_prefix, repos_path, baseui): | |
@@ -72,6 +79,7 b' class HgModel(object):' | |||||
72 | :param repos_path: path to directory it could take syntax with |
|
79 | :param repos_path: path to directory it could take syntax with | |
73 | * or ** for deep recursive displaying repositories |
|
80 | * or ** for deep recursive displaying repositories | |
74 | """ |
|
81 | """ | |
|
82 | sa = Session() | |||
75 | def check_repo_dir(path): |
|
83 | def check_repo_dir(path): | |
76 | """ |
|
84 | """ | |
77 | Checks the repository |
|
85 | Checks the repository | |
@@ -102,8 +110,13 b' class HgModel(object):' | |||||
102 | raise RepositoryError('Duplicate repository name %s found in' |
|
110 | raise RepositoryError('Duplicate repository name %s found in' | |
103 | ' %s' % (name, path)) |
|
111 | ' %s' % (name, path)) | |
104 | else: |
|
112 | else: | |
|
113 | ||||
105 | repos_list[name] = MercurialRepository(path, baseui=baseui) |
|
114 | repos_list[name] = MercurialRepository(path, baseui=baseui) | |
106 | repos_list[name].name = name |
|
115 | repos_list[name].name = name | |
|
116 | dbrepo = sa.query(Repository).get(name) | |||
|
117 | if dbrepo: | |||
|
118 | repos_list[name].description = dbrepo.description | |||
|
119 | repos_list[name].contact = dbrepo.user.full_contact | |||
107 | except OSError: |
|
120 | except OSError: | |
108 | continue |
|
121 | continue | |
109 | return repos_list |
|
122 | return repos_list |
@@ -26,6 +26,8 b' Model for users' | |||||
26 |
|
26 | |||
27 | from pylons_app.model.db import User |
|
27 | from pylons_app.model.db import User | |
28 | from pylons_app.model.meta import Session |
|
28 | from pylons_app.model.meta import Session | |
|
29 | import logging | |||
|
30 | log = logging.getLogger(__name__) | |||
29 |
|
31 | |||
30 | class UserModel(object): |
|
32 | class UserModel(object): | |
31 |
|
33 | |||
@@ -43,7 +45,8 b' class UserModel(object):' | |||||
43 |
|
45 | |||
44 | self.sa.add(new_user) |
|
46 | self.sa.add(new_user) | |
45 | self.sa.commit() |
|
47 | self.sa.commit() | |
46 | except: |
|
48 | except Exception as e: | |
|
49 | log.error(e) | |||
47 | self.sa.rollback() |
|
50 | self.sa.rollback() | |
48 | raise |
|
51 | raise | |
49 |
|
52 | |||
@@ -59,6 +62,16 b' class UserModel(object):' | |||||
59 |
|
62 | |||
60 | self.sa.add(new_user) |
|
63 | self.sa.add(new_user) | |
61 | self.sa.commit() |
|
64 | self.sa.commit() | |
62 | except: |
|
65 | except Exception as e: | |
|
66 | log.error(e) | |||
63 | self.sa.rollback() |
|
67 | self.sa.rollback() | |
64 | raise |
|
68 | raise | |
|
69 | ||||
|
70 | def delete(self, id): | |||
|
71 | try: | |||
|
72 | self.sa.delete(self.sa.query(User).get(id)) | |||
|
73 | self.sa.commit() | |||
|
74 | except Exception as e: | |||
|
75 | log.error(e) | |||
|
76 | self.sa.rollback() | |||
|
77 | raise |
@@ -20,15 +20,18 b'' | |||||
20 | <table> |
|
20 | <table> | |
21 | <tr> |
|
21 | <tr> | |
22 | <td>${_('Name')}</td> |
|
22 | <td>${_('Name')}</td> | |
23 | <td>${h.text('name',c.new_repo)}</td> |
|
23 | <td>${h.text('repo_name',c.new_repo)}</td> | |
|
24 | <td>${self.get_form_error('repo_name')}</td> | |||
24 | </tr> |
|
25 | </tr> | |
25 | <tr> |
|
26 | <tr> | |
26 | <td>${_('Description')}</td> |
|
27 | <td>${_('Description')}</td> | |
27 | <td>${h.textarea('description',cols=23,rows=5)}</td> |
|
28 | <td>${h.textarea('description',cols=23,rows=5)}</td> | |
|
29 | <td>${self.get_form_error('description')}</td> | |||
28 | </tr> |
|
30 | </tr> | |
29 | <tr> |
|
31 | <tr> | |
30 | <td>${_('Private')}</td> |
|
32 | <td>${_('Private')}</td> | |
31 | <td>${h.checkbox('private')}</td> |
|
33 | <td>${h.checkbox('private')}</td> | |
|
34 | <td>${self.get_form_error('private')}</td> | |||
32 | </tr> |
|
35 | </tr> | |
33 | <tr> |
|
36 | <tr> | |
34 | <td></td> |
|
37 | <td></td> |
@@ -16,19 +16,27 b'' | |||||
16 | <%def name="main()"> |
|
16 | <%def name="main()"> | |
17 | <div> |
|
17 | <div> | |
18 | <h2>${_('Repositories')} - ${_('edit')}</h2> |
|
18 | <h2>${_('Repositories')} - ${_('edit')}</h2> | |
19 |
${h.form(url('repo', id=c. |
|
19 | ${h.form(url('repo', id=c.repo_info.repo_name),method='put')} | |
20 | <table> |
|
20 | <table> | |
21 | <tr> |
|
21 | <tr> | |
22 | <td>${_('Name')}</td> |
|
22 | <td>${_('Name')}</td> | |
23 |
<td>${h.text('name' |
|
23 | <td>${h.text('repo_name')}</td> | |
|
24 | <td>${self.get_form_error('repo_name')}</td> | |||
24 | </tr> |
|
25 | </tr> | |
25 | <tr> |
|
26 | <tr> | |
26 | <td>${_('Description')}</td> |
|
27 | <td>${_('Description')}</td> | |
27 | <td>${h.textarea('description',cols=23,rows=5)}</td> |
|
28 | <td>${h.textarea('description',cols=23,rows=5)}</td> | |
|
29 | <td>${self.get_form_error('description')}</td> | |||
28 | </tr> |
|
30 | </tr> | |
29 | <tr> |
|
31 | <tr> | |
30 | <td>${_('Private')}</td> |
|
32 | <td>${_('Private')}</td> | |
31 | <td>${h.checkbox('private')}</td> |
|
33 | <td>${h.checkbox('private')}</td> | |
|
34 | <td>${self.get_form_error('private')}</td> | |||
|
35 | </tr> | |||
|
36 | <tr> | |||
|
37 | <td>${_('Owner')}</td> | |||
|
38 | <td>${h.text('user')}</td> | |||
|
39 | <td>${self.get_form_error('user')}</td> | |||
32 | </tr> |
|
40 | </tr> | |
33 | <tr> |
|
41 | <tr> | |
34 | <td></td> |
|
42 | <td></td> |
@@ -70,7 +70,7 b' E.onDOMReady(function(e){' | |||||
70 | <td>${cs.author|n,filters.person}</td> |
|
70 | <td>${cs.author|n,filters.person}</td> | |
71 | <td>r${cs.revision}</td> |
|
71 | <td>r${cs.revision}</td> | |
72 | <td> |
|
72 | <td> | |
73 | ${h.link_to(truncate(cs.message,60), |
|
73 | ${h.link_to(h.truncate(cs.message,60), | |
74 | h.url('changeset_home',repo_name=c.repo_name,revision=cs._short), |
|
74 | h.url('changeset_home',repo_name=c.repo_name,revision=cs._short), | |
75 | title=cs.message)} |
|
75 | title=cs.message)} | |
76 | </td> |
|
76 | </td> |
General Comments 0
You need to be logged in to leave comments.
Login now