move all User and EpisodetUserState db queries into separate module
[mygpo.git] / mygpo / web / views / episode.py
blobc64a1bbc925e3ef8bfed0945d78691197b7696b7
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.core.urlresolvers import reverse
26 from django.contrib.auth.decorators import login_required
27 from django.contrib.sites.models import RequestSite
28 from django.views.decorators.vary import vary_on_cookie
29 from django.views.decorators.cache import never_cache, cache_control
30 from django.views.generic.base import View
31 from django.utils.decorators import method_decorator
32 from django.contrib import messages
33 from django.utils.translation import ugettext as _
35 from mygpo.api.constants import EPISODE_ACTION_TYPES
36 from mygpo.decorators import repeat_on_conflict
37 from mygpo.core import models
38 from mygpo.core.proxy import proxy_object
39 from mygpo.core.models import Podcast
40 from mygpo.users.models import Chapter, HistoryEntry, EpisodeAction
41 from mygpo.utils import parse_time
42 from mygpo.web.heatmap import EpisodeHeatmap
43 from mygpo.web.utils import get_episode_link_target, fetch_episode_data
44 from mygpo.db.couchdb.episode import episode_for_slug_id, episode_for_oldid, \
45 favorite_episodes_for_user
46 from mygpo.db.couchdb.podcast import podcast_by_id, podcast_for_url, \
47 podcasts_to_dict
48 from mygpo.db.couchdb.episode_state import episode_state_for_user_episode
49 from mygpo.db.couchdb.user import get_latest_episodes
52 @vary_on_cookie
53 @cache_control(private=True)
54 def episode(request, episode):
56 podcast = podcast_by_id(episode.podcast)
58 if not podcast:
59 raise Http404
61 if request.user.is_authenticated():
63 episode_state = episode_state_for_user_episode(request.user, episode)
64 is_fav = episode_state.is_favorite()
67 # pre-populate data for fetch_data
68 podcasts_dict = {podcast.get_id(): podcast}
69 episodes_dict = {episode._id: episode}
71 history = list(episode_state.get_history_entries())
72 HistoryEntry.fetch_data(request.user, history,
73 podcasts=podcasts_dict, episodes=episodes_dict)
75 played_parts = EpisodeHeatmap(podcast.get_id(),
76 episode._id, request.user._id, duration=episode.duration)
78 devices = dict( (d.id, d.name) for d in request.user.devices )
80 else:
81 history = []
82 is_fav = False
83 played_parts = None
84 devices = {}
87 chapters = []
88 for user, chapter in Chapter.for_episode(episode._id):
89 chapter.is_own = request.user.is_authenticated() and \
90 user == request.user._id
91 chapters.append(chapter)
94 prev = podcast.get_episode_before(episode)
95 next = podcast.get_episode_after(episode)
97 return render(request, 'episode.html', {
98 'episode': episode,
99 'podcast': podcast,
100 'prev': prev,
101 'next': next,
102 'history': history,
103 'chapters': chapters,
104 'is_favorite': is_fav,
105 'played_parts': played_parts,
106 'actions': EPISODE_ACTION_TYPES,
107 'devices': devices,
111 @never_cache
112 @login_required
113 def add_chapter(request, episode):
114 e_state = episode_state_for_user_episode(request.user, episode)
116 podcast = podcast_by_id(episode.podcast)
118 try:
119 start = parse_time(request.POST.get('start', '0'))
121 if request.POST.get('end', '0'):
122 end = parse_time(request.POST.get('end', '0'))
123 else:
124 end = start
126 adv = 'advertisement' in request.POST
127 label = request.POST.get('label')
129 except ValueError as e:
130 messages.error(request,
131 _('Could not add Chapter: {msg}'.format(msg=str(e))))
133 return HttpResponseRedirect(get_episode_link_target(episode, podcast))
136 chapter = Chapter()
137 chapter.start = start
138 chapter.end = end
139 chapter.advertisement = adv
140 chapter.label = label
142 e_state.update_chapters(add=[chapter])
144 return HttpResponseRedirect(get_episode_link_target(episode, podcast))
147 @never_cache
148 @login_required
149 def remove_chapter(request, episode, start, end):
150 e_state = episode_state_for_user_episode(request.user, episode)
152 remove = (int(start), int(end))
153 e_state.update_chapters(rem=[remove])
155 podcast = podcast_by_id(episode.podcast)
157 return HttpResponseRedirect(get_episode_link_target(episode, podcast))
160 @never_cache
161 @login_required
162 def toggle_favorite(request, episode):
163 episode_state = episode_state_for_user_episode(request.user, episode)
164 is_fav = episode_state.is_favorite()
165 episode_state.set_favorite(not is_fav)
167 episode_state.save()
169 podcast = podcast_by_id(episode.podcast)
171 return HttpResponseRedirect(get_episode_link_target(episode, podcast))
175 @vary_on_cookie
176 @cache_control(private=True)
177 @login_required
178 def list_favorites(request):
179 user = request.user
180 site = RequestSite(request)
182 episodes = favorite_episodes_for_user(user)
184 recently_listened = get_latest_episodes(user)
186 podcast_ids = [episode.podcast for episode in episodes + recently_listened]
187 podcasts = podcasts_to_dict(podcast_ids)
189 recently_listened = fetch_episode_data(recently_listened, podcasts=podcasts)
190 episodes = fetch_episode_data(episodes, podcasts=podcasts)
192 favfeed = FavoriteFeed(user)
193 feed_url = favfeed.get_public_url(site.domain)
195 podcast = podcast_for_url(feed_url)
197 token = request.user.favorite_feeds_token
199 return render(request, 'favorites.html', {
200 'episodes': episodes,
201 'feed_token': token,
202 'site': site,
203 'podcast': podcast,
204 'recently_listened': recently_listened,
208 @never_cache
209 def add_action(request, episode):
211 device = request.user.get_device(request.POST.get('device'))
213 action_str = request.POST.get('action')
214 timestamp = request.POST.get('timestamp', '')
216 if timestamp:
217 try:
218 timestamp = dateutil.parser.parse(timestamp)
219 except (ValueError, AttributeError):
220 timestamp = datetime.utcnow()
221 else:
222 timestamp = datetime.utcnow()
224 action = EpisodeAction()
225 action.timestamp = timestamp
226 action.device = device.id if device else None
227 action.action = action_str
229 state = episode_state_for_user_episode(request.user, episode)
231 @repeat_on_conflict(['action'])
232 def _add_action(action):
233 state.add_actions([action])
234 state.save()
236 _add_action(action=action)
238 podcast = podcast_by_id(episode.podcast)
240 return HttpResponseRedirect(get_episode_link_target(episode, podcast))
242 # To make all view accessible via either CouchDB-ID for Slugs
243 # a decorator queries the episode and passes the Id on to the
244 # regular views
246 def slug_id_decorator(f):
247 @wraps(f)
248 def _decorator(request, p_slug_id, e_slug_id, *args, **kwargs):
249 episode = episode_for_slug_id(p_slug_id, e_slug_id)
251 if episode is None:
252 raise Http404
254 return f(request, episode, *args, **kwargs)
256 return _decorator
259 def oldid_decorator(f):
260 @wraps(f)
261 def _decorator(request, id, *args, **kwargs):
262 episode = episode_for_oldid(id)
264 if episode is None:
265 raise Http404
267 return f(request, episode, *args, **kwargs)
269 return _decorator
271 show_slug_id = slug_id_decorator(episode)
272 add_chapter_slug_id = slug_id_decorator(add_chapter)
273 remove_chapter_slug_id = slug_id_decorator(remove_chapter)
274 toggle_favorite_slug_id = slug_id_decorator(toggle_favorite)
275 add_action_slug_id = slug_id_decorator(add_action)
277 show_oldid = oldid_decorator(episode)
278 add_chapter_oldid = oldid_decorator(add_chapter)
279 remove_chapter_oldid = oldid_decorator(remove_chapter)
280 toggle_favorite_oldid = oldid_decorator(toggle_favorite)
281 add_action_oldid = oldid_decorator(add_action)