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
.utils
import parse_time
, get_timestamp
37 from mygpo
.users
.settings
import FLATTR_TOKEN
38 from mygpo
.web
.heatmap
import EpisodeHeatmap
39 from mygpo
.history
.stats
import last_played_episodes
40 from mygpo
.publisher
.utils
import check_publisher_permission
41 from mygpo
.web
.utils
import get_episode_link_target
, check_restrictions
42 from mygpo
.history
.models
import EpisodeHistoryEntry
43 from mygpo
.favorites
.models
import FavoriteEpisode
44 from mygpo
.userfeeds
.feeds
import FavoriteFeed
48 @cache_control(private
=True)
49 def episode(request
, episode
):
51 podcast
= episode
.podcast
53 podcast
= check_restrictions(podcast
)
60 if user
.is_authenticated():
62 is_fav
= FavoriteEpisode
.objects
.filter(user
=user
, episode
=episode
)\
65 # pre-populate data for fetch_data
66 podcasts_dict
= {podcast
.get_id(): podcast
}
67 episodes_dict
= {episode
.id.hex: episode
}
69 has_history
= EpisodeHistoryEntry
.objects
.filter(user
=user
,
73 played_parts
= EpisodeHeatmap(podcast
, episode
, user
, episode
.duration
)
75 devices
= {c
.id.hex: c
for c
in user
.client_set
.all()}
76 can_flattr
= user
.profile
.settings
.get_wksetting(FLATTR_TOKEN
) and episode
.flattr_url
85 is_publisher
= check_publisher_permission(user
, podcast
)
87 prev
= None #podcast.get_episode_before(episode)
88 next
= None #podcast.get_episode_after(episode)
90 return render(request
, 'episode.html', {
95 'has_history': has_history
,
96 'is_favorite': is_fav
,
97 'played_parts': played_parts
,
98 'actions': EPISODE_ACTION_TYPES
,
100 'can_flattr': can_flattr
,
101 'is_publisher': is_publisher
,
108 @cache_control(private
=True)
109 def history(request
, episode
):
110 """ shows the history of the episode """
113 podcast
= episode
.podcast
115 history
= EpisodeHistoryEntry
.objects
.filter(user
=user
,
117 .order_by('-timestamp')\
118 .prefetch_related('episode',
121 'episode__podcast__slugs',
124 clients
= user
.client_set
.all()
126 return render(request
, 'episode-history.html', {
130 'actions': EPISODE_ACTION_TYPES
,
137 def toggle_favorite(request
, episode
):
140 fav
, created
= FavoriteEpisode
.objects
.get_or_create(
145 # if the episode was already a favorite, remove it
149 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 favorites
= FavoriteEpisode
.episodes_for_user(user
)
163 recently_listened
= last_played_episodes(user
)
165 favfeed
= FavoriteFeed(user
)
166 feed_url
= favfeed
.get_public_url(site
.domain
)
168 podcast
= Podcast
.objects
.filter(urls__url
=feed_url
).first()
170 token
= request
.user
.profile
.favorite_feeds_token
172 return render(request
, 'favorites.html', {
173 'episodes': favorites
,
177 'recently_listened': recently_listened
,
182 def add_action(request
, episode
):
185 client
= user
.client_set
.get(id=request
.POST
.get('device'))
187 action_str
= request
.POST
.get('action')
188 timestamp
= request
.POST
.get('timestamp', '')
192 timestamp
= dateutil
.parser
.parse(timestamp
)
193 except (ValueError, AttributeError):
194 timestamp
= datetime
.utcnow()
196 timestamp
= datetime
.utcnow()
198 EpisodeHistoryEntry
.create_entry(user
, episode
, action_str
, client
,
200 podcast
= episode
.podcast
201 return HttpResponseRedirect(get_episode_link_target(episode
, podcast
))
206 def flattr_episode(request
, episode
):
207 """ Flattrs an episode, records an event and redirects to the episode """
210 site
= RequestSite(request
)
212 # Flattr via the tasks queue, but wait for the result
213 task
= flattr_thing
.delay(user
, episode
._id
, site
.domain
,
214 request
.is_secure(), 'Episode')
215 success
, msg
= task
.get()
218 messages
.success(request
, _("Flattr\'d"))
221 messages
.error(request
, msg
)
223 podcast
= episode
.podcast
224 return HttpResponseRedirect(get_episode_link_target(episode
, podcast
))
227 # To make all view accessible via either IDs or Slugs
228 # a decorator queries the episode and passes the Id on to the
231 def slug_decorator(f
):
233 def _decorator(request
, p_slug
, e_slug
, *args
, **kwargs
):
235 pquery
= Podcast
.objects
.filter(
241 podcast
= pquery
.prefetch_related('slugs').get()
242 except Podcast
.DoesNotExist
:
245 equery
= Episode
.objects
.filter(
248 slugs__scope
=podcast
.id.hex,
252 episode
= equery
.prefetch_related('urls', 'slugs').get()
254 # set previously fetched podcast, to avoid additional query
255 episode
.podcast
= podcast
257 except Episode
.DoesNotExist
:
260 # redirect when Id or a merged (non-cannonical) slug is used
261 if episode
.slug
and episode
.slug
!= e_slug
:
262 return HttpResponseRedirect(
263 get_episode_link_target(episode
, podcast
))
265 return f(request
, episode
, *args
, **kwargs
)
272 def _decorator(request
, p_id
, e_id
, *args
, **kwargs
):
275 query
= Episode
.objects
.filter(id=e_id
,
277 episode
= query
.select_related('podcast').get()
279 except Episode
.DoesNotExist
:
282 # redirect when Id or a merged (non-cannonical) slug is used
283 if episode
.slug
and episode
.slug
!= e_id
:
284 podcast
= episode
.podcast
285 return HttpResponseRedirect(
286 get_episode_link_target(episode
, podcast
))
288 return f(request
, episode
, *args
, **kwargs
)
294 show_slug
= slug_decorator(episode
)
295 toggle_favorite_slug
= slug_decorator(toggle_favorite
)
296 add_action_slug
= slug_decorator(add_action
)
297 flattr_episode_slug
= slug_decorator(flattr_episode
)
298 episode_history_slug
= slug_decorator(history
)
300 show_id
= id_decorator(episode
)
301 toggle_favorite_id
= id_decorator(toggle_favorite
)
302 add_action_id
= id_decorator(add_action
)
303 flattr_episode_id
= id_decorator(flattr_episode
)
304 episode_history_id
= id_decorator(history
)