replace EpisodeUserState.update_chapters
[mygpo.git] / mygpo / web / views / episode.py
blob9eb92d2ab1fb4e6871cbb4e70f73e5660f7da30b
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 set_episode_favorite
45 from mygpo.db.couchdb.podcast import podcast_by_id, podcast_for_url, \
46 podcasts_to_dict
47 from mygpo.db.couchdb.episode_state import episode_state_for_user_episode, \
48 add_episode_actions, update_episode_chapters
49 from mygpo.db.couchdb.user import get_latest_episodes
50 from mygpo.userfeeds.feeds import FavoriteFeed
53 @vary_on_cookie
54 @cache_control(private=True)
55 def episode(request, episode):
57 podcast = podcast_by_id(episode.podcast)
58 user = request.user
60 if not podcast:
61 raise Http404
63 if user.is_authenticated():
65 episode_state = episode_state_for_user_episode(user, episode)
66 is_fav = episode_state.is_favorite()
69 # pre-populate data for fetch_data
70 podcasts_dict = {podcast.get_id(): podcast}
71 episodes_dict = {episode._id: episode}
73 history = list(episode_state.get_history_entries())
74 HistoryEntry.fetch_data(user, history,
75 podcasts=podcasts_dict, episodes=episodes_dict)
77 played_parts = EpisodeHeatmap(podcast.get_id(),
78 episode._id, user._id, duration=episode.duration)
80 devices = dict( (d.id, d.name) for d in user.devices )
81 can_flattr = user.get_wksetting(FLATTR_TOKEN) and episode.flattr_url
83 else:
84 history = []
85 is_fav = False
86 played_parts = None
87 devices = {}
88 can_flattr = False
90 is_publisher = check_publisher_permission(user, podcast)
92 chapters = []
93 for user_id, chapter in chapters_for_episode(episode._id):
94 chapter.is_own = user.is_authenticated() and \
95 user_id == user._id
96 chapters.append(chapter)
99 prev = podcast.get_episode_before(episode)
100 next = podcast.get_episode_after(episode)
102 return render(request, 'episode.html', {
103 'episode': episode,
104 'podcast': podcast,
105 'prev': prev,
106 'next': next,
107 'history': history,
108 'chapters': chapters,
109 'is_favorite': is_fav,
110 'played_parts': played_parts,
111 'actions': EPISODE_ACTION_TYPES,
112 'devices': devices,
113 'can_flattr': can_flattr,
114 'is_publisher': is_publisher,
118 @never_cache
119 @login_required
120 def add_chapter(request, episode):
121 e_state = episode_state_for_user_episode(request.user, episode)
123 podcast = podcast_by_id(episode.podcast)
125 try:
126 start = parse_time(request.POST.get('start', '0'))
128 if request.POST.get('end', '0'):
129 end = parse_time(request.POST.get('end', '0'))
130 else:
131 end = start
133 adv = 'advertisement' in request.POST
134 label = request.POST.get('label')
136 except ValueError as e:
137 messages.error(request,
138 _('Could not add Chapter: {msg}'.format(msg=str(e))))
140 return HttpResponseRedirect(get_episode_link_target(episode, podcast))
143 chapter = Chapter()
144 chapter.start = start
145 chapter.end = end
146 chapter.advertisement = adv
147 chapter.label = label
149 update_episode_chapters(e_state, add=[chapter])
151 return HttpResponseRedirect(get_episode_link_target(episode, podcast))
154 @never_cache
155 @login_required
156 def remove_chapter(request, episode, start, end):
157 e_state = episode_state_for_user_episode(request.user, episode)
159 remove = (int(start), int(end))
160 update_episode_chapters(e_state, rem=[remove])
162 podcast = podcast_by_id(episode.podcast)
164 return HttpResponseRedirect(get_episode_link_target(episode, podcast))
167 @never_cache
168 @login_required
169 def toggle_favorite(request, episode):
170 episode_state = episode_state_for_user_episode(request.user, episode)
172 is_fav = episode_state.is_favorite()
173 set_episode_favorite(episode_state, not is_fav)
175 podcast = podcast_by_id(episode.podcast)
177 return HttpResponseRedirect(get_episode_link_target(episode, podcast))
181 @vary_on_cookie
182 @cache_control(private=True)
183 @login_required
184 def list_favorites(request):
185 user = request.user
186 site = RequestSite(request)
188 episodes = favorite_episodes_for_user(user)
190 recently_listened = get_latest_episodes(user)
192 podcast_ids = [episode.podcast for episode in episodes + recently_listened]
193 podcasts = podcasts_to_dict(podcast_ids)
195 recently_listened = fetch_episode_data(recently_listened, podcasts=podcasts)
196 episodes = fetch_episode_data(episodes, podcasts=podcasts)
198 favfeed = FavoriteFeed(user)
199 feed_url = favfeed.get_public_url(site.domain)
201 podcast = podcast_for_url(feed_url)
203 token = request.user.favorite_feeds_token
205 return render(request, 'favorites.html', {
206 'episodes': episodes,
207 'feed_token': token,
208 'site': site,
209 'podcast': podcast,
210 'recently_listened': recently_listened,
214 @never_cache
215 def add_action(request, episode):
217 device = request.user.get_device(request.POST.get('device'))
219 action_str = request.POST.get('action')
220 timestamp = request.POST.get('timestamp', '')
222 if timestamp:
223 try:
224 timestamp = dateutil.parser.parse(timestamp)
225 except (ValueError, AttributeError):
226 timestamp = datetime.utcnow()
227 else:
228 timestamp = datetime.utcnow()
230 action = EpisodeAction()
231 action.timestamp = timestamp
232 action.upload_timestamp = get_timestamp(datetime.utcnow())
233 action.device = device.id if device else None
234 action.action = action_str
236 state = episode_state_for_user_episode(request.user, episode)
237 add_episode_actions(state, [action])
239 podcast = podcast_by_id(episode.podcast)
240 return HttpResponseRedirect(get_episode_link_target(episode, podcast))
243 @never_cache
244 @login_required
245 def flattr_episode(request, episode):
246 """ Flattrs an episode, records an event and redirects to the episode """
248 user = request.user
249 site = RequestSite(request)
251 # Flattr via the tasks queue, but wait for the result
252 task = flattr_thing.delay(user, episode._id, site.domain,
253 request.is_secure(), 'Episode')
254 success, msg = task.get()
256 if success:
257 action = EpisodeAction()
258 action.action = 'flattr'
259 action.upload_timestamp = get_timestamp(datetime.utcnow())
260 state = episode_state_for_user_episode(request.user, episode)
261 add_episode_actions(state, [action])
262 messages.success(request, _("Flattr\'d"))
264 else:
265 messages.error(request, msg)
267 podcast = podcast_by_id(episode.podcast)
268 return HttpResponseRedirect(get_episode_link_target(episode, podcast))
271 # To make all view accessible via either CouchDB-ID for Slugs
272 # a decorator queries the episode and passes the Id on to the
273 # regular views
275 def slug_id_decorator(f):
276 @wraps(f)
277 def _decorator(request, p_slug_id, e_slug_id, *args, **kwargs):
278 episode = episode_for_slug_id(p_slug_id, e_slug_id)
280 if episode is None:
281 raise Http404
283 # redirect when Id or a merged (non-cannonical) slug is used
284 if episode.slug and episode.slug != e_slug_id:
285 podcast = podcast_by_id(episode.podcast)
286 return HttpResponseRedirect(
287 get_episode_link_target(episode, podcast))
289 return f(request, episode, *args, **kwargs)
291 return _decorator
294 def oldid_decorator(f):
295 @wraps(f)
296 def _decorator(request, id, *args, **kwargs):
297 episode = episode_for_oldid(id)
299 if episode is None:
300 raise Http404
302 # redirect to Id or slug URL
303 podcast = podcast_by_id(episode.podcast)
304 return HttpResponseRedirect(get_episode_link_target(episode, podcast))
306 return _decorator
308 show_slug_id = slug_id_decorator(episode)
309 add_chapter_slug_id = slug_id_decorator(add_chapter)
310 remove_chapter_slug_id = slug_id_decorator(remove_chapter)
311 toggle_favorite_slug_id = slug_id_decorator(toggle_favorite)
312 add_action_slug_id = slug_id_decorator(add_action)
313 flattr_episode_slug_id = slug_id_decorator(flattr_episode)
315 show_oldid = oldid_decorator(episode)
316 add_chapter_oldid = oldid_decorator(add_chapter)
317 remove_chapter_oldid = oldid_decorator(remove_chapter)
318 toggle_favorite_oldid = oldid_decorator(toggle_favorite)
319 add_action_oldid = oldid_decorator(add_action)
320 flattr_episode_oldid = oldid_decorator(flattr_episode)