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
.core
.urlresolvers
import reverse
26 from django
.contrib
.auth
.decorators
import login_required
27 from django
.contrib
.sites
.models
import RequestSite
28 from django
.views
.decorators
.vary
import vary_on_cookie
29 from django
.views
.decorators
.cache
import never_cache
, cache_control
31 from mygpo
.api
.constants
import EPISODE_ACTION_TYPES
32 from mygpo
.decorators
import repeat_on_conflict
33 from mygpo
.core
import models
34 from mygpo
.core
.models
import Podcast
35 from mygpo
.core
.proxy
import proxy_object
36 from mygpo
.core
.models
import Episode
37 from mygpo
.users
.models
import Chapter
, HistoryEntry
, EpisodeAction
38 from mygpo
.api
import backend
39 from mygpo
.utils
import parse_time
, get_to_dict
40 from mygpo
.web
.heatmap
import EpisodeHeatmap
41 from mygpo
.web
.utils
import get_episode_link_target
45 @cache_control(private
=True)
46 def episode(request
, episode
):
48 podcast
= Podcast
.get(episode
.podcast
)
53 if request
.user
.is_authenticated():
55 episode_state
= episode
.get_user_state(request
.user
)
56 is_fav
= episode_state
.is_favorite()
59 # pre-populate data for fetch_data
60 podcasts_dict
= {podcast
.get_id(): podcast
}
61 episodes_dict
= {episode
._id
: episode
}
63 history
= list(episode_state
.get_history_entries())
64 HistoryEntry
.fetch_data(request
.user
, history
,
65 podcasts
=podcasts_dict
, episodes
=episodes_dict
)
67 played_parts
= EpisodeHeatmap(podcast
.get_id(),
68 episode
._id
, request
.user
._id
, duration
=episode
.duration
)
70 devices
= dict( (d
.id, d
.name
) for d
in request
.user
.devices
)
80 for user
, chapter
in Chapter
.for_episode(episode
._id
):
81 chapter
.is_own
= request
.user
.is_authenticated() and \
82 user
== request
.user
._id
83 chapters
.append(chapter
)
86 prev
= podcast
.get_episode_before(episode
)
87 next
= podcast
.get_episode_after(episode
)
89 return render(request
, 'episode.html', {
96 'is_favorite': is_fav
,
97 'played_parts': played_parts
,
98 'actions': EPISODE_ACTION_TYPES
,
105 def add_chapter(request
, episode
):
106 e_state
= episode
.get_user_state(request
.user
)
108 podcast
= Podcast
.get(episode
.podcast
)
111 start
= parse_time(request
.POST
.get('start', '0'))
113 if request
.POST
.get('end', '0'):
114 end
= parse_time(request
.POST
.get('end', '0'))
118 adv
= 'advertisement' in request
.POST
119 label
= request
.POST
.get('label')
121 except Exception as e
:
122 # FIXME: when using Django's messaging system, set error message
124 return HttpResponseRedirect(get_episode_link_target(episode
, podcast
))
128 chapter
.start
= start
130 chapter
.advertisement
= adv
131 chapter
.label
= label
133 e_state
.update_chapters(add
=[chapter
])
135 return HttpResponseRedirect(get_episode_link_target(episode
, podcast
))
140 def remove_chapter(request
, episode
, start
, end
):
141 e_state
= episode
.get_user_state(request
.user
)
143 remove
= (int(start
), int(end
))
144 e_state
.update_chapters(rem
=[remove
])
146 podcast
= Podcast
.get(episode
.podcast
)
148 return HttpResponseRedirect(get_episode_link_target(episode
, podcast
))
153 def toggle_favorite(request
, episode
):
154 episode_state
= episode
.get_user_state(request
.user
)
155 is_fav
= episode_state
.is_favorite()
156 episode_state
.set_favorite(not is_fav
)
160 podcast
= Podcast
.get(episode
.podcast
)
162 return HttpResponseRedirect(get_episode_link_target(episode
, podcast
))
166 @cache_control(private
=True)
168 def list_favorites(request
):
169 site
= RequestSite(request
)
171 episodes
= backend
.get_favorites(request
.user
)
172 podcast_ids
= [episode
.podcast
for episode
in episodes
]
173 podcasts
= get_to_dict(Podcast
, podcast_ids
, Podcast
.get_id
)
175 def set_podcast(episode
):
176 episode
= proxy_object(episode
)
177 episode
.podcast
= podcasts
.get(episode
.podcast
, None)
180 episodes
= map(set_podcast
, episodes
)
182 feed_url
= 'http://%s/%s' % (site
.domain
, reverse('favorites-feed', args
=[request
.user
.username
]))
184 podcast
= Podcast
.for_url(feed_url
)
186 if 'public_feed' in request
.GET
:
187 request
.user
.favorite_feeds_token
= ''
190 elif 'private_feed' in request
.GET
:
191 request
.user
.create_new_token('favorite_feeds_token', 8)
194 token
= request
.user
.favorite_feeds_token
196 return render(request
, 'favorites.html', {
197 'episodes': episodes
,
205 def add_action(request
, episode
):
207 device
= request
.user
.get_device(request
.POST
.get('device'))
209 action_str
= request
.POST
.get('action')
210 timestamp
= request
.POST
.get('timestamp', '')
214 timestamp
= dateutil
.parser
.parse(timestamp
)
216 timestamp
= datetime
.utcnow()
218 timestamp
= datetime
.utcnow()
220 action
= EpisodeAction()
221 action
.timestamp
= timestamp
222 action
.device
= device
.id if device
else None
223 action
.action
= action_str
225 state
= episode
.get_user_state(request
.user
)
227 @repeat_on_conflict(['action'])
228 def _add_action(action
):
229 state
.add_actions([action
])
232 _add_action(action
=action
)
234 podcast
= Podcast
.get(episode
.podcast
)
236 return HttpResponseRedirect(get_episode_link_target(episode
, podcast
))
238 # To make all view accessible via either CouchDB-ID for Slugs
239 # a decorator queries the episode and passes the Id on to the
242 def slug_id_decorator(f
):
244 def _decorator(request
, p_slug_id
, e_slug_id
, *args
, **kwargs
):
245 episode
= Episode
.for_slug_id(p_slug_id
, e_slug_id
)
250 return f(request
, episode
, *args
, **kwargs
)
255 def oldid_decorator(f
):
257 def _decorator(request
, id, *args
, **kwargs
):
258 episode
= Episode
.for_oldid(id)
263 return f(request
, episode
, *args
, **kwargs
)
267 show_slug_id
= slug_id_decorator(episode
)
268 add_chapter_slug_id
= slug_id_decorator(add_chapter
)
269 remove_chapter_slug_id
= slug_id_decorator(remove_chapter
)
270 toggle_favorite_slug_id
= slug_id_decorator(toggle_favorite
)
271 add_action_slug_id
= slug_id_decorator(add_action
)
273 show_oldid
= oldid_decorator(episode
)
274 add_chapter_oldid
= oldid_decorator(add_chapter
)
275 remove_chapter_oldid
= oldid_decorator(remove_chapter
)
276 toggle_favorite_oldid
= oldid_decorator(toggle_favorite
)
277 add_action_oldid
= oldid_decorator(add_action
)