add @cache_control(private=True) to @vary_on_cookie
[mygpo.git] / mygpo / web / views / episode.py
blob071db018cd15d443690ba4fdaa6e93fbc6741c06
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
31 from mygpo.api.constants import EPISODE_ACTION_TYPES
32 from mygpo.decorators import repeat_on_conflict
33 from mygpo.core import models
34 from mygpo.core.models import Podcast
35 from mygpo.core.proxy import proxy_object
36 from mygpo.core.models import Episode
37 from mygpo.users.models import Chapter, HistoryEntry, EpisodeAction
38 from mygpo.api import backend
39 from mygpo.utils import parse_time, get_to_dict
40 from mygpo.web.heatmap import EpisodeHeatmap
41 from mygpo.web.utils import get_episode_link_target
44 @vary_on_cookie
45 @cache_control(private=True)
46 def episode(request, episode):
48 podcast = Podcast.get(episode.podcast)
50 if not podcast:
51 raise Http404
53 if request.user.is_authenticated():
55 episode_state = episode.get_user_state(request.user)
56 is_fav = episode_state.is_favorite()
59 # pre-populate data for fetch_data
60 podcasts_dict = {podcast.get_id(): podcast}
61 episodes_dict = {episode._id: episode}
63 history = list(episode_state.get_history_entries())
64 HistoryEntry.fetch_data(request.user, history,
65 podcasts=podcasts_dict, episodes=episodes_dict)
67 played_parts = EpisodeHeatmap(podcast.get_id(),
68 episode._id, request.user._id, duration=episode.duration)
70 devices = dict( (d.id, d.name) for d in request.user.devices )
72 else:
73 history = []
74 is_fav = False
75 played_parts = None
76 devices = {}
79 chapters = []
80 for user, chapter in Chapter.for_episode(episode._id):
81 chapter.is_own = request.user.is_authenticated() and \
82 user == request.user._id
83 chapters.append(chapter)
86 prev = podcast.get_episode_before(episode)
87 next = podcast.get_episode_after(episode)
89 return render(request, 'episode.html', {
90 'episode': episode,
91 'podcast': podcast,
92 'prev': prev,
93 'next': next,
94 'history': history,
95 'chapters': chapters,
96 'is_favorite': is_fav,
97 'played_parts': played_parts,
98 'actions': EPISODE_ACTION_TYPES,
99 'devices': devices,
103 @never_cache
104 @login_required
105 def add_chapter(request, episode):
106 e_state = episode.get_user_state(request.user)
108 podcast = Podcast.get(episode.podcast)
110 try:
111 start = parse_time(request.POST.get('start', '0'))
113 if request.POST.get('end', '0'):
114 end = parse_time(request.POST.get('end', '0'))
115 else:
116 end = start
118 adv = 'advertisement' in request.POST
119 label = request.POST.get('label')
121 except Exception as e:
122 # FIXME: when using Django's messaging system, set error message
124 return HttpResponseRedirect(get_episode_link_target(episode, podcast))
127 chapter = Chapter()
128 chapter.start = start
129 chapter.end = end
130 chapter.advertisement = adv
131 chapter.label = label
133 e_state.update_chapters(add=[chapter])
135 return HttpResponseRedirect(get_episode_link_target(episode, podcast))
138 @never_cache
139 @login_required
140 def remove_chapter(request, episode, start, end):
141 e_state = episode.get_user_state(request.user)
143 remove = (int(start), int(end))
144 e_state.update_chapters(rem=[remove])
146 podcast = Podcast.get(episode.podcast)
148 return HttpResponseRedirect(get_episode_link_target(episode, podcast))
151 @never_cache
152 @login_required
153 def toggle_favorite(request, episode):
154 episode_state = episode.get_user_state(request.user)
155 is_fav = episode_state.is_favorite()
156 episode_state.set_favorite(not is_fav)
158 episode_state.save()
160 podcast = Podcast.get(episode.podcast)
162 return HttpResponseRedirect(get_episode_link_target(episode, podcast))
165 @vary_on_cookie
166 @cache_control(private=True)
167 @login_required
168 def list_favorites(request):
169 site = RequestSite(request)
171 episodes = backend.get_favorites(request.user)
172 podcast_ids = [episode.podcast for episode in episodes]
173 podcasts = get_to_dict(Podcast, podcast_ids, Podcast.get_id)
175 def set_podcast(episode):
176 episode = proxy_object(episode)
177 episode.podcast = podcasts.get(episode.podcast, None)
178 return episode
180 episodes = map(set_podcast, episodes)
182 feed_url = 'http://%s/%s' % (site.domain, reverse('favorites-feed', args=[request.user.username]))
184 podcast = Podcast.for_url(feed_url)
186 if 'public_feed' in request.GET:
187 request.user.favorite_feeds_token = ''
188 request.user.save()
190 elif 'private_feed' in request.GET:
191 request.user.create_new_token('favorite_feeds_token', 8)
192 request.user.save()
194 token = request.user.favorite_feeds_token
196 return render(request, 'favorites.html', {
197 'episodes': episodes,
198 'feed_token': token,
199 'site': site,
200 'podcast': podcast,
204 @never_cache
205 def add_action(request, episode):
207 device = request.user.get_device(request.POST.get('device'))
209 action_str = request.POST.get('action')
210 timestamp = request.POST.get('timestamp', '')
212 if timestamp:
213 try:
214 timestamp = dateutil.parser.parse(timestamp)
215 except:
216 timestamp = datetime.utcnow()
217 else:
218 timestamp = datetime.utcnow()
220 action = EpisodeAction()
221 action.timestamp = timestamp
222 action.device = device.id if device else None
223 action.action = action_str
225 state = episode.get_user_state(request.user)
227 @repeat_on_conflict(['action'])
228 def _add_action(action):
229 state.add_actions([action])
230 state.save()
232 _add_action(action=action)
234 podcast = Podcast.get(episode.podcast)
236 return HttpResponseRedirect(get_episode_link_target(episode, podcast))
238 # To make all view accessible via either CouchDB-ID for Slugs
239 # a decorator queries the episode and passes the Id on to the
240 # regular views
242 def slug_id_decorator(f):
243 @wraps(f)
244 def _decorator(request, p_slug_id, e_slug_id, *args, **kwargs):
245 episode = Episode.for_slug_id(p_slug_id, e_slug_id)
247 if episode is None:
248 raise Http404
250 return f(request, episode, *args, **kwargs)
252 return _decorator
255 def oldid_decorator(f):
256 @wraps(f)
257 def _decorator(request, id, *args, **kwargs):
258 episode = Episode.for_oldid(id)
260 if episode is None:
261 raise Http404
263 return f(request, episode, *args, **kwargs)
265 return _decorator
267 show_slug_id = slug_id_decorator(episode)
268 add_chapter_slug_id = slug_id_decorator(add_chapter)
269 remove_chapter_slug_id = slug_id_decorator(remove_chapter)
270 toggle_favorite_slug_id = slug_id_decorator(toggle_favorite)
271 add_action_slug_id = slug_id_decorator(add_action)
273 show_oldid = oldid_decorator(episode)
274 add_chapter_oldid = oldid_decorator(add_chapter)
275 remove_chapter_oldid = oldid_decorator(remove_chapter)
276 toggle_favorite_oldid = oldid_decorator(toggle_favorite)
277 add_action_oldid = oldid_decorator(add_action)