##// END OF EJS Templates
Add API access to personal journal, and forbid anonymous access on them
marcink -
r2410:a1595b6e beta
parent child Browse files
Show More
@@ -1,292 +1,294 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.controllers.journal
3 rhodecode.controllers.journal
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 Journal controller for pylons
6 Journal controller for pylons
7
7
8 :created_on: Nov 21, 2010
8 :created_on: Nov 21, 2010
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software: you can redistribute it and/or modify
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
16 # (at your option) any later version.
17 #
17 #
18 # This program is distributed in the hope that it will be useful,
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # GNU General Public License for more details.
21 # GNU General Public License for more details.
22 #
22 #
23 # You should have received a copy of the GNU General Public License
23 # You should have received a copy of the GNU General Public License
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 import logging
25 import logging
26 from itertools import groupby
26 from itertools import groupby
27
27
28 from sqlalchemy import or_
28 from sqlalchemy import or_
29 from sqlalchemy.orm import joinedload
29 from sqlalchemy.orm import joinedload
30 from webhelpers.paginate import Page
30 from webhelpers.paginate import Page
31 from webhelpers.feedgenerator import Atom1Feed, Rss201rev2Feed
31 from webhelpers.feedgenerator import Atom1Feed, Rss201rev2Feed
32
32
33 from paste.httpexceptions import HTTPBadRequest
33 from paste.httpexceptions import HTTPBadRequest
34 from pylons import request, tmpl_context as c, response, url
34 from pylons import request, tmpl_context as c, response, url
35 from pylons.i18n.translation import _
35 from pylons.i18n.translation import _
36
36
37 import rhodecode.lib.helpers as h
37 import rhodecode.lib.helpers as h
38 from rhodecode.lib.auth import LoginRequired, NotAnonymous
38 from rhodecode.lib.auth import LoginRequired, NotAnonymous
39 from rhodecode.lib.base import BaseController, render
39 from rhodecode.lib.base import BaseController, render
40 from rhodecode.model.db import UserLog, UserFollowing, Repository, User
40 from rhodecode.model.db import UserLog, UserFollowing, Repository, User
41 from rhodecode.model.meta import Session
41 from rhodecode.model.meta import Session
42 from sqlalchemy.sql.expression import func
42 from sqlalchemy.sql.expression import func
43 from rhodecode.model.scm import ScmModel
43 from rhodecode.model.scm import ScmModel
44
44
45 log = logging.getLogger(__name__)
45 log = logging.getLogger(__name__)
46
46
47
47
48 class JournalController(BaseController):
48 class JournalController(BaseController):
49
49
50 def __before__(self):
50 def __before__(self):
51 super(JournalController, self).__before__()
51 super(JournalController, self).__before__()
52 self.language = 'en-us'
52 self.language = 'en-us'
53 self.ttl = "5"
53 self.ttl = "5"
54 self.feed_nr = 20
54 self.feed_nr = 20
55
55
56 @LoginRequired()
56 @LoginRequired()
57 @NotAnonymous()
57 @NotAnonymous()
58 def index(self):
58 def index(self):
59 # Return a rendered template
59 # Return a rendered template
60 p = int(request.params.get('page', 1))
60 p = int(request.params.get('page', 1))
61
61
62 c.user = User.get(self.rhodecode_user.user_id)
62 c.user = User.get(self.rhodecode_user.user_id)
63 all_repos = self.sa.query(Repository)\
63 all_repos = self.sa.query(Repository)\
64 .filter(Repository.user_id == c.user.user_id)\
64 .filter(Repository.user_id == c.user.user_id)\
65 .order_by(func.lower(Repository.repo_name)).all()
65 .order_by(func.lower(Repository.repo_name)).all()
66
66
67 c.user_repos = ScmModel().get_repos(all_repos)
67 c.user_repos = ScmModel().get_repos(all_repos)
68
68
69 c.following = self.sa.query(UserFollowing)\
69 c.following = self.sa.query(UserFollowing)\
70 .filter(UserFollowing.user_id == self.rhodecode_user.user_id)\
70 .filter(UserFollowing.user_id == self.rhodecode_user.user_id)\
71 .options(joinedload(UserFollowing.follows_repository))\
71 .options(joinedload(UserFollowing.follows_repository))\
72 .all()
72 .all()
73
73
74 journal = self._get_journal_data(c.following)
74 journal = self._get_journal_data(c.following)
75
75
76 c.journal_pager = Page(journal, page=p, items_per_page=20)
76 c.journal_pager = Page(journal, page=p, items_per_page=20)
77
77
78 c.journal_day_aggreagate = self._get_daily_aggregate(c.journal_pager)
78 c.journal_day_aggreagate = self._get_daily_aggregate(c.journal_pager)
79
79
80 c.journal_data = render('journal/journal_data.html')
80 c.journal_data = render('journal/journal_data.html')
81 if request.environ.get('HTTP_X_PARTIAL_XHR'):
81 if request.environ.get('HTTP_X_PARTIAL_XHR'):
82 return c.journal_data
82 return c.journal_data
83 return render('journal/journal.html')
83 return render('journal/journal.html')
84
84
85 @LoginRequired(api_access=True)
85 @LoginRequired(api_access=True)
86 @NotAnonymous()
86 def journal_atom(self):
87 def journal_atom(self):
87 """
88 """
88 Produce an atom-1.0 feed via feedgenerator module
89 Produce an atom-1.0 feed via feedgenerator module
89 """
90 """
90 following = self.sa.query(UserFollowing)\
91 following = self.sa.query(UserFollowing)\
91 .filter(UserFollowing.user_id == self.rhodecode_user.user_id)\
92 .filter(UserFollowing.user_id == self.rhodecode_user.user_id)\
92 .options(joinedload(UserFollowing.follows_repository))\
93 .options(joinedload(UserFollowing.follows_repository))\
93 .all()
94 .all()
94 return self._atom_feed(following, public=False)
95 return self._atom_feed(following, public=False)
95
96
96 @LoginRequired(api_access=True)
97 @LoginRequired(api_access=True)
98 @NotAnonymous()
97 def journal_rss(self):
99 def journal_rss(self):
98 """
100 """
99 Produce an rss feed via feedgenerator module
101 Produce an rss feed via feedgenerator module
100 """
102 """
101 following = self.sa.query(UserFollowing)\
103 following = self.sa.query(UserFollowing)\
102 .filter(UserFollowing.user_id == self.rhodecode_user.user_id)\
104 .filter(UserFollowing.user_id == self.rhodecode_user.user_id)\
103 .options(joinedload(UserFollowing.follows_repository))\
105 .options(joinedload(UserFollowing.follows_repository))\
104 .all()
106 .all()
105 return self._rss_feed(following, public=False)
107 return self._rss_feed(following, public=False)
106
108
107 def _get_daily_aggregate(self, journal):
109 def _get_daily_aggregate(self, journal):
108 groups = []
110 groups = []
109 for k, g in groupby(journal, lambda x: x.action_as_day):
111 for k, g in groupby(journal, lambda x: x.action_as_day):
110 user_group = []
112 user_group = []
111 for k2, g2 in groupby(list(g), lambda x: x.user.email):
113 for k2, g2 in groupby(list(g), lambda x: x.user.email):
112 l = list(g2)
114 l = list(g2)
113 user_group.append((l[0].user, l))
115 user_group.append((l[0].user, l))
114
116
115 groups.append((k, user_group,))
117 groups.append((k, user_group,))
116
118
117 return groups
119 return groups
118
120
119 def _get_journal_data(self, following_repos):
121 def _get_journal_data(self, following_repos):
120 repo_ids = [x.follows_repository.repo_id for x in following_repos
122 repo_ids = [x.follows_repository.repo_id for x in following_repos
121 if x.follows_repository is not None]
123 if x.follows_repository is not None]
122 user_ids = [x.follows_user.user_id for x in following_repos
124 user_ids = [x.follows_user.user_id for x in following_repos
123 if x.follows_user is not None]
125 if x.follows_user is not None]
124
126
125 filtering_criterion = None
127 filtering_criterion = None
126
128
127 if repo_ids and user_ids:
129 if repo_ids and user_ids:
128 filtering_criterion = or_(UserLog.repository_id.in_(repo_ids),
130 filtering_criterion = or_(UserLog.repository_id.in_(repo_ids),
129 UserLog.user_id.in_(user_ids))
131 UserLog.user_id.in_(user_ids))
130 if repo_ids and not user_ids:
132 if repo_ids and not user_ids:
131 filtering_criterion = UserLog.repository_id.in_(repo_ids)
133 filtering_criterion = UserLog.repository_id.in_(repo_ids)
132 if not repo_ids and user_ids:
134 if not repo_ids and user_ids:
133 filtering_criterion = UserLog.user_id.in_(user_ids)
135 filtering_criterion = UserLog.user_id.in_(user_ids)
134 if filtering_criterion is not None:
136 if filtering_criterion is not None:
135 journal = self.sa.query(UserLog)\
137 journal = self.sa.query(UserLog)\
136 .options(joinedload(UserLog.user))\
138 .options(joinedload(UserLog.user))\
137 .options(joinedload(UserLog.repository))\
139 .options(joinedload(UserLog.repository))\
138 .filter(filtering_criterion)\
140 .filter(filtering_criterion)\
139 .order_by(UserLog.action_date.desc())
141 .order_by(UserLog.action_date.desc())
140 else:
142 else:
141 journal = []
143 journal = []
142
144
143 return journal
145 return journal
144
146
145 @LoginRequired()
147 @LoginRequired()
146 @NotAnonymous()
148 @NotAnonymous()
147 def toggle_following(self):
149 def toggle_following(self):
148 cur_token = request.POST.get('auth_token')
150 cur_token = request.POST.get('auth_token')
149 token = h.get_token()
151 token = h.get_token()
150 if cur_token == token:
152 if cur_token == token:
151
153
152 user_id = request.POST.get('follows_user_id')
154 user_id = request.POST.get('follows_user_id')
153 if user_id:
155 if user_id:
154 try:
156 try:
155 self.scm_model.toggle_following_user(user_id,
157 self.scm_model.toggle_following_user(user_id,
156 self.rhodecode_user.user_id)
158 self.rhodecode_user.user_id)
157 Session.commit()
159 Session.commit()
158 return 'ok'
160 return 'ok'
159 except:
161 except:
160 raise HTTPBadRequest()
162 raise HTTPBadRequest()
161
163
162 repo_id = request.POST.get('follows_repo_id')
164 repo_id = request.POST.get('follows_repo_id')
163 if repo_id:
165 if repo_id:
164 try:
166 try:
165 self.scm_model.toggle_following_repo(repo_id,
167 self.scm_model.toggle_following_repo(repo_id,
166 self.rhodecode_user.user_id)
168 self.rhodecode_user.user_id)
167 Session.commit()
169 Session.commit()
168 return 'ok'
170 return 'ok'
169 except:
171 except:
170 raise HTTPBadRequest()
172 raise HTTPBadRequest()
171
173
172 log.debug('token mismatch %s vs %s' % (cur_token, token))
174 log.debug('token mismatch %s vs %s' % (cur_token, token))
173 raise HTTPBadRequest()
175 raise HTTPBadRequest()
174
176
175 @LoginRequired()
177 @LoginRequired()
176 def public_journal(self):
178 def public_journal(self):
177 # Return a rendered template
179 # Return a rendered template
178 p = int(request.params.get('page', 1))
180 p = int(request.params.get('page', 1))
179
181
180 c.following = self.sa.query(UserFollowing)\
182 c.following = self.sa.query(UserFollowing)\
181 .filter(UserFollowing.user_id == self.rhodecode_user.user_id)\
183 .filter(UserFollowing.user_id == self.rhodecode_user.user_id)\
182 .options(joinedload(UserFollowing.follows_repository))\
184 .options(joinedload(UserFollowing.follows_repository))\
183 .all()
185 .all()
184
186
185 journal = self._get_journal_data(c.following)
187 journal = self._get_journal_data(c.following)
186
188
187 c.journal_pager = Page(journal, page=p, items_per_page=20)
189 c.journal_pager = Page(journal, page=p, items_per_page=20)
188
190
189 c.journal_day_aggreagate = self._get_daily_aggregate(c.journal_pager)
191 c.journal_day_aggreagate = self._get_daily_aggregate(c.journal_pager)
190
192
191 c.journal_data = render('journal/journal_data.html')
193 c.journal_data = render('journal/journal_data.html')
192 if request.environ.get('HTTP_X_PARTIAL_XHR'):
194 if request.environ.get('HTTP_X_PARTIAL_XHR'):
193 return c.journal_data
195 return c.journal_data
194 return render('journal/public_journal.html')
196 return render('journal/public_journal.html')
195
197
196 def _atom_feed(self, repos, public=True):
198 def _atom_feed(self, repos, public=True):
197 journal = self._get_journal_data(repos)
199 journal = self._get_journal_data(repos)
198 if public:
200 if public:
199 _link = url('public_journal_atom', qualified=True)
201 _link = url('public_journal_atom', qualified=True)
200 _desc = '%s %s %s' % (c.rhodecode_name, _('public journal'),
202 _desc = '%s %s %s' % (c.rhodecode_name, _('public journal'),
201 'atom feed')
203 'atom feed')
202 else:
204 else:
203 _link = url('journal_atom', qualified=True)
205 _link = url('journal_atom', qualified=True)
204 _desc = '%s %s %s' % (c.rhodecode_name, _('journal'), 'atom feed')
206 _desc = '%s %s %s' % (c.rhodecode_name, _('journal'), 'atom feed')
205
207
206 feed = Atom1Feed(title=_desc,
208 feed = Atom1Feed(title=_desc,
207 link=_link,
209 link=_link,
208 description=_desc,
210 description=_desc,
209 language=self.language,
211 language=self.language,
210 ttl=self.ttl)
212 ttl=self.ttl)
211
213
212 for entry in journal[:self.feed_nr]:
214 for entry in journal[:self.feed_nr]:
213 action, action_extra, ico = h.action_parser(entry, feed=True)
215 action, action_extra, ico = h.action_parser(entry, feed=True)
214 title = "%s - %s %s" % (entry.user.short_contact, action(),
216 title = "%s - %s %s" % (entry.user.short_contact, action(),
215 entry.repository.repo_name)
217 entry.repository.repo_name)
216 desc = action_extra()
218 desc = action_extra()
217 _url = None
219 _url = None
218 if entry.repository is not None:
220 if entry.repository is not None:
219 _url = url('changelog_home',
221 _url = url('changelog_home',
220 repo_name=entry.repository.repo_name,
222 repo_name=entry.repository.repo_name,
221 qualified=True)
223 qualified=True)
222
224
223 feed.add_item(title=title,
225 feed.add_item(title=title,
224 pubdate=entry.action_date,
226 pubdate=entry.action_date,
225 link=_url or url('', qualified=True),
227 link=_url or url('', qualified=True),
226 author_email=entry.user.email,
228 author_email=entry.user.email,
227 author_name=entry.user.full_contact,
229 author_name=entry.user.full_contact,
228 description=desc)
230 description=desc)
229
231
230 response.content_type = feed.mime_type
232 response.content_type = feed.mime_type
231 return feed.writeString('utf-8')
233 return feed.writeString('utf-8')
232
234
233 def _rss_feed(self, repos, public=True):
235 def _rss_feed(self, repos, public=True):
234 journal = self._get_journal_data(repos)
236 journal = self._get_journal_data(repos)
235 if public:
237 if public:
236 _link = url('public_journal_atom', qualified=True)
238 _link = url('public_journal_atom', qualified=True)
237 _desc = '%s %s %s' % (c.rhodecode_name, _('public journal'),
239 _desc = '%s %s %s' % (c.rhodecode_name, _('public journal'),
238 'rss feed')
240 'rss feed')
239 else:
241 else:
240 _link = url('journal_atom', qualified=True)
242 _link = url('journal_atom', qualified=True)
241 _desc = '%s %s %s' % (c.rhodecode_name, _('journal'), 'rss feed')
243 _desc = '%s %s %s' % (c.rhodecode_name, _('journal'), 'rss feed')
242
244
243 feed = Rss201rev2Feed(title=_desc,
245 feed = Rss201rev2Feed(title=_desc,
244 link=_link,
246 link=_link,
245 description=_desc,
247 description=_desc,
246 language=self.language,
248 language=self.language,
247 ttl=self.ttl)
249 ttl=self.ttl)
248
250
249 for entry in journal[:self.feed_nr]:
251 for entry in journal[:self.feed_nr]:
250 action, action_extra, ico = h.action_parser(entry, feed=True)
252 action, action_extra, ico = h.action_parser(entry, feed=True)
251 title = "%s - %s %s" % (entry.user.short_contact, action(),
253 title = "%s - %s %s" % (entry.user.short_contact, action(),
252 entry.repository.repo_name)
254 entry.repository.repo_name)
253 desc = action_extra()
255 desc = action_extra()
254 _url = None
256 _url = None
255 if entry.repository is not None:
257 if entry.repository is not None:
256 _url = url('changelog_home',
258 _url = url('changelog_home',
257 repo_name=entry.repository.repo_name,
259 repo_name=entry.repository.repo_name,
258 qualified=True)
260 qualified=True)
259
261
260 feed.add_item(title=title,
262 feed.add_item(title=title,
261 pubdate=entry.action_date,
263 pubdate=entry.action_date,
262 link=_url or url('', qualified=True),
264 link=_url or url('', qualified=True),
263 author_email=entry.user.email,
265 author_email=entry.user.email,
264 author_name=entry.user.full_contact,
266 author_name=entry.user.full_contact,
265 description=desc)
267 description=desc)
266
268
267 response.content_type = feed.mime_type
269 response.content_type = feed.mime_type
268 return feed.writeString('utf-8')
270 return feed.writeString('utf-8')
269
271
270 @LoginRequired(api_access=True)
272 @LoginRequired(api_access=True)
271 def public_journal_atom(self):
273 def public_journal_atom(self):
272 """
274 """
273 Produce an atom-1.0 feed via feedgenerator module
275 Produce an atom-1.0 feed via feedgenerator module
274 """
276 """
275 c.following = self.sa.query(UserFollowing)\
277 c.following = self.sa.query(UserFollowing)\
276 .filter(UserFollowing.user_id == self.rhodecode_user.user_id)\
278 .filter(UserFollowing.user_id == self.rhodecode_user.user_id)\
277 .options(joinedload(UserFollowing.follows_repository))\
279 .options(joinedload(UserFollowing.follows_repository))\
278 .all()
280 .all()
279
281
280 return self._atom_feed(c.following)
282 return self._atom_feed(c.following)
281
283
282 @LoginRequired(api_access=True)
284 @LoginRequired(api_access=True)
283 def public_journal_rss(self):
285 def public_journal_rss(self):
284 """
286 """
285 Produce an rss2 feed via feedgenerator module
287 Produce an rss2 feed via feedgenerator module
286 """
288 """
287 c.following = self.sa.query(UserFollowing)\
289 c.following = self.sa.query(UserFollowing)\
288 .filter(UserFollowing.user_id == self.rhodecode_user.user_id)\
290 .filter(UserFollowing.user_id == self.rhodecode_user.user_id)\
289 .options(joinedload(UserFollowing.follows_repository))\
291 .options(joinedload(UserFollowing.follows_repository))\
290 .all()
292 .all()
291
293
292 return self._rss_feed(c.following)
294 return self._rss_feed(c.following)
@@ -1,225 +1,225 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <%inherit file="/base/base.html"/>
2 <%inherit file="/base/base.html"/>
3 <%def name="title()">
3 <%def name="title()">
4 ${_('Journal')} - ${c.rhodecode_name}
4 ${_('Journal')} - ${c.rhodecode_name}
5 </%def>
5 </%def>
6 <%def name="breadcrumbs()">
6 <%def name="breadcrumbs()">
7 ${c.rhodecode_name}
7 ${c.rhodecode_name}
8 </%def>
8 </%def>
9 <%def name="page_nav()">
9 <%def name="page_nav()">
10 ${self.menu('home')}
10 ${self.menu('home')}
11 </%def>
11 </%def>
12 <%def name="main()">
12 <%def name="main()">
13
13
14 <div class="box box-left">
14 <div class="box box-left">
15 <!-- box / title -->
15 <!-- box / title -->
16 <div class="title">
16 <div class="title">
17 <h5>${_('Journal')}</h5>
17 <h5>${_('Journal')}</h5>
18 <ul class="links">
18 <ul class="links">
19 <li>
19 <li>
20 <span><a id="refresh" href="${h.url('journal')}"><img class="icon" title="${_('Refresh')}" alt="${_('Refresh')}" src="${h.url('/images/icons/arrow_refresh.png')}"/></a></span>
20 <span><a id="refresh" href="${h.url('journal')}"><img class="icon" title="${_('Refresh')}" alt="${_('Refresh')}" src="${h.url('/images/icons/arrow_refresh.png')}"/></a></span>
21 </li>
21 </li>
22 <li>
22 <li>
23 <span><a href="${h.url('journal_rss')}"><img class="icon" title="${_('RSS feed')}" alt="${_('RSS feed')}" src="${h.url('/images/icons/atom.png')}"/></a></span>
23 <span><a href="${h.url('journal_rss', api_key=c.rhodecode_user.api_key)}"><img class="icon" title="${_('RSS feed')}" alt="${_('RSS feed')}" src="${h.url('/images/icons/atom.png')}"/></a></span>
24 </li>
24 </li>
25 <li>
25 <li>
26 <span><a href="${h.url('journal_atom')}"><img class="icon" title="${_('ATOM feed')}" alt="${_('ATOM feed')}" src="${h.url('/images/icons/rss_16.png')}"/></a></span>
26 <span><a href="${h.url('journal_atom', api_key=c.rhodecode_user.api_key)}"><img class="icon" title="${_('ATOM feed')}" alt="${_('ATOM feed')}" src="${h.url('/images/icons/rss_16.png')}"/></a></span>
27 </li>
27 </li>
28 </ul>
28 </ul>
29 </div>
29 </div>
30 <div id="journal">${c.journal_data}</div>
30 <div id="journal">${c.journal_data}</div>
31 </div>
31 </div>
32 <div class="box box-right">
32 <div class="box box-right">
33 <!-- box / title -->
33 <!-- box / title -->
34 <div class="title">
34 <div class="title">
35 <h5>
35 <h5>
36 <input class="q_filter_box" id="q_filter" size="15" type="text" name="filter" value="${_('quick filter...')}"/>
36 <input class="q_filter_box" id="q_filter" size="15" type="text" name="filter" value="${_('quick filter...')}"/>
37 <a id="show_my" class="link-white" href="#my">${_('My repos')}</a> / <a id="show_watched" class="link-white" href="#watched">${_('Watched')}</a>
37 <a id="show_my" class="link-white" href="#my">${_('My repos')}</a> / <a id="show_watched" class="link-white" href="#watched">${_('Watched')}</a>
38 </h5>
38 </h5>
39 %if h.HasPermissionAny('hg.admin','hg.create.repository')():
39 %if h.HasPermissionAny('hg.admin','hg.create.repository')():
40 <ul class="links">
40 <ul class="links">
41 <li>
41 <li>
42 <span>${h.link_to(_('ADD'),h.url('admin_settings_create_repository'))}</span>
42 <span>${h.link_to(_('ADD'),h.url('admin_settings_create_repository'))}</span>
43 </li>
43 </li>
44 </ul>
44 </ul>
45 %endif
45 %endif
46 </div>
46 </div>
47 <!-- end box / title -->
47 <!-- end box / title -->
48 <div id="my" class="table">
48 <div id="my" class="table">
49 %if c.user_repos:
49 %if c.user_repos:
50 <div id='repos_list_wrap' class="yui-skin-sam">
50 <div id='repos_list_wrap' class="yui-skin-sam">
51 <table id="repos_list">
51 <table id="repos_list">
52 <thead>
52 <thead>
53 <tr>
53 <tr>
54 <th></th>
54 <th></th>
55 <th class="left">${_('Name')}</th>
55 <th class="left">${_('Name')}</th>
56 <th class="left">${_('Revision')}</th>
56 <th class="left">${_('Revision')}</th>
57 <th class="left">${_('Action')}</th>
57 <th class="left">${_('Action')}</th>
58 <th class="left">${_('Action')}</th>
58 <th class="left">${_('Action')}</th>
59 </thead>
59 </thead>
60 <tbody>
60 <tbody>
61 <%namespace name="dt" file="/data_table/_dt_elements.html"/>
61 <%namespace name="dt" file="/data_table/_dt_elements.html"/>
62 %for repo in c.user_repos:
62 %for repo in c.user_repos:
63 <tr>
63 <tr>
64 ##QUICK MENU
64 ##QUICK MENU
65 <td class="quick_repo_menu">
65 <td class="quick_repo_menu">
66 ${dt.quick_menu(repo['name'])}
66 ${dt.quick_menu(repo['name'])}
67 </td>
67 </td>
68 ##REPO NAME AND ICONS
68 ##REPO NAME AND ICONS
69 <td class="reponame">
69 <td class="reponame">
70 ${dt.repo_name(repo['name'],repo['dbrepo']['repo_type'],repo['dbrepo']['private'],repo['dbrepo_fork'].get('repo_name'))}
70 ${dt.repo_name(repo['name'],repo['dbrepo']['repo_type'],repo['dbrepo']['private'],repo['dbrepo_fork'].get('repo_name'))}
71 </td>
71 </td>
72 ##LAST REVISION
72 ##LAST REVISION
73 <td>
73 <td>
74 ${dt.revision(repo['name'],repo['rev'],repo['tip'],repo['author'],repo['last_msg'])}
74 ${dt.revision(repo['name'],repo['rev'],repo['tip'],repo['author'],repo['last_msg'])}
75 </td>
75 </td>
76 ##
76 ##
77 <td><a href="${h.url('repo_settings_home',repo_name=repo['name'])}" title="${_('edit')}"><img class="icon" alt="${_('private')}" src="${h.url('/images/icons/application_form_edit.png')}"/></a></td>
77 <td><a href="${h.url('repo_settings_home',repo_name=repo['name'])}" title="${_('edit')}"><img class="icon" alt="${_('private')}" src="${h.url('/images/icons/application_form_edit.png')}"/></a></td>
78 <td>
78 <td>
79 ${h.form(url('repo_settings_delete', repo_name=repo['name']),method='delete')}
79 ${h.form(url('repo_settings_delete', repo_name=repo['name']),method='delete')}
80 ${h.submit('remove_%s' % repo['name'],'',class_="delete_icon action_button",onclick="return confirm('Confirm to delete this repository');")}
80 ${h.submit('remove_%s' % repo['name'],'',class_="delete_icon action_button",onclick="return confirm('Confirm to delete this repository');")}
81 ${h.end_form()}
81 ${h.end_form()}
82 </td>
82 </td>
83 </tr>
83 </tr>
84 %endfor
84 %endfor
85 </tbody>
85 </tbody>
86 </table>
86 </table>
87 </div>
87 </div>
88 %else:
88 %else:
89 <div style="padding:5px 0px 10px 0px;">
89 <div style="padding:5px 0px 10px 0px;">
90 ${_('No repositories yet')}
90 ${_('No repositories yet')}
91 %if h.HasPermissionAny('hg.admin','hg.create.repository')():
91 %if h.HasPermissionAny('hg.admin','hg.create.repository')():
92 ${h.link_to(_('create one now'),h.url('admin_settings_create_repository'),class_="ui-btn")}
92 ${h.link_to(_('create one now'),h.url('admin_settings_create_repository'),class_="ui-btn")}
93 %endif
93 %endif
94 </div>
94 </div>
95 %endif
95 %endif
96 </div>
96 </div>
97
97
98 <div id="watched" class="table" style="display:none">
98 <div id="watched" class="table" style="display:none">
99 %if c.following:
99 %if c.following:
100 <table>
100 <table>
101 <thead>
101 <thead>
102 <tr>
102 <tr>
103 <th class="left">${_('Name')}</th>
103 <th class="left">${_('Name')}</th>
104 </thead>
104 </thead>
105 <tbody>
105 <tbody>
106 %for entry in c.following:
106 %for entry in c.following:
107 <tr>
107 <tr>
108 <td>
108 <td>
109 %if entry.follows_user_id:
109 %if entry.follows_user_id:
110 <img title="${_('following user')}" alt="${_('user')}" src="${h.url('/images/icons/user.png')}"/>
110 <img title="${_('following user')}" alt="${_('user')}" src="${h.url('/images/icons/user.png')}"/>
111 ${entry.follows_user.full_contact}
111 ${entry.follows_user.full_contact}
112 %endif
112 %endif
113
113
114 %if entry.follows_repo_id:
114 %if entry.follows_repo_id:
115 <div style="float:right;padding-right:5px">
115 <div style="float:right;padding-right:5px">
116 <span id="follow_toggle_${entry.follows_repository.repo_id}" class="following" title="${_('Stop following this repository')}"
116 <span id="follow_toggle_${entry.follows_repository.repo_id}" class="following" title="${_('Stop following this repository')}"
117 onclick="javascript:toggleFollowingRepo(this,${entry.follows_repository.repo_id},'${str(h.get_token())}')">
117 onclick="javascript:toggleFollowingRepo(this,${entry.follows_repository.repo_id},'${str(h.get_token())}')">
118 </span>
118 </span>
119 </div>
119 </div>
120
120
121 %if h.is_hg(entry.follows_repository):
121 %if h.is_hg(entry.follows_repository):
122 <img class="icon" title="${_('Mercurial repository')}" alt="${_('Mercurial repository')}" src="${h.url('/images/icons/hgicon.png')}"/>
122 <img class="icon" title="${_('Mercurial repository')}" alt="${_('Mercurial repository')}" src="${h.url('/images/icons/hgicon.png')}"/>
123 %elif h.is_git(entry.follows_repository):
123 %elif h.is_git(entry.follows_repository):
124 <img class="icon" title="${_('Git repository')}" alt="${_('Git repository')}" src="${h.url('/images/icons/giticon.png')}"/>
124 <img class="icon" title="${_('Git repository')}" alt="${_('Git repository')}" src="${h.url('/images/icons/giticon.png')}"/>
125 %endif
125 %endif
126
126
127 %if entry.follows_repository.private:
127 %if entry.follows_repository.private:
128 <img class="icon" title="${_('private repository')}" alt="${_('private repository')}" src="${h.url('/images/icons/lock.png')}"/>
128 <img class="icon" title="${_('private repository')}" alt="${_('private repository')}" src="${h.url('/images/icons/lock.png')}"/>
129 %else:
129 %else:
130 <img class="icon" title="${_('public repository')}" alt="${_('public repository')}" src="${h.url('/images/icons/lock_open.png')}"/>
130 <img class="icon" title="${_('public repository')}" alt="${_('public repository')}" src="${h.url('/images/icons/lock_open.png')}"/>
131 %endif
131 %endif
132 <span class="watched_repo">
132 <span class="watched_repo">
133 ${h.link_to(entry.follows_repository.repo_name,h.url('summary_home',repo_name=entry.follows_repository.repo_name))}
133 ${h.link_to(entry.follows_repository.repo_name,h.url('summary_home',repo_name=entry.follows_repository.repo_name))}
134 </span>
134 </span>
135 %endif
135 %endif
136 </td>
136 </td>
137 </tr>
137 </tr>
138 %endfor
138 %endfor
139 </tbody>
139 </tbody>
140 </table>
140 </table>
141 %else:
141 %else:
142 <div style="padding:5px 0px 10px 0px;">
142 <div style="padding:5px 0px 10px 0px;">
143 ${_('You are not following any users or repositories')}
143 ${_('You are not following any users or repositories')}
144 </div>
144 </div>
145 %endif
145 %endif
146 </div>
146 </div>
147 </div>
147 </div>
148
148
149 <script type="text/javascript">
149 <script type="text/javascript">
150
150
151 YUE.on('show_my','click',function(e){
151 YUE.on('show_my','click',function(e){
152 YUD.setStyle('watched','display','none');
152 YUD.setStyle('watched','display','none');
153 YUD.setStyle('my','display','');
153 YUD.setStyle('my','display','');
154 var nodes = YUQ('#my tr td a.repo_name');
154 var nodes = YUQ('#my tr td a.repo_name');
155 var target = 'q_filter';
155 var target = 'q_filter';
156 var func = function(node){
156 var func = function(node){
157 return node.parentNode.parentNode.parentNode.parentNode;
157 return node.parentNode.parentNode.parentNode.parentNode;
158 }
158 }
159 q_filter(target,nodes,func);
159 q_filter(target,nodes,func);
160 YUE.preventDefault(e);
160 YUE.preventDefault(e);
161 })
161 })
162 YUE.on('show_watched','click',function(e){
162 YUE.on('show_watched','click',function(e){
163 YUD.setStyle('my','display','none');
163 YUD.setStyle('my','display','none');
164 YUD.setStyle('watched','display','');
164 YUD.setStyle('watched','display','');
165 var nodes = YUQ('#watched .watched_repo a');
165 var nodes = YUQ('#watched .watched_repo a');
166 var target = 'q_filter';
166 var target = 'q_filter';
167 var func = function(node){
167 var func = function(node){
168 return node.parentNode.parentNode;
168 return node.parentNode.parentNode;
169 }
169 }
170 q_filter(target,nodes,func);
170 q_filter(target,nodes,func);
171 YUE.preventDefault(e);
171 YUE.preventDefault(e);
172 })
172 })
173 YUE.on('refresh','click',function(e){
173 YUE.on('refresh','click',function(e){
174 ypjax(e.currentTarget.href,"journal",function(){show_more_event();tooltip_activate();});
174 ypjax(e.currentTarget.href,"journal",function(){show_more_event();tooltip_activate();});
175 YUE.preventDefault(e);
175 YUE.preventDefault(e);
176 });
176 });
177
177
178
178
179 // main table sorting
179 // main table sorting
180 var myColumnDefs = [
180 var myColumnDefs = [
181 {key:"menu",label:"",sortable:false,className:"quick_repo_menu hidden"},
181 {key:"menu",label:"",sortable:false,className:"quick_repo_menu hidden"},
182 {key:"name",label:"${_('Name')}",sortable:true,
182 {key:"name",label:"${_('Name')}",sortable:true,
183 sortOptions: { sortFunction: nameSort }},
183 sortOptions: { sortFunction: nameSort }},
184 {key:"tip",label:"${_('Tip')}",sortable:true,
184 {key:"tip",label:"${_('Tip')}",sortable:true,
185 sortOptions: { sortFunction: revisionSort }},
185 sortOptions: { sortFunction: revisionSort }},
186 {key:"action1",label:"",sortable:false},
186 {key:"action1",label:"",sortable:false},
187 {key:"action2",label:"",sortable:false},
187 {key:"action2",label:"",sortable:false},
188 ];
188 ];
189
189
190 var myDataSource = new YAHOO.util.DataSource(YUD.get("repos_list"));
190 var myDataSource = new YAHOO.util.DataSource(YUD.get("repos_list"));
191
191
192 myDataSource.responseType = YAHOO.util.DataSource.TYPE_HTMLTABLE;
192 myDataSource.responseType = YAHOO.util.DataSource.TYPE_HTMLTABLE;
193
193
194 myDataSource.responseSchema = {
194 myDataSource.responseSchema = {
195 fields: [
195 fields: [
196 {key:"menu"},
196 {key:"menu"},
197 {key:"name"},
197 {key:"name"},
198 {key:"tip"},
198 {key:"tip"},
199 {key:"action1"},
199 {key:"action1"},
200 {key:"action2"}
200 {key:"action2"}
201 ]
201 ]
202 };
202 };
203
203
204 var myDataTable = new YAHOO.widget.DataTable("repos_list_wrap", myColumnDefs, myDataSource,
204 var myDataTable = new YAHOO.widget.DataTable("repos_list_wrap", myColumnDefs, myDataSource,
205 {
205 {
206 sortedBy:{key:"name",dir:"asc"},
206 sortedBy:{key:"name",dir:"asc"},
207 MSG_SORTASC:"${_('Click to sort ascending')}",
207 MSG_SORTASC:"${_('Click to sort ascending')}",
208 MSG_SORTDESC:"${_('Click to sort descending')}",
208 MSG_SORTDESC:"${_('Click to sort descending')}",
209 MSG_EMPTY:"${_('No records found.')}",
209 MSG_EMPTY:"${_('No records found.')}",
210 MSG_ERROR:"${_('Data error.')}",
210 MSG_ERROR:"${_('Data error.')}",
211 MSG_LOADING:"${_('Loading...')}",
211 MSG_LOADING:"${_('Loading...')}",
212 }
212 }
213 );
213 );
214 myDataTable.subscribe('postRenderEvent',function(oArgs) {
214 myDataTable.subscribe('postRenderEvent',function(oArgs) {
215 tooltip_activate();
215 tooltip_activate();
216 quick_repo_menu();
216 quick_repo_menu();
217 var func = function(node){
217 var func = function(node){
218 return node.parentNode.parentNode.parentNode.parentNode;
218 return node.parentNode.parentNode.parentNode.parentNode;
219 }
219 }
220 q_filter('q_filter',YUQ('#my tr td a.repo_name'),func);
220 q_filter('q_filter',YUQ('#my tr td a.repo_name'),func);
221 });
221 });
222
222
223
223
224 </script>
224 </script>
225 </%def>
225 </%def>
General Comments 0
You need to be logged in to leave comments. Login now