##// END OF EJS Templates
controllers: remove empty __before__ methods...
Thomas De Schampheleire -
r6512:2f931307 default
parent child Browse files
Show More
@@ -1,58 +1,55 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 # This program is free software: you can redistribute it and/or modify
2 # This program is free software: you can redistribute it and/or modify
3 # it under the terms of the GNU General Public License as published by
3 # it under the terms of the GNU General Public License as published by
4 # the Free Software Foundation, either version 3 of the License, or
4 # the Free Software Foundation, either version 3 of the License, or
5 # (at your option) any later version.
5 # (at your option) any later version.
6 #
6 #
7 # This program is distributed in the hope that it will be useful,
7 # This program is distributed in the hope that it will be useful,
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # GNU General Public License for more details.
10 # GNU General Public License for more details.
11 #
11 #
12 # You should have received a copy of the GNU General Public License
12 # You should have received a copy of the GNU General Public License
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 """
14 """
15 kallithea.controllers.followers
15 kallithea.controllers.followers
16 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
16 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
17
17
18 Followers controller for Kallithea
18 Followers controller for Kallithea
19
19
20 This file was forked by the Kallithea project in July 2014.
20 This file was forked by the Kallithea project in July 2014.
21 Original author and date, and relevant copyright and licensing information is below:
21 Original author and date, and relevant copyright and licensing information is below:
22 :created_on: Apr 23, 2011
22 :created_on: Apr 23, 2011
23 :author: marcink
23 :author: marcink
24 :copyright: (c) 2013 RhodeCode GmbH, and others.
24 :copyright: (c) 2013 RhodeCode GmbH, and others.
25 :license: GPLv3, see LICENSE.md for more details.
25 :license: GPLv3, see LICENSE.md for more details.
26 """
26 """
27
27
28 import logging
28 import logging
29
29
30 from tg import tmpl_context as c, request
30 from tg import tmpl_context as c, request
31
31
32 from kallithea.lib.auth import LoginRequired, HasRepoPermissionLevelDecorator
32 from kallithea.lib.auth import LoginRequired, HasRepoPermissionLevelDecorator
33 from kallithea.lib.base import BaseRepoController, render
33 from kallithea.lib.base import BaseRepoController, render
34 from kallithea.lib.page import Page
34 from kallithea.lib.page import Page
35 from kallithea.lib.utils2 import safe_int
35 from kallithea.lib.utils2 import safe_int
36 from kallithea.model.db import UserFollowing
36 from kallithea.model.db import UserFollowing
37
37
38 log = logging.getLogger(__name__)
38 log = logging.getLogger(__name__)
39
39
40
40
41 class FollowersController(BaseRepoController):
41 class FollowersController(BaseRepoController):
42
42
43 def __before__(self):
44 super(FollowersController, self).__before__()
45
46 @LoginRequired()
43 @LoginRequired()
47 @HasRepoPermissionLevelDecorator('read')
44 @HasRepoPermissionLevelDecorator('read')
48 def followers(self, repo_name):
45 def followers(self, repo_name):
49 p = safe_int(request.GET.get('page'), 1)
46 p = safe_int(request.GET.get('page'), 1)
50 repo_id = c.db_repo.repo_id
47 repo_id = c.db_repo.repo_id
51 d = UserFollowing.get_repo_followers(repo_id) \
48 d = UserFollowing.get_repo_followers(repo_id) \
52 .order_by(UserFollowing.follows_from)
49 .order_by(UserFollowing.follows_from)
53 c.followers_pager = Page(d, page=p, items_per_page=20)
50 c.followers_pager = Page(d, page=p, items_per_page=20)
54
51
55 if request.environ.get('HTTP_X_PARTIAL_XHR'):
52 if request.environ.get('HTTP_X_PARTIAL_XHR'):
56 return render('/followers/followers_data.html')
53 return render('/followers/followers_data.html')
57
54
58 return render('/followers/followers.html')
55 return render('/followers/followers.html')
@@ -1,184 +1,181 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 # This program is free software: you can redistribute it and/or modify
2 # This program is free software: you can redistribute it and/or modify
3 # it under the terms of the GNU General Public License as published by
3 # it under the terms of the GNU General Public License as published by
4 # the Free Software Foundation, either version 3 of the License, or
4 # the Free Software Foundation, either version 3 of the License, or
5 # (at your option) any later version.
5 # (at your option) any later version.
6 #
6 #
7 # This program is distributed in the hope that it will be useful,
7 # This program is distributed in the hope that it will be useful,
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # GNU General Public License for more details.
10 # GNU General Public License for more details.
11 #
11 #
12 # You should have received a copy of the GNU General Public License
12 # You should have received a copy of the GNU General Public License
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 """
14 """
15 kallithea.controllers.forks
15 kallithea.controllers.forks
16 ~~~~~~~~~~~~~~~~~~~~~~~~~~~
16 ~~~~~~~~~~~~~~~~~~~~~~~~~~~
17
17
18 forks controller for Kallithea
18 forks controller for Kallithea
19
19
20 This file was forked by the Kallithea project in July 2014.
20 This file was forked by the Kallithea project in July 2014.
21 Original author and date, and relevant copyright and licensing information is below:
21 Original author and date, and relevant copyright and licensing information is below:
22 :created_on: Apr 23, 2011
22 :created_on: Apr 23, 2011
23 :author: marcink
23 :author: marcink
24 :copyright: (c) 2013 RhodeCode GmbH, and others.
24 :copyright: (c) 2013 RhodeCode GmbH, and others.
25 :license: GPLv3, see LICENSE.md for more details.
25 :license: GPLv3, see LICENSE.md for more details.
26 """
26 """
27
27
28 import logging
28 import logging
29 import formencode
29 import formencode
30 import traceback
30 import traceback
31 from formencode import htmlfill
31 from formencode import htmlfill
32
32
33 from tg import tmpl_context as c, request
33 from tg import tmpl_context as c, request
34 from tg.i18n import ugettext as _
34 from tg.i18n import ugettext as _
35 from webob.exc import HTTPFound
35 from webob.exc import HTTPFound
36
36
37 import kallithea.lib.helpers as h
37 import kallithea.lib.helpers as h
38
38
39 from kallithea.config.routing import url
39 from kallithea.config.routing import url
40 from kallithea.lib.auth import LoginRequired, HasRepoPermissionLevelDecorator, \
40 from kallithea.lib.auth import LoginRequired, HasRepoPermissionLevelDecorator, \
41 NotAnonymous, HasRepoPermissionLevel, HasPermissionAnyDecorator, HasPermissionAny
41 NotAnonymous, HasRepoPermissionLevel, HasPermissionAnyDecorator, HasPermissionAny
42 from kallithea.lib.base import BaseRepoController, render
42 from kallithea.lib.base import BaseRepoController, render
43 from kallithea.lib.page import Page
43 from kallithea.lib.page import Page
44 from kallithea.lib.utils2 import safe_int
44 from kallithea.lib.utils2 import safe_int
45 from kallithea.model.db import Repository, UserFollowing, User, Ui
45 from kallithea.model.db import Repository, UserFollowing, User, Ui
46 from kallithea.model.repo import RepoModel
46 from kallithea.model.repo import RepoModel
47 from kallithea.model.forms import RepoForkForm
47 from kallithea.model.forms import RepoForkForm
48 from kallithea.model.scm import ScmModel, AvailableRepoGroupChoices
48 from kallithea.model.scm import ScmModel, AvailableRepoGroupChoices
49
49
50 log = logging.getLogger(__name__)
50 log = logging.getLogger(__name__)
51
51
52
52
53 class ForksController(BaseRepoController):
53 class ForksController(BaseRepoController):
54
54
55 def __before__(self):
56 super(ForksController, self).__before__()
57
58 def __load_defaults(self):
55 def __load_defaults(self):
59 if HasPermissionAny('hg.create.write_on_repogroup.true')():
56 if HasPermissionAny('hg.create.write_on_repogroup.true')():
60 repo_group_perm_level = 'write'
57 repo_group_perm_level = 'write'
61 else:
58 else:
62 repo_group_perm_level = 'admin'
59 repo_group_perm_level = 'admin'
63 c.repo_groups = AvailableRepoGroupChoices(['hg.create.repository'], repo_group_perm_level)
60 c.repo_groups = AvailableRepoGroupChoices(['hg.create.repository'], repo_group_perm_level)
64
61
65 c.landing_revs_choices, c.landing_revs = ScmModel().get_repo_landing_revs()
62 c.landing_revs_choices, c.landing_revs = ScmModel().get_repo_landing_revs()
66
63
67 c.can_update = Ui.get_by_key('hooks', Ui.HOOK_UPDATE).ui_active
64 c.can_update = Ui.get_by_key('hooks', Ui.HOOK_UPDATE).ui_active
68
65
69 def __load_data(self):
66 def __load_data(self):
70 """
67 """
71 Load defaults settings for edit, and update
68 Load defaults settings for edit, and update
72 """
69 """
73 self.__load_defaults()
70 self.__load_defaults()
74
71
75 c.repo_info = c.db_repo
72 c.repo_info = c.db_repo
76 repo = c.db_repo.scm_instance
73 repo = c.db_repo.scm_instance
77
74
78 if c.repo_info is None:
75 if c.repo_info is None:
79 h.not_mapped_error(c.repo_name)
76 h.not_mapped_error(c.repo_name)
80 raise HTTPFound(location=url('repos'))
77 raise HTTPFound(location=url('repos'))
81
78
82 c.default_user_id = User.get_default_user().user_id
79 c.default_user_id = User.get_default_user().user_id
83 c.in_public_journal = UserFollowing.query() \
80 c.in_public_journal = UserFollowing.query() \
84 .filter(UserFollowing.user_id == c.default_user_id) \
81 .filter(UserFollowing.user_id == c.default_user_id) \
85 .filter(UserFollowing.follows_repository == c.repo_info).scalar()
82 .filter(UserFollowing.follows_repository == c.repo_info).scalar()
86
83
87 if c.repo_info.stats:
84 if c.repo_info.stats:
88 last_rev = c.repo_info.stats.stat_on_revision+1
85 last_rev = c.repo_info.stats.stat_on_revision+1
89 else:
86 else:
90 last_rev = 0
87 last_rev = 0
91 c.stats_revision = last_rev
88 c.stats_revision = last_rev
92
89
93 c.repo_last_rev = repo.count() if repo.revisions else 0
90 c.repo_last_rev = repo.count() if repo.revisions else 0
94
91
95 if last_rev == 0 or c.repo_last_rev == 0:
92 if last_rev == 0 or c.repo_last_rev == 0:
96 c.stats_percentage = 0
93 c.stats_percentage = 0
97 else:
94 else:
98 c.stats_percentage = '%.2f' % ((float((last_rev)) /
95 c.stats_percentage = '%.2f' % ((float((last_rev)) /
99 c.repo_last_rev) * 100)
96 c.repo_last_rev) * 100)
100
97
101 defaults = RepoModel()._get_defaults(c.repo_name)
98 defaults = RepoModel()._get_defaults(c.repo_name)
102 # alter the description to indicate a fork
99 # alter the description to indicate a fork
103 defaults['description'] = ('fork of repository: %s \n%s'
100 defaults['description'] = ('fork of repository: %s \n%s'
104 % (defaults['repo_name'],
101 % (defaults['repo_name'],
105 defaults['description']))
102 defaults['description']))
106 # add suffix to fork
103 # add suffix to fork
107 defaults['repo_name'] = '%s-fork' % defaults['repo_name']
104 defaults['repo_name'] = '%s-fork' % defaults['repo_name']
108
105
109 return defaults
106 return defaults
110
107
111 @LoginRequired()
108 @LoginRequired()
112 @HasRepoPermissionLevelDecorator('read')
109 @HasRepoPermissionLevelDecorator('read')
113 def forks(self, repo_name):
110 def forks(self, repo_name):
114 p = safe_int(request.GET.get('page'), 1)
111 p = safe_int(request.GET.get('page'), 1)
115 repo_id = c.db_repo.repo_id
112 repo_id = c.db_repo.repo_id
116 d = []
113 d = []
117 for r in Repository.get_repo_forks(repo_id):
114 for r in Repository.get_repo_forks(repo_id):
118 if not HasRepoPermissionLevel('read')(r.repo_name, 'get forks check'):
115 if not HasRepoPermissionLevel('read')(r.repo_name, 'get forks check'):
119 continue
116 continue
120 d.append(r)
117 d.append(r)
121 c.forks_pager = Page(d, page=p, items_per_page=20)
118 c.forks_pager = Page(d, page=p, items_per_page=20)
122
119
123 if request.environ.get('HTTP_X_PARTIAL_XHR'):
120 if request.environ.get('HTTP_X_PARTIAL_XHR'):
124 return render('/forks/forks_data.html')
121 return render('/forks/forks_data.html')
125
122
126 return render('/forks/forks.html')
123 return render('/forks/forks.html')
127
124
128 @LoginRequired()
125 @LoginRequired()
129 @NotAnonymous()
126 @NotAnonymous()
130 @HasPermissionAnyDecorator('hg.admin', 'hg.fork.repository')
127 @HasPermissionAnyDecorator('hg.admin', 'hg.fork.repository')
131 @HasRepoPermissionLevelDecorator('read')
128 @HasRepoPermissionLevelDecorator('read')
132 def fork(self, repo_name):
129 def fork(self, repo_name):
133 c.repo_info = Repository.get_by_repo_name(repo_name)
130 c.repo_info = Repository.get_by_repo_name(repo_name)
134 if not c.repo_info:
131 if not c.repo_info:
135 h.not_mapped_error(repo_name)
132 h.not_mapped_error(repo_name)
136 raise HTTPFound(location=url('home'))
133 raise HTTPFound(location=url('home'))
137
134
138 defaults = self.__load_data()
135 defaults = self.__load_data()
139
136
140 return htmlfill.render(
137 return htmlfill.render(
141 render('forks/fork.html'),
138 render('forks/fork.html'),
142 defaults=defaults,
139 defaults=defaults,
143 encoding="UTF-8",
140 encoding="UTF-8",
144 force_defaults=False)
141 force_defaults=False)
145
142
146 @LoginRequired()
143 @LoginRequired()
147 @NotAnonymous()
144 @NotAnonymous()
148 @HasPermissionAnyDecorator('hg.admin', 'hg.fork.repository')
145 @HasPermissionAnyDecorator('hg.admin', 'hg.fork.repository')
149 @HasRepoPermissionLevelDecorator('read')
146 @HasRepoPermissionLevelDecorator('read')
150 def fork_create(self, repo_name):
147 def fork_create(self, repo_name):
151 self.__load_defaults()
148 self.__load_defaults()
152 c.repo_info = Repository.get_by_repo_name(repo_name)
149 c.repo_info = Repository.get_by_repo_name(repo_name)
153 _form = RepoForkForm(old_data={'repo_type': c.repo_info.repo_type},
150 _form = RepoForkForm(old_data={'repo_type': c.repo_info.repo_type},
154 repo_groups=c.repo_groups,
151 repo_groups=c.repo_groups,
155 landing_revs=c.landing_revs_choices)()
152 landing_revs=c.landing_revs_choices)()
156 form_result = {}
153 form_result = {}
157 task_id = None
154 task_id = None
158 try:
155 try:
159 form_result = _form.to_python(dict(request.POST))
156 form_result = _form.to_python(dict(request.POST))
160
157
161 # an approximation that is better than nothing
158 # an approximation that is better than nothing
162 if not Ui.get_by_key('hooks', Ui.HOOK_UPDATE).ui_active:
159 if not Ui.get_by_key('hooks', Ui.HOOK_UPDATE).ui_active:
163 form_result['update_after_clone'] = False
160 form_result['update_after_clone'] = False
164
161
165 # create fork is done sometimes async on celery, db transaction
162 # create fork is done sometimes async on celery, db transaction
166 # management is handled there.
163 # management is handled there.
167 task = RepoModel().create_fork(form_result, request.authuser.user_id)
164 task = RepoModel().create_fork(form_result, request.authuser.user_id)
168 task_id = task.task_id
165 task_id = task.task_id
169 except formencode.Invalid as errors:
166 except formencode.Invalid as errors:
170 return htmlfill.render(
167 return htmlfill.render(
171 render('forks/fork.html'),
168 render('forks/fork.html'),
172 defaults=errors.value,
169 defaults=errors.value,
173 errors=errors.error_dict or {},
170 errors=errors.error_dict or {},
174 prefix_error=False,
171 prefix_error=False,
175 encoding="UTF-8",
172 encoding="UTF-8",
176 force_defaults=False)
173 force_defaults=False)
177 except Exception:
174 except Exception:
178 log.error(traceback.format_exc())
175 log.error(traceback.format_exc())
179 h.flash(_('An error occurred during repository forking %s') %
176 h.flash(_('An error occurred during repository forking %s') %
180 repo_name, category='error')
177 repo_name, category='error')
181
178
182 raise HTTPFound(location=h.url('repo_creating_home',
179 raise HTTPFound(location=h.url('repo_creating_home',
183 repo_name=form_result['repo_name_full'],
180 repo_name=form_result['repo_name_full'],
184 task_id=task_id))
181 task_id=task_id))
@@ -1,148 +1,145 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 # This program is free software: you can redistribute it and/or modify
2 # This program is free software: you can redistribute it and/or modify
3 # it under the terms of the GNU General Public License as published by
3 # it under the terms of the GNU General Public License as published by
4 # the Free Software Foundation, either version 3 of the License, or
4 # the Free Software Foundation, either version 3 of the License, or
5 # (at your option) any later version.
5 # (at your option) any later version.
6 #
6 #
7 # This program is distributed in the hope that it will be useful,
7 # This program is distributed in the hope that it will be useful,
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # GNU General Public License for more details.
10 # GNU General Public License for more details.
11 #
11 #
12 # You should have received a copy of the GNU General Public License
12 # You should have received a copy of the GNU General Public License
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 """
14 """
15 kallithea.controllers.home
15 kallithea.controllers.home
16 ~~~~~~~~~~~~~~~~~~~~~~~~~~
16 ~~~~~~~~~~~~~~~~~~~~~~~~~~
17
17
18 Home controller for Kallithea
18 Home controller for Kallithea
19
19
20 This file was forked by the Kallithea project in July 2014.
20 This file was forked by the Kallithea project in July 2014.
21 Original author and date, and relevant copyright and licensing information is below:
21 Original author and date, and relevant copyright and licensing information is below:
22 :created_on: Feb 18, 2010
22 :created_on: Feb 18, 2010
23 :author: marcink
23 :author: marcink
24 :copyright: (c) 2013 RhodeCode GmbH, and others.
24 :copyright: (c) 2013 RhodeCode GmbH, and others.
25 :license: GPLv3, see LICENSE.md for more details.
25 :license: GPLv3, see LICENSE.md for more details.
26
26
27 """
27 """
28
28
29 import logging
29 import logging
30
30
31 from tg import tmpl_context as c, request
31 from tg import tmpl_context as c, request
32 from tg.i18n import ugettext as _
32 from tg.i18n import ugettext as _
33 from webob.exc import HTTPBadRequest
33 from webob.exc import HTTPBadRequest
34 from sqlalchemy.sql.expression import func
34 from sqlalchemy.sql.expression import func
35
35
36 from kallithea.lib.utils import conditional_cache
36 from kallithea.lib.utils import conditional_cache
37 from kallithea.lib.auth import LoginRequired, HasRepoPermissionLevelDecorator
37 from kallithea.lib.auth import LoginRequired, HasRepoPermissionLevelDecorator
38 from kallithea.lib.base import BaseController, render, jsonify
38 from kallithea.lib.base import BaseController, render, jsonify
39 from kallithea.model.db import Repository, RepoGroup
39 from kallithea.model.db import Repository, RepoGroup
40 from kallithea.model.repo import RepoModel
40 from kallithea.model.repo import RepoModel
41
41
42
42
43 log = logging.getLogger(__name__)
43 log = logging.getLogger(__name__)
44
44
45
45
46 class HomeController(BaseController):
46 class HomeController(BaseController):
47
47
48 def __before__(self):
49 super(HomeController, self).__before__()
50
51 def about(self):
48 def about(self):
52 return render('/about.html')
49 return render('/about.html')
53
50
54 @LoginRequired()
51 @LoginRequired()
55 def index(self):
52 def index(self):
56 c.groups = self.scm_model.get_repo_groups()
53 c.groups = self.scm_model.get_repo_groups()
57 c.group = None
54 c.group = None
58
55
59 repos_list = Repository.query(sorted=True).filter_by(group=None).all()
56 repos_list = Repository.query(sorted=True).filter_by(group=None).all()
60
57
61 repos_data = RepoModel().get_repos_as_dict(repos_list=repos_list,
58 repos_data = RepoModel().get_repos_as_dict(repos_list=repos_list,
62 admin=False, short_name=True)
59 admin=False, short_name=True)
63 #data used to render the grid
60 #data used to render the grid
64 c.data = repos_data
61 c.data = repos_data
65
62
66 return render('/index.html')
63 return render('/index.html')
67
64
68 @LoginRequired()
65 @LoginRequired()
69 @jsonify
66 @jsonify
70 def repo_switcher_data(self):
67 def repo_switcher_data(self):
71 #wrapper for conditional cache
68 #wrapper for conditional cache
72 def _c():
69 def _c():
73 log.debug('generating switcher repo/groups list')
70 log.debug('generating switcher repo/groups list')
74 all_repos = Repository.query(sorted=True).all()
71 all_repos = Repository.query(sorted=True).all()
75 repo_iter = self.scm_model.get_repos(all_repos)
72 repo_iter = self.scm_model.get_repos(all_repos)
76 all_groups = RepoGroup.query(sorted=True).all()
73 all_groups = RepoGroup.query(sorted=True).all()
77 repo_groups_iter = self.scm_model.get_repo_groups(all_groups)
74 repo_groups_iter = self.scm_model.get_repo_groups(all_groups)
78
75
79 res = [{
76 res = [{
80 'text': _('Groups'),
77 'text': _('Groups'),
81 'children': [
78 'children': [
82 {'id': obj.group_name,
79 {'id': obj.group_name,
83 'text': obj.group_name,
80 'text': obj.group_name,
84 'type': 'group',
81 'type': 'group',
85 'obj': {}}
82 'obj': {}}
86 for obj in repo_groups_iter
83 for obj in repo_groups_iter
87 ],
84 ],
88 },
85 },
89 {
86 {
90 'text': _('Repositories'),
87 'text': _('Repositories'),
91 'children': [
88 'children': [
92 {'id': obj.repo_name,
89 {'id': obj.repo_name,
93 'text': obj.repo_name,
90 'text': obj.repo_name,
94 'type': 'repo',
91 'type': 'repo',
95 'obj': obj.get_dict()}
92 'obj': obj.get_dict()}
96 for obj in repo_iter
93 for obj in repo_iter
97 ],
94 ],
98 }]
95 }]
99
96
100 data = {
97 data = {
101 'more': False,
98 'more': False,
102 'results': res,
99 'results': res,
103 }
100 }
104 return data
101 return data
105
102
106 if request.is_xhr:
103 if request.is_xhr:
107 condition = False
104 condition = False
108 compute = conditional_cache('short_term', 'cache_desc',
105 compute = conditional_cache('short_term', 'cache_desc',
109 condition=condition, func=_c)
106 condition=condition, func=_c)
110 return compute()
107 return compute()
111 else:
108 else:
112 raise HTTPBadRequest()
109 raise HTTPBadRequest()
113
110
114 @LoginRequired()
111 @LoginRequired()
115 @HasRepoPermissionLevelDecorator('read')
112 @HasRepoPermissionLevelDecorator('read')
116 @jsonify
113 @jsonify
117 def repo_refs_data(self, repo_name):
114 def repo_refs_data(self, repo_name):
118 repo = Repository.get_by_repo_name(repo_name).scm_instance
115 repo = Repository.get_by_repo_name(repo_name).scm_instance
119 res = []
116 res = []
120 _branches = repo.branches.items()
117 _branches = repo.branches.items()
121 if _branches:
118 if _branches:
122 res.append({
119 res.append({
123 'text': _('Branch'),
120 'text': _('Branch'),
124 'children': [{'id': rev, 'text': name, 'type': 'branch'} for name, rev in _branches]
121 'children': [{'id': rev, 'text': name, 'type': 'branch'} for name, rev in _branches]
125 })
122 })
126 _closed_branches = repo.closed_branches.items()
123 _closed_branches = repo.closed_branches.items()
127 if _closed_branches:
124 if _closed_branches:
128 res.append({
125 res.append({
129 'text': _('Closed Branches'),
126 'text': _('Closed Branches'),
130 'children': [{'id': rev, 'text': name, 'type': 'closed-branch'} for name, rev in _closed_branches]
127 'children': [{'id': rev, 'text': name, 'type': 'closed-branch'} for name, rev in _closed_branches]
131 })
128 })
132 _tags = repo.tags.items()
129 _tags = repo.tags.items()
133 if _tags:
130 if _tags:
134 res.append({
131 res.append({
135 'text': _('Tag'),
132 'text': _('Tag'),
136 'children': [{'id': rev, 'text': name, 'type': 'tag'} for name, rev in _tags]
133 'children': [{'id': rev, 'text': name, 'type': 'tag'} for name, rev in _tags]
137 })
134 })
138 _bookmarks = repo.bookmarks.items()
135 _bookmarks = repo.bookmarks.items()
139 if _bookmarks:
136 if _bookmarks:
140 res.append({
137 res.append({
141 'text': _('Bookmark'),
138 'text': _('Bookmark'),
142 'children': [{'id': rev, 'text': name, 'type': 'book'} for name, rev in _bookmarks]
139 'children': [{'id': rev, 'text': name, 'type': 'book'} for name, rev in _bookmarks]
143 })
140 })
144 data = {
141 data = {
145 'more': False,
142 'more': False,
146 'results': res
143 'results': res
147 }
144 }
148 return data
145 return data
@@ -1,264 +1,261 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 # This program is free software: you can redistribute it and/or modify
2 # This program is free software: you can redistribute it and/or modify
3 # it under the terms of the GNU General Public License as published by
3 # it under the terms of the GNU General Public License as published by
4 # the Free Software Foundation, either version 3 of the License, or
4 # the Free Software Foundation, either version 3 of the License, or
5 # (at your option) any later version.
5 # (at your option) any later version.
6 #
6 #
7 # This program is distributed in the hope that it will be useful,
7 # This program is distributed in the hope that it will be useful,
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # GNU General Public License for more details.
10 # GNU General Public License for more details.
11 #
11 #
12 # You should have received a copy of the GNU General Public License
12 # You should have received a copy of the GNU General Public License
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 """
14 """
15 kallithea.controllers.login
15 kallithea.controllers.login
16 ~~~~~~~~~~~~~~~~~~~~~~~~~~~
16 ~~~~~~~~~~~~~~~~~~~~~~~~~~~
17
17
18 Login controller for Kallithea
18 Login controller for Kallithea
19
19
20 This file was forked by the Kallithea project in July 2014.
20 This file was forked by the Kallithea project in July 2014.
21 Original author and date, and relevant copyright and licensing information is below:
21 Original author and date, and relevant copyright and licensing information is below:
22 :created_on: Apr 22, 2010
22 :created_on: Apr 22, 2010
23 :author: marcink
23 :author: marcink
24 :copyright: (c) 2013 RhodeCode GmbH, and others.
24 :copyright: (c) 2013 RhodeCode GmbH, and others.
25 :license: GPLv3, see LICENSE.md for more details.
25 :license: GPLv3, see LICENSE.md for more details.
26 """
26 """
27
27
28
28
29 import logging
29 import logging
30 import re
30 import re
31 import formencode
31 import formencode
32
32
33 from formencode import htmlfill
33 from formencode import htmlfill
34 from tg.i18n import ugettext as _
34 from tg.i18n import ugettext as _
35 from tg import request, session, tmpl_context as c
35 from tg import request, session, tmpl_context as c
36 from webob.exc import HTTPFound, HTTPBadRequest
36 from webob.exc import HTTPFound, HTTPBadRequest
37
37
38 import kallithea.lib.helpers as h
38 import kallithea.lib.helpers as h
39 from kallithea.config.routing import url
39 from kallithea.config.routing import url
40 from kallithea.lib.auth import AuthUser, HasPermissionAnyDecorator
40 from kallithea.lib.auth import AuthUser, HasPermissionAnyDecorator
41 from kallithea.lib.base import BaseController, log_in_user, render
41 from kallithea.lib.base import BaseController, log_in_user, render
42 from kallithea.lib.exceptions import UserCreationError
42 from kallithea.lib.exceptions import UserCreationError
43 from kallithea.lib.utils2 import safe_str
43 from kallithea.lib.utils2 import safe_str
44 from kallithea.model.db import User, Setting
44 from kallithea.model.db import User, Setting
45 from kallithea.model.forms import \
45 from kallithea.model.forms import \
46 LoginForm, RegisterForm, PasswordResetRequestForm, PasswordResetConfirmationForm
46 LoginForm, RegisterForm, PasswordResetRequestForm, PasswordResetConfirmationForm
47 from kallithea.model.user import UserModel
47 from kallithea.model.user import UserModel
48 from kallithea.model.meta import Session
48 from kallithea.model.meta import Session
49
49
50
50
51 log = logging.getLogger(__name__)
51 log = logging.getLogger(__name__)
52
52
53
53
54 class LoginController(BaseController):
54 class LoginController(BaseController):
55
55
56 def __before__(self):
57 super(LoginController, self).__before__()
58
59 def _validate_came_from(self, came_from,
56 def _validate_came_from(self, came_from,
60 _re=re.compile(r"/(?!/)[-!#$%&'()*+,./:;=?@_~0-9A-Za-z]*$")):
57 _re=re.compile(r"/(?!/)[-!#$%&'()*+,./:;=?@_~0-9A-Za-z]*$")):
61 """Return True if came_from is valid and can and should be used.
58 """Return True if came_from is valid and can and should be used.
62
59
63 Determines if a URI reference is valid and relative to the origin;
60 Determines if a URI reference is valid and relative to the origin;
64 or in RFC 3986 terms, whether it matches this production:
61 or in RFC 3986 terms, whether it matches this production:
65
62
66 origin-relative-ref = path-absolute [ "?" query ] [ "#" fragment ]
63 origin-relative-ref = path-absolute [ "?" query ] [ "#" fragment ]
67
64
68 with the exception that '%' escapes are not validated and '#' is
65 with the exception that '%' escapes are not validated and '#' is
69 allowed inside the fragment part.
66 allowed inside the fragment part.
70 """
67 """
71 return _re.match(came_from) is not None
68 return _re.match(came_from) is not None
72
69
73 def index(self):
70 def index(self):
74 c.came_from = safe_str(request.GET.get('came_from', ''))
71 c.came_from = safe_str(request.GET.get('came_from', ''))
75 if c.came_from:
72 if c.came_from:
76 if not self._validate_came_from(c.came_from):
73 if not self._validate_came_from(c.came_from):
77 log.error('Invalid came_from (not server-relative): %r', c.came_from)
74 log.error('Invalid came_from (not server-relative): %r', c.came_from)
78 raise HTTPBadRequest()
75 raise HTTPBadRequest()
79 else:
76 else:
80 c.came_from = url('home')
77 c.came_from = url('home')
81
78
82 ip_allowed = AuthUser.check_ip_allowed(request.authuser, request.ip_addr)
79 ip_allowed = AuthUser.check_ip_allowed(request.authuser, request.ip_addr)
83
80
84 # redirect if already logged in
81 # redirect if already logged in
85 if request.authuser.is_authenticated and ip_allowed:
82 if request.authuser.is_authenticated and ip_allowed:
86 raise HTTPFound(location=c.came_from)
83 raise HTTPFound(location=c.came_from)
87
84
88 if request.POST:
85 if request.POST:
89 # import Login Form validator class
86 # import Login Form validator class
90 login_form = LoginForm()()
87 login_form = LoginForm()()
91 try:
88 try:
92 c.form_result = login_form.to_python(dict(request.POST))
89 c.form_result = login_form.to_python(dict(request.POST))
93 # form checks for username/password, now we're authenticated
90 # form checks for username/password, now we're authenticated
94 username = c.form_result['username']
91 username = c.form_result['username']
95 user = User.get_by_username_or_email(username, case_insensitive=True)
92 user = User.get_by_username_or_email(username, case_insensitive=True)
96 except formencode.Invalid as errors:
93 except formencode.Invalid as errors:
97 defaults = errors.value
94 defaults = errors.value
98 # remove password from filling in form again
95 # remove password from filling in form again
99 defaults.pop('password', None)
96 defaults.pop('password', None)
100 return htmlfill.render(
97 return htmlfill.render(
101 render('/login.html'),
98 render('/login.html'),
102 defaults=errors.value,
99 defaults=errors.value,
103 errors=errors.error_dict or {},
100 errors=errors.error_dict or {},
104 prefix_error=False,
101 prefix_error=False,
105 encoding="UTF-8",
102 encoding="UTF-8",
106 force_defaults=False)
103 force_defaults=False)
107 except UserCreationError as e:
104 except UserCreationError as e:
108 # container auth or other auth functions that create users on
105 # container auth or other auth functions that create users on
109 # the fly can throw this exception signaling that there's issue
106 # the fly can throw this exception signaling that there's issue
110 # with user creation, explanation should be provided in
107 # with user creation, explanation should be provided in
111 # Exception itself
108 # Exception itself
112 h.flash(e, 'error')
109 h.flash(e, 'error')
113 else:
110 else:
114 log_in_user(user, c.form_result['remember'],
111 log_in_user(user, c.form_result['remember'],
115 is_external_auth=False)
112 is_external_auth=False)
116 raise HTTPFound(location=c.came_from)
113 raise HTTPFound(location=c.came_from)
117
114
118 return render('/login.html')
115 return render('/login.html')
119
116
120 @HasPermissionAnyDecorator('hg.admin', 'hg.register.auto_activate',
117 @HasPermissionAnyDecorator('hg.admin', 'hg.register.auto_activate',
121 'hg.register.manual_activate')
118 'hg.register.manual_activate')
122 def register(self):
119 def register(self):
123 c.auto_active = 'hg.register.auto_activate' in User.get_default_user() \
120 c.auto_active = 'hg.register.auto_activate' in User.get_default_user() \
124 .AuthUser.permissions['global']
121 .AuthUser.permissions['global']
125
122
126 settings = Setting.get_app_settings()
123 settings = Setting.get_app_settings()
127 captcha_private_key = settings.get('captcha_private_key')
124 captcha_private_key = settings.get('captcha_private_key')
128 c.captcha_active = bool(captcha_private_key)
125 c.captcha_active = bool(captcha_private_key)
129 c.captcha_public_key = settings.get('captcha_public_key')
126 c.captcha_public_key = settings.get('captcha_public_key')
130
127
131 if request.POST:
128 if request.POST:
132 register_form = RegisterForm()()
129 register_form = RegisterForm()()
133 try:
130 try:
134 form_result = register_form.to_python(dict(request.POST))
131 form_result = register_form.to_python(dict(request.POST))
135 form_result['active'] = c.auto_active
132 form_result['active'] = c.auto_active
136
133
137 if c.captcha_active:
134 if c.captcha_active:
138 from kallithea.lib.recaptcha import submit
135 from kallithea.lib.recaptcha import submit
139 response = submit(request.POST.get('recaptcha_challenge_field'),
136 response = submit(request.POST.get('recaptcha_challenge_field'),
140 request.POST.get('recaptcha_response_field'),
137 request.POST.get('recaptcha_response_field'),
141 private_key=captcha_private_key,
138 private_key=captcha_private_key,
142 remoteip=request.ip_addr)
139 remoteip=request.ip_addr)
143 if c.captcha_active and not response.is_valid:
140 if c.captcha_active and not response.is_valid:
144 _value = form_result
141 _value = form_result
145 _msg = _('Bad captcha')
142 _msg = _('Bad captcha')
146 error_dict = {'recaptcha_field': _msg}
143 error_dict = {'recaptcha_field': _msg}
147 raise formencode.Invalid(_msg, _value, None,
144 raise formencode.Invalid(_msg, _value, None,
148 error_dict=error_dict)
145 error_dict=error_dict)
149
146
150 UserModel().create_registration(form_result)
147 UserModel().create_registration(form_result)
151 h.flash(_('You have successfully registered with %s') % (c.site_name or 'Kallithea'),
148 h.flash(_('You have successfully registered with %s') % (c.site_name or 'Kallithea'),
152 category='success')
149 category='success')
153 Session().commit()
150 Session().commit()
154 raise HTTPFound(location=url('login_home'))
151 raise HTTPFound(location=url('login_home'))
155
152
156 except formencode.Invalid as errors:
153 except formencode.Invalid as errors:
157 return htmlfill.render(
154 return htmlfill.render(
158 render('/register.html'),
155 render('/register.html'),
159 defaults=errors.value,
156 defaults=errors.value,
160 errors=errors.error_dict or {},
157 errors=errors.error_dict or {},
161 prefix_error=False,
158 prefix_error=False,
162 encoding="UTF-8",
159 encoding="UTF-8",
163 force_defaults=False)
160 force_defaults=False)
164 except UserCreationError as e:
161 except UserCreationError as e:
165 # container auth or other auth functions that create users on
162 # container auth or other auth functions that create users on
166 # the fly can throw this exception signaling that there's issue
163 # the fly can throw this exception signaling that there's issue
167 # with user creation, explanation should be provided in
164 # with user creation, explanation should be provided in
168 # Exception itself
165 # Exception itself
169 h.flash(e, 'error')
166 h.flash(e, 'error')
170
167
171 return render('/register.html')
168 return render('/register.html')
172
169
173 def password_reset(self):
170 def password_reset(self):
174 settings = Setting.get_app_settings()
171 settings = Setting.get_app_settings()
175 captcha_private_key = settings.get('captcha_private_key')
172 captcha_private_key = settings.get('captcha_private_key')
176 c.captcha_active = bool(captcha_private_key)
173 c.captcha_active = bool(captcha_private_key)
177 c.captcha_public_key = settings.get('captcha_public_key')
174 c.captcha_public_key = settings.get('captcha_public_key')
178
175
179 if request.POST:
176 if request.POST:
180 password_reset_form = PasswordResetRequestForm()()
177 password_reset_form = PasswordResetRequestForm()()
181 try:
178 try:
182 form_result = password_reset_form.to_python(dict(request.POST))
179 form_result = password_reset_form.to_python(dict(request.POST))
183 if c.captcha_active:
180 if c.captcha_active:
184 from kallithea.lib.recaptcha import submit
181 from kallithea.lib.recaptcha import submit
185 response = submit(request.POST.get('recaptcha_challenge_field'),
182 response = submit(request.POST.get('recaptcha_challenge_field'),
186 request.POST.get('recaptcha_response_field'),
183 request.POST.get('recaptcha_response_field'),
187 private_key=captcha_private_key,
184 private_key=captcha_private_key,
188 remoteip=request.ip_addr)
185 remoteip=request.ip_addr)
189 if c.captcha_active and not response.is_valid:
186 if c.captcha_active and not response.is_valid:
190 _value = form_result
187 _value = form_result
191 _msg = _('Bad captcha')
188 _msg = _('Bad captcha')
192 error_dict = {'recaptcha_field': _msg}
189 error_dict = {'recaptcha_field': _msg}
193 raise formencode.Invalid(_msg, _value, None,
190 raise formencode.Invalid(_msg, _value, None,
194 error_dict=error_dict)
191 error_dict=error_dict)
195 redirect_link = UserModel().send_reset_password_email(form_result)
192 redirect_link = UserModel().send_reset_password_email(form_result)
196 h.flash(_('A password reset confirmation code has been sent'),
193 h.flash(_('A password reset confirmation code has been sent'),
197 category='success')
194 category='success')
198 raise HTTPFound(location=redirect_link)
195 raise HTTPFound(location=redirect_link)
199
196
200 except formencode.Invalid as errors:
197 except formencode.Invalid as errors:
201 return htmlfill.render(
198 return htmlfill.render(
202 render('/password_reset.html'),
199 render('/password_reset.html'),
203 defaults=errors.value,
200 defaults=errors.value,
204 errors=errors.error_dict or {},
201 errors=errors.error_dict or {},
205 prefix_error=False,
202 prefix_error=False,
206 encoding="UTF-8",
203 encoding="UTF-8",
207 force_defaults=False)
204 force_defaults=False)
208
205
209 return render('/password_reset.html')
206 return render('/password_reset.html')
210
207
211 def password_reset_confirmation(self):
208 def password_reset_confirmation(self):
212 # This controller handles both GET and POST requests, though we
209 # This controller handles both GET and POST requests, though we
213 # only ever perform the actual password change on POST (since
210 # only ever perform the actual password change on POST (since
214 # GET requests are not allowed to have side effects, and do not
211 # GET requests are not allowed to have side effects, and do not
215 # receive automatic CSRF protection).
212 # receive automatic CSRF protection).
216
213
217 # The template needs the email address outside of the form.
214 # The template needs the email address outside of the form.
218 c.email = request.params.get('email')
215 c.email = request.params.get('email')
219
216
220 if not request.POST:
217 if not request.POST:
221 return htmlfill.render(
218 return htmlfill.render(
222 render('/password_reset_confirmation.html'),
219 render('/password_reset_confirmation.html'),
223 defaults=dict(request.params),
220 defaults=dict(request.params),
224 encoding='UTF-8')
221 encoding='UTF-8')
225
222
226 form = PasswordResetConfirmationForm()()
223 form = PasswordResetConfirmationForm()()
227 try:
224 try:
228 form_result = form.to_python(dict(request.POST))
225 form_result = form.to_python(dict(request.POST))
229 except formencode.Invalid as errors:
226 except formencode.Invalid as errors:
230 return htmlfill.render(
227 return htmlfill.render(
231 render('/password_reset_confirmation.html'),
228 render('/password_reset_confirmation.html'),
232 defaults=errors.value,
229 defaults=errors.value,
233 errors=errors.error_dict or {},
230 errors=errors.error_dict or {},
234 prefix_error=False,
231 prefix_error=False,
235 encoding='UTF-8')
232 encoding='UTF-8')
236
233
237 if not UserModel().verify_reset_password_token(
234 if not UserModel().verify_reset_password_token(
238 form_result['email'],
235 form_result['email'],
239 form_result['timestamp'],
236 form_result['timestamp'],
240 form_result['token'],
237 form_result['token'],
241 ):
238 ):
242 return htmlfill.render(
239 return htmlfill.render(
243 render('/password_reset_confirmation.html'),
240 render('/password_reset_confirmation.html'),
244 defaults=form_result,
241 defaults=form_result,
245 errors={'token': _('Invalid password reset token')},
242 errors={'token': _('Invalid password reset token')},
246 prefix_error=False,
243 prefix_error=False,
247 encoding='UTF-8')
244 encoding='UTF-8')
248
245
249 UserModel().reset_password(form_result['email'], form_result['password'])
246 UserModel().reset_password(form_result['email'], form_result['password'])
250 h.flash(_('Successfully updated password'), category='success')
247 h.flash(_('Successfully updated password'), category='success')
251 raise HTTPFound(location=url('login_home'))
248 raise HTTPFound(location=url('login_home'))
252
249
253 def logout(self):
250 def logout(self):
254 session.delete()
251 session.delete()
255 log.info('Logging out and deleting session for user')
252 log.info('Logging out and deleting session for user')
256 raise HTTPFound(location=url('home'))
253 raise HTTPFound(location=url('home'))
257
254
258 def authentication_token(self):
255 def authentication_token(self):
259 """Return the CSRF protection token for the session - just like it
256 """Return the CSRF protection token for the session - just like it
260 could have been screen scraped from a page with a form.
257 could have been screen scraped from a page with a form.
261 Only intended for testing but might also be useful for other kinds
258 Only intended for testing but might also be useful for other kinds
262 of automation.
259 of automation.
263 """
260 """
264 return h.authentication_token()
261 return h.authentication_token()
@@ -1,149 +1,146 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 # This program is free software: you can redistribute it and/or modify
2 # This program is free software: you can redistribute it and/or modify
3 # it under the terms of the GNU General Public License as published by
3 # it under the terms of the GNU General Public License as published by
4 # the Free Software Foundation, either version 3 of the License, or
4 # the Free Software Foundation, either version 3 of the License, or
5 # (at your option) any later version.
5 # (at your option) any later version.
6 #
6 #
7 # This program is distributed in the hope that it will be useful,
7 # This program is distributed in the hope that it will be useful,
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # GNU General Public License for more details.
10 # GNU General Public License for more details.
11 #
11 #
12 # You should have received a copy of the GNU General Public License
12 # You should have received a copy of the GNU General Public License
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 """
14 """
15 kallithea.controllers.search
15 kallithea.controllers.search
16 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
16 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
17
17
18 Search controller for Kallithea
18 Search controller for Kallithea
19
19
20 This file was forked by the Kallithea project in July 2014.
20 This file was forked by the Kallithea project in July 2014.
21 Original author and date, and relevant copyright and licensing information is below:
21 Original author and date, and relevant copyright and licensing information is below:
22 :created_on: Aug 7, 2010
22 :created_on: Aug 7, 2010
23 :author: marcink
23 :author: marcink
24 :copyright: (c) 2013 RhodeCode GmbH, and others.
24 :copyright: (c) 2013 RhodeCode GmbH, and others.
25 :license: GPLv3, see LICENSE.md for more details.
25 :license: GPLv3, see LICENSE.md for more details.
26 """
26 """
27
27
28 import logging
28 import logging
29 import traceback
29 import traceback
30 import urllib
30 import urllib
31 from tg.i18n import ugettext as _
31 from tg.i18n import ugettext as _
32 from tg import request, config, tmpl_context as c
32 from tg import request, config, tmpl_context as c
33
33
34 from whoosh.index import open_dir, EmptyIndexError
34 from whoosh.index import open_dir, EmptyIndexError
35 from whoosh.qparser import QueryParser, QueryParserError
35 from whoosh.qparser import QueryParser, QueryParserError
36 from whoosh.query import Phrase, Prefix
36 from whoosh.query import Phrase, Prefix
37 from webhelpers.util import update_params
37 from webhelpers.util import update_params
38
38
39 from kallithea.lib.auth import LoginRequired
39 from kallithea.lib.auth import LoginRequired
40 from kallithea.lib.base import BaseRepoController, render
40 from kallithea.lib.base import BaseRepoController, render
41 from kallithea.lib.indexers import CHGSETS_SCHEMA, SCHEMA, CHGSET_IDX_NAME, \
41 from kallithea.lib.indexers import CHGSETS_SCHEMA, SCHEMA, CHGSET_IDX_NAME, \
42 IDX_NAME, WhooshResultWrapper
42 IDX_NAME, WhooshResultWrapper
43 from kallithea.lib.page import Page
43 from kallithea.lib.page import Page
44 from kallithea.lib.utils2 import safe_str, safe_int
44 from kallithea.lib.utils2 import safe_str, safe_int
45 from kallithea.model.repo import RepoModel
45 from kallithea.model.repo import RepoModel
46
46
47 log = logging.getLogger(__name__)
47 log = logging.getLogger(__name__)
48
48
49
49
50 class SearchController(BaseRepoController):
50 class SearchController(BaseRepoController):
51
51
52 def __before__(self):
53 super(SearchController, self).__before__()
54
55 @LoginRequired()
52 @LoginRequired()
56 def index(self, repo_name=None):
53 def index(self, repo_name=None):
57 c.repo_name = repo_name
54 c.repo_name = repo_name
58 c.formated_results = []
55 c.formated_results = []
59 c.runtime = ''
56 c.runtime = ''
60 c.cur_query = request.GET.get('q', None)
57 c.cur_query = request.GET.get('q', None)
61 c.cur_type = request.GET.get('type', 'content')
58 c.cur_type = request.GET.get('type', 'content')
62 c.cur_search = search_type = {'content': 'content',
59 c.cur_search = search_type = {'content': 'content',
63 'commit': 'message',
60 'commit': 'message',
64 'path': 'path',
61 'path': 'path',
65 'repository': 'repository'
62 'repository': 'repository'
66 }.get(c.cur_type, 'content')
63 }.get(c.cur_type, 'content')
67
64
68 index_name = {
65 index_name = {
69 'content': IDX_NAME,
66 'content': IDX_NAME,
70 'commit': CHGSET_IDX_NAME,
67 'commit': CHGSET_IDX_NAME,
71 'path': IDX_NAME
68 'path': IDX_NAME
72 }.get(c.cur_type, IDX_NAME)
69 }.get(c.cur_type, IDX_NAME)
73
70
74 schema_defn = {
71 schema_defn = {
75 'content': SCHEMA,
72 'content': SCHEMA,
76 'commit': CHGSETS_SCHEMA,
73 'commit': CHGSETS_SCHEMA,
77 'path': SCHEMA
74 'path': SCHEMA
78 }.get(c.cur_type, SCHEMA)
75 }.get(c.cur_type, SCHEMA)
79
76
80 log.debug('IDX: %s', index_name)
77 log.debug('IDX: %s', index_name)
81 log.debug('SCHEMA: %s', schema_defn)
78 log.debug('SCHEMA: %s', schema_defn)
82
79
83 if c.cur_query:
80 if c.cur_query:
84 cur_query = c.cur_query.lower()
81 cur_query = c.cur_query.lower()
85 log.debug(cur_query)
82 log.debug(cur_query)
86
83
87 if c.cur_query:
84 if c.cur_query:
88 p = safe_int(request.GET.get('page'), 1)
85 p = safe_int(request.GET.get('page'), 1)
89 highlight_items = set()
86 highlight_items = set()
90 try:
87 try:
91 idx = open_dir(config['app_conf']['index_dir'],
88 idx = open_dir(config['app_conf']['index_dir'],
92 indexname=index_name)
89 indexname=index_name)
93 searcher = idx.searcher()
90 searcher = idx.searcher()
94
91
95 qp = QueryParser(search_type, schema=schema_defn)
92 qp = QueryParser(search_type, schema=schema_defn)
96 if c.repo_name:
93 if c.repo_name:
97 # use "repository_rawname:" instead of "repository:"
94 # use "repository_rawname:" instead of "repository:"
98 # for case-sensitive matching
95 # for case-sensitive matching
99 cur_query = u'repository_rawname:%s %s' % (c.repo_name, cur_query)
96 cur_query = u'repository_rawname:%s %s' % (c.repo_name, cur_query)
100 try:
97 try:
101 query = qp.parse(unicode(cur_query))
98 query = qp.parse(unicode(cur_query))
102 # extract words for highlight
99 # extract words for highlight
103 if isinstance(query, Phrase):
100 if isinstance(query, Phrase):
104 highlight_items.update(query.words)
101 highlight_items.update(query.words)
105 elif isinstance(query, Prefix):
102 elif isinstance(query, Prefix):
106 highlight_items.add(query.text)
103 highlight_items.add(query.text)
107 else:
104 else:
108 for i in query.all_terms():
105 for i in query.all_terms():
109 if i[0] in ['content', 'message']:
106 if i[0] in ['content', 'message']:
110 highlight_items.add(i[1])
107 highlight_items.add(i[1])
111
108
112 matcher = query.matcher(searcher)
109 matcher = query.matcher(searcher)
113
110
114 log.debug('query: %s', query)
111 log.debug('query: %s', query)
115 log.debug('hl terms: %s', highlight_items)
112 log.debug('hl terms: %s', highlight_items)
116 results = searcher.search(query)
113 results = searcher.search(query)
117 res_ln = len(results)
114 res_ln = len(results)
118 c.runtime = '%s results (%.3f seconds)' % (
115 c.runtime = '%s results (%.3f seconds)' % (
119 res_ln, results.runtime
116 res_ln, results.runtime
120 )
117 )
121
118
122 def url_generator(**kw):
119 def url_generator(**kw):
123 q = urllib.quote(safe_str(c.cur_query))
120 q = urllib.quote(safe_str(c.cur_query))
124 return update_params("?q=%s&type=%s" \
121 return update_params("?q=%s&type=%s" \
125 % (q, safe_str(c.cur_type)), **kw)
122 % (q, safe_str(c.cur_type)), **kw)
126 repo_location = RepoModel().repos_path
123 repo_location = RepoModel().repos_path
127 c.formated_results = Page(
124 c.formated_results = Page(
128 WhooshResultWrapper(search_type, searcher, matcher,
125 WhooshResultWrapper(search_type, searcher, matcher,
129 highlight_items, repo_location),
126 highlight_items, repo_location),
130 page=p,
127 page=p,
131 item_count=res_ln,
128 item_count=res_ln,
132 items_per_page=10,
129 items_per_page=10,
133 url=url_generator
130 url=url_generator
134 )
131 )
135
132
136 except QueryParserError:
133 except QueryParserError:
137 c.runtime = _('Invalid search query. Try quoting it.')
134 c.runtime = _('Invalid search query. Try quoting it.')
138 searcher.close()
135 searcher.close()
139 except (EmptyIndexError, IOError):
136 except (EmptyIndexError, IOError):
140 log.error(traceback.format_exc())
137 log.error(traceback.format_exc())
141 log.error('Empty Index data')
138 log.error('Empty Index data')
142 c.runtime = _('There is no index to search in. '
139 c.runtime = _('There is no index to search in. '
143 'Please run whoosh indexer')
140 'Please run whoosh indexer')
144 except Exception:
141 except Exception:
145 log.error(traceback.format_exc())
142 log.error(traceback.format_exc())
146 c.runtime = _('An error occurred during search operation.')
143 c.runtime = _('An error occurred during search operation.')
147
144
148 # Return a rendered template
145 # Return a rendered template
149 return render('/search/search.html')
146 return render('/search/search.html')
@@ -1,224 +1,221 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 # This program is free software: you can redistribute it and/or modify
2 # This program is free software: you can redistribute it and/or modify
3 # it under the terms of the GNU General Public License as published by
3 # it under the terms of the GNU General Public License as published by
4 # the Free Software Foundation, either version 3 of the License, or
4 # the Free Software Foundation, either version 3 of the License, or
5 # (at your option) any later version.
5 # (at your option) any later version.
6 #
6 #
7 # This program is distributed in the hope that it will be useful,
7 # This program is distributed in the hope that it will be useful,
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # GNU General Public License for more details.
10 # GNU General Public License for more details.
11 #
11 #
12 # You should have received a copy of the GNU General Public License
12 # You should have received a copy of the GNU General Public License
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 """
14 """
15 kallithea.controllers.summary
15 kallithea.controllers.summary
16 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
16 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
17
17
18 Summary controller for Kallithea
18 Summary controller for Kallithea
19
19
20 This file was forked by the Kallithea project in July 2014.
20 This file was forked by the Kallithea project in July 2014.
21 Original author and date, and relevant copyright and licensing information is below:
21 Original author and date, and relevant copyright and licensing information is below:
22 :created_on: Apr 18, 2010
22 :created_on: Apr 18, 2010
23 :author: marcink
23 :author: marcink
24 :copyright: (c) 2013 RhodeCode GmbH, and others.
24 :copyright: (c) 2013 RhodeCode GmbH, and others.
25 :license: GPLv3, see LICENSE.md for more details.
25 :license: GPLv3, see LICENSE.md for more details.
26 """
26 """
27
27
28 import traceback
28 import traceback
29 import calendar
29 import calendar
30 import logging
30 import logging
31 import itertools
31 import itertools
32 from time import mktime
32 from time import mktime
33 from datetime import timedelta, date
33 from datetime import timedelta, date
34
34
35 from tg import tmpl_context as c, request
35 from tg import tmpl_context as c, request
36 from tg.i18n import ugettext as _
36 from tg.i18n import ugettext as _
37 from webob.exc import HTTPBadRequest
37 from webob.exc import HTTPBadRequest
38
38
39 from beaker.cache import cache_region, region_invalidate
39 from beaker.cache import cache_region, region_invalidate
40
40
41 from kallithea.lib.vcs.exceptions import ChangesetError, EmptyRepositoryError, \
41 from kallithea.lib.vcs.exceptions import ChangesetError, EmptyRepositoryError, \
42 NodeDoesNotExistError
42 NodeDoesNotExistError
43 from kallithea.config.conf import ALL_READMES, ALL_EXTS, LANGUAGES_EXTENSIONS_MAP
43 from kallithea.config.conf import ALL_READMES, ALL_EXTS, LANGUAGES_EXTENSIONS_MAP
44 from kallithea.model.db import Statistics, CacheInvalidation, User
44 from kallithea.model.db import Statistics, CacheInvalidation, User
45 from kallithea.lib.utils2 import safe_str
45 from kallithea.lib.utils2 import safe_str
46 from kallithea.lib.auth import LoginRequired, HasRepoPermissionLevelDecorator, \
46 from kallithea.lib.auth import LoginRequired, HasRepoPermissionLevelDecorator, \
47 NotAnonymous
47 NotAnonymous
48 from kallithea.lib.base import BaseRepoController, render, jsonify
48 from kallithea.lib.base import BaseRepoController, render, jsonify
49 from kallithea.lib.vcs.backends.base import EmptyChangeset
49 from kallithea.lib.vcs.backends.base import EmptyChangeset
50 from kallithea.lib.markup_renderer import MarkupRenderer
50 from kallithea.lib.markup_renderer import MarkupRenderer
51 from kallithea.lib.celerylib.tasks import get_commits_stats
51 from kallithea.lib.celerylib.tasks import get_commits_stats
52 from kallithea.lib.compat import json
52 from kallithea.lib.compat import json
53 from kallithea.lib.vcs.nodes import FileNode
53 from kallithea.lib.vcs.nodes import FileNode
54 from kallithea.controllers.changelog import _load_changelog_summary
54 from kallithea.controllers.changelog import _load_changelog_summary
55
55
56 log = logging.getLogger(__name__)
56 log = logging.getLogger(__name__)
57
57
58 README_FILES = [''.join([x[0][0], x[1][0]]) for x in
58 README_FILES = [''.join([x[0][0], x[1][0]]) for x in
59 sorted(list(itertools.product(ALL_READMES, ALL_EXTS)),
59 sorted(list(itertools.product(ALL_READMES, ALL_EXTS)),
60 key=lambda y:y[0][1] + y[1][1])]
60 key=lambda y:y[0][1] + y[1][1])]
61
61
62
62
63 class SummaryController(BaseRepoController):
63 class SummaryController(BaseRepoController):
64
64
65 def __before__(self):
66 super(SummaryController, self).__before__()
67
68 def __get_readme_data(self, db_repo):
65 def __get_readme_data(self, db_repo):
69 repo_name = db_repo.repo_name
66 repo_name = db_repo.repo_name
70 log.debug('Looking for README file')
67 log.debug('Looking for README file')
71
68
72 @cache_region('long_term', '_get_readme_from_cache')
69 @cache_region('long_term', '_get_readme_from_cache')
73 def _get_readme_from_cache(key, kind):
70 def _get_readme_from_cache(key, kind):
74 readme_data = None
71 readme_data = None
75 readme_file = None
72 readme_file = None
76 try:
73 try:
77 # gets the landing revision! or tip if fails
74 # gets the landing revision! or tip if fails
78 cs = db_repo.get_landing_changeset()
75 cs = db_repo.get_landing_changeset()
79 if isinstance(cs, EmptyChangeset):
76 if isinstance(cs, EmptyChangeset):
80 raise EmptyRepositoryError()
77 raise EmptyRepositoryError()
81 renderer = MarkupRenderer()
78 renderer = MarkupRenderer()
82 for f in README_FILES:
79 for f in README_FILES:
83 try:
80 try:
84 readme = cs.get_node(f)
81 readme = cs.get_node(f)
85 if not isinstance(readme, FileNode):
82 if not isinstance(readme, FileNode):
86 continue
83 continue
87 readme_file = f
84 readme_file = f
88 log.debug('Found README file `%s` rendering...',
85 log.debug('Found README file `%s` rendering...',
89 readme_file)
86 readme_file)
90 readme_data = renderer.render(readme.content,
87 readme_data = renderer.render(readme.content,
91 filename=f)
88 filename=f)
92 break
89 break
93 except NodeDoesNotExistError:
90 except NodeDoesNotExistError:
94 continue
91 continue
95 except ChangesetError:
92 except ChangesetError:
96 log.error(traceback.format_exc())
93 log.error(traceback.format_exc())
97 pass
94 pass
98 except EmptyRepositoryError:
95 except EmptyRepositoryError:
99 pass
96 pass
100
97
101 return readme_data, readme_file
98 return readme_data, readme_file
102
99
103 kind = 'README'
100 kind = 'README'
104 valid = CacheInvalidation.test_and_set_valid(repo_name, kind)
101 valid = CacheInvalidation.test_and_set_valid(repo_name, kind)
105 if not valid:
102 if not valid:
106 region_invalidate(_get_readme_from_cache, None, '_get_readme_from_cache', repo_name, kind)
103 region_invalidate(_get_readme_from_cache, None, '_get_readme_from_cache', repo_name, kind)
107 return _get_readme_from_cache(repo_name, kind)
104 return _get_readme_from_cache(repo_name, kind)
108
105
109 @LoginRequired()
106 @LoginRequired()
110 @HasRepoPermissionLevelDecorator('read')
107 @HasRepoPermissionLevelDecorator('read')
111 def index(self, repo_name):
108 def index(self, repo_name):
112 _load_changelog_summary()
109 _load_changelog_summary()
113
110
114 if request.authuser.is_default_user:
111 if request.authuser.is_default_user:
115 username = ''
112 username = ''
116 else:
113 else:
117 username = safe_str(request.authuser.username)
114 username = safe_str(request.authuser.username)
118
115
119 _def_clone_uri = _def_clone_uri_by_id = c.clone_uri_tmpl
116 _def_clone_uri = _def_clone_uri_by_id = c.clone_uri_tmpl
120 if '{repo}' in _def_clone_uri:
117 if '{repo}' in _def_clone_uri:
121 _def_clone_uri_by_id = _def_clone_uri.replace('{repo}', '_{repoid}')
118 _def_clone_uri_by_id = _def_clone_uri.replace('{repo}', '_{repoid}')
122 elif '{repoid}' in _def_clone_uri:
119 elif '{repoid}' in _def_clone_uri:
123 _def_clone_uri_by_id = _def_clone_uri.replace('_{repoid}', '{repo}')
120 _def_clone_uri_by_id = _def_clone_uri.replace('_{repoid}', '{repo}')
124
121
125 c.clone_repo_url = c.db_repo.clone_url(user=username,
122 c.clone_repo_url = c.db_repo.clone_url(user=username,
126 uri_tmpl=_def_clone_uri)
123 uri_tmpl=_def_clone_uri)
127 c.clone_repo_url_id = c.db_repo.clone_url(user=username,
124 c.clone_repo_url_id = c.db_repo.clone_url(user=username,
128 uri_tmpl=_def_clone_uri_by_id)
125 uri_tmpl=_def_clone_uri_by_id)
129
126
130 if c.db_repo.enable_statistics:
127 if c.db_repo.enable_statistics:
131 c.show_stats = True
128 c.show_stats = True
132 else:
129 else:
133 c.show_stats = False
130 c.show_stats = False
134
131
135 stats = Statistics.query() \
132 stats = Statistics.query() \
136 .filter(Statistics.repository == c.db_repo) \
133 .filter(Statistics.repository == c.db_repo) \
137 .scalar()
134 .scalar()
138
135
139 c.stats_percentage = 0
136 c.stats_percentage = 0
140
137
141 if stats and stats.languages:
138 if stats and stats.languages:
142 c.no_data = False is c.db_repo.enable_statistics
139 c.no_data = False is c.db_repo.enable_statistics
143 lang_stats_d = json.loads(stats.languages)
140 lang_stats_d = json.loads(stats.languages)
144
141
145 lang_stats = ((x, {"count": y,
142 lang_stats = ((x, {"count": y,
146 "desc": LANGUAGES_EXTENSIONS_MAP.get(x)})
143 "desc": LANGUAGES_EXTENSIONS_MAP.get(x)})
147 for x, y in lang_stats_d.items())
144 for x, y in lang_stats_d.items())
148
145
149 c.trending_languages = (
146 c.trending_languages = (
150 sorted(lang_stats, reverse=True, key=lambda k: k[1])[:10]
147 sorted(lang_stats, reverse=True, key=lambda k: k[1])[:10]
151 )
148 )
152 else:
149 else:
153 c.no_data = True
150 c.no_data = True
154 c.trending_languages = []
151 c.trending_languages = []
155
152
156 c.enable_downloads = c.db_repo.enable_downloads
153 c.enable_downloads = c.db_repo.enable_downloads
157 c.readme_data, c.readme_file = \
154 c.readme_data, c.readme_file = \
158 self.__get_readme_data(c.db_repo)
155 self.__get_readme_data(c.db_repo)
159 return render('summary/summary.html')
156 return render('summary/summary.html')
160
157
161 @LoginRequired()
158 @LoginRequired()
162 @NotAnonymous()
159 @NotAnonymous()
163 @HasRepoPermissionLevelDecorator('read')
160 @HasRepoPermissionLevelDecorator('read')
164 @jsonify
161 @jsonify
165 def repo_size(self, repo_name):
162 def repo_size(self, repo_name):
166 if request.is_xhr:
163 if request.is_xhr:
167 return c.db_repo._repo_size()
164 return c.db_repo._repo_size()
168 else:
165 else:
169 raise HTTPBadRequest()
166 raise HTTPBadRequest()
170
167
171 @LoginRequired()
168 @LoginRequired()
172 @HasRepoPermissionLevelDecorator('read')
169 @HasRepoPermissionLevelDecorator('read')
173 def statistics(self, repo_name):
170 def statistics(self, repo_name):
174 if c.db_repo.enable_statistics:
171 if c.db_repo.enable_statistics:
175 c.show_stats = True
172 c.show_stats = True
176 c.no_data_msg = _('No data ready yet')
173 c.no_data_msg = _('No data ready yet')
177 else:
174 else:
178 c.show_stats = False
175 c.show_stats = False
179 c.no_data_msg = _('Statistics are disabled for this repository')
176 c.no_data_msg = _('Statistics are disabled for this repository')
180
177
181 td = date.today() + timedelta(days=1)
178 td = date.today() + timedelta(days=1)
182 td_1m = td - timedelta(days=calendar.mdays[td.month])
179 td_1m = td - timedelta(days=calendar.mdays[td.month])
183 td_1y = td - timedelta(days=365)
180 td_1y = td - timedelta(days=365)
184
181
185 ts_min_m = mktime(td_1m.timetuple())
182 ts_min_m = mktime(td_1m.timetuple())
186 ts_min_y = mktime(td_1y.timetuple())
183 ts_min_y = mktime(td_1y.timetuple())
187 ts_max_y = mktime(td.timetuple())
184 ts_max_y = mktime(td.timetuple())
188 c.ts_min = ts_min_m
185 c.ts_min = ts_min_m
189 c.ts_max = ts_max_y
186 c.ts_max = ts_max_y
190
187
191 stats = Statistics.query() \
188 stats = Statistics.query() \
192 .filter(Statistics.repository == c.db_repo) \
189 .filter(Statistics.repository == c.db_repo) \
193 .scalar()
190 .scalar()
194 c.stats_percentage = 0
191 c.stats_percentage = 0
195 if stats and stats.languages:
192 if stats and stats.languages:
196 c.no_data = False is c.db_repo.enable_statistics
193 c.no_data = False is c.db_repo.enable_statistics
197 lang_stats_d = json.loads(stats.languages)
194 lang_stats_d = json.loads(stats.languages)
198 c.commit_data = stats.commit_activity
195 c.commit_data = stats.commit_activity
199 c.overview_data = stats.commit_activity_combined
196 c.overview_data = stats.commit_activity_combined
200
197
201 lang_stats = ((x, {"count": y,
198 lang_stats = ((x, {"count": y,
202 "desc": LANGUAGES_EXTENSIONS_MAP.get(x)})
199 "desc": LANGUAGES_EXTENSIONS_MAP.get(x)})
203 for x, y in lang_stats_d.items())
200 for x, y in lang_stats_d.items())
204
201
205 c.trending_languages = (
202 c.trending_languages = (
206 sorted(lang_stats, reverse=True, key=lambda k: k[1])[:10]
203 sorted(lang_stats, reverse=True, key=lambda k: k[1])[:10]
207 )
204 )
208 last_rev = stats.stat_on_revision + 1
205 last_rev = stats.stat_on_revision + 1
209 c.repo_last_rev = c.db_repo_scm_instance.count() \
206 c.repo_last_rev = c.db_repo_scm_instance.count() \
210 if c.db_repo_scm_instance.revisions else 0
207 if c.db_repo_scm_instance.revisions else 0
211 if last_rev == 0 or c.repo_last_rev == 0:
208 if last_rev == 0 or c.repo_last_rev == 0:
212 pass
209 pass
213 else:
210 else:
214 c.stats_percentage = '%.2f' % ((float((last_rev)) /
211 c.stats_percentage = '%.2f' % ((float((last_rev)) /
215 c.repo_last_rev) * 100)
212 c.repo_last_rev) * 100)
216 else:
213 else:
217 c.commit_data = {}
214 c.commit_data = {}
218 c.overview_data = ([[ts_min_y, 0], [ts_max_y, 10]])
215 c.overview_data = ([[ts_min_y, 0], [ts_max_y, 10]])
219 c.trending_languages = {}
216 c.trending_languages = {}
220 c.no_data = True
217 c.no_data = True
221
218
222 recurse_limit = 500 # don't recurse more than 500 times when parsing
219 recurse_limit = 500 # don't recurse more than 500 times when parsing
223 get_commits_stats(c.db_repo.repo_name, ts_min_y, ts_max_y, recurse_limit)
220 get_commits_stats(c.db_repo.repo_name, ts_min_y, ts_max_y, recurse_limit)
224 return render('summary/statistics.html')
221 return render('summary/statistics.html')
General Comments 0
You need to be logged in to leave comments. Login now