show Flattr button only when logged into flattr and payment_url is available
[mygpo.git] / mygpo / web / views / podcast.py
blob7b5e61c030b33c5f3c5a4c6be1b3652e6f3c829c
1 from functools import wraps, partial
3 from django.core.urlresolvers import reverse
4 from django.http import HttpResponseBadRequest, HttpResponseRedirect, Http404
5 from django.shortcuts import render
6 from django.contrib.auth.decorators import login_required
7 from django.contrib.sites.models import RequestSite
8 from django.utils.translation import ugettext as _
9 from django.contrib import messages
10 from django.views.decorators.vary import vary_on_cookie
11 from django.views.decorators.cache import never_cache, cache_control
13 from mygpo.core.models import PodcastGroup, SubscriptionException
14 from mygpo.core.proxy import proxy_object
15 from mygpo.core.tasks import flattr_thing
16 from mygpo.api.sanitizing import sanitize_url
17 from mygpo.users.settings import PUBLIC_SUB_PODCAST, FLATTR_TOKEN
18 from mygpo.users.models import HistoryEntry, DeviceDoesNotExist, SubscriptionAction
19 from mygpo.web.forms import SyncForm
20 from mygpo.decorators import allowed_methods, repeat_on_conflict
21 from mygpo.web.utils import get_podcast_link_target
22 from mygpo.log import log
23 from mygpo.db.couchdb.episode import episodes_for_podcast
24 from mygpo.db.couchdb.podcast import podcast_for_slug, podcast_for_slug_id, \
25 podcast_for_oldid, podcast_for_url
26 from mygpo.db.couchdb.podcast_state import podcast_state_for_user_podcast, \
27 add_subscription_action
28 from mygpo.db.couchdb.episode_state import get_podcasts_episode_states, \
29 episode_listener_counts
30 from mygpo.db.couchdb.directory import tags_for_user, tags_for_podcast
33 MAX_TAGS_ON_PAGE=50
36 @repeat_on_conflict(['state'])
37 def update_podcast_settings(state, is_public):
38 state.settings[PUBLIC_SUB_PODCAST.name] = is_public
39 state.save()
42 @vary_on_cookie
43 @cache_control(private=True)
44 @allowed_methods(['GET'])
45 def show_slug(request, slug):
46 podcast = podcast_for_slug(slug)
48 if slug != podcast.slug:
49 target = reverse('podcast_slug', args=[podcast.slug])
50 return HttpResponseRedirect(target)
52 return show(request, podcast.oldid)
55 @vary_on_cookie
56 @cache_control(private=True)
57 @allowed_methods(['GET'])
58 def show(request, podcast):
60 episodes = episode_list(podcast, request.user, limit=20)
62 max_listeners = max([e.listeners for e in episodes] + [0])
64 episode = None
66 if episodes:
67 episode = episodes[0]
68 episodes = episodes[1:]
70 if podcast.group:
71 group = PodcastGroup.get(podcast.group)
72 rel_podcasts = filter(lambda x: x != podcast, group.podcasts)
73 else:
74 rel_podcasts = []
76 tags = get_tags(podcast, request.user)
78 if request.user.is_authenticated():
79 state = podcast_state_for_user_podcast(request.user, podcast)
80 subscribed_devices = state.get_subscribed_device_ids()
81 subscribed_devices = [request.user.get_device(x) for x in subscribed_devices]
83 subscribe_targets = podcast.subscribe_targets(request.user)
85 history = list(state.actions)
86 def _set_objects(h):
87 dev = request.user.get_device(h.device)
88 return proxy_object(h, device=dev)
89 history = map(_set_objects, history)
91 is_public = state.get_wksetting(PUBLIC_SUB_PODCAST)
92 can_flattr = request.user.get_wksetting(FLATTR_TOKEN) and podcast.flattr_url
94 return render(request, 'podcast.html', {
95 'tags': tags,
96 'history': history,
97 'podcast': podcast,
98 'is_public': is_public,
99 'devices': subscribed_devices,
100 'related_podcasts': rel_podcasts,
101 'can_subscribe': len(subscribe_targets) > 0,
102 'subscribe_targets': subscribe_targets,
103 'episode': episode,
104 'episodes': episodes,
105 'max_listeners': max_listeners,
106 'can_flattr': can_flattr,
108 else:
109 current_site = RequestSite(request)
110 return render(request, 'podcast.html', {
111 'podcast': podcast,
112 'related_podcasts': rel_podcasts,
113 'tags': tags,
114 'url': current_site,
115 'episode': episode,
116 'episodes': episodes,
117 'max_listeners': max_listeners,
118 'can_flattr': False,
122 def get_tags(podcast, user):
123 tags = {}
124 for t in tags_for_podcast(podcast):
125 tag_str = t.lower()
126 tags[tag_str] = False
128 if not user.is_anonymous():
129 users_tags = tags_for_user(user, podcast.get_id())
130 for t in users_tags.get(podcast.get_id(), []):
131 tag_str = t.lower()
132 tags[tag_str] = True
134 tag_list = [{'tag': key, 'is_own': value} for key, value in tags.iteritems()]
135 tag_list.sort(key=lambda x: x['tag'])
137 if len(tag_list) > MAX_TAGS_ON_PAGE:
138 tag_list = filter(lambda x: x['is_own'], tag_list)
139 tag_list.append({'tag': '...', 'is_own': False})
141 return tag_list
144 def episode_list(podcast, user, limit=None):
146 Returns a list of episodes, with their action-attribute set to the latest
147 action. The attribute is unsert if there is no episode-action for
148 the episode.
151 listeners = dict(episode_listener_counts(podcast))
152 episodes = episodes_for_podcast(podcast, descending=True, limit=limit)
154 if user.is_authenticated():
156 # prepare pre-populated data for HistoryEntry.fetch_data
157 podcasts_dict = dict( (p_id, podcast) for p_id in podcast.get_ids())
158 episodes_dict = dict( (episode._id, episode) for episode in episodes)
160 actions = get_podcasts_episode_states(podcast, user._id)
161 actions = map(HistoryEntry.from_action_dict, actions)
163 HistoryEntry.fetch_data(user, actions,
164 podcasts=podcasts_dict, episodes=episodes_dict)
166 episode_actions = dict( (action.episode_id, action) for action in actions)
167 else:
168 episode_actions = {}
170 annotate_episode = partial(_annotate_episode, listeners, episode_actions)
171 return map(annotate_episode, episodes)
175 def all_episodes(request, podcast):
177 episodes = episode_list(podcast, request.user)
179 max_listeners = max([e.listeners for e in episodes] + [0])
181 return render(request, 'episodes.html', {
182 'podcast': podcast,
183 'episodes': episodes,
184 'max_listeners': max_listeners,
189 def _annotate_episode(listeners, episode_actions, episode):
190 listener_count = listeners.pop(episode._id, None)
191 action = episode_actions.pop(episode._id, None)
192 return proxy_object(episode, listeners=listener_count, action=action)
196 @never_cache
197 @login_required
198 def add_tag(request, podcast):
199 podcast_state = podcast_state_for_user_podcast(request.user, podcast)
201 tag_str = request.GET.get('tag', '')
202 if not tag_str:
203 return HttpResponseBadRequest()
205 tags = tag_str.split(',')
207 @repeat_on_conflict(['state'])
208 def update(state):
209 state.add_tags(tags)
210 state.save()
212 update(state=podcast_state)
214 if request.GET.get('next', '') == 'mytags':
215 return HttpResponseRedirect('/tags/')
217 return HttpResponseRedirect(get_podcast_link_target(podcast))
220 @never_cache
221 @login_required
222 def remove_tag(request, podcast):
223 podcast_state = podcast_state_for_user_podcast(request.user, podcast)
225 tag_str = request.GET.get('tag', '')
226 if not tag_str:
227 return HttpResponseBadRequest()
229 @repeat_on_conflict(['state'])
230 def update(state):
231 tags = list(state.tags)
232 if tag_str in tags:
233 state.tags.remove(tag_str)
234 state.save()
236 update(state=podcast_state)
238 if request.GET.get('next', '') == 'mytags':
239 return HttpResponseRedirect('/tags/')
241 return HttpResponseRedirect(get_podcast_link_target(podcast))
244 @never_cache
245 @login_required
246 @allowed_methods(['GET', 'POST'])
247 def subscribe(request, podcast):
249 if request.method == 'POST':
251 # multiple UIDs from the /podcast/<slug>/subscribe
252 device_uids = [k for (k,v) in request.POST.items() if k==v]
254 # single UID from /podcast/<slug>
255 if 'targets' in request.POST:
256 device_uids.append(request.POST.get('targets'))
258 for uid in device_uids:
259 try:
260 device = request.user.get_device_by_uid(uid)
261 podcast.subscribe(request.user, device)
263 except (SubscriptionException, DeviceDoesNotExist, ValueError) as e:
264 messages.error(request, str(e))
266 return HttpResponseRedirect(get_podcast_link_target(podcast))
268 targets = podcast.subscribe_targets(request.user)
270 return render(request, 'subscribe.html', {
271 'targets': targets,
272 'podcast': podcast,
276 @never_cache
277 @login_required
278 def unsubscribe(request, podcast, device_uid):
280 return_to = request.GET.get('return_to', None)
282 if not return_to:
283 raise Http404('Wrong URL')
285 try:
286 device = request.user.get_device_by_uid(device_uid)
288 except DeviceDoesNotExist as e:
289 messages.error(request, str(e))
291 try:
292 podcast.unsubscribe(request.user, device)
293 except SubscriptionException as e:
294 log('Web: %(username)s: could not unsubscribe from podcast %(podcast_url)s on device %(device_id)s: %(exception)s' %
295 {'username': request.user.username, 'podcast_url': podcast.url, 'device_id': device.id, 'exception': e})
297 return HttpResponseRedirect(return_to)
300 @never_cache
301 @login_required
302 def subscribe_url(request):
303 url = request.GET.get('url', None)
305 if not url:
306 raise Http404('http://my.gpodder.org/subscribe?url=http://www.example.com/podcast.xml')
308 url = sanitize_url(url)
310 if url == '':
311 raise Http404('Please specify a valid url')
313 podcast = podcast_for_url(url, create=True)
315 return HttpResponseRedirect(get_podcast_link_target(podcast, 'subscribe'))
318 @never_cache
319 @allowed_methods(['POST'])
320 def set_public(request, podcast, public):
321 state = podcast_state_for_user_podcast(request.user, podcast)
322 update_podcast_settings(state=state, is_public=public)
323 return HttpResponseRedirect(get_podcast_link_target(podcast))
326 @never_cache
327 @login_required
328 def flattr_podcast(request, podcast):
329 user = request.user
330 site = RequestSite(request)
331 task = flattr_thing.delay(user, podcast.get_id(), site.domain, 'Podcast')
332 success, msg = task.get()
334 if success:
335 action = SubscriptionAction()
336 action.action = 'flattr'
337 state = podcast_state_for_user_podcast(request.user, podcast)
338 add_subscription_action(state, action)
339 messages.success(request, _("Flattr\'d"))
341 else:
342 messages.error(request, msg)
344 return HttpResponseRedirect(get_podcast_link_target(podcast))
347 # To make all view accessible via either CouchDB-ID or Slugs
348 # a decorator queries the podcast and passes the Id on to the
349 # regular views
351 def slug_id_decorator(f):
352 @wraps(f)
353 def _decorator(request, slug_id, *args, **kwargs):
354 podcast = podcast_for_slug_id(slug_id)
356 if podcast is None:
357 raise Http404
359 return f(request, podcast, *args, **kwargs)
361 return _decorator
364 def oldid_decorator(f):
365 @wraps(f)
366 def _decorator(request, pid, *args, **kwargs):
367 try:
368 pid = int(pid)
369 except (TypeError, ValueError):
370 raise Http404
372 podcast = podcast_for_oldid(pid)
374 if not podcast:
375 raise Http404
377 return f(request, podcast, *args, **kwargs)
379 return _decorator
382 show_slug_id = slug_id_decorator(show)
383 subscribe_slug_id = slug_id_decorator(subscribe)
384 unsubscribe_slug_id = slug_id_decorator(unsubscribe)
385 add_tag_slug_id = slug_id_decorator(add_tag)
386 remove_tag_slug_id = slug_id_decorator(remove_tag)
387 set_public_slug_id = slug_id_decorator(set_public)
388 all_episodes_slug_id= slug_id_decorator(all_episodes)
389 flattr_podcast_slug_id=slug_id_decorator(flattr_podcast)
392 show_oldid = oldid_decorator(show)
393 subscribe_oldid = oldid_decorator(subscribe)
394 unsubscribe_oldid = oldid_decorator(unsubscribe)
395 add_tag_oldid = oldid_decorator(add_tag)
396 remove_tag_oldid = oldid_decorator(remove_tag)
397 set_public_oldid = oldid_decorator(set_public)
398 all_episodes_oldid = oldid_decorator(all_episodes)
399 flattr_podcast_oldid= oldid_decorator(flattr_podcast)