[Migration] Get Podcasts / Episodes from PostgreSQL
[mygpo.git] / mygpo / web / views / episode.py
blob2b26cb792e2ec03f2636bd815a059070c2917ff1
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.podcasts.models import Podcast, Episode
33 from mygpo.api.constants import EPISODE_ACTION_TYPES
34 from mygpo.decorators import repeat_on_conflict
35 from mygpo.core.proxy import proxy_object
36 from mygpo.core.tasks import flattr_thing
37 from mygpo.users.models import Chapter, HistoryEntry, EpisodeAction
38 from mygpo.utils import parse_time, get_timestamp
39 from mygpo.users.settings import FLATTR_TOKEN
40 from mygpo.web.heatmap import EpisodeHeatmap
41 from mygpo.publisher.utils import check_publisher_permission
42 from mygpo.web.utils import get_episode_link_target, fetch_episode_data, \
43 check_restrictions
44 from mygpo.db.couchdb.episode import favorite_episodes_for_user, \
45 chapters_for_episode, set_episode_favorite
46 from mygpo.db.couchdb.podcast import podcast_by_id, 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 = episode.podcast
59 podcast = check_restrictions(podcast)
61 user = request.user
63 if not podcast:
64 raise Http404
66 if user.is_authenticated():
68 episode_state = episode_state_for_user_episode(user, episode)
69 is_fav = episode_state.is_favorite()
72 # pre-populate data for fetch_data
73 podcasts_dict = {podcast.get_id(): podcast}
74 episodes_dict = {episode._id: episode}
76 has_history = bool(list(episode_state.get_history_entries()))
78 played_parts = EpisodeHeatmap(podcast.get_id(),
79 episode._id, user._id, duration=episode.duration)
81 devices = dict( (d.id, d.name) for d in user.devices )
82 can_flattr = user.get_wksetting(FLATTR_TOKEN) and episode.flattr_url
84 else:
85 has_history = False
86 is_fav = False
87 played_parts = None
88 devices = {}
89 can_flattr = False
91 is_publisher = check_publisher_permission(user, podcast)
93 prev = None #podcast.get_episode_before(episode)
94 next = None #podcast.get_episode_after(episode)
96 return render(request, 'episode.html', {
97 'episode': episode,
98 'podcast': podcast,
99 'prev': prev,
100 'next': next,
101 'has_history': has_history,
102 'is_favorite': is_fav,
103 'played_parts': played_parts,
104 'actions': EPISODE_ACTION_TYPES,
105 'devices': devices,
106 'can_flattr': can_flattr,
107 'is_publisher': is_publisher,
111 @never_cache
112 @login_required
113 @vary_on_cookie
114 @cache_control(private=True)
115 def history(request, episode):
116 """ shows the history of the episode """
118 user = request.user
119 podcast = podcast_by_id(episode.podcast)
120 episode_state = episode_state_for_user_episode(user, episode)
122 # pre-populate data for fetch_data
123 podcasts_dict = {podcast.get_id(): podcast}
124 episodes_dict = {episode._id: episode}
126 history = list(episode_state.get_history_entries())
127 HistoryEntry.fetch_data(user, history,
128 podcasts=podcasts_dict, episodes=episodes_dict)
130 devices = dict( (d.id, d.name) for d in user.devices )
132 return render(request, 'episode-history.html', {
133 'episode': episode,
134 'podcast': podcast,
135 'history': history,
136 'actions': EPISODE_ACTION_TYPES,
137 'devices': devices,
141 @never_cache
142 @login_required
143 def toggle_favorite(request, episode):
144 episode_state = episode_state_for_user_episode(request.user, episode)
146 is_fav = episode_state.is_favorite()
147 set_episode_favorite(episode_state, not is_fav)
149 podcast = podcast_by_id(episode.podcast)
151 return HttpResponseRedirect(get_episode_link_target(episode, podcast))
155 @vary_on_cookie
156 @cache_control(private=True)
157 @login_required
158 def list_favorites(request):
159 user = request.user
160 site = RequestSite(request)
162 episodes = favorite_episodes_for_user(user)
164 recently_listened = get_latest_episodes(user)
166 podcast_ids = [episode.podcast for episode in episodes + recently_listened]
167 podcasts = podcasts_to_dict(podcast_ids)
169 recently_listened = fetch_episode_data(recently_listened, podcasts=podcasts)
170 episodes = fetch_episode_data(episodes, podcasts=podcasts)
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.favorite_feeds_token
179 return render(request, 'favorites.html', {
180 'episodes': episodes,
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 = podcast_by_id(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 = podcast_by_id(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 query = Episode.objects.filter(slugs__slug=e_slug,
254 podcast__slugs__slug=p_slug)
255 episode = query.select_related('podcast').get()
257 # redirect when Id or a merged (non-cannonical) slug is used
258 if episode.slug and episode.slug != e_slug:
259 podcast = podcast_by_id(episode.podcast)
260 return HttpResponseRedirect(
261 get_episode_link_target(episode, podcast))
263 return f(request, episode, *args, **kwargs)
265 return _decorator
268 def id_decorator(f):
269 @wraps(f)
270 def _decorator(request, p_id, e_id, *args, **kwargs):
272 query = Episode.objects.filter(id=e_id,
273 podcast_id=p_id)
274 episode = query.selected_related('podcast').get()
276 if episode is None:
277 raise Http404
279 # redirect when Id or a merged (non-cannonical) slug is used
280 if episode.slug and episode.slug != e_slug_id:
281 podcast = podcast_by_id(episode.podcast)
282 return HttpResponseRedirect(
283 get_episode_link_target(episode, podcast))
285 return f(request, episode, *args, **kwargs)
287 return _decorator
291 show_slug = slug_decorator(episode)
292 toggle_favorite_slug = slug_decorator(toggle_favorite)
293 add_action_slug = slug_decorator(add_action)
294 flattr_episode_slug = slug_decorator(flattr_episode)
295 episode_history_slug = slug_decorator(history)
297 show_id = id_decorator(episode)
298 toggle_favorite_id = id_decorator(toggle_favorite)
299 add_action_id = id_decorator(add_action)
300 flattr_episode_id = id_decorator(flattr_episode)
301 episode_history_id = id_decorator(history)