[Chapters] use Django ORM in Chapters API
[mygpo.git] / mygpo / web / views / episode.py
blobf7486d9b8cba75cb0ac98f843c878490847025bd
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.core.tasks import flattr_thing
36 from mygpo.users.models import 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.history.stats import last_played_episodes
41 from mygpo.publisher.utils import check_publisher_permission
42 from mygpo.web.utils import get_episode_link_target, check_restrictions
43 from mygpo.history.models import EpisodeHistoryEntry
44 from mygpo.favorites.models import FavoriteEpisode
45 from mygpo.db.couchdb.episode_state import episode_state_for_user_episode, \
46 add_episode_actions
47 from mygpo.userfeeds.feeds import FavoriteFeed
50 @vary_on_cookie
51 @cache_control(private=True)
52 def episode(request, episode):
54 podcast = episode.podcast
56 podcast = check_restrictions(podcast)
58 user = request.user
60 if not podcast:
61 raise Http404
63 if user.is_authenticated():
65 is_fav = FavoriteEpisode.objects.filter(user=user, episode=episode)\
66 .exists()
68 # pre-populate data for fetch_data
69 podcasts_dict = {podcast.get_id(): podcast}
70 episodes_dict = {episode.id.hex: episode}
72 has_history = EpisodeHistoryEntry.objects.filter(user=user,
73 episode=episode)\
74 .exists()
76 played_parts = EpisodeHeatmap(podcast.get_id(),
77 episode.id, user.profile.uuid.hex, duration=episode.duration)
79 devices = {c.id.hex: c for c in user.client_set.all()}
80 can_flattr = user.profile.get_wksetting(FLATTR_TOKEN) and episode.flattr_url
82 else:
83 has_history = False
84 is_fav = False
85 played_parts = None
86 devices = {}
87 can_flattr = False
89 is_publisher = check_publisher_permission(user, podcast)
91 prev = None #podcast.get_episode_before(episode)
92 next = None #podcast.get_episode_after(episode)
94 return render(request, 'episode.html', {
95 'episode': episode,
96 'podcast': podcast,
97 'prev': prev,
98 'next': next,
99 'has_history': has_history,
100 'is_favorite': is_fav,
101 'played_parts': played_parts,
102 'actions': EPISODE_ACTION_TYPES,
103 'devices': devices,
104 'can_flattr': can_flattr,
105 'is_publisher': is_publisher,
109 @never_cache
110 @login_required
111 @vary_on_cookie
112 @cache_control(private=True)
113 def history(request, episode):
114 """ shows the history of the episode """
116 user = request.user
117 podcast = episode.podcast
119 history = EpisodeHistoryEntry.objects.filter(user=user,
120 episode=episode,)\
121 .order_by('-timestamp')\
122 .prefetch_related('episode',
123 'episode__slugs',
124 'episode__podcast',
125 'episode__podcast__slugs',
126 'client')
128 clients = user.client_set.all()
130 return render(request, 'episode-history.html', {
131 'episode': episode,
132 'podcast': podcast,
133 'history': history,
134 'actions': EPISODE_ACTION_TYPES,
135 'clients': clients,
139 @never_cache
140 @login_required
141 def toggle_favorite(request, episode):
142 user = request.user
144 fav, created = FavoriteEpisode.objects.get_or_create(
145 user=user,
146 episode=episode,
149 # if the episode was already a favorite, remove it
150 if not created:
151 fav.delete()
153 podcast = episode.podcast
154 return HttpResponseRedirect(get_episode_link_target(episode, podcast))
158 @vary_on_cookie
159 @cache_control(private=True)
160 @login_required
161 def list_favorites(request):
162 user = request.user
163 site = RequestSite(request)
165 favorites = FavoriteEpisode.episodes_for_user(user)
167 recently_listened = last_played_episodes(user)
169 favfeed = FavoriteFeed(user)
170 feed_url = favfeed.get_public_url(site.domain)
172 podcast = Podcast.objects.filter(urls__url=feed_url).first()
174 token = request.user.profile.favorite_feeds_token
176 return render(request, 'favorites.html', {
177 'episodes': favorites,
178 'feed_token': token,
179 'site': site,
180 'podcast': podcast,
181 'recently_listened': recently_listened,
185 @never_cache
186 def add_action(request, episode):
188 user = request.user
189 client = user.client_set.get(id=request.POST.get('device'))
191 action_str = request.POST.get('action')
192 timestamp = request.POST.get('timestamp', '')
194 if timestamp:
195 try:
196 timestamp = dateutil.parser.parse(timestamp)
197 except (ValueError, AttributeError):
198 timestamp = datetime.utcnow()
199 else:
200 timestamp = datetime.utcnow()
202 action = EpisodeAction()
203 action.timestamp = timestamp
204 action.upload_timestamp = get_timestamp(datetime.utcnow())
205 action.device = client.id.hex if client else None
206 action.action = action_str
208 state = episode_state_for_user_episode(user, episode)
209 add_episode_actions(state, [action])
211 podcast = episode.podcast
212 return HttpResponseRedirect(get_episode_link_target(episode, podcast))
215 @never_cache
216 @login_required
217 def flattr_episode(request, episode):
218 """ Flattrs an episode, records an event and redirects to the episode """
220 user = request.user
221 site = RequestSite(request)
223 # Flattr via the tasks queue, but wait for the result
224 task = flattr_thing.delay(user, episode._id, site.domain,
225 request.is_secure(), 'Episode')
226 success, msg = task.get()
228 if success:
229 action = EpisodeAction()
230 action.action = 'flattr'
231 action.upload_timestamp = get_timestamp(datetime.utcnow())
232 state = episode_state_for_user_episode(request.user, episode)
233 add_episode_actions(state, [action])
234 messages.success(request, _("Flattr\'d"))
236 else:
237 messages.error(request, msg)
239 podcast = episode.podcast
240 return HttpResponseRedirect(get_episode_link_target(episode, podcast))
243 # To make all view accessible via either CouchDB-ID for Slugs
244 # a decorator queries the episode and passes the Id on to the
245 # regular views
247 def slug_decorator(f):
248 @wraps(f)
249 def _decorator(request, p_slug, e_slug, *args, **kwargs):
251 pquery = Podcast.objects.filter(
252 slugs__slug=p_slug,
253 slugs__scope='',
256 try:
257 podcast = pquery.prefetch_related('slugs').get()
258 except Podcast.DoesNotExist:
259 raise Http404
261 equery = Episode.objects.filter(
262 podcast = podcast,
263 slugs__slug=e_slug,
264 slugs__scope=podcast.id.hex,
267 try:
268 episode = equery.prefetch_related('urls', 'slugs').get()
270 # set previously fetched podcast, to avoid additional query
271 episode.podcast = podcast
273 except Episode.DoesNotExist:
274 raise Http404
276 # redirect when Id or a merged (non-cannonical) slug is used
277 if episode.slug and episode.slug != e_slug:
278 return HttpResponseRedirect(
279 get_episode_link_target(episode, podcast))
281 return f(request, episode, *args, **kwargs)
283 return _decorator
286 def id_decorator(f):
287 @wraps(f)
288 def _decorator(request, p_id, e_id, *args, **kwargs):
290 try:
291 query = Episode.objects.filter(id=e_id,
292 podcast_id=p_id)
293 episode = query.select_related('podcast').get()
295 except Episode.DoesNotExist:
296 raise Http404
298 # redirect when Id or a merged (non-cannonical) slug is used
299 if episode.slug and episode.slug != e_id:
300 podcast = episode.podcast
301 return HttpResponseRedirect(
302 get_episode_link_target(episode, podcast))
304 return f(request, episode, *args, **kwargs)
306 return _decorator
310 show_slug = slug_decorator(episode)
311 toggle_favorite_slug = slug_decorator(toggle_favorite)
312 add_action_slug = slug_decorator(add_action)
313 flattr_episode_slug = slug_decorator(flattr_episode)
314 episode_history_slug = slug_decorator(history)
316 show_id = id_decorator(episode)
317 toggle_favorite_id = id_decorator(toggle_favorite)
318 add_action_id = id_decorator(add_action)
319 flattr_episode_id = id_decorator(flattr_episode)
320 episode_history_id = id_decorator(history)