[Web] fix broken test
[mygpo.git] / mygpo / web / views / episode.py
blobad8e05b762d9b996c63442a424a6b4d48dd34369
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.contrib.contenttypes.models import ContentType
28 from django.views.decorators.vary import vary_on_cookie
29 from django.views.decorators.cache import never_cache, cache_control
30 from django.contrib import messages
31 from django.utils.translation import ugettext as _
33 from mygpo.podcasts.models import Podcast, Episode
34 from mygpo.api.constants import EPISODE_ACTION_TYPES
35 from mygpo.decorators import repeat_on_conflict
36 from mygpo.core.proxy import proxy_object
37 from mygpo.core.tasks import flattr_thing
38 from mygpo.users.models import Chapter, HistoryEntry, EpisodeAction
39 from mygpo.utils import parse_time, get_timestamp
40 from mygpo.users.settings import FLATTR_TOKEN
41 from mygpo.web.heatmap import EpisodeHeatmap
42 from mygpo.publisher.utils import check_publisher_permission
43 from mygpo.web.utils import get_episode_link_target, check_restrictions
44 from mygpo.db.couchdb.episode_state import favorite_episode_ids_for_user, \
45 chapters_for_episode, set_episode_favorite
46 from mygpo.db.couchdb.episode_state import episode_state_for_user_episode, \
47 add_episode_actions, update_episode_chapters
48 from mygpo.db.couchdb.user import get_latest_episode_ids
49 from mygpo.userfeeds.feeds import FavoriteFeed
52 @vary_on_cookie
53 @cache_control(private=True)
54 def episode(request, episode):
56 podcast = episode.podcast
58 podcast = check_restrictions(podcast)
60 user = request.user
62 if not podcast:
63 raise Http404
65 if user.is_authenticated():
67 episode_state = episode_state_for_user_episode(user, episode)
68 is_fav = episode_state.is_favorite()
71 # pre-populate data for fetch_data
72 podcasts_dict = {podcast.get_id(): podcast}
73 episodes_dict = {episode.id.hex: episode}
75 has_history = bool(list(episode_state.get_history_entries()))
77 played_parts = EpisodeHeatmap(podcast.get_id(),
78 episode.id, user.profile.uuid.hex, duration=episode.duration)
80 devices = {c.id.hex: c for c in user.client_set.all()}
81 can_flattr = user.profile.get_wksetting(FLATTR_TOKEN) and episode.flattr_url
83 else:
84 has_history = False
85 is_fav = False
86 played_parts = None
87 devices = {}
88 can_flattr = False
90 is_publisher = check_publisher_permission(user, podcast)
92 prev = None #podcast.get_episode_before(episode)
93 next = None #podcast.get_episode_after(episode)
95 return render(request, 'episode.html', {
96 'episode': episode,
97 'podcast': podcast,
98 'prev': prev,
99 'next': next,
100 'has_history': has_history,
101 'is_favorite': is_fav,
102 'played_parts': played_parts,
103 'actions': EPISODE_ACTION_TYPES,
104 'devices': devices,
105 'can_flattr': can_flattr,
106 'is_publisher': is_publisher,
110 @never_cache
111 @login_required
112 @vary_on_cookie
113 @cache_control(private=True)
114 def history(request, episode):
115 """ shows the history of the episode """
117 user = request.user
118 podcast = episode.podcast
119 episode_state = episode_state_for_user_episode(user, episode)
121 # pre-populate data for fetch_data
122 podcasts_dict = {podcast.get_id(): podcast}
123 episodes_dict = {episode._id: episode}
125 history = list(episode_state.get_history_entries())
126 HistoryEntry.fetch_data(user, history,
127 podcasts=podcasts_dict, episodes=episodes_dict)
129 devices = {c.id.hex: c.name for c in user.client_set.all()}
131 return render(request, 'episode-history.html', {
132 'episode': episode,
133 'podcast': podcast,
134 'history': history,
135 'actions': EPISODE_ACTION_TYPES,
136 'devices': devices,
140 @never_cache
141 @login_required
142 def toggle_favorite(request, episode):
143 episode_state = episode_state_for_user_episode(request.user, episode)
145 is_fav = episode_state.is_favorite()
146 set_episode_favorite(episode_state, not is_fav)
148 podcast = episode.podcast
150 return HttpResponseRedirect(get_episode_link_target(episode, podcast))
154 @vary_on_cookie
155 @cache_control(private=True)
156 @login_required
157 def list_favorites(request):
158 user = request.user
159 site = RequestSite(request)
161 favorite_ids = favorite_episode_ids_for_user(user)
162 favorites = Episode.objects.filter(id__in=favorite_ids)\
163 .select_related('podcast')\
164 .prefetch_related('slugs', 'podcast__slugs')
166 recently_listened_ids = get_latest_episode_ids(user)
167 recently_listened = Episode.objects.filter(id__in=recently_listened_ids)\
168 .select_related('podcast')\
169 .prefetch_related('slugs',
170 'podcast__slugs')
172 favfeed = FavoriteFeed(user)
173 feed_url = favfeed.get_public_url(site.domain)
175 podcast = Podcast.objects.filter(urls__url=feed_url).first()
177 token = request.user.profile.favorite_feeds_token
179 return render(request, 'favorites.html', {
180 'episodes': favorites,
181 'feed_token': token,
182 'site': site,
183 'podcast': podcast,
184 'recently_listened': recently_listened,
188 @never_cache
189 def add_action(request, episode):
191 device = request.user.get_device(request.POST.get('device'))
193 action_str = request.POST.get('action')
194 timestamp = request.POST.get('timestamp', '')
196 if timestamp:
197 try:
198 timestamp = dateutil.parser.parse(timestamp)
199 except (ValueError, AttributeError):
200 timestamp = datetime.utcnow()
201 else:
202 timestamp = datetime.utcnow()
204 action = EpisodeAction()
205 action.timestamp = timestamp
206 action.upload_timestamp = get_timestamp(datetime.utcnow())
207 action.device = device.id if device else None
208 action.action = action_str
210 state = episode_state_for_user_episode(request.user, episode)
211 add_episode_actions(state, [action])
213 podcast = episode.podcast
214 return HttpResponseRedirect(get_episode_link_target(episode, podcast))
217 @never_cache
218 @login_required
219 def flattr_episode(request, episode):
220 """ Flattrs an episode, records an event and redirects to the episode """
222 user = request.user
223 site = RequestSite(request)
225 # Flattr via the tasks queue, but wait for the result
226 task = flattr_thing.delay(user, episode._id, site.domain,
227 request.is_secure(), 'Episode')
228 success, msg = task.get()
230 if success:
231 action = EpisodeAction()
232 action.action = 'flattr'
233 action.upload_timestamp = get_timestamp(datetime.utcnow())
234 state = episode_state_for_user_episode(request.user, episode)
235 add_episode_actions(state, [action])
236 messages.success(request, _("Flattr\'d"))
238 else:
239 messages.error(request, msg)
241 podcast = episode.podcast
242 return HttpResponseRedirect(get_episode_link_target(episode, podcast))
245 # To make all view accessible via either CouchDB-ID for Slugs
246 # a decorator queries the episode and passes the Id on to the
247 # regular views
249 def slug_decorator(f):
250 @wraps(f)
251 def _decorator(request, p_slug, e_slug, *args, **kwargs):
253 pquery = Podcast.objects.filter(
254 slugs__slug=p_slug,
255 slugs__scope='',
258 try:
259 podcast = pquery.prefetch_related('slugs').get()
260 except Podcast.DoesNotExist:
261 raise Http404
263 equery = Episode.objects.filter(
264 podcast = podcast,
265 slugs__slug=e_slug,
266 slugs__scope=podcast.id.hex,
269 try:
270 episode = equery.prefetch_related('urls', 'slugs').get()
272 # set previously fetched podcast, to avoid additional query
273 episode.podcast = podcast
275 except Episode.DoesNotExist:
276 raise Http404
278 # redirect when Id or a merged (non-cannonical) slug is used
279 if episode.slug and episode.slug != e_slug:
280 return HttpResponseRedirect(
281 get_episode_link_target(episode, podcast))
283 return f(request, episode, *args, **kwargs)
285 return _decorator
288 def id_decorator(f):
289 @wraps(f)
290 def _decorator(request, p_id, e_id, *args, **kwargs):
292 try:
293 query = Episode.objects.filter(id=e_id,
294 podcast_id=p_id)
295 episode = query.select_related('podcast').get()
297 except Episode.DoesNotExist:
298 raise Http404
300 # redirect when Id or a merged (non-cannonical) slug is used
301 if episode.slug and episode.slug != e_id:
302 podcast = episode.podcast
303 return HttpResponseRedirect(
304 get_episode_link_target(episode, podcast))
306 return f(request, episode, *args, **kwargs)
308 return _decorator
312 show_slug = slug_decorator(episode)
313 toggle_favorite_slug = slug_decorator(toggle_favorite)
314 add_action_slug = slug_decorator(add_action)
315 flattr_episode_slug = slug_decorator(flattr_episode)
316 episode_history_slug = slug_decorator(history)
318 show_id = id_decorator(episode)
319 toggle_favorite_id = id_decorator(toggle_favorite)
320 add_action_id = id_decorator(add_action)
321 flattr_episode_id = id_decorator(flattr_episode)
322 episode_history_id = id_decorator(history)