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