##// END OF EJS Templates
implemented #84 downloads can be enabled/disabled per each repository from now.
marcink -
r962:72f008ed beta
parent child Browse files
Show More
@@ -1,267 +1,271 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.controllers.files
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 Files controller for RhodeCode
7 7
8 8 :created_on: Apr 21, 2010
9 9 :author: marcink
10 10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software; you can redistribute it and/or
14 14 # modify it under the terms of the GNU General Public License
15 15 # as published by the Free Software Foundation; version 2
16 16 # of the License or (at your opinion) any later version of the license.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 24 # along with this program; if not, write to the Free Software
25 25 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
26 26 # MA 02110-1301, USA.
27 27 import tempfile
28 28 import logging
29 29 import rhodecode.lib.helpers as h
30 30
31 31 from mercurial import archival
32 32
33 33 from pylons import request, response, session, tmpl_context as c, url
34 34 from pylons.i18n.translation import _
35 35 from pylons.controllers.util import redirect
36 36
37 37 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
38 38 from rhodecode.lib.base import BaseController, render
39 39 from rhodecode.lib.utils import EmptyChangeset
40 40 from rhodecode.model.scm import ScmModel
41 41
42 42 from vcs.backends import ARCHIVE_SPECS
43 43 from vcs.exceptions import RepositoryError, ChangesetError, \
44 44 ChangesetDoesNotExistError, EmptyRepositoryError, ImproperArchiveTypeError
45 45 from vcs.nodes import FileNode
46 46 from vcs.utils import diffs as differ
47 47
48 48 log = logging.getLogger(__name__)
49 49
50 50 class FilesController(BaseController):
51 51
52 52 @LoginRequired()
53 53 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
54 54 'repository.admin')
55 55 def __before__(self):
56 56 super(FilesController, self).__before__()
57 57 c.cut_off_limit = self.cut_off_limit
58 58
59 59 def index(self, repo_name, revision, f_path):
60 60 hg_model = ScmModel()
61 61 c.repo = hg_model.get_repo(c.repo_name)
62 62
63 63 try:
64 64 #reditect to given revision from form
65 65 post_revision = request.POST.get('at_rev', None)
66 66 if post_revision:
67 67 post_revision = c.repo.get_changeset(post_revision).raw_id
68 68 redirect(url('files_home', repo_name=c.repo_name,
69 69 revision=post_revision, f_path=f_path))
70 70
71 71 c.branch = request.GET.get('branch', None)
72 72
73 73 c.f_path = f_path
74 74
75 75 c.changeset = c.repo.get_changeset(revision)
76 76 cur_rev = c.changeset.revision
77 77
78 78 #prev link
79 79 try:
80 80 prev_rev = c.repo.get_changeset(cur_rev).prev(c.branch).raw_id
81 81 c.url_prev = url('files_home', repo_name=c.repo_name,
82 82 revision=prev_rev, f_path=f_path)
83 83 if c.branch:
84 84 c.url_prev += '?branch=%s' % c.branch
85 85 except ChangesetDoesNotExistError:
86 86 c.url_prev = '#'
87 87
88 88 #next link
89 89 try:
90 90 next_rev = c.repo.get_changeset(cur_rev).next(c.branch).raw_id
91 91 c.url_next = url('files_home', repo_name=c.repo_name,
92 92 revision=next_rev, f_path=f_path)
93 93 if c.branch:
94 94 c.url_next += '?branch=%s' % c.branch
95 95 except ChangesetDoesNotExistError:
96 96 c.url_next = '#'
97 97
98 98 #files
99 99 try:
100 100 c.files_list = c.changeset.get_node(f_path)
101 101 c.file_history = self._get_history(c.repo, c.files_list, f_path)
102 102 except RepositoryError, e:
103 103 h.flash(str(e), category='warning')
104 104 redirect(h.url('files_home', repo_name=repo_name, revision=revision))
105 105
106 106 except EmptyRepositoryError, e:
107 107 h.flash(_('There are no files yet'), category='warning')
108 108 redirect(h.url('summary_home', repo_name=repo_name))
109 109
110 110 except RepositoryError, e:
111 111 h.flash(str(e), category='warning')
112 112 redirect(h.url('files_home', repo_name=repo_name, revision='tip'))
113 113
114 114
115 115
116 116 return render('files/files.html')
117 117
118 118 def rawfile(self, repo_name, revision, f_path):
119 119 hg_model = ScmModel()
120 120 c.repo = hg_model.get_repo(c.repo_name)
121 121 file_node = c.repo.get_changeset(revision).get_node(f_path)
122 122 response.content_type = file_node.mimetype
123 123 response.content_disposition = 'attachment; filename=%s' \
124 124 % f_path.split('/')[-1]
125 125 return file_node.content
126 126
127 127 def raw(self, repo_name, revision, f_path):
128 128 hg_model = ScmModel()
129 129 c.repo = hg_model.get_repo(c.repo_name)
130 130 file_node = c.repo.get_changeset(revision).get_node(f_path)
131 131 response.content_type = 'text/plain'
132 132
133 133 return file_node.content
134 134
135 135 def annotate(self, repo_name, revision, f_path):
136 136 hg_model = ScmModel()
137 137 c.repo = hg_model.get_repo(c.repo_name)
138 138
139 139 try:
140 140 c.cs = c.repo.get_changeset(revision)
141 141 c.file = c.cs.get_node(f_path)
142 142 except RepositoryError, e:
143 143 h.flash(str(e), category='warning')
144 144 redirect(h.url('files_home', repo_name=repo_name, revision=revision))
145 145
146 146 c.file_history = self._get_history(c.repo, c.file, f_path)
147 147
148 148 c.f_path = f_path
149 149
150 150 return render('files/files_annotate.html')
151 151
152 152 def archivefile(self, repo_name, fname):
153 153
154 154 fileformat = None
155 155 revision = None
156 156 ext = None
157 157
158 158 for a_type, ext_data in ARCHIVE_SPECS.items():
159 159 archive_spec = fname.split(ext_data[1])
160 160 if len(archive_spec) == 2 and archive_spec[1] == '':
161 161 fileformat = a_type or ext_data[1]
162 162 revision = archive_spec[0]
163 163 ext = ext_data[1]
164 164
165 165 try:
166 166 repo = ScmModel().get_repo(repo_name)
167
168 if repo.dbrepo.enable_downloads is False:
169 return _('downloads disabled')
170
167 171 cs = repo.get_changeset(revision)
168 172 content_type = ARCHIVE_SPECS[fileformat][0]
169 173 except ChangesetDoesNotExistError:
170 174 return _('Unknown revision %s') % revision
171 175 except EmptyRepositoryError:
172 176 return _('Empty repository')
173 177 except (ImproperArchiveTypeError, KeyError):
174 178 return _('Unknown archive type')
175 179
176 180 response.content_type = content_type
177 181 response.content_disposition = 'attachment; filename=%s-%s%s' \
178 182 % (repo_name, revision, ext)
179 183
180 184 return cs.get_chunked_archive(kind=fileformat)
181 185
182 186
183 187 def diff(self, repo_name, f_path):
184 188 hg_model = ScmModel()
185 189 diff1 = request.GET.get('diff1')
186 190 diff2 = request.GET.get('diff2')
187 191 c.action = request.GET.get('diff')
188 192 c.no_changes = diff1 == diff2
189 193 c.f_path = f_path
190 194 c.repo = hg_model.get_repo(c.repo_name)
191 195
192 196 try:
193 197 if diff1 not in ['', None, 'None', '0' * 12, '0' * 40]:
194 198 c.changeset_1 = c.repo.get_changeset(diff1)
195 199 node1 = c.changeset_1.get_node(f_path)
196 200 else:
197 201 c.changeset_1 = EmptyChangeset()
198 202 node1 = FileNode('.', '', changeset=c.changeset_1)
199 203
200 204 if diff2 not in ['', None, 'None', '0' * 12, '0' * 40]:
201 205 c.changeset_2 = c.repo.get_changeset(diff2)
202 206 node2 = c.changeset_2.get_node(f_path)
203 207 else:
204 208 c.changeset_2 = EmptyChangeset()
205 209 node2 = FileNode('.', '', changeset=c.changeset_2)
206 210 except RepositoryError:
207 211 return redirect(url('files_home',
208 212 repo_name=c.repo_name, f_path=f_path))
209 213
210 214 f_udiff = differ.get_udiff(node1, node2)
211 215 diff = differ.DiffProcessor(f_udiff)
212 216
213 217 if c.action == 'download':
214 218 diff_name = '%s_vs_%s.diff' % (diff1, diff2)
215 219 response.content_type = 'text/plain'
216 220 response.content_disposition = 'attachment; filename=%s' \
217 221 % diff_name
218 222 return diff.raw_diff()
219 223
220 224 elif c.action == 'raw':
221 225 response.content_type = 'text/plain'
222 226 return diff.raw_diff()
223 227
224 228 elif c.action == 'diff':
225 229 if node1.size > self.cut_off_limit or node2.size > self.cut_off_limit:
226 230 c.cur_diff = _('Diff is to big to display')
227 231 else:
228 232 c.cur_diff = diff.as_html()
229 233 else:
230 234 #default option
231 235 if node1.size > self.cut_off_limit or node2.size > self.cut_off_limit:
232 236 c.cur_diff = _('Diff is to big to display')
233 237 else:
234 238 c.cur_diff = diff.as_html()
235 239
236 240 if not c.cur_diff: c.no_changes = True
237 241 return render('files/file_diff.html')
238 242
239 243 def _get_history(self, repo, node, f_path):
240 244 from vcs.nodes import NodeKind
241 245 if not node.kind is NodeKind.FILE:
242 246 return []
243 247 changesets = node.history
244 248 hist_l = []
245 249
246 250 changesets_group = ([], _("Changesets"))
247 251 branches_group = ([], _("Branches"))
248 252 tags_group = ([], _("Tags"))
249 253
250 254 for chs in changesets:
251 255 n_desc = 'r%s:%s' % (chs.revision, chs.short_id)
252 256 changesets_group[0].append((chs.raw_id, n_desc,))
253 257
254 258 hist_l.append(changesets_group)
255 259
256 260 for name, chs in c.repository_branches.items():
257 261 #chs = chs.split(':')[-1]
258 262 branches_group[0].append((chs, name),)
259 263 hist_l.append(branches_group)
260 264
261 265 for name, chs in c.repository_tags.items():
262 266 #chs = chs.split(':')[-1]
263 267 tags_group[0].append((chs, name),)
264 268 hist_l.append(tags_group)
265 269
266 270 return hist_l
267 271
@@ -1,168 +1,170 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.controllers.summary
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 Summary controller for Rhodecode
7 7
8 8 :created_on: Apr 18, 2010
9 9 :author: marcink
10 10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software; you can redistribute it and/or
14 14 # modify it under the terms of the GNU General Public License
15 15 # as published by the Free Software Foundation; version 2
16 16 # of the License or (at your opinion) any later version of the license.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 24 # along with this program; if not, write to the Free Software
25 25 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
26 26 # MA 02110-1301, USA.
27 27
28 28 import calendar
29 29 import logging
30 30 from time import mktime
31 31 from datetime import datetime, timedelta, date
32 32
33 33 from vcs.exceptions import ChangesetError
34 34
35 35 from pylons import tmpl_context as c, request, url
36 36 from pylons.i18n.translation import _
37 37
38 38 from rhodecode.model.scm import ScmModel
39 39 from rhodecode.model.db import Statistics
40 40
41 41 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
42 42 from rhodecode.lib.base import BaseController, render
43 43 from rhodecode.lib.utils import OrderedDict, EmptyChangeset
44 44
45 45 from rhodecode.lib.celerylib import run_task
46 46 from rhodecode.lib.celerylib.tasks import get_commits_stats
47 47
48 48 from webhelpers.paginate import Page
49 49
50 50 try:
51 51 import json
52 52 except ImportError:
53 53 #python 2.5 compatibility
54 54 import simplejson as json
55 55 log = logging.getLogger(__name__)
56 56
57 57 class SummaryController(BaseController):
58 58
59 59 @LoginRequired()
60 60 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
61 61 'repository.admin')
62 62 def __before__(self):
63 63 super(SummaryController, self).__before__()
64 64
65 65 def index(self):
66 66 scm_model = ScmModel()
67 67 c.repo_info = scm_model.get_repo(c.repo_name)
68 68 c.following = scm_model.is_following_repo(c.repo_name,
69 69 c.rhodecode_user.user_id)
70 70 def url_generator(**kw):
71 71 return url('shortlog_home', repo_name=c.repo_name, **kw)
72 72
73 73 c.repo_changesets = Page(c.repo_info, page=1, items_per_page=10,
74 74 url=url_generator)
75 75
76 76 e = request.environ
77 77
78 78 if self.rhodecode_user.username == 'default':
79 79 #for default(anonymous) user we don't need to pass credentials
80 80 username = ''
81 81 password = ''
82 82 else:
83 83 username = str(c.rhodecode_user.username)
84 84 password = '@'
85 85
86 86 uri = u'%(protocol)s://%(user)s%(password)s%(host)s%(prefix)s/%(repo_name)s' % {
87 87 'protocol': e.get('wsgi.url_scheme'),
88 88 'user':username,
89 89 'password':password,
90 90 'host':e.get('HTTP_HOST'),
91 91 'prefix':e.get('SCRIPT_NAME'),
92 92 'repo_name':c.repo_name, }
93 93 c.clone_repo_url = uri
94 94 c.repo_tags = OrderedDict()
95 95 for name, hash in c.repo_info.tags.items()[:10]:
96 96 try:
97 97 c.repo_tags[name] = c.repo_info.get_changeset(hash)
98 98 except ChangesetError:
99 99 c.repo_tags[name] = EmptyChangeset(hash)
100 100
101 101 c.repo_branches = OrderedDict()
102 102 for name, hash in c.repo_info.branches.items()[:10]:
103 103 try:
104 104 c.repo_branches[name] = c.repo_info.get_changeset(hash)
105 105 except ChangesetError:
106 106 c.repo_branches[name] = EmptyChangeset(hash)
107 107
108 108 td = date.today() + timedelta(days=1)
109 109 td_1m = td - timedelta(days=calendar.mdays[td.month])
110 110 td_1y = td - timedelta(days=365)
111 111
112 112 ts_min_m = mktime(td_1m.timetuple())
113 113 ts_min_y = mktime(td_1y.timetuple())
114 114 ts_max_y = mktime(td.timetuple())
115 115
116 116 if c.repo_info.dbrepo.enable_statistics:
117 117 c.no_data_msg = _('No data loaded yet')
118 118 run_task(get_commits_stats, c.repo_info.name, ts_min_y, ts_max_y)
119 119 else:
120 120 c.no_data_msg = _('Statistics update are disabled for this repository')
121 121 c.ts_min = ts_min_m
122 122 c.ts_max = ts_max_y
123 123
124 124 stats = self.sa.query(Statistics)\
125 125 .filter(Statistics.repository == c.repo_info.dbrepo)\
126 126 .scalar()
127 127
128 128
129 129 if stats and stats.languages:
130 130 c.no_data = False is c.repo_info.dbrepo.enable_statistics
131 131 lang_stats = json.loads(stats.languages)
132 132 c.commit_data = stats.commit_activity
133 133 c.overview_data = stats.commit_activity_combined
134 134 c.trending_languages = json.dumps(OrderedDict(
135 135 sorted(lang_stats.items(), reverse=True,
136 136 key=lambda k: k[1])[:10]
137 137 )
138 138 )
139 139 else:
140 140 c.commit_data = json.dumps({})
141 141 c.overview_data = json.dumps([[ts_min_y, 0], [ts_max_y, 10] ])
142 142 c.trending_languages = json.dumps({})
143 143 c.no_data = True
144 144
145 c.enable_downloads = c.repo_info.dbrepo.enable_downloads
146 if c.enable_downloads:
145 147 c.download_options = self._get_download_links(c.repo_info)
146 148
147 149 return render('summary/summary.html')
148 150
149 151
150 152
151 153 def _get_download_links(self, repo):
152 154
153 155 download_l = []
154 156
155 157 branches_group = ([], _("Branches"))
156 158 tags_group = ([], _("Tags"))
157 159
158 160 for name, chs in c.repository_branches.items():
159 161 #chs = chs.split(':')[-1]
160 162 branches_group[0].append((chs, name),)
161 163 download_l.append(branches_group)
162 164
163 165 for name, chs in c.repository_tags.items():
164 166 #chs = chs.split(':')[-1]
165 167 tags_group[0].append((chs, name),)
166 168 download_l.append(tags_group)
167 169
168 170 return download_l
@@ -1,330 +1,331 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.model.db
4 4 ~~~~~~~~~~~~~~~~~~
5 5
6 6 Database Models for RhodeCode
7 7
8 8 :created_on: Apr 08, 2010
9 9 :author: marcink
10 10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software; you can redistribute it and/or
14 14 # modify it under the terms of the GNU General Public License
15 15 # as published by the Free Software Foundation; version 2
16 16 # of the License or (at your opinion) any later version of the license.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 24 # along with this program; if not, write to the Free Software
25 25 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
26 26 # MA 02110-1301, USA.
27 27 import logging
28 28 import datetime
29 29
30 30 from sqlalchemy import *
31 31 from sqlalchemy.exc import DatabaseError
32 32 from sqlalchemy.orm import relation, backref, class_mapper
33 33 from sqlalchemy.orm.session import Session
34 34
35 35 from rhodecode.model.meta import Base
36 36
37 37 log = logging.getLogger(__name__)
38 38
39 39 class BaseModel(object):
40 40
41 41 @classmethod
42 42 def _get_keys(cls):
43 43 """return column names for this model """
44 44 return class_mapper(cls).c.keys()
45 45
46 46 def get_dict(self):
47 47 """return dict with keys and values corresponding
48 48 to this model data """
49 49
50 50 d = {}
51 51 for k in self._get_keys():
52 52 d[k] = getattr(self, k)
53 53 return d
54 54
55 55 def get_appstruct(self):
56 56 """return list with keys and values tupples corresponding
57 57 to this model data """
58 58
59 59 l = []
60 60 for k in self._get_keys():
61 61 l.append((k, getattr(self, k),))
62 62 return l
63 63
64 64 def populate_obj(self, populate_dict):
65 65 """populate model with data from given populate_dict"""
66 66
67 67 for k in self._get_keys():
68 68 if k in populate_dict:
69 69 setattr(self, k, populate_dict[k])
70 70
71 71 class RhodeCodeSettings(Base, BaseModel):
72 72 __tablename__ = 'rhodecode_settings'
73 73 __table_args__ = (UniqueConstraint('app_settings_name'), {'useexisting':True})
74 74 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
75 75 app_settings_name = Column("app_settings_name", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
76 76 app_settings_value = Column("app_settings_value", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
77 77
78 78 def __init__(self, k='', v=''):
79 79 self.app_settings_name = k
80 80 self.app_settings_value = v
81 81
82 82 def __repr__(self):
83 83 return "<%s('%s:%s')>" % (self.__class__.__name__,
84 84 self.app_settings_name, self.app_settings_value)
85 85
86 86 class RhodeCodeUi(Base, BaseModel):
87 87 __tablename__ = 'rhodecode_ui'
88 88 __table_args__ = {'useexisting':True}
89 89 ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
90 90 ui_section = Column("ui_section", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
91 91 ui_key = Column("ui_key", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
92 92 ui_value = Column("ui_value", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
93 93 ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True)
94 94
95 95
96 96 class User(Base, BaseModel):
97 97 __tablename__ = 'users'
98 98 __table_args__ = (UniqueConstraint('username'), UniqueConstraint('email'), {'useexisting':True})
99 99 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
100 100 username = Column("username", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
101 101 password = Column("password", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
102 102 active = Column("active", Boolean(), nullable=True, unique=None, default=None)
103 103 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
104 104 name = Column("name", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
105 105 lastname = Column("lastname", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
106 106 email = Column("email", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
107 107 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
108 108 is_ldap = Column("is_ldap", Boolean(), nullable=False, unique=None, default=False)
109 109
110 110 user_log = relation('UserLog', cascade='all')
111 111 user_perms = relation('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
112 112
113 113 repositories = relation('Repository')
114 114 user_followers = relation('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
115 115
116 116 @property
117 117 def full_contact(self):
118 118 return '%s %s <%s>' % (self.name, self.lastname, self.email)
119 119
120 120
121 121 @property
122 122 def is_admin(self):
123 123 return self.admin
124 124
125 125 def __repr__(self):
126 126 return "<%s('id:%s:%s')>" % (self.__class__.__name__,
127 127 self.user_id, self.username)
128 128
129 129 def update_lastlogin(self):
130 130 """Update user lastlogin"""
131 131
132 132 try:
133 133 session = Session.object_session(self)
134 134 self.last_login = datetime.datetime.now()
135 135 session.add(self)
136 136 session.commit()
137 137 log.debug('updated user %s lastlogin', self.username)
138 138 except (DatabaseError,):
139 139 session.rollback()
140 140
141 141
142 142 class UserLog(Base, BaseModel):
143 143 __tablename__ = 'user_logs'
144 144 __table_args__ = {'useexisting':True}
145 145 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
146 146 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
147 147 repository_id = Column("repository_id", Integer(length=None, convert_unicode=False, assert_unicode=None), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
148 148 repository_name = Column("repository_name", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
149 149 user_ip = Column("user_ip", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
150 150 action = Column("action", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
151 151 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
152 152
153 153 user = relation('User')
154 154 repository = relation('Repository')
155 155
156 156
157 157 class UsersGroup(Base, BaseModel):
158 158 __tablename__ = 'users_groups'
159 159 __table_args__ = {'useexisting':True}
160 160
161 161 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
162 162 users_group_name = Column("users_group_name", String(length=None, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
163 163 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
164 164
165 165 members = relation('UsersGroupMember')
166 166
167 167 class UsersGroupMember(Base, BaseModel):
168 168 __tablename__ = 'users_groups_members'
169 169 __table_args__ = {'useexisting':True}
170 170
171 171 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
172 172 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
173 173 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
174 174
175 175 user = relation('User')
176 176 users_group = relation('UsersGroup')
177 177
178 178 class Repository(Base, BaseModel):
179 179 __tablename__ = 'repositories'
180 180 __table_args__ = (UniqueConstraint('repo_name'), {'useexisting':True},)
181 181 repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
182 182 repo_name = Column("repo_name", String(length=None, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
183 183 repo_type = Column("repo_type", String(length=None, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default='hg')
184 184 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
185 185 private = Column("private", Boolean(), nullable=True, unique=None, default=None)
186 186 enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True)
187 enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True)
187 188 description = Column("description", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
188 189 fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None)
189 190 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None)
190 191
191 192 user = relation('User')
192 193 fork = relation('Repository', remote_side=repo_id)
193 194 group = relation('Group')
194 195 repo_to_perm = relation('RepoToPerm', cascade='all')
195 196 stats = relation('Statistics', cascade='all', uselist=False)
196 197
197 198 repo_followers = relation('UserFollowing', primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id', cascade='all')
198 199
199 200 def __repr__(self):
200 201 return "<%s('%s:%s')>" % (self.__class__.__name__,
201 202 self.repo_id, self.repo_name)
202 203
203 204 class Group(Base, BaseModel):
204 205 __tablename__ = 'groups'
205 206 __table_args__ = (UniqueConstraint('group_name'), {'useexisting':True},)
206 207
207 208 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
208 209 group_name = Column("group_name", String(length=None, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
209 210 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
210 211
211 212 parent_group = relation('Group', remote_side=group_id)
212 213
213 214
214 215 def __init__(self, group_name='', parent_group=None):
215 216 self.group_name = group_name
216 217 self.parent_group = parent_group
217 218
218 219 def __repr__(self):
219 220 return "<%s('%s:%s')>" % (self.__class__.__name__, self.group_id,
220 221 self.group_name)
221 222
222 223 class Permission(Base, BaseModel):
223 224 __tablename__ = 'permissions'
224 225 __table_args__ = {'useexisting':True}
225 226 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
226 227 permission_name = Column("permission_name", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
227 228 permission_longname = Column("permission_longname", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
228 229
229 230 def __repr__(self):
230 231 return "<%s('%s:%s')>" % (self.__class__.__name__,
231 232 self.permission_id, self.permission_name)
232 233
233 234 class RepoToPerm(Base, BaseModel):
234 235 __tablename__ = 'repo_to_perm'
235 236 __table_args__ = (UniqueConstraint('user_id', 'repository_id'), {'useexisting':True})
236 237 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
237 238 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
238 239 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
239 240 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
240 241
241 242 user = relation('User')
242 243 permission = relation('Permission')
243 244 repository = relation('Repository')
244 245
245 246 class UserToPerm(Base, BaseModel):
246 247 __tablename__ = 'user_to_perm'
247 248 __table_args__ = (UniqueConstraint('user_id', 'permission_id'), {'useexisting':True})
248 249 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
249 250 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
250 251 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
251 252
252 253 user = relation('User')
253 254 permission = relation('Permission')
254 255
255 256 class UsersGroupToPerm(Base, BaseModel):
256 257 __tablename__ = 'users_group_to_perm'
257 258 __table_args__ = (UniqueConstraint('users_group_id', 'permission_id'), {'useexisting':True})
258 259 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
259 260 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
260 261 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
261 262
262 263 users_group = relation('UsersGroup')
263 264 permission = relation('Permission')
264 265
265 266 class GroupToPerm(Base, BaseModel):
266 267 __tablename__ = 'group_to_perm'
267 268 __table_args__ = (UniqueConstraint('group_id', 'permission_id'), {'useexisting':True})
268 269
269 270 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
270 271 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
271 272 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
272 273 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
273 274
274 275 user = relation('User')
275 276 permission = relation('Permission')
276 277 group = relation('Group')
277 278
278 279 class Statistics(Base, BaseModel):
279 280 __tablename__ = 'statistics'
280 281 __table_args__ = (UniqueConstraint('repository_id'), {'useexisting':True})
281 282 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
282 283 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
283 284 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
284 285 commit_activity = Column("commit_activity", LargeBinary(), nullable=False)#JSON data
285 286 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
286 287 languages = Column("languages", LargeBinary(), nullable=False)#JSON data
287 288
288 289 repository = relation('Repository', single_parent=True)
289 290
290 291 class UserFollowing(Base, BaseModel):
291 292 __tablename__ = 'user_followings'
292 293 __table_args__ = (UniqueConstraint('user_id', 'follows_repository_id'),
293 294 UniqueConstraint('user_id', 'follows_user_id')
294 295 , {'useexisting':True})
295 296
296 297 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
297 298 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
298 299 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
299 300 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
300 301
301 302 user = relation('User', primaryjoin='User.user_id==UserFollowing.user_id')
302 303
303 304 follows_user = relation('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
304 305 follows_repository = relation('Repository')
305 306
306 307 class CacheInvalidation(Base, BaseModel):
307 308 __tablename__ = 'cache_invalidation'
308 309 __table_args__ = (UniqueConstraint('cache_key'), {'useexisting':True})
309 310 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
310 311 cache_key = Column("cache_key", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
311 312 cache_args = Column("cache_args", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
312 313 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
313 314
314 315
315 316 def __init__(self, cache_key, cache_args=''):
316 317 self.cache_key = cache_key
317 318 self.cache_args = cache_args
318 319 self.cache_active = False
319 320
320 321 def __repr__(self):
321 322 return "<%s('%s:%s')>" % (self.__class__.__name__,
322 323 self.cache_id, self.cache_key)
323 324
324 325 class DbMigrateVersion(Base, BaseModel):
325 326 __tablename__ = 'db_migrate_version'
326 327 __table_args__ = {'useexisting':True}
327 328 repository_id = Column('repository_id', String(250), primary_key=True)
328 329 repository_path = Column('repository_path', Text)
329 330 version = Column('version', Integer)
330 331
@@ -1,530 +1,531 b''
1 1 """ this is forms validation classes
2 2 http://formencode.org/module-formencode.validators.html
3 3 for list off all availible validators
4 4
5 5 we can create our own validators
6 6
7 7 The table below outlines the options which can be used in a schema in addition to the validators themselves
8 8 pre_validators [] These validators will be applied before the schema
9 9 chained_validators [] These validators will be applied after the schema
10 10 allow_extra_fields False If True, then it is not an error when keys that aren't associated with a validator are present
11 11 filter_extra_fields False If True, then keys that aren't associated with a validator are removed
12 12 if_key_missing NoDefault If this is given, then any keys that aren't available but are expected will be replaced with this value (and then validated). This does not override a present .if_missing attribute on validators. NoDefault is a special FormEncode class to mean that no default values has been specified and therefore missing keys shouldn't take a default value.
13 13 ignore_key_missing False If True, then missing keys will be missing in the result, if the validator doesn't have .if_missing on it already
14 14
15 15
16 16 <name> = formencode.validators.<name of validator>
17 17 <name> must equal form name
18 18 list=[1,2,3,4,5]
19 19 for SELECT use formencode.All(OneOf(list), Int())
20 20
21 21 """
22 22 import os
23 23 import re
24 24 import logging
25 25
26 26 import formencode
27 27 from formencode import All
28 28 from formencode.validators import UnicodeString, OneOf, Int, Number, Regex, \
29 29 Email, Bool, StringBoolean
30 30
31 31 from pylons.i18n.translation import _
32 32
33 33 import rhodecode.lib.helpers as h
34 34 from rhodecode.lib.auth import authenticate, get_crypt_password
35 35 from rhodecode.lib.exceptions import LdapImportError
36 36 from rhodecode.model import meta
37 37 from rhodecode.model.user import UserModel
38 38 from rhodecode.model.repo import RepoModel
39 39 from rhodecode.model.users_group import UsersGroupModel
40 40 from rhodecode.model.db import User
41 41 from rhodecode import BACKENDS
42 42
43 43 from webhelpers.pylonslib.secure_form import authentication_token
44 44
45 45 log = logging.getLogger(__name__)
46 46
47 47 #this is needed to translate the messages using _() in validators
48 48 class State_obj(object):
49 49 _ = staticmethod(_)
50 50
51 51 #===============================================================================
52 52 # VALIDATORS
53 53 #===============================================================================
54 54 class ValidAuthToken(formencode.validators.FancyValidator):
55 55 messages = {'invalid_token':_('Token mismatch')}
56 56
57 57 def validate_python(self, value, state):
58 58
59 59 if value != authentication_token():
60 60 raise formencode.Invalid(self.message('invalid_token', state,
61 61 search_number=value), value, state)
62 62
63 63 def ValidUsername(edit, old_data):
64 64 class _ValidUsername(formencode.validators.FancyValidator):
65 65
66 66 def validate_python(self, value, state):
67 67 if value in ['default', 'new_user']:
68 68 raise formencode.Invalid(_('Invalid username'), value, state)
69 69 #check if user is unique
70 70 old_un = None
71 71 if edit:
72 72 old_un = UserModel().get(old_data.get('user_id')).username
73 73
74 74 if old_un != value or not edit:
75 75 if UserModel().get_by_username(value, cache=False,
76 76 case_insensitive=True):
77 77 raise formencode.Invalid(_('This username already exists') ,
78 78 value, state)
79 79
80 80
81 81 if re.match(r'^[a-zA-Z0-9]{1}[a-zA-Z0-9\-\_\.]+$', value) is None:
82 82 raise formencode.Invalid(_('Username may only contain '
83 83 'alphanumeric characters underscores, '
84 84 'periods or dashes and must begin with '
85 85 'alphanumeric character'),
86 86 value, state)
87 87
88 88
89 89
90 90 return _ValidUsername
91 91
92 92
93 93
94 94 def ValidUsersGroup(edit, old_data):
95 95
96 96 class _ValidUsersGroup(formencode.validators.FancyValidator):
97 97
98 98 def validate_python(self, value, state):
99 99 if value in ['default']:
100 100 raise formencode.Invalid(_('Invalid group name'), value, state)
101 101 #check if group is unique
102 102 old_un = None
103 103 if edit:
104 104 old_un = UserModel().get(old_data.get('users_group_id')).username
105 105
106 106 if old_un != value or not edit:
107 107 if UsersGroupModel().get_by_groupname(value, cache=False,
108 108 case_insensitive=True):
109 109 raise formencode.Invalid(_('This users group already exists') ,
110 110 value, state)
111 111
112 112
113 113 if re.match(r'^[a-zA-Z0-9]{1}[a-zA-Z0-9\-\_\.]+$', value) is None:
114 114 raise formencode.Invalid(_('Group name may only contain '
115 115 'alphanumeric characters underscores, '
116 116 'periods or dashes and must begin with '
117 117 'alphanumeric character'),
118 118 value, state)
119 119
120 120 return _ValidUsersGroup
121 121
122 122
123 123
124 124 class ValidPassword(formencode.validators.FancyValidator):
125 125
126 126 def to_python(self, value, state):
127 127
128 128 if value:
129 129
130 130 if value.get('password'):
131 131 try:
132 132 value['password'] = get_crypt_password(value['password'])
133 133 except UnicodeEncodeError:
134 134 e_dict = {'password':_('Invalid characters in password')}
135 135 raise formencode.Invalid('', value, state, error_dict=e_dict)
136 136
137 137 if value.get('password_confirmation'):
138 138 try:
139 139 value['password_confirmation'] = \
140 140 get_crypt_password(value['password_confirmation'])
141 141 except UnicodeEncodeError:
142 142 e_dict = {'password_confirmation':_('Invalid characters in password')}
143 143 raise formencode.Invalid('', value, state, error_dict=e_dict)
144 144
145 145 if value.get('new_password'):
146 146 try:
147 147 value['new_password'] = \
148 148 get_crypt_password(value['new_password'])
149 149 except UnicodeEncodeError:
150 150 e_dict = {'new_password':_('Invalid characters in password')}
151 151 raise formencode.Invalid('', value, state, error_dict=e_dict)
152 152
153 153 return value
154 154
155 155 class ValidPasswordsMatch(formencode.validators.FancyValidator):
156 156
157 157 def validate_python(self, value, state):
158 158
159 159 if value['password'] != value['password_confirmation']:
160 160 e_dict = {'password_confirmation':
161 161 _('Password do not match')}
162 162 raise formencode.Invalid('', value, state, error_dict=e_dict)
163 163
164 164 class ValidAuth(formencode.validators.FancyValidator):
165 165 messages = {
166 166 'invalid_password':_('invalid password'),
167 167 'invalid_login':_('invalid user name'),
168 168 'disabled_account':_('Your account is disabled')
169 169
170 170 }
171 171 #error mapping
172 172 e_dict = {'username':messages['invalid_login'],
173 173 'password':messages['invalid_password']}
174 174 e_dict_disable = {'username':messages['disabled_account']}
175 175
176 176 def validate_python(self, value, state):
177 177 password = value['password']
178 178 username = value['username']
179 179 user = UserModel().get_by_username(username)
180 180
181 181 if authenticate(username, password):
182 182 return value
183 183 else:
184 184 if user and user.active is False:
185 185 log.warning('user %s is disabled', username)
186 186 raise formencode.Invalid(self.message('disabled_account',
187 187 state=State_obj),
188 188 value, state,
189 189 error_dict=self.e_dict_disable)
190 190 else:
191 191 log.warning('user %s not authenticated', username)
192 192 raise formencode.Invalid(self.message('invalid_password',
193 193 state=State_obj), value, state,
194 194 error_dict=self.e_dict)
195 195
196 196 class ValidRepoUser(formencode.validators.FancyValidator):
197 197
198 198 def to_python(self, value, state):
199 199 sa = meta.Session()
200 200 try:
201 201 self.user_db = sa.query(User)\
202 202 .filter(User.active == True)\
203 203 .filter(User.username == value).one()
204 204 except Exception:
205 205 raise formencode.Invalid(_('This username is not valid'),
206 206 value, state)
207 207 finally:
208 208 meta.Session.remove()
209 209
210 210 return self.user_db.user_id
211 211
212 212 def ValidRepoName(edit, old_data):
213 213 class _ValidRepoName(formencode.validators.FancyValidator):
214 214
215 215 def to_python(self, value, state):
216 216 slug = h.repo_name_slug(value)
217 217 if slug in ['_admin']:
218 218 raise formencode.Invalid(_('This repository name is disallowed'),
219 219 value, state)
220 220 if old_data.get('repo_name') != value or not edit:
221 221 if RepoModel().get_by_repo_name(slug, cache=False):
222 222 raise formencode.Invalid(_('This repository already exists') ,
223 223 value, state)
224 224 return slug
225 225
226 226
227 227 return _ValidRepoName
228 228
229 229 def ValidForkType(old_data):
230 230 class _ValidForkType(formencode.validators.FancyValidator):
231 231
232 232 def to_python(self, value, state):
233 233 if old_data['repo_type'] != value:
234 234 raise formencode.Invalid(_('Fork have to be the same type as original'),
235 235 value, state)
236 236 return value
237 237 return _ValidForkType
238 238
239 239 class ValidPerms(formencode.validators.FancyValidator):
240 240 messages = {'perm_new_user_name':_('This username is not valid')}
241 241
242 242 def to_python(self, value, state):
243 243 perms_update = []
244 244 perms_new = []
245 245 #build a list of permission to update and new permission to create
246 246 for k, v in value.items():
247 247 if k.startswith('perm_'):
248 248 if k.startswith('perm_new_user'):
249 249 new_perm = value.get('perm_new_user', False)
250 250 new_user = value.get('perm_new_user_name', False)
251 251 if new_user and new_perm:
252 252 if (new_user, new_perm) not in perms_new:
253 253 perms_new.append((new_user, new_perm))
254 254 else:
255 255 usr = k[5:]
256 256 if usr == 'default':
257 257 if value['private']:
258 258 #set none for default when updating to private repo
259 259 v = 'repository.none'
260 260 perms_update.append((usr, v))
261 261 value['perms_updates'] = perms_update
262 262 value['perms_new'] = perms_new
263 263 sa = meta.Session
264 264 for k, v in perms_new:
265 265 try:
266 266 self.user_db = sa.query(User)\
267 267 .filter(User.active == True)\
268 268 .filter(User.username == k).one()
269 269 except Exception:
270 270 msg = self.message('perm_new_user_name',
271 271 state=State_obj)
272 272 raise formencode.Invalid(msg, value, state,
273 273 error_dict={'perm_new_user_name':msg})
274 274 return value
275 275
276 276 class ValidSettings(formencode.validators.FancyValidator):
277 277
278 278 def to_python(self, value, state):
279 279 #settings form can't edit user
280 280 if value.has_key('user'):
281 281 del['value']['user']
282 282
283 283 return value
284 284
285 285 class ValidPath(formencode.validators.FancyValidator):
286 286 def to_python(self, value, state):
287 287
288 288 if not os.path.isdir(value):
289 289 msg = _('This is not a valid path')
290 290 raise formencode.Invalid(msg, value, state,
291 291 error_dict={'paths_root_path':msg})
292 292 return value
293 293
294 294 def UniqSystemEmail(old_data):
295 295 class _UniqSystemEmail(formencode.validators.FancyValidator):
296 296 def to_python(self, value, state):
297 297 value = value.lower()
298 298 if old_data.get('email') != value:
299 299 sa = meta.Session()
300 300 try:
301 301 user = sa.query(User).filter(User.email == value).scalar()
302 302 if user:
303 303 raise formencode.Invalid(_("This e-mail address is already taken") ,
304 304 value, state)
305 305 finally:
306 306 meta.Session.remove()
307 307
308 308 return value
309 309
310 310 return _UniqSystemEmail
311 311
312 312 class ValidSystemEmail(formencode.validators.FancyValidator):
313 313 def to_python(self, value, state):
314 314 value = value.lower()
315 315 sa = meta.Session
316 316 try:
317 317 user = sa.query(User).filter(User.email == value).scalar()
318 318 if user is None:
319 319 raise formencode.Invalid(_("This e-mail address doesn't exist.") ,
320 320 value, state)
321 321 finally:
322 322 meta.Session.remove()
323 323
324 324 return value
325 325
326 326 class LdapLibValidator(formencode.validators.FancyValidator):
327 327
328 328 def to_python(self, value, state):
329 329
330 330 try:
331 331 import ldap
332 332 except ImportError:
333 333 raise LdapImportError
334 334 return value
335 335
336 336 class BaseDnValidator(formencode.validators.FancyValidator):
337 337
338 338 def to_python(self, value, state):
339 339
340 340 try:
341 341 value % {'user':'valid'}
342 342
343 343 if value.find('%(user)s') == -1:
344 344 raise formencode.Invalid(_("You need to specify %(user)s in "
345 345 "template for example uid=%(user)s "
346 346 ",dc=company...") ,
347 347 value, state)
348 348
349 349 except KeyError:
350 350 raise formencode.Invalid(_("Wrong template used, only %(user)s "
351 351 "is an valid entry") ,
352 352 value, state)
353 353
354 354 return value
355 355
356 356 #===============================================================================
357 357 # FORMS
358 358 #===============================================================================
359 359 class LoginForm(formencode.Schema):
360 360 allow_extra_fields = True
361 361 filter_extra_fields = True
362 362 username = UnicodeString(
363 363 strip=True,
364 364 min=1,
365 365 not_empty=True,
366 366 messages={
367 367 'empty':_('Please enter a login'),
368 368 'tooShort':_('Enter a value %(min)i characters long or more')}
369 369 )
370 370
371 371 password = UnicodeString(
372 372 strip=True,
373 373 min=6,
374 374 not_empty=True,
375 375 messages={
376 376 'empty':_('Please enter a password'),
377 377 'tooShort':_('Enter %(min)i characters or more')}
378 378 )
379 379
380 380
381 381 #chained validators have access to all data
382 382 chained_validators = [ValidAuth]
383 383
384 384 def UserForm(edit=False, old_data={}):
385 385 class _UserForm(formencode.Schema):
386 386 allow_extra_fields = True
387 387 filter_extra_fields = True
388 388 username = All(UnicodeString(strip=True, min=1, not_empty=True),
389 389 ValidUsername(edit, old_data))
390 390 if edit:
391 391 new_password = All(UnicodeString(strip=True, min=6, not_empty=False))
392 392 admin = StringBoolean(if_missing=False)
393 393 else:
394 394 password = All(UnicodeString(strip=True, min=6, not_empty=True))
395 395 active = StringBoolean(if_missing=False)
396 396 name = UnicodeString(strip=True, min=1, not_empty=True)
397 397 lastname = UnicodeString(strip=True, min=1, not_empty=True)
398 398 email = All(Email(not_empty=True), UniqSystemEmail(old_data))
399 399
400 400 chained_validators = [ValidPassword]
401 401
402 402 return _UserForm
403 403
404 404
405 405 def UsersGroupForm(edit=False, old_data={}):
406 406 class _UsersGroupForm(formencode.Schema):
407 407 allow_extra_fields = True
408 408 filter_extra_fields = True
409 409
410 410 users_group_name = All(UnicodeString(strip=True, min=1, not_empty=True),
411 411 ValidUsersGroup(edit, old_data))
412 412
413 413 users_group_active = StringBoolean(if_missing=False)
414 414
415 415 return _UsersGroupForm
416 416
417 417 def RegisterForm(edit=False, old_data={}):
418 418 class _RegisterForm(formencode.Schema):
419 419 allow_extra_fields = True
420 420 filter_extra_fields = True
421 421 username = All(ValidUsername(edit, old_data),
422 422 UnicodeString(strip=True, min=1, not_empty=True))
423 423 password = All(UnicodeString(strip=True, min=6, not_empty=True))
424 424 password_confirmation = All(UnicodeString(strip=True, min=6, not_empty=True))
425 425 active = StringBoolean(if_missing=False)
426 426 name = UnicodeString(strip=True, min=1, not_empty=True)
427 427 lastname = UnicodeString(strip=True, min=1, not_empty=True)
428 428 email = All(Email(not_empty=True), UniqSystemEmail(old_data))
429 429
430 430 chained_validators = [ValidPasswordsMatch, ValidPassword]
431 431
432 432 return _RegisterForm
433 433
434 434 def PasswordResetForm():
435 435 class _PasswordResetForm(formencode.Schema):
436 436 allow_extra_fields = True
437 437 filter_extra_fields = True
438 438 email = All(ValidSystemEmail(), Email(not_empty=True))
439 439 return _PasswordResetForm
440 440
441 441 def RepoForm(edit=False, old_data={}, supported_backends=BACKENDS.keys()):
442 442 class _RepoForm(formencode.Schema):
443 443 allow_extra_fields = True
444 444 filter_extra_fields = False
445 445 repo_name = All(UnicodeString(strip=True, min=1, not_empty=True),
446 446 ValidRepoName(edit, old_data))
447 447 description = UnicodeString(strip=True, min=1, not_empty=True)
448 448 private = StringBoolean(if_missing=False)
449 449 enable_statistics = StringBoolean(if_missing=False)
450 enable_downloads = StringBoolean(if_missing=False)
450 451 repo_type = OneOf(supported_backends)
451 452 if edit:
452 453 user = All(Int(not_empty=True), ValidRepoUser)
453 454
454 455 chained_validators = [ValidPerms]
455 456 return _RepoForm
456 457
457 458 def RepoForkForm(edit=False, old_data={}, supported_backends=BACKENDS.keys()):
458 459 class _RepoForkForm(formencode.Schema):
459 460 allow_extra_fields = True
460 461 filter_extra_fields = False
461 462 fork_name = All(UnicodeString(strip=True, min=1, not_empty=True),
462 463 ValidRepoName(edit, old_data))
463 464 description = UnicodeString(strip=True, min=1, not_empty=True)
464 465 private = StringBoolean(if_missing=False)
465 466 repo_type = All(ValidForkType(old_data), OneOf(supported_backends))
466 467 return _RepoForkForm
467 468
468 469 def RepoSettingsForm(edit=False, old_data={}):
469 470 class _RepoForm(formencode.Schema):
470 471 allow_extra_fields = True
471 472 filter_extra_fields = False
472 473 repo_name = All(UnicodeString(strip=True, min=1, not_empty=True),
473 474 ValidRepoName(edit, old_data))
474 475 description = UnicodeString(strip=True, min=1, not_empty=True)
475 476 private = StringBoolean(if_missing=False)
476 477
477 478 chained_validators = [ValidPerms, ValidSettings]
478 479 return _RepoForm
479 480
480 481
481 482 def ApplicationSettingsForm():
482 483 class _ApplicationSettingsForm(formencode.Schema):
483 484 allow_extra_fields = True
484 485 filter_extra_fields = False
485 486 rhodecode_title = UnicodeString(strip=True, min=1, not_empty=True)
486 487 rhodecode_realm = UnicodeString(strip=True, min=1, not_empty=True)
487 488 rhodecode_ga_code = UnicodeString(strip=True, min=1, not_empty=False)
488 489
489 490 return _ApplicationSettingsForm
490 491
491 492 def ApplicationUiSettingsForm():
492 493 class _ApplicationUiSettingsForm(formencode.Schema):
493 494 allow_extra_fields = True
494 495 filter_extra_fields = False
495 496 web_push_ssl = OneOf(['true', 'false'], if_missing='false')
496 497 paths_root_path = All(ValidPath(), UnicodeString(strip=True, min=1, not_empty=True))
497 498 hooks_changegroup_update = OneOf(['True', 'False'], if_missing=False)
498 499 hooks_changegroup_repo_size = OneOf(['True', 'False'], if_missing=False)
499 500 hooks_pretxnchangegroup_push_logger = OneOf(['True', 'False'], if_missing=False)
500 501 hooks_preoutgoing_pull_logger = OneOf(['True', 'False'], if_missing=False)
501 502
502 503 return _ApplicationUiSettingsForm
503 504
504 505 def DefaultPermissionsForm(perms_choices, register_choices, create_choices):
505 506 class _DefaultPermissionsForm(formencode.Schema):
506 507 allow_extra_fields = True
507 508 filter_extra_fields = True
508 509 overwrite_default = StringBoolean(if_missing=False)
509 510 anonymous = OneOf(['True', 'False'], if_missing=False)
510 511 default_perm = OneOf(perms_choices)
511 512 default_register = OneOf(register_choices)
512 513 default_create = OneOf(create_choices)
513 514
514 515 return _DefaultPermissionsForm
515 516
516 517
517 518 def LdapSettingsForm():
518 519 class _LdapSettingsForm(formencode.Schema):
519 520 allow_extra_fields = True
520 521 filter_extra_fields = True
521 522 pre_validators = [LdapLibValidator]
522 523 ldap_active = StringBoolean(if_missing=False)
523 524 ldap_host = UnicodeString(strip=True,)
524 525 ldap_port = Number(strip=True,)
525 526 ldap_ldaps = StringBoolean(if_missing=False)
526 527 ldap_dn_user = UnicodeString(strip=True,)
527 528 ldap_dn_pass = UnicodeString(strip=True,)
528 529 ldap_base_dn = All(BaseDnValidator, UnicodeString(strip=True,))
529 530
530 531 return _LdapSettingsForm
@@ -1,336 +1,344 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <%inherit file="/base/base.html"/>
3 3
4 4 <%def name="title()">
5 5 ${_('Edit repository')} ${c.repo_info.repo_name} - ${c.rhodecode_name}
6 6 </%def>
7 7
8 8 <%def name="breadcrumbs_links()">
9 9 ${h.link_to(_('Admin'),h.url('admin_home'))}
10 10 &raquo;
11 11 ${h.link_to(_('Repositories'),h.url('repos'))}
12 12 &raquo;
13 13 ${_('edit')} "${c.repo_name}"
14 14 </%def>
15 15
16 16 <%def name="page_nav()">
17 17 ${self.menu('admin')}
18 18 </%def>
19 19
20 20 <%def name="main()">
21 21 <div class="box box-left">
22 22 <!-- box / title -->
23 23 <div class="title">
24 24 ${self.breadcrumbs()}
25 25 </div>
26 26 ${h.form(url('repo', repo_name=c.repo_info.repo_name),method='put')}
27 27 <div class="form">
28 28 <!-- fields -->
29 29 <div class="fields">
30 30 <div class="field">
31 31 <div class="label">
32 32 <label for="repo_name">${_('Name')}:</label>
33 33 </div>
34 34 <div class="input">
35 35 ${h.text('repo_name',class_="medium")}
36 36 </div>
37 37 </div>
38 38 <div class="field">
39 39 <div class="label">
40 40 <label for="repo_type">${_('Type')}:</label>
41 41 </div>
42 42 <div class="input">
43 43 ${h.select('repo_type','hg',c.backends,class_="medium")}
44 44 </div>
45 45 </div>
46 46 <div class="field">
47 47 <div class="label label-textarea">
48 48 <label for="description">${_('Description')}:</label>
49 49 </div>
50 50 <div class="textarea text-area editor">
51 51 ${h.textarea('description',cols=23,rows=5)}
52 52 </div>
53 53 </div>
54 54
55 55 <div class="field">
56 56 <div class="label label-checkbox">
57 57 <label for="private">${_('Private')}:</label>
58 58 </div>
59 59 <div class="checkboxes">
60 60 ${h.checkbox('private',value="True")}
61 61 </div>
62 62 </div>
63 63 <div class="field">
64 64 <div class="label label-checkbox">
65 65 <label for="enable_statistics">${_('Enable statistics')}:</label>
66 66 </div>
67 67 <div class="checkboxes">
68 68 ${h.checkbox('enable_statistics',value="True")}
69 69 </div>
70 70 </div>
71 71 <div class="field">
72 <div class="label label-checkbox">
73 <label for="enable_downloads">${_('Enable downloads')}:</label>
74 </div>
75 <div class="checkboxes">
76 ${h.checkbox('enable_downloads',value="True")}
77 </div>
78 </div>
79 <div class="field">
72 80 <div class="label">
73 81 <label for="user">${_('Owner')}:</label>
74 82 </div>
75 83 <div class="input input-small ac">
76 84 <div class="perm_ac">
77 85 ${h.text('user',class_='yui-ac-input')}
78 86 <div id="owner_container"></div>
79 87 </div>
80 88 </div>
81 89 </div>
82 90
83 91 <div class="field">
84 92 <div class="label">
85 93 <label for="input">${_('Permissions')}:</label>
86 94 </div>
87 95 <div class="input">
88 96 <table id="permissions_manage">
89 97 <tr>
90 98 <td>${_('none')}</td>
91 99 <td>${_('read')}</td>
92 100 <td>${_('write')}</td>
93 101 <td>${_('admin')}</td>
94 102 <td>${_('user')}</td>
95 103 <td></td>
96 104 </tr>
97 105
98 106 %for r2p in c.repo_info.repo_to_perm:
99 107 %if r2p.user.username =='default' and c.repo_info.private:
100 108 <tr>
101 109 <td colspan="4">
102 110 <span class="private_repo_msg">
103 111 ${_('private repository')}
104 112 </span>
105 113 </td>
106 114 <td class="private_repo_msg">${r2p.user.username}</td>
107 115 </tr>
108 116 %else:
109 117 <tr id="id${id(r2p.user.username)}">
110 118 <td>${h.radio('perm_%s' % r2p.user.username,'repository.none')}</td>
111 119 <td>${h.radio('perm_%s' % r2p.user.username,'repository.read')}</td>
112 120 <td>${h.radio('perm_%s' % r2p.user.username,'repository.write')}</td>
113 121 <td>${h.radio('perm_%s' % r2p.user.username,'repository.admin')}</td>
114 122 <td>${r2p.user.username}</td>
115 123 <td>
116 124 %if r2p.user.username !='default':
117 125 <span class="delete_icon action_button" onclick="ajaxAction(${r2p.user.user_id},'${'id%s'%id(r2p.user.username)}')">
118 126 <script type="text/javascript">
119 127 function ajaxAction(user_id,field_id){
120 128 var sUrl = "${h.url('delete_repo_user',repo_name=c.repo_name)}";
121 129 var callback = { success:function(o){
122 130 var tr = YAHOO.util.Dom.get(String(field_id));
123 131 tr.parentNode.removeChild(tr);},failure:function(o){
124 132 alert("${_('Failed to remove user')}");},};
125 133 var postData = '_method=delete&user_id='+user_id;
126 134 var request = YAHOO.util.Connect.asyncRequest('POST', sUrl, callback, postData);};
127 135 </script>
128 136 </span>
129 137 %endif
130 138 </td>
131 139 </tr>
132 140 %endif
133 141 %endfor
134 142
135 143 <tr id="add_perm_input">
136 144 <td>${h.radio('perm_new_user','repository.none')}</td>
137 145 <td>${h.radio('perm_new_user','repository.read')}</td>
138 146 <td>${h.radio('perm_new_user','repository.write')}</td>
139 147 <td>${h.radio('perm_new_user','repository.admin')}</td>
140 148 <td class='ac'>
141 149 <div class="perm_ac" id="perm_ac">
142 150 ${h.text('perm_new_user_name',class_='yui-ac-input')}
143 151 <div id="perm_container"></div>
144 152 </div>
145 153 </td>
146 154 <td></td>
147 155 </tr>
148 156 <tr>
149 157 <td colspan="6">
150 158 <span id="add_perm" class="add_icon" style="cursor: pointer;">
151 159 ${_('Add another user')}
152 160 </span>
153 161 </td>
154 162 </tr>
155 163 </table>
156 164 </div>
157 165
158 166 <div class="buttons">
159 167 ${h.submit('save','Save',class_="ui-button")}
160 168 ${h.reset('reset','Reset',class_="ui-button")}
161 169 </div>
162 170 </div>
163 171 </div>
164 172 </div>
165 173 ${h.end_form()}
166 174 <script type="text/javascript">
167 175 YAHOO.util.Event.onDOMReady(function(){
168 176 var D = YAHOO.util.Dom;
169 177 if(!D.hasClass('perm_new_user_name','error')){
170 178 D.setStyle('add_perm_input','display','none');
171 179 }
172 180 YAHOO.util.Event.addListener('add_perm','click',function(){
173 181 D.setStyle('add_perm_input','display','');
174 182 D.setStyle('add_perm','opacity','0.6');
175 183 D.setStyle('add_perm','cursor','default');
176 184 });
177 185 });
178 186 </script>
179 187 <script type="text/javascript">
180 188 YAHOO.example.FnMultipleFields = function(){
181 189 var myContacts = ${c.users_array|n}
182 190
183 191 // Define a custom search function for the DataSource
184 192 var matchNames = function(sQuery) {
185 193 // Case insensitive matching
186 194 var query = sQuery.toLowerCase(),
187 195 contact,
188 196 i=0,
189 197 l=myContacts.length,
190 198 matches = [];
191 199
192 200 // Match against each name of each contact
193 201 for(; i<l; i++) {
194 202 contact = myContacts[i];
195 203 if((contact.fname.toLowerCase().indexOf(query) > -1) ||
196 204 (contact.lname.toLowerCase().indexOf(query) > -1) ||
197 205 (contact.nname && (contact.nname.toLowerCase().indexOf(query) > -1))) {
198 206 matches[matches.length] = contact;
199 207 }
200 208 }
201 209
202 210 return matches;
203 211 };
204 212
205 213 // Use a FunctionDataSource
206 214 var oDS = new YAHOO.util.FunctionDataSource(matchNames);
207 215 oDS.responseSchema = {
208 216 fields: ["id", "fname", "lname", "nname"]
209 217 }
210 218
211 219 // Instantiate AutoComplete for perms
212 220 var oAC_perms = new YAHOO.widget.AutoComplete("perm_new_user_name", "perm_container", oDS);
213 221 oAC_perms.useShadow = false;
214 222 oAC_perms.resultTypeList = false;
215 223
216 224 // Instantiate AutoComplete for owner
217 225 var oAC_owner = new YAHOO.widget.AutoComplete("user", "owner_container", oDS);
218 226 oAC_owner.useShadow = false;
219 227 oAC_owner.resultTypeList = false;
220 228
221 229
222 230 // Custom formatter to highlight the matching letters
223 231 var custom_formatter = function(oResultData, sQuery, sResultMatch) {
224 232 var query = sQuery.toLowerCase(),
225 233 fname = oResultData.fname,
226 234 lname = oResultData.lname,
227 235 nname = oResultData.nname || "", // Guard against null value
228 236 query = sQuery.toLowerCase(),
229 237 fnameMatchIndex = fname.toLowerCase().indexOf(query),
230 238 lnameMatchIndex = lname.toLowerCase().indexOf(query),
231 239 nnameMatchIndex = nname.toLowerCase().indexOf(query),
232 240 displayfname, displaylname, displaynname;
233 241
234 242 if(fnameMatchIndex > -1) {
235 243 displayfname = highlightMatch(fname, query, fnameMatchIndex);
236 244 }
237 245 else {
238 246 displayfname = fname;
239 247 }
240 248
241 249 if(lnameMatchIndex > -1) {
242 250 displaylname = highlightMatch(lname, query, lnameMatchIndex);
243 251 }
244 252 else {
245 253 displaylname = lname;
246 254 }
247 255
248 256 if(nnameMatchIndex > -1) {
249 257 displaynname = "(" + highlightMatch(nname, query, nnameMatchIndex) + ")";
250 258 }
251 259 else {
252 260 displaynname = nname ? "(" + nname + ")" : "";
253 261 }
254 262
255 263 return displayfname + " " + displaylname + " " + displaynname;
256 264
257 265 };
258 266 oAC_perms.formatResult = custom_formatter;
259 267 oAC_owner.formatResult = custom_formatter;
260 268
261 269 // Helper function for the formatter
262 270 var highlightMatch = function(full, snippet, matchindex) {
263 271 return full.substring(0, matchindex) +
264 272 "<span class='match'>" +
265 273 full.substr(matchindex, snippet.length) +
266 274 "</span>" +
267 275 full.substring(matchindex + snippet.length);
268 276 };
269 277
270 278 var myHandler = function(sType, aArgs) {
271 279 var myAC = aArgs[0]; // reference back to the AC instance
272 280 var elLI = aArgs[1]; // reference to the selected LI element
273 281 var oData = aArgs[2]; // object literal of selected item's result data
274 282 myAC.getInputEl().value = oData.nname;
275 283 };
276 284
277 285 oAC_perms.itemSelectEvent.subscribe(myHandler);
278 286 oAC_owner.itemSelectEvent.subscribe(myHandler);
279 287
280 288 return {
281 289 oDS: oDS,
282 290 oAC_perms: oAC_perms,
283 291 oAC_owner: oAC_owner,
284 292 };
285 293 }();
286 294
287 295 </script>
288 296
289 297 </div>
290 298
291 299 <div class="box box-right">
292 300 <div class="title">
293 301 <h5>${_('Administration')}</h5>
294 302 </div>
295 303
296 304 <h3>${_('Statistics')}</h3>
297 305
298 306 ${h.form(url('repo_stats', repo_name=c.repo_info.repo_name),method='delete')}
299 307 <div class="form">
300 308 <div class="fields">
301 309 ${h.submit('reset_stats_%s' % c.repo_info.repo_name,_('Reset current statistics'),class_="refresh_icon action_button",onclick="return confirm('Confirm to remove current statistics');")}
302 310
303 311 <div class="field">
304 312 <ul>
305 313 <li>${_('Fetched to rev')}: ${c.stats_revision}/${c.repo_last_rev}</li>
306 314 <li>${_('Percentage of stats gathered')}: ${c.stats_percentage} %</li>
307 315 </ul>
308 316 </div>
309 317
310 318 </div>
311 319 </div>
312 320 ${h.end_form()}
313 321
314 322 <h3>${_('Cache')}</h3>
315 323 ${h.form(url('repo_cache', repo_name=c.repo_info.repo_name),method='delete')}
316 324 <div class="form">
317 325 <div class="fields">
318 326 ${h.submit('reset_cache_%s' % c.repo_info.repo_name,_('Invalidate repository cache'),class_="refresh_icon action_button",onclick="return confirm('Confirm to invalidate repository cache');")}
319 327 </div>
320 328 </div>
321 329 ${h.end_form()}
322 330
323 331
324 332 <h3>${_('Delete')}</h3>
325 333 ${h.form(url('repo', repo_name=c.repo_info.repo_name),method='delete')}
326 334 <div class="form">
327 335 <div class="fields">
328 336 ${h.submit('remove_%s' % c.repo_info.repo_name,_('Remove this repository'),class_="delete_icon action_button",onclick="return confirm('Confirm to delete this repository');")}
329 337 </div>
330 338 </div>
331 339 ${h.end_form()}
332 340
333 341 </div>
334 342
335 343
336 344 </%def> No newline at end of file
@@ -1,669 +1,674 b''
1 1 <%inherit file="/base/base.html"/>
2 2
3 3 <%def name="title()">
4 4 ${c.repo_name} ${_('Summary')} - ${c.rhodecode_name}
5 5 </%def>
6 6
7 7 <%def name="breadcrumbs_links()">
8 8 ${h.link_to(u'Home',h.url('/'))}
9 9 &raquo;
10 10 ${h.link_to(c.repo_name,h.url('summary_home',repo_name=c.repo_name))}
11 11 &raquo;
12 12 ${_('summary')}
13 13 </%def>
14 14
15 15 <%def name="page_nav()">
16 16 ${self.menu('summary')}
17 17 </%def>
18 18
19 19 <%def name="main()">
20 20 <div class="box box-left">
21 21 <!-- box / title -->
22 22 <div class="title">
23 23 ${self.breadcrumbs()}
24 24 </div>
25 25 <!-- end box / title -->
26 26 <div class="form">
27 27 <div class="fields">
28 28
29 29 <div class="field">
30 30 <div class="label">
31 31 <label>${_('Name')}:</label>
32 32 </div>
33 33 <div class="input-short">
34 34 %if c.repo_info.dbrepo.repo_type =='hg':
35 35 <img style="margin-bottom:2px" class="icon" title="${_('Mercurial repository')}" alt="${_('Mercurial repository')}" src="/images/icons/hgicon.png"/>
36 36 %endif
37 37 %if c.repo_info.dbrepo.repo_type =='git':
38 38 <img style="margin-bottom:2px" class="icon" title="${_('Git repository')}" alt="${_('Git repository')}" src="/images/icons/giticon.png"/>
39 39 %endif
40 40
41 41 %if c.repo_info.dbrepo.private:
42 42 <img style="margin-bottom:2px" class="icon" title="${_('private repository')}" alt="${_('private repository')}" src="/images/icons/lock.png"/>
43 43 %else:
44 44 <img style="margin-bottom:2px" class="icon" title="${_('public repository')}" alt="${_('public repository')}" src="/images/icons/lock_open.png"/>
45 45 %endif
46 46 <span style="font-size: 1.6em;font-weight: bold;vertical-align: baseline;">${c.repo_info.name}</span>
47 47 %if c.rhodecode_user.username != 'default':
48 48 %if c.following:
49 49 <span id="follow_toggle" class="following" title="${_('Stop following this repository')}"
50 50 onclick="javascript:toggleFollowingRepo(${c.repo_info.dbrepo.repo_id},'${str(h.get_token())}')">
51 51 </span>
52 52 %else:
53 53 <span id="follow_toggle" class="follow" title="${_('Start following this repository')}"
54 54 onclick="javascript:toggleFollowingRepo(${c.repo_info.dbrepo.repo_id},'${str(h.get_token())}')">
55 55 </span>
56 56 %endif
57 57 %endif:
58 58 <br/>
59 59 %if c.repo_info.dbrepo.fork:
60 60 <span style="margin-top:5px">
61 61 <a href="${h.url('summary_home',repo_name=c.repo_info.dbrepo.fork.repo_name)}">
62 62 <img class="icon" alt="${_('public')}"
63 63 title="${_('Fork of')} ${c.repo_info.dbrepo.fork.repo_name}"
64 64 src="/images/icons/arrow_divide.png"/>
65 65 ${_('Fork of')} ${c.repo_info.dbrepo.fork.repo_name}
66 66 </a>
67 67 </span>
68 68 %endif
69 69 </div>
70 70 </div>
71 71
72 72
73 73 <div class="field">
74 74 <div class="label">
75 75 <label>${_('Description')}:</label>
76 76 </div>
77 77 <div class="input-short">
78 78 ${c.repo_info.dbrepo.description}
79 79 </div>
80 80 </div>
81 81
82 82
83 83 <div class="field">
84 84 <div class="label">
85 85 <label>${_('Contact')}:</label>
86 86 </div>
87 87 <div class="input-short">
88 88 <div class="gravatar">
89 89 <img alt="gravatar" src="${h.gravatar_url(c.repo_info.dbrepo.user.email)}"/>
90 90 </div>
91 91 ${_('Username')}: ${c.repo_info.dbrepo.user.username}<br/>
92 92 ${_('Name')}: ${c.repo_info.dbrepo.user.name} ${c.repo_info.dbrepo.user.lastname}<br/>
93 93 ${_('Email')}: <a href="mailto:${c.repo_info.dbrepo.user.email}">${c.repo_info.dbrepo.user.email}</a>
94 94 </div>
95 95 </div>
96 96
97 97 <div class="field">
98 98 <div class="label">
99 99 <label>${_('Last change')}:</label>
100 100 </div>
101 101 <div class="input-short">
102 102 ${h.age(c.repo_info.last_change)} - ${c.repo_info.last_change}
103 103 ${_('by')} ${h.get_changeset_safe(c.repo_info,'tip').author}
104 104
105 105 </div>
106 106 </div>
107 107
108 108 <div class="field">
109 109 <div class="label">
110 110 <label>${_('Clone url')}:</label>
111 111 </div>
112 112 <div class="input-short">
113 113 <input type="text" id="clone_url" readonly="readonly" value="hg clone ${c.clone_repo_url}" size="70"/>
114 114 </div>
115 115 </div>
116 116
117 117 <div class="field">
118 118 <div class="label">
119 119 <label>${_('Trending source files')}:</label>
120 120 </div>
121 121 <div class="input-short">
122 122 <div id="lang_stats"></div>
123 123 </div>
124 124 </div>
125 125
126 126 <div class="field">
127 127 <div class="label">
128 128 <label>${_('Download')}:</label>
129 129 </div>
130 130 <div class="input-short">
131 131 %if len(c.repo_info.revisions) == 0:
132 132 ${_('There are no downloads yet')}
133 %elif c.enable_downloads is False:
134 ${_('Downloads are disabled for this repository')}
135 %if h.HasPermissionAll('hg.admin')('enable stats on from summary'):
136 [${h.link_to(_('enable'),h.url('edit_repo',repo_name=c.repo_name))}]
137 %endif
133 138 %else:
134 139 ${h.select('download_options',c.repo_info.get_changeset().raw_id,c.download_options)}
135 140 %for cnt,archive in enumerate(c.repo_info._get_archives()):
136 141 %if cnt >=1:
137 142 |
138 143 %endif
139 144 <span class="tooltip" title="${_('Download %s as %s') %('tip',archive['type'])}"
140 145 id="${archive['type']+'_link'}">${h.link_to(archive['type'],
141 146 h.url('files_archive_home',repo_name=c.repo_info.name,
142 147 fname='tip'+archive['extension']),class_="archive_icon")}</span>
143 148 %endfor
144 149 %endif
145 150 </div>
146 151 </div>
147 152
148 153 <div class="field">
149 154 <div class="label">
150 155 <label>${_('Feeds')}:</label>
151 156 </div>
152 157 <div class="input-short">
153 158 ${h.link_to(_('RSS'),h.url('rss_feed_home',repo_name=c.repo_info.name),class_='rss_icon')}
154 159 ${h.link_to(_('Atom'),h.url('atom_feed_home',repo_name=c.repo_info.name),class_='atom_icon')}
155 160 </div>
156 161 </div>
157 162 </div>
158 163 </div>
159 164 <script type="text/javascript">
160 165 YUE.onDOMReady(function(e){
161 166 id = 'clone_url';
162 167 YUE.on(id,'click',function(e){
163 168 YUD.get('clone_url').select();
164 169 })
165 170 })
166 171 var data = ${c.trending_languages|n};
167 172 var total = 0;
168 173 var no_data = true;
169 174 for (k in data){
170 175 total += data[k];
171 176 no_data = false;
172 177 }
173 178 var tbl = document.createElement('table');
174 179 tbl.setAttribute('class','trending_language_tbl');
175 180 var cnt =0;
176 181 for (k in data){
177 182 cnt+=1;
178 183 var hide = cnt>2;
179 184 var tr = document.createElement('tr');
180 185 if (hide){
181 186 tr.setAttribute('style','display:none');
182 187 tr.setAttribute('class','stats_hidden');
183 188 }
184 189 var percentage = Math.round((data[k]/total*100),2);
185 190 var value = data[k];
186 191 var td1 = document.createElement('td');
187 192 td1.width=150;
188 193 var trending_language_label = document.createElement('div');
189 194 trending_language_label.innerHTML = k;
190 195 td1.appendChild(trending_language_label);
191 196
192 197 var td2 = document.createElement('td');
193 198 td2.setAttribute('style','padding-right:14px !important');
194 199 var trending_language = document.createElement('div');
195 200 var nr_files = value+" ${_('files')}";
196 201
197 202 trending_language.title = k+" "+nr_files;
198 203
199 204 if (percentage>20){
200 205 trending_language.innerHTML = "<b style='font-size:0.8em'>"+percentage+"% "+nr_files+ "</b>";
201 206 }
202 207 else{
203 208 trending_language.innerHTML = "<b style='font-size:0.8em'>"+percentage+"%</b>";
204 209 }
205 210
206 211 trending_language.setAttribute("class", 'trending_language top-right-rounded-corner bottom-right-rounded-corner');
207 212 trending_language.style.width=percentage+"%";
208 213 td2.appendChild(trending_language);
209 214
210 215 tr.appendChild(td1);
211 216 tr.appendChild(td2);
212 217 tbl.appendChild(tr);
213 218 if(cnt == 2){
214 219 var show_more = document.createElement('tr');
215 220 var td=document.createElement('td');
216 221 lnk = document.createElement('a');
217 222 lnk.href='#';
218 223 lnk.innerHTML = "${_("show more")}";
219 224 lnk.id='code_stats_show_more';
220 225 td.appendChild(lnk);
221 226 show_more.appendChild(td);
222 227 show_more.appendChild(document.createElement('td'));
223 228 tbl.appendChild(show_more);
224 229 }
225 230
226 231 }
227 232 if(no_data){
228 233 var tr = document.createElement('tr');
229 234 var td1 = document.createElement('td');
230 235 td1.innerHTML = "${c.no_data_msg}";
231 236 tr.appendChild(td1);
232 237 tbl.appendChild(tr);
233 238 }
234 239 YUD.get('lang_stats').appendChild(tbl);
235 240 YUE.on('code_stats_show_more','click',function(){
236 241 l = YUD.getElementsByClassName('stats_hidden')
237 242 for (e in l){
238 243 YUD.setStyle(l[e],'display','');
239 244 };
240 245 YUD.setStyle(YUD.get('code_stats_show_more'),
241 246 'display','none');
242 247 })
243 248
244 249
245 250 YUE.on('download_options','change',function(e){
246 251 var new_cs = e.target.options[e.target.selectedIndex];
247 252 var tmpl_links = {}
248 253 %for cnt,archive in enumerate(c.repo_info._get_archives()):
249 254 tmpl_links['${archive['type']}'] = '${h.link_to(archive['type'],
250 255 h.url('files_archive_home',repo_name=c.repo_info.name,
251 256 fname='__CS__'+archive['extension']),class_="archive_icon")}';
252 257 %endfor
253 258
254 259
255 260 for(k in tmpl_links){
256 261 var s = YUD.get(k+'_link')
257 262 title_tmpl = "${_('Download %s as %s') % ('__CS_NAME__',archive['type'])}";
258 263 s.title = title_tmpl.replace('__CS_NAME__',new_cs.text)
259 264 s.innerHTML = tmpl_links[k].replace('__CS__',new_cs.value);
260 265 }
261 266
262 267 })
263 268
264 269 </script>
265 270 </div>
266 271
267 272 <div class="box box-right" style="min-height:455px">
268 273 <!-- box / title -->
269 274 <div class="title">
270 275 <h5>${_('Commit activity by day / author')}</h5>
271 276 </div>
272 277
273 278 <div class="table">
274 279 %if c.no_data:
275 280 <div style="padding:0 10px 10px 15px;font-size: 1.2em;">${c.no_data_msg}
276 281 %if h.HasPermissionAll('hg.admin')('enable stats on from summary'):
277 282 [${h.link_to(_('enable'),h.url('edit_repo',repo_name=c.repo_name))}]
278 283 %endif
279 284 </div>
280 285 %endif:
281 286 <div id="commit_history" style="width:460px;height:300px;float:left"></div>
282 287 <div style="clear: both;height: 10px"></div>
283 288 <div id="overview" style="width:460px;height:100px;float:left"></div>
284 289
285 290 <div id="legend_data" style="clear:both;margin-top:10px;">
286 291 <div id="legend_container"></div>
287 292 <div id="legend_choices">
288 293 <table id="legend_choices_tables" style="font-size:smaller;color:#545454"></table>
289 294 </div>
290 295 </div>
291 296 <script type="text/javascript">
292 297 /**
293 298 * Plots summary graph
294 299 *
295 300 * @class SummaryPlot
296 301 * @param {from} initial from for detailed graph
297 302 * @param {to} initial to for detailed graph
298 303 * @param {dataset}
299 304 * @param {overview_dataset}
300 305 */
301 306 function SummaryPlot(from,to,dataset,overview_dataset) {
302 307 var initial_ranges = {
303 308 "xaxis":{
304 309 "from":from,
305 310 "to":to,
306 311 },
307 312 };
308 313 var dataset = dataset;
309 314 var overview_dataset = [overview_dataset];
310 315 var choiceContainer = YUD.get("legend_choices");
311 316 var choiceContainerTable = YUD.get("legend_choices_tables");
312 317 var plotContainer = YUD.get('commit_history');
313 318 var overviewContainer = YUD.get('overview');
314 319
315 320 var plot_options = {
316 321 bars: {show:true,align:'center',lineWidth:4},
317 322 legend: {show:true, container:"legend_container"},
318 323 points: {show:true,radius:0,fill:false},
319 324 yaxis: {tickDecimals:0,},
320 325 xaxis: {
321 326 mode: "time",
322 327 timeformat: "%d/%m",
323 328 min:from,
324 329 max:to,
325 330 },
326 331 grid: {
327 332 hoverable: true,
328 333 clickable: true,
329 334 autoHighlight:true,
330 335 color: "#999"
331 336 },
332 337 //selection: {mode: "x"}
333 338 };
334 339 var overview_options = {
335 340 legend:{show:false},
336 341 bars: {show:true,barWidth: 2,},
337 342 shadowSize: 0,
338 343 xaxis: {mode: "time", timeformat: "%d/%m/%y",},
339 344 yaxis: {ticks: 3, min: 0,tickDecimals:0,},
340 345 grid: {color: "#999",},
341 346 selection: {mode: "x"}
342 347 };
343 348
344 349 /**
345 350 *get dummy data needed in few places
346 351 */
347 352 function getDummyData(label){
348 353 return {"label":label,
349 354 "data":[{"time":0,
350 355 "commits":0,
351 356 "added":0,
352 357 "changed":0,
353 358 "removed":0,
354 359 }],
355 360 "schema":["commits"],
356 361 "color":'#ffffff',
357 362 }
358 363 }
359 364
360 365 /**
361 366 * generate checkboxes accordindly to data
362 367 * @param keys
363 368 * @returns
364 369 */
365 370 function generateCheckboxes(data) {
366 371 //append checkboxes
367 372 var i = 0;
368 373 choiceContainerTable.innerHTML = '';
369 374 for(var pos in data) {
370 375
371 376 data[pos].color = i;
372 377 i++;
373 378 if(data[pos].label != ''){
374 379 choiceContainerTable.innerHTML += '<tr><td>'+
375 380 '<input type="checkbox" name="' + data[pos].label +'" checked="checked" />'
376 381 +data[pos].label+
377 382 '</td></tr>';
378 383 }
379 384 }
380 385 }
381 386
382 387 /**
383 388 * ToolTip show
384 389 */
385 390 function showTooltip(x, y, contents) {
386 391 var div=document.getElementById('tooltip');
387 392 if(!div) {
388 393 div = document.createElement('div');
389 394 div.id="tooltip";
390 395 div.style.position="absolute";
391 396 div.style.border='1px solid #fdd';
392 397 div.style.padding='2px';
393 398 div.style.backgroundColor='#fee';
394 399 document.body.appendChild(div);
395 400 }
396 401 YUD.setStyle(div, 'opacity', 0);
397 402 div.innerHTML = contents;
398 403 div.style.top=(y + 5) + "px";
399 404 div.style.left=(x + 5) + "px";
400 405
401 406 var anim = new YAHOO.util.Anim(div, {opacity: {to: 0.8}}, 0.2);
402 407 anim.animate();
403 408 }
404 409
405 410 /**
406 411 * This function will detect if selected period has some changesets
407 412 for this user if it does this data is then pushed for displaying
408 413 Additionally it will only display users that are selected by the checkbox
409 414 */
410 415 function getDataAccordingToRanges(ranges) {
411 416
412 417 var data = [];
413 418 var keys = [];
414 419 for(var key in dataset){
415 420 var push = false;
416 421
417 422 //method1 slow !!
418 423 //*
419 424 for(var ds in dataset[key].data){
420 425 commit_data = dataset[key].data[ds];
421 426 if (commit_data.time >= ranges.xaxis.from && commit_data.time <= ranges.xaxis.to){
422 427 push = true;
423 428 break;
424 429 }
425 430 }
426 431 //*/
427 432
428 433 /*//method2 sorted commit data !!!
429 434
430 435 var first_commit = dataset[key].data[0].time;
431 436 var last_commit = dataset[key].data[dataset[key].data.length-1].time;
432 437
433 438 if (first_commit >= ranges.xaxis.from && last_commit <= ranges.xaxis.to){
434 439 push = true;
435 440 }
436 441 //*/
437 442
438 443 if(push){
439 444 data.push(dataset[key]);
440 445 }
441 446 }
442 447 if(data.length >= 1){
443 448 return data;
444 449 }
445 450 else{
446 451 //just return dummy data for graph to plot itself
447 452 return [getDummyData('')];
448 453 }
449 454
450 455 }
451 456
452 457 /**
453 458 * redraw using new checkbox data
454 459 */
455 460 function plotchoiced(e,args){
456 461 var cur_data = args[0];
457 462 var cur_ranges = args[1];
458 463
459 464 var new_data = [];
460 465 var inputs = choiceContainer.getElementsByTagName("input");
461 466
462 467 //show only checked labels
463 468 for(var i=0; i<inputs.length; i++) {
464 469 var checkbox_key = inputs[i].name;
465 470
466 471 if(inputs[i].checked){
467 472 for(var d in cur_data){
468 473 if(cur_data[d].label == checkbox_key){
469 474 new_data.push(cur_data[d]);
470 475 }
471 476 }
472 477 }
473 478 else{
474 479 //push dummy data to not hide the label
475 480 new_data.push(getDummyData(checkbox_key));
476 481 }
477 482 }
478 483
479 484 var new_options = YAHOO.lang.merge(plot_options, {
480 485 xaxis: {
481 486 min: cur_ranges.xaxis.from,
482 487 max: cur_ranges.xaxis.to,
483 488 mode:"time",
484 489 timeformat: "%d/%m",
485 490 },
486 491 });
487 492 if (!new_data){
488 493 new_data = [[0,1]];
489 494 }
490 495 // do the zooming
491 496 plot = YAHOO.widget.Flot(plotContainer, new_data, new_options);
492 497
493 498 plot.subscribe("plotselected", plotselected);
494 499
495 500 //resubscribe plothover
496 501 plot.subscribe("plothover", plothover);
497 502
498 503 // don't fire event on the overview to prevent eternal loop
499 504 overview.setSelection(cur_ranges, true);
500 505
501 506 }
502 507
503 508 /**
504 509 * plot only selected items from overview
505 510 * @param ranges
506 511 * @returns
507 512 */
508 513 function plotselected(ranges,cur_data) {
509 514 //updates the data for new plot
510 515 data = getDataAccordingToRanges(ranges);
511 516 generateCheckboxes(data);
512 517
513 518 var new_options = YAHOO.lang.merge(plot_options, {
514 519 xaxis: {
515 520 min: ranges.xaxis.from,
516 521 max: ranges.xaxis.to,
517 522 mode:"time",
518 523 timeformat: "%d/%m",
519 524 },
520 525 yaxis: {
521 526 min: ranges.yaxis.from,
522 527 max: ranges.yaxis.to,
523 528 },
524 529
525 530 });
526 531 // do the zooming
527 532 plot = YAHOO.widget.Flot(plotContainer, data, new_options);
528 533
529 534 plot.subscribe("plotselected", plotselected);
530 535
531 536 //resubscribe plothover
532 537 plot.subscribe("plothover", plothover);
533 538
534 539 // don't fire event on the overview to prevent eternal loop
535 540 overview.setSelection(ranges, true);
536 541
537 542 //resubscribe choiced
538 543 YUE.on(choiceContainer.getElementsByTagName("input"), "click", plotchoiced, [data, ranges]);
539 544 }
540 545
541 546 var previousPoint = null;
542 547
543 548 function plothover(o) {
544 549 var pos = o.pos;
545 550 var item = o.item;
546 551
547 552 //YUD.get("x").innerHTML = pos.x.toFixed(2);
548 553 //YUD.get("y").innerHTML = pos.y.toFixed(2);
549 554 if (item) {
550 555 if (previousPoint != item.datapoint) {
551 556 previousPoint = item.datapoint;
552 557
553 558 var tooltip = YUD.get("tooltip");
554 559 if(tooltip) {
555 560 tooltip.parentNode.removeChild(tooltip);
556 561 }
557 562 var x = item.datapoint.x.toFixed(2);
558 563 var y = item.datapoint.y.toFixed(2);
559 564
560 565 if (!item.series.label){
561 566 item.series.label = 'commits';
562 567 }
563 568 var d = new Date(x*1000);
564 569 var fd = d.toDateString()
565 570 var nr_commits = parseInt(y);
566 571
567 572 var cur_data = dataset[item.series.label].data[item.dataIndex];
568 573 var added = cur_data.added;
569 574 var changed = cur_data.changed;
570 575 var removed = cur_data.removed;
571 576
572 577 var nr_commits_suffix = " ${_('commits')} ";
573 578 var added_suffix = " ${_('files added')} ";
574 579 var changed_suffix = " ${_('files changed')} ";
575 580 var removed_suffix = " ${_('files removed')} ";
576 581
577 582
578 583 if(nr_commits == 1){nr_commits_suffix = " ${_('commit')} ";}
579 584 if(added==1){added_suffix=" ${_('file added')} ";}
580 585 if(changed==1){changed_suffix=" ${_('file changed')} ";}
581 586 if(removed==1){removed_suffix=" ${_('file removed')} ";}
582 587
583 588 showTooltip(item.pageX, item.pageY, item.series.label + " on " + fd
584 589 +'<br/>'+
585 590 nr_commits + nr_commits_suffix+'<br/>'+
586 591 added + added_suffix +'<br/>'+
587 592 changed + changed_suffix + '<br/>'+
588 593 removed + removed_suffix + '<br/>');
589 594 }
590 595 }
591 596 else {
592 597 var tooltip = YUD.get("tooltip");
593 598
594 599 if(tooltip) {
595 600 tooltip.parentNode.removeChild(tooltip);
596 601 }
597 602 previousPoint = null;
598 603 }
599 604 }
600 605
601 606 /**
602 607 * MAIN EXECUTION
603 608 */
604 609
605 610 var data = getDataAccordingToRanges(initial_ranges);
606 611 generateCheckboxes(data);
607 612
608 613 //main plot
609 614 var plot = YAHOO.widget.Flot(plotContainer,data,plot_options);
610 615
611 616 //overview
612 617 var overview = YAHOO.widget.Flot(overviewContainer, overview_dataset, overview_options);
613 618
614 619 //show initial selection on overview
615 620 overview.setSelection(initial_ranges);
616 621
617 622 plot.subscribe("plotselected", plotselected);
618 623
619 624 overview.subscribe("plotselected", function (ranges) {
620 625 plot.setSelection(ranges);
621 626 });
622 627
623 628 plot.subscribe("plothover", plothover);
624 629
625 630 YUE.on(choiceContainer.getElementsByTagName("input"), "click", plotchoiced, [data, initial_ranges]);
626 631 }
627 632 SummaryPlot(${c.ts_min},${c.ts_max},${c.commit_data|n},${c.overview_data|n});
628 633 </script>
629 634
630 635 </div>
631 636 </div>
632 637
633 638 <div class="box">
634 639 <div class="title">
635 640 <div class="breadcrumbs">${h.link_to(_('Shortlog'),h.url('shortlog_home',repo_name=c.repo_name))}</div>
636 641 </div>
637 642 <div class="table">
638 643 <div id="shortlog_data">
639 644 <%include file='../shortlog/shortlog_data.html'/>
640 645 </div>
641 646 ##%if c.repo_changesets:
642 647 ## ${h.link_to(_('show more'),h.url('changelog_home',repo_name=c.repo_name))}
643 648 ##%endif
644 649 </div>
645 650 </div>
646 651 <div class="box">
647 652 <div class="title">
648 653 <div class="breadcrumbs">${h.link_to(_('Tags'),h.url('tags_home',repo_name=c.repo_name))}</div>
649 654 </div>
650 655 <div class="table">
651 656 <%include file='../tags/tags_data.html'/>
652 657 %if c.repo_changesets:
653 658 ${h.link_to(_('show more'),h.url('tags_home',repo_name=c.repo_name))}
654 659 %endif
655 660 </div>
656 661 </div>
657 662 <div class="box">
658 663 <div class="title">
659 664 <div class="breadcrumbs">${h.link_to(_('Branches'),h.url('branches_home',repo_name=c.repo_name))}</div>
660 665 </div>
661 666 <div class="table">
662 667 <%include file='../branches/branches_data.html'/>
663 668 %if c.repo_changesets:
664 669 ${h.link_to(_('show more'),h.url('branches_home',repo_name=c.repo_name))}
665 670 %endif
666 671 </div>
667 672 </div>
668 673
669 674 </%def>
General Comments 0
You need to be logged in to leave comments. Login now