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
.api
.constants
import EPISODE_ACTION_TYPES
33 from mygpo
.decorators
import repeat_on_conflict
34 from mygpo
.core
.proxy
import proxy_object
35 from mygpo
.core
.tasks
import flattr_thing
36 from mygpo
.users
.models
import Chapter
, 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
.publisher
.utils
import check_publisher_permission
41 from mygpo
.web
.utils
import get_episode_link_target
, fetch_episode_data
, \
43 from mygpo
.db
.couchdb
.episode
import episode_for_slug_id
, episode_for_oldid
, \
44 favorite_episodes_for_user
, chapters_for_episode
, \
46 from mygpo
.db
.couchdb
.podcast
import podcast_by_id
, podcast_for_url
, \
48 from mygpo
.db
.couchdb
.episode_state
import episode_state_for_user_episode
, \
49 add_episode_actions
, update_episode_chapters
50 from mygpo
.db
.couchdb
.user
import get_latest_episodes
51 from mygpo
.userfeeds
.feeds
import FavoriteFeed
55 @cache_control(private
=True)
56 def episode(request
, episode
):
58 podcast
= podcast_by_id(episode
.podcast
)
60 podcast
= check_restrictions(podcast
)
67 if user
.is_authenticated():
69 episode_state
= episode_state_for_user_episode(user
, episode
)
70 is_fav
= episode_state
.is_favorite()
73 # pre-populate data for fetch_data
74 podcasts_dict
= {podcast
.get_id(): podcast
}
75 episodes_dict
= {episode
._id
: episode
}
77 has_history
= bool(list(episode_state
.get_history_entries()))
79 played_parts
= EpisodeHeatmap(podcast
.get_id(),
80 episode
._id
, user
._id
, duration
=episode
.duration
)
82 devices
= dict( (d
.id, d
.name
) for d
in user
.devices
)
83 can_flattr
= user
.get_wksetting(FLATTR_TOKEN
) and episode
.flattr_url
92 is_publisher
= check_publisher_permission(user
, podcast
)
94 prev
= podcast
.get_episode_before(episode
)
95 next
= podcast
.get_episode_after(episode
)
97 return render(request
, 'episode.html', {
102 'has_history': has_history
,
103 'is_favorite': is_fav
,
104 'played_parts': played_parts
,
105 'actions': EPISODE_ACTION_TYPES
,
107 'can_flattr': can_flattr
,
108 'is_publisher': is_publisher
,
115 @cache_control(private
=True)
116 def history(request
, episode
):
117 """ shows the history of the episode """
120 podcast
= podcast_by_id(episode
.podcast
)
121 episode_state
= episode_state_for_user_episode(user
, episode
)
123 # pre-populate data for fetch_data
124 podcasts_dict
= {podcast
.get_id(): podcast
}
125 episodes_dict
= {episode
._id
: episode
}
127 history
= list(episode_state
.get_history_entries())
128 HistoryEntry
.fetch_data(user
, history
,
129 podcasts
=podcasts_dict
, episodes
=episodes_dict
)
131 devices
= dict( (d
.id, d
.name
) for d
in user
.devices
)
133 return render(request
, 'episode-history.html', {
137 'actions': EPISODE_ACTION_TYPES
,
144 def toggle_favorite(request
, episode
):
145 episode_state
= episode_state_for_user_episode(request
.user
, episode
)
147 is_fav
= episode_state
.is_favorite()
148 set_episode_favorite(episode_state
, not is_fav
)
150 podcast
= podcast_by_id(episode
.podcast
)
152 return HttpResponseRedirect(get_episode_link_target(episode
, podcast
))
157 @cache_control(private
=True)
159 def list_favorites(request
):
161 site
= RequestSite(request
)
163 episodes
= favorite_episodes_for_user(user
)
165 recently_listened
= get_latest_episodes(user
)
167 podcast_ids
= [episode
.podcast
for episode
in episodes
+ recently_listened
]
168 podcasts
= podcasts_to_dict(podcast_ids
)
170 recently_listened
= fetch_episode_data(recently_listened
, podcasts
=podcasts
)
171 episodes
= fetch_episode_data(episodes
, podcasts
=podcasts
)
173 favfeed
= FavoriteFeed(user
)
174 feed_url
= favfeed
.get_public_url(site
.domain
)
176 podcast
= podcast_for_url(feed_url
)
178 token
= request
.user
.favorite_feeds_token
180 return render(request
, 'favorites.html', {
181 'episodes': episodes
,
185 'recently_listened': recently_listened
,
190 def add_action(request
, episode
):
192 device
= request
.user
.get_device(request
.POST
.get('device'))
194 action_str
= request
.POST
.get('action')
195 timestamp
= request
.POST
.get('timestamp', '')
199 timestamp
= dateutil
.parser
.parse(timestamp
)
200 except (ValueError, AttributeError):
201 timestamp
= datetime
.utcnow()
203 timestamp
= datetime
.utcnow()
205 action
= EpisodeAction()
206 action
.timestamp
= timestamp
207 action
.upload_timestamp
= get_timestamp(datetime
.utcnow())
208 action
.device
= device
.id if device
else None
209 action
.action
= action_str
211 state
= episode_state_for_user_episode(request
.user
, episode
)
212 add_episode_actions(state
, [action
])
214 podcast
= podcast_by_id(episode
.podcast
)
215 return HttpResponseRedirect(get_episode_link_target(episode
, podcast
))
220 def flattr_episode(request
, episode
):
221 """ Flattrs an episode, records an event and redirects to the episode """
224 site
= RequestSite(request
)
226 # Flattr via the tasks queue, but wait for the result
227 task
= flattr_thing
.delay(user
, episode
._id
, site
.domain
,
228 request
.is_secure(), 'Episode')
229 success
, msg
= task
.get()
232 action
= EpisodeAction()
233 action
.action
= 'flattr'
234 action
.upload_timestamp
= get_timestamp(datetime
.utcnow())
235 state
= episode_state_for_user_episode(request
.user
, episode
)
236 add_episode_actions(state
, [action
])
237 messages
.success(request
, _("Flattr\'d"))
240 messages
.error(request
, msg
)
242 podcast
= podcast_by_id(episode
.podcast
)
243 return HttpResponseRedirect(get_episode_link_target(episode
, podcast
))
246 # To make all view accessible via either CouchDB-ID for Slugs
247 # a decorator queries the episode and passes the Id on to the
250 def slug_id_decorator(f
):
252 def _decorator(request
, p_slug_id
, e_slug_id
, *args
, **kwargs
):
253 episode
= episode_for_slug_id(p_slug_id
, e_slug_id
)
258 # redirect when Id or a merged (non-cannonical) slug is used
259 if episode
.slug
and episode
.slug
!= e_slug_id
:
260 podcast
= podcast_by_id(episode
.podcast
)
261 return HttpResponseRedirect(
262 get_episode_link_target(episode
, podcast
))
264 return f(request
, episode
, *args
, **kwargs
)
269 def oldid_decorator(f
):
271 def _decorator(request
, id, *args
, **kwargs
):
272 episode
= episode_for_oldid(id)
277 # redirect to Id or slug URL
278 podcast
= podcast_by_id(episode
.podcast
)
279 return HttpResponseRedirect(get_episode_link_target(episode
, podcast
))
283 show_slug_id
= slug_id_decorator(episode
)
284 toggle_favorite_slug_id
= slug_id_decorator(toggle_favorite
)
285 add_action_slug_id
= slug_id_decorator(add_action
)
286 flattr_episode_slug_id
= slug_id_decorator(flattr_episode
)
287 episode_history_slug_id
= slug_id_decorator(history
)
289 show_oldid
= oldid_decorator(episode
)
290 toggle_favorite_oldid
= oldid_decorator(toggle_favorite
)
291 add_action_oldid
= oldid_decorator(add_action
)
292 flattr_episode_oldid
= oldid_decorator(flattr_episode
)
293 episode_history_oldid
= oldid_decorator(history
)