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
.users
.models
import Chapter
, HistoryEntry
, EpisodeAction
36 from mygpo
.utils
import parse_time
37 from mygpo
.web
.heatmap
import EpisodeHeatmap
38 from mygpo
.web
.utils
import get_episode_link_target
, fetch_episode_data
39 from mygpo
.db
.couchdb
.episode
import episode_for_slug_id
, episode_for_oldid
, \
40 favorite_episodes_for_user
, chapters_for_episode
41 from mygpo
.db
.couchdb
.podcast
import podcast_by_id
, podcast_for_url
, \
43 from mygpo
.db
.couchdb
.episode_state
import episode_state_for_user_episode
44 from mygpo
.db
.couchdb
.user
import get_latest_episodes
45 from mygpo
.userfeeds
.feeds
import FavoriteFeed
49 @cache_control(private
=True)
50 def episode(request
, episode
):
52 podcast
= podcast_by_id(episode
.podcast
)
57 if request
.user
.is_authenticated():
59 episode_state
= episode_state_for_user_episode(request
.user
, episode
)
60 is_fav
= episode_state
.is_favorite()
63 # pre-populate data for fetch_data
64 podcasts_dict
= {podcast
.get_id(): podcast
}
65 episodes_dict
= {episode
._id
: episode
}
67 history
= list(episode_state
.get_history_entries())
68 HistoryEntry
.fetch_data(request
.user
, history
,
69 podcasts
=podcasts_dict
, episodes
=episodes_dict
)
71 played_parts
= EpisodeHeatmap(podcast
.get_id(),
72 episode
._id
, request
.user
._id
, duration
=episode
.duration
)
74 devices
= dict( (d
.id, d
.name
) for d
in request
.user
.devices
)
84 for user
, chapter
in chapters_for_episode(episode
._id
):
85 chapter
.is_own
= request
.user
.is_authenticated() and \
86 user
== request
.user
._id
87 chapters
.append(chapter
)
90 prev
= podcast
.get_episode_before(episode
)
91 next
= podcast
.get_episode_after(episode
)
93 return render(request
, 'episode.html', {
100 'is_favorite': is_fav
,
101 'played_parts': played_parts
,
102 'actions': EPISODE_ACTION_TYPES
,
109 def add_chapter(request
, episode
):
110 e_state
= episode_state_for_user_episode(request
.user
, episode
)
112 podcast
= podcast_by_id(episode
.podcast
)
115 start
= parse_time(request
.POST
.get('start', '0'))
117 if request
.POST
.get('end', '0'):
118 end
= parse_time(request
.POST
.get('end', '0'))
122 adv
= 'advertisement' in request
.POST
123 label
= request
.POST
.get('label')
125 except ValueError as e
:
126 messages
.error(request
,
127 _('Could not add Chapter: {msg}'.format(msg
=str(e
))))
129 return HttpResponseRedirect(get_episode_link_target(episode
, podcast
))
133 chapter
.start
= start
135 chapter
.advertisement
= adv
136 chapter
.label
= label
138 e_state
.update_chapters(add
=[chapter
])
140 return HttpResponseRedirect(get_episode_link_target(episode
, podcast
))
145 def remove_chapter(request
, episode
, start
, end
):
146 e_state
= episode_state_for_user_episode(request
.user
, episode
)
148 remove
= (int(start
), int(end
))
149 e_state
.update_chapters(rem
=[remove
])
151 podcast
= podcast_by_id(episode
.podcast
)
153 return HttpResponseRedirect(get_episode_link_target(episode
, podcast
))
158 def toggle_favorite(request
, episode
):
159 episode_state
= episode_state_for_user_episode(request
.user
, episode
)
161 @repeat_on_conflict(['episode_state'])
162 def _set_fav(episode_state
, is_fav
):
163 episode_state
.set_favorite(is_fav
)
166 is_fav
= episode_state
.is_favorite()
167 _set_fav(episode_state
=episode_state
, is_fav
=not is_fav
)
169 podcast
= podcast_by_id(episode
.podcast
)
171 return HttpResponseRedirect(get_episode_link_target(episode
, podcast
))
176 @cache_control(private
=True)
178 def list_favorites(request
):
180 site
= RequestSite(request
)
182 episodes
= favorite_episodes_for_user(user
)
184 recently_listened
= get_latest_episodes(user
)
186 podcast_ids
= [episode
.podcast
for episode
in episodes
+ recently_listened
]
187 podcasts
= podcasts_to_dict(podcast_ids
)
189 recently_listened
= fetch_episode_data(recently_listened
, podcasts
=podcasts
)
190 episodes
= fetch_episode_data(episodes
, podcasts
=podcasts
)
192 favfeed
= FavoriteFeed(user
)
193 feed_url
= favfeed
.get_public_url(site
.domain
)
195 podcast
= podcast_for_url(feed_url
)
197 token
= request
.user
.favorite_feeds_token
199 return render(request
, 'favorites.html', {
200 'episodes': episodes
,
204 'recently_listened': recently_listened
,
209 def add_action(request
, episode
):
211 device
= request
.user
.get_device(request
.POST
.get('device'))
213 action_str
= request
.POST
.get('action')
214 timestamp
= request
.POST
.get('timestamp', '')
218 timestamp
= dateutil
.parser
.parse(timestamp
)
219 except (ValueError, AttributeError):
220 timestamp
= datetime
.utcnow()
222 timestamp
= datetime
.utcnow()
224 action
= EpisodeAction()
225 action
.timestamp
= timestamp
226 action
.device
= device
.id if device
else None
227 action
.action
= action_str
229 state
= episode_state_for_user_episode(request
.user
, episode
)
231 @repeat_on_conflict(['state'])
232 def _add_action(state
, action
):
233 state
.add_actions([action
])
236 _add_action(state
, action
)
238 podcast
= podcast_by_id(episode
.podcast
)
240 return HttpResponseRedirect(get_episode_link_target(episode
, podcast
))
242 # To make all view accessible via either CouchDB-ID for Slugs
243 # a decorator queries the episode and passes the Id on to the
246 def slug_id_decorator(f
):
248 def _decorator(request
, p_slug_id
, e_slug_id
, *args
, **kwargs
):
249 episode
= episode_for_slug_id(p_slug_id
, e_slug_id
)
254 return f(request
, episode
, *args
, **kwargs
)
259 def oldid_decorator(f
):
261 def _decorator(request
, id, *args
, **kwargs
):
262 episode
= episode_for_oldid(id)
267 return f(request
, episode
, *args
, **kwargs
)
271 show_slug_id
= slug_id_decorator(episode
)
272 add_chapter_slug_id
= slug_id_decorator(add_chapter
)
273 remove_chapter_slug_id
= slug_id_decorator(remove_chapter
)
274 toggle_favorite_slug_id
= slug_id_decorator(toggle_favorite
)
275 add_action_slug_id
= slug_id_decorator(add_action
)
277 show_oldid
= oldid_decorator(episode
)
278 add_chapter_oldid
= oldid_decorator(add_chapter
)
279 remove_chapter_oldid
= oldid_decorator(remove_chapter
)
280 toggle_favorite_oldid
= oldid_decorator(toggle_favorite
)
281 add_action_oldid
= oldid_decorator(add_action
)
283 #TODO: add view for flattr action that
284 # * carries out the flattring
285 # * redirects to the episode page