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
53 @cache_control(private
=True)
54 def episode(request
, episode
):
56 podcast
= episode
.podcast
58 podcast
= check_restrictions(podcast
)
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
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', {
100 'has_history': has_history
,
101 'is_favorite': is_fav
,
102 'played_parts': played_parts
,
103 'actions': EPISODE_ACTION_TYPES
,
105 'can_flattr': can_flattr
,
106 'is_publisher': is_publisher
,
113 @cache_control(private
=True)
114 def history(request
, episode
):
115 """ shows the history of the episode """
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', {
135 'actions': EPISODE_ACTION_TYPES
,
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
))
155 @cache_control(private
=True)
157 def list_favorites(request
):
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',
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
,
184 'recently_listened': recently_listened
,
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', '')
198 timestamp
= dateutil
.parser
.parse(timestamp
)
199 except (ValueError, AttributeError):
200 timestamp
= datetime
.utcnow()
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
))
219 def flattr_episode(request
, episode
):
220 """ Flattrs an episode, records an event and redirects to the episode """
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()
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"))
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
249 def slug_decorator(f
):
251 def _decorator(request
, p_slug
, e_slug
, *args
, **kwargs
):
253 query
= Episode
.objects
.filter(
255 slugs__content_type
=ContentType
.objects
.get_for_model(Episode
),
256 podcast__slugs__slug
=p_slug
,
257 podcast__slugs__content_type
=ContentType
.objects
.get_for_model(Podcast
),
261 episode
= query
.prefetch_related('urls', 'slugs', 'podcast',
262 'podcast__slugs').get()
263 except Episode
.DoesNotExist
:
266 # redirect when Id or a merged (non-cannonical) slug is used
267 if episode
.slug
and episode
.slug
!= e_slug
:
268 podcast
= episode
.podcast
269 return HttpResponseRedirect(
270 get_episode_link_target(episode
, podcast
))
272 return f(request
, episode
, *args
, **kwargs
)
279 def _decorator(request
, p_id
, e_id
, *args
, **kwargs
):
282 query
= Episode
.objects
.filter(id=e_id
,
284 episode
= query
.select_related('podcast').get()
286 except Episode
.DoesNotExist
:
289 # redirect when Id or a merged (non-cannonical) slug is used
290 if episode
.slug
and episode
.slug
!= e_id
:
291 podcast
= episode
.podcast
292 return HttpResponseRedirect(
293 get_episode_link_target(episode
, podcast
))
295 return f(request
, episode
, *args
, **kwargs
)
301 show_slug
= slug_decorator(episode
)
302 toggle_favorite_slug
= slug_decorator(toggle_favorite
)
303 add_action_slug
= slug_decorator(add_action
)
304 flattr_episode_slug
= slug_decorator(flattr_episode
)
305 episode_history_slug
= slug_decorator(history
)
307 show_id
= id_decorator(episode
)
308 toggle_favorite_id
= id_decorator(toggle_favorite
)
309 add_action_id
= id_decorator(add_action
)
310 flattr_episode_id
= id_decorator(flattr_episode
)
311 episode_history_id
= id_decorator(history
)