let podcasts, episodes link to their publisher pages
[mygpo.git] / mygpo / web / views / episode.py
blob27a1bc6e01da924ca19df526f70e4fe50a4aef88
2 # This file is part of my.gpodder.org.
4 # my.gpodder.org is free software: you can redistribute it and/or modify it
5 # under the terms of the GNU Affero General Public License as published by
6 # the Free Software Foundation, either version 3 of the License, or (at your
7 # option) any later version.
9 # my.gpodder.org is distributed in the hope that it will be useful, but
10 # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
11 # or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
12 # License for more details.
14 # You should have received a copy of the GNU Affero General Public License
15 # along with my.gpodder.org. If not, see <http://www.gnu.org/licenses/>.
18 from datetime import datetime
19 from functools import wraps
21 import dateutil.parser
23 from django.shortcuts import render
24 from django.http import HttpResponseRedirect, Http404
25 from django.contrib.auth.decorators import login_required
26 from django.contrib.sites.models import RequestSite
27 from django.views.decorators.vary import vary_on_cookie
28 from django.views.decorators.cache import never_cache, cache_control
29 from django.contrib import messages
30 from django.utils.translation import ugettext as _
32 from mygpo.api.constants import EPISODE_ACTION_TYPES
33 from mygpo.decorators import repeat_on_conflict
34 from mygpo.core.proxy import proxy_object
35 from mygpo.core.tasks import flattr_thing
36 from mygpo.users.models import Chapter, HistoryEntry, EpisodeAction
37 from mygpo.utils import parse_time, get_timestamp
38 from mygpo.users.settings import FLATTR_TOKEN
39 from mygpo.web.heatmap import EpisodeHeatmap
40 from mygpo.publisher.utils import check_publisher_permission
41 from mygpo.web.utils import get_episode_link_target, fetch_episode_data
42 from mygpo.db.couchdb.episode import episode_for_slug_id, episode_for_oldid, \
43 favorite_episodes_for_user, chapters_for_episode
44 from mygpo.db.couchdb.podcast import podcast_by_id, podcast_for_url, \
45 podcasts_to_dict
46 from mygpo.db.couchdb.episode_state import episode_state_for_user_episode, \
47 add_episode_actions
48 from mygpo.db.couchdb.user import get_latest_episodes
49 from mygpo.userfeeds.feeds import FavoriteFeed
52 @vary_on_cookie
53 @cache_control(private=True)
54 def episode(request, episode):
56 podcast = podcast_by_id(episode.podcast)
57 user = request.user
59 if not podcast:
60 raise Http404
62 if user.is_authenticated():
64 episode_state = episode_state_for_user_episode(user, episode)
65 is_fav = episode_state.is_favorite()
68 # pre-populate data for fetch_data
69 podcasts_dict = {podcast.get_id(): podcast}
70 episodes_dict = {episode._id: episode}
72 history = list(episode_state.get_history_entries())
73 HistoryEntry.fetch_data(user, history,
74 podcasts=podcasts_dict, episodes=episodes_dict)
76 played_parts = EpisodeHeatmap(podcast.get_id(),
77 episode._id, user._id, duration=episode.duration)
79 devices = dict( (d.id, d.name) for d in user.devices )
80 can_flattr = user.get_wksetting(FLATTR_TOKEN) and episode.flattr_url
82 else:
83 history = []
84 is_fav = False
85 played_parts = None
86 devices = {}
87 can_flattr = False
89 is_publisher = check_publisher_permission(user, podcast)
91 chapters = []
92 for user_id, chapter in chapters_for_episode(episode._id):
93 chapter.is_own = user.is_authenticated() and \
94 user_id == user._id
95 chapters.append(chapter)
98 prev = podcast.get_episode_before(episode)
99 next = podcast.get_episode_after(episode)
101 return render(request, 'episode.html', {
102 'episode': episode,
103 'podcast': podcast,
104 'prev': prev,
105 'next': next,
106 'history': history,
107 'chapters': chapters,
108 'is_favorite': is_fav,
109 'played_parts': played_parts,
110 'actions': EPISODE_ACTION_TYPES,
111 'devices': devices,
112 'can_flattr': can_flattr,
113 'is_publisher': is_publisher,
117 @never_cache
118 @login_required
119 def add_chapter(request, episode):
120 e_state = episode_state_for_user_episode(request.user, episode)
122 podcast = podcast_by_id(episode.podcast)
124 try:
125 start = parse_time(request.POST.get('start', '0'))
127 if request.POST.get('end', '0'):
128 end = parse_time(request.POST.get('end', '0'))
129 else:
130 end = start
132 adv = 'advertisement' in request.POST
133 label = request.POST.get('label')
135 except ValueError as e:
136 messages.error(request,
137 _('Could not add Chapter: {msg}'.format(msg=str(e))))
139 return HttpResponseRedirect(get_episode_link_target(episode, podcast))
142 chapter = Chapter()
143 chapter.start = start
144 chapter.end = end
145 chapter.advertisement = adv
146 chapter.label = label
148 e_state.update_chapters(add=[chapter])
150 return HttpResponseRedirect(get_episode_link_target(episode, podcast))
153 @never_cache
154 @login_required
155 def remove_chapter(request, episode, start, end):
156 e_state = episode_state_for_user_episode(request.user, episode)
158 remove = (int(start), int(end))
159 e_state.update_chapters(rem=[remove])
161 podcast = podcast_by_id(episode.podcast)
163 return HttpResponseRedirect(get_episode_link_target(episode, podcast))
166 @never_cache
167 @login_required
168 def toggle_favorite(request, episode):
169 episode_state = episode_state_for_user_episode(request.user, episode)
171 @repeat_on_conflict(['episode_state'])
172 def _set_fav(episode_state, is_fav):
173 episode_state.set_favorite(is_fav)
174 episode_state.save()
176 is_fav = episode_state.is_favorite()
177 _set_fav(episode_state=episode_state, is_fav=not is_fav)
179 podcast = podcast_by_id(episode.podcast)
181 return HttpResponseRedirect(get_episode_link_target(episode, podcast))
185 @vary_on_cookie
186 @cache_control(private=True)
187 @login_required
188 def list_favorites(request):
189 user = request.user
190 site = RequestSite(request)
192 episodes = favorite_episodes_for_user(user)
194 recently_listened = get_latest_episodes(user)
196 podcast_ids = [episode.podcast for episode in episodes + recently_listened]
197 podcasts = podcasts_to_dict(podcast_ids)
199 recently_listened = fetch_episode_data(recently_listened, podcasts=podcasts)
200 episodes = fetch_episode_data(episodes, podcasts=podcasts)
202 favfeed = FavoriteFeed(user)
203 feed_url = favfeed.get_public_url(site.domain)
205 podcast = podcast_for_url(feed_url)
207 token = request.user.favorite_feeds_token
209 return render(request, 'favorites.html', {
210 'episodes': episodes,
211 'feed_token': token,
212 'site': site,
213 'podcast': podcast,
214 'recently_listened': recently_listened,
218 @never_cache
219 def add_action(request, episode):
221 device = request.user.get_device(request.POST.get('device'))
223 action_str = request.POST.get('action')
224 timestamp = request.POST.get('timestamp', '')
226 if timestamp:
227 try:
228 timestamp = dateutil.parser.parse(timestamp)
229 except (ValueError, AttributeError):
230 timestamp = datetime.utcnow()
231 else:
232 timestamp = datetime.utcnow()
234 action = EpisodeAction()
235 action.timestamp = timestamp
236 action.upload_timestamp = get_timestamp(datetime.utcnow())
237 action.device = device.id if device else None
238 action.action = action_str
240 state = episode_state_for_user_episode(request.user, episode)
241 add_episode_actions(state, [action])
243 podcast = podcast_by_id(episode.podcast)
244 return HttpResponseRedirect(get_episode_link_target(episode, podcast))
247 @never_cache
248 @login_required
249 def flattr_episode(request, episode):
250 """ Flattrs an episode, records an event and redirects to the episode """
252 user = request.user
253 site = RequestSite(request)
255 # Flattr via the tasks queue, but wait for the result
256 task = flattr_thing.delay(user, episode._id, site.domain,
257 request.is_secure(), 'Episode')
258 success, msg = task.get()
260 if success:
261 action = EpisodeAction()
262 action.action = 'flattr'
263 action.upload_timestamp = get_timestamp(datetime.utcnow())
264 state = episode_state_for_user_episode(request.user, episode)
265 add_episode_actions(state, [action])
266 messages.success(request, _("Flattr\'d"))
268 else:
269 messages.error(request, msg)
271 podcast = podcast_by_id(episode.podcast)
272 return HttpResponseRedirect(get_episode_link_target(episode, podcast))
275 # To make all view accessible via either CouchDB-ID for Slugs
276 # a decorator queries the episode and passes the Id on to the
277 # regular views
279 def slug_id_decorator(f):
280 @wraps(f)
281 def _decorator(request, p_slug_id, e_slug_id, *args, **kwargs):
282 episode = episode_for_slug_id(p_slug_id, e_slug_id)
284 if episode is None:
285 raise Http404
287 # redirect when Id or a merged (non-cannonical) slug is used
288 if episode.slug and episode.slug != e_slug_id:
289 podcast = podcast_by_id(episode.podcast)
290 return HttpResponseRedirect(
291 get_episode_link_target(episode, podcast))
293 return f(request, episode, *args, **kwargs)
295 return _decorator
298 def oldid_decorator(f):
299 @wraps(f)
300 def _decorator(request, id, *args, **kwargs):
301 episode = episode_for_oldid(id)
303 if episode is None:
304 raise Http404
306 # redirect to Id or slug URL
307 podcast = podcast_by_id(episode.podcast)
308 return HttpResponseRedirect(get_episode_link_target(episode, podcast))
310 return _decorator
312 show_slug_id = slug_id_decorator(episode)
313 add_chapter_slug_id = slug_id_decorator(add_chapter)
314 remove_chapter_slug_id = slug_id_decorator(remove_chapter)
315 toggle_favorite_slug_id = slug_id_decorator(toggle_favorite)
316 add_action_slug_id = slug_id_decorator(add_action)
317 flattr_episode_slug_id = slug_id_decorator(flattr_episode)
319 show_oldid = oldid_decorator(episode)
320 add_chapter_oldid = oldid_decorator(add_chapter)
321 remove_chapter_oldid = oldid_decorator(remove_chapter)
322 toggle_favorite_oldid = oldid_decorator(toggle_favorite)
323 add_action_oldid = oldid_decorator(add_action)
324 flattr_episode_oldid = oldid_decorator(flattr_episode)