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
12 from django
.shortcuts
import get_object_or_404
14 from mygpo
.podcasts
.models
import Podcast
, PodcastGroup
, Episode
15 from mygpo
.users
.models
import SubscriptionException
16 from mygpo
.core
.proxy
import proxy_object
17 from mygpo
.core
.tasks
import flattr_thing
18 from mygpo
.utils
import normalize_feed_url
19 from mygpo
.users
.settings
import PUBLIC_SUB_PODCAST
, FLATTR_TOKEN
20 from mygpo
.publisher
.utils
import check_publisher_permission
21 from mygpo
.users
.models
import HistoryEntry
, DeviceDoesNotExist
, SubscriptionAction
22 from mygpo
.web
.forms
import SyncForm
23 from mygpo
.decorators
import allowed_methods
, repeat_on_conflict
24 from mygpo
.web
.utils
import get_podcast_link_target
, get_page_list
, \
26 from mygpo
.db
.couchdb
.podcast_state
import podcast_state_for_user_podcast
, \
27 add_subscription_action
, add_podcast_tags
, remove_podcast_tags
, \
28 set_podcast_privacy_settings
, subscribe
as subscribe_podcast
, \
29 unsubscribe
as unsubscribe_podcast
30 from mygpo
.db
.couchdb
.episode_state
import get_podcasts_episode_states
, \
31 episode_listener_counts
32 from mygpo
.db
.couchdb
.directory
import tags_for_user
, tags_for_podcast
35 logger
= logging
.getLogger(__name__
)
42 @cache_control(private
=True)
43 @allowed_methods(['GET'])
44 def show(request
, podcast
):
45 """ Shows a podcast detail page """
47 podcast
= check_restrictions(podcast
)
49 current_site
= RequestSite(request
)
51 episodes
= episode_list(podcast
, request
.user
, limit
=num_episodes
)
54 max_listeners
= max([e
.listeners
for e
in episodes
] + [0])
60 episodes
= episodes
[1:]
64 rel_podcasts
= group
.podcast_set
.exclude(pk
=podcast
.pk
)
68 tags
, has_tagged
= get_tags(podcast
, user
)
70 if user
.is_authenticated():
71 state
= podcast_state_for_user_podcast(user
, podcast
)
72 subscribed_devices
= state
.get_subscribed_device_ids()
73 subscribed_devices
= user
.get_devices(subscribed_devices
)
75 subscribe_targets
= podcast
.subscribe_targets(user
)
77 has_history
= bool(state
.actions
)
78 is_public
= state
.settings
.get('public_subscription', True)
79 can_flattr
= request
.user
.get_wksetting(FLATTR_TOKEN
) and podcast
.flattr_url
84 subscribed_devices
= []
85 subscribe_targets
= []
88 is_publisher
= check_publisher_permission(user
, podcast
)
90 episodes_total
= podcast
.episode_count
or 0
91 num_pages
= episodes_total
/ num_episodes
92 page_list
= get_page_list(1, num_pages
, 1, 15)
94 return render(request
, 'podcast.html', {
96 'has_tagged': has_tagged
,
98 'has_history': has_history
,
100 'is_public': is_public
,
101 'devices': subscribed_devices
,
102 'related_podcasts': rel_podcasts
,
103 'can_subscribe': len(subscribe_targets
) > 0,
104 'subscribe_targets': subscribe_targets
,
106 'episodes': episodes
,
107 'max_listeners': max_listeners
,
108 'can_flattr': can_flattr
,
109 'is_publisher': is_publisher
,
110 'page_list': page_list
,
115 def get_tags(podcast
, user
):
117 for t
in tags_for_podcast(podcast
):
119 tags
[tag_str
] = False
121 if not user
.is_anonymous():
122 users_tags
= tags_for_user(user
, podcast
.get_id())
123 for t
in users_tags
.get(podcast
.get_id(), []):
127 tag_list
= [{'tag': key
, 'is_own': value
} for key
, value
in tags
.iteritems()]
128 tag_list
.sort(key
=lambda x
: x
['tag'])
130 if len(tag_list
) > MAX_TAGS_ON_PAGE
:
131 tag_list
= filter(lambda x
: x
['is_own'], tag_list
)
132 tag_list
.append({'tag': '...', 'is_own': False})
134 has_own
= any(t
['is_own'] for t
in tag_list
)
136 return tag_list
, has_own
139 def episode_list(podcast
, user
, offset
=0, limit
=None):
141 Returns a list of episodes, with their action-attribute set to the latest
142 action. The attribute is unsert if there is no episode-action for
146 listeners
= dict(episode_listener_counts(podcast
))
147 episodes
= Episode
.objects
.filter(podcast
=podcast
).all().by_released()
148 episodes
= list(episodes
.prefetch_related('slugs')[offset
:offset
+limit
])
150 if user
.is_authenticated():
152 # prepare pre-populated data for HistoryEntry.fetch_data
153 podcasts_dict
= {podcast
.get_id(): podcast
}
154 episodes_dict
= dict( (episode
.id, episode
) for episode
in episodes
)
156 actions
= get_podcasts_episode_states(podcast
, user
._id
)
157 actions
= map(HistoryEntry
.from_action_dict
, actions
)
159 HistoryEntry
.fetch_data(user
, actions
,
160 podcasts
=podcasts_dict
, episodes
=episodes_dict
)
162 episode_actions
= dict( (action
.episode_id
, action
) for action
in actions
)
166 annotate_episode
= partial(_annotate_episode
, listeners
, episode_actions
)
167 return map(annotate_episode
, episodes
)
173 def history(request
, podcast
):
174 """ shows the subscription history of the user """
177 state
= podcast_state_for_user_podcast(user
, podcast
)
178 history
= list(state
.actions
)
181 dev
= user
.get_device(h
.device
)
182 return proxy_object(h
, device
=dev
)
183 history
= map(_set_objects
, history
)
185 return render(request
, 'podcast-history.html', {
191 def all_episodes(request
, podcast
, page_size
=20):
193 # Make sure page request is an int. If not, deliver first page.
195 page
= int(request
.GET
.get('page', '1'))
201 episodes
= episode_list(podcast
, user
, (page
-1) * page_size
, page_size
)
202 episodes_total
= podcast
.episode_count
or 0
203 num_pages
= episodes_total
/ page_size
204 page_list
= get_page_list(1, num_pages
, page
, 15)
206 max_listeners
= max([e
.listeners
for e
in episodes
] + [0])
208 is_publisher
= check_publisher_permission(user
, podcast
)
210 return render(request
, 'episodes.html', {
212 'episodes': episodes
,
213 'max_listeners': max_listeners
,
214 'page_list': page_list
,
215 'current_page': page
,
216 'is_publisher': is_publisher
,
221 def _annotate_episode(listeners
, episode_actions
, episode
):
222 episode
.listener_count
= listeners
.pop(episode
.get_id(), None)
223 episode
.action
= episode_actions
.pop(episode
.get_id(), None)
230 def add_tag(request
, podcast
):
231 podcast_state
= podcast_state_for_user_podcast(request
.user
, podcast
)
233 tag_str
= request
.GET
.get('tag', '')
235 return HttpResponseBadRequest()
237 tags
= tag_str
.split(',')
238 add_podcast_tags(podcast_state
, tags
)
240 if request
.GET
.get('next', '') == 'mytags':
241 return HttpResponseRedirect('/tags/')
243 return HttpResponseRedirect(get_podcast_link_target(podcast
))
248 def remove_tag(request
, podcast
):
249 podcast_state
= podcast_state_for_user_podcast(request
.user
, podcast
)
251 tag_str
= request
.GET
.get('tag', '')
253 return HttpResponseBadRequest()
255 remove_podcast_tags(podcast_state
, tag_str
)
257 if request
.GET
.get('next', '') == 'mytags':
258 return HttpResponseRedirect('/tags/')
260 return HttpResponseRedirect(get_podcast_link_target(podcast
))
265 @allowed_methods(['GET', 'POST'])
266 def subscribe(request
, podcast
):
268 if request
.method
== 'POST':
270 # multiple UIDs from the /podcast/<slug>/subscribe
271 device_uids
= [k
for (k
,v
) in request
.POST
.items() if k
==v
]
273 # single UID from /podcast/<slug>
274 if 'targets' in request
.POST
:
275 devices
= request
.POST
.get('targets')
276 devices
= devices
.split(',')
277 device_uids
.extend(devices
)
279 for uid
in device_uids
:
281 device
= request
.user
.get_device_by_uid(uid
)
282 subscribe_podcast(podcast
, request
.user
, device
)
284 except (SubscriptionException
, DeviceDoesNotExist
, ValueError) as e
:
285 messages
.error(request
, str(e
))
287 return HttpResponseRedirect(get_podcast_link_target(podcast
))
289 targets
= podcast
.subscribe_targets(request
.user
)
291 return render(request
, 'subscribe.html', {
299 @allowed_methods(['POST'])
300 def subscribe_all(request
, podcast
):
301 """ subscribe all of the user's devices to the podcast """
304 devs
= podcast
.subscribe_targets(user
)
306 devs
= [dev
[0] if isinstance(dev
, list) else dev
for dev
in devs
]
309 subscribe_podcast(podcast
, user
, devs
)
310 except (SubscriptionException
, DeviceDoesNotExist
, ValueError) as e
:
311 messages
.error(request
, str(e
))
313 return HttpResponseRedirect(get_podcast_link_target(podcast
))
318 def unsubscribe(request
, podcast
, device_uid
):
320 return_to
= request
.GET
.get('return_to', None)
323 raise Http404('Wrong URL')
326 device
= request
.user
.get_device_by_uid(device_uid
)
328 except DeviceDoesNotExist
as e
:
329 messages
.error(request
, str(e
))
330 return HttpResponseRedirect(return_to
)
333 unsubscribe_podcast(podcast
, request
.user
, device
)
334 except SubscriptionException
as e
:
335 logger
.exception('Web: %(username)s: could not unsubscribe from podcast %(podcast_url)s on device %(device_id)s' %
336 {'username': request
.user
.username
, 'podcast_url': podcast
.url
, 'device_id': device
.id})
338 return HttpResponseRedirect(return_to
)
343 @allowed_methods(['POST'])
344 def unsubscribe_all(request
, podcast
):
345 """ unsubscribe all of the user's devices from the podcast """
348 state
= podcast_state_for_user_podcast(user
, podcast
)
350 dev_ids
= state
.get_subscribed_device_ids()
351 devs
= user
.get_devices(dev_ids
)
353 devs
= [dev
[0] if isinstance(dev
, list) else dev
for dev
in devs
]
356 unsubscribe_podcast(podcast
, user
, devs
)
357 except (SubscriptionException
, DeviceDoesNotExist
, ValueError) as e
:
358 messages
.error(request
, str(e
))
360 return HttpResponseRedirect(get_podcast_link_target(podcast
))
365 def subscribe_url(request
):
366 url
= request
.GET
.get('url', None)
369 raise Http404('http://my.gpodder.org/subscribe?url=http://www.example.com/podcast.xml')
371 url
= normalize_feed_url(url
)
374 raise Http404('Please specify a valid url')
376 podcast
= Podcasts
.objects
.get_or_create_for_url(url
)
378 return HttpResponseRedirect(get_podcast_link_target(podcast
, 'subscribe'))
382 @allowed_methods(['POST'])
383 def set_public(request
, podcast
, public
):
384 state
= podcast_state_for_user_podcast(request
.user
, podcast
)
385 set_podcast_privacy_settings(state
, public
)
386 return HttpResponseRedirect(get_podcast_link_target(podcast
))
391 def flattr_podcast(request
, podcast
):
392 """ Flattrs a podcast, records an event and redirects to the podcast """
395 site
= RequestSite(request
)
397 # do flattring via the tasks queue, but wait for the result
398 task
= flattr_thing
.delay(user
, podcast
.get_id(), site
.domain
,
399 request
.is_secure(), 'Podcast')
400 success
, msg
= task
.get()
403 action
= SubscriptionAction()
404 action
.action
= 'flattr'
405 state
= podcast_state_for_user_podcast(request
.user
, podcast
)
406 add_subscription_action(state
, action
)
407 messages
.success(request
, _("Flattr\'d"))
410 messages
.error(request
, msg
)
412 return HttpResponseRedirect(get_podcast_link_target(podcast
))
415 # To make all view accessible via either IDs or Slugs
416 # a decorator queries the podcast and passes the Id on to the
419 def slug_decorator(f
):
421 def _decorator(request
, slug
, *args
, **kwargs
):
424 podcast
= Podcast
.objects
.filter(slugs__slug
=slug
)
425 podcast
= podcast
.prefetch_related('slugs', 'urls').get()
426 except Podcast
.DoesNotExist
:
429 # redirect when a non-cannonical slug is used
430 if slug
!= podcast
.slug
:
431 return HttpResponseRedirect(get_podcast_link_target(podcast
))
433 return f(request
, podcast
, *args
, **kwargs
)
440 def _decorator(request
, podcast_id
, *args
, **kwargs
):
443 podcast
= Podcast
.objects
.filter(id=podcast_id
)
444 podcast
= podcast
.prefetch_related('slugs', 'urls').get()
446 # if the podcast has a slug, redirect to its canonical URL
448 return HttpResponseRedirect(get_podcast_link_target(podcast
))
450 return f(request
, podcast
, *args
, **kwargs
)
452 except Podcast
.DoesNotExist
:
453 podcast
= get_object_or_404(Podcast
, merged_uuids__uuid
=podcast_id
)
454 return HttpResponseRedirect(get_podcast_link_target(podcast
))
459 show_slug
= slug_decorator(show
)
460 subscribe_slug
= slug_decorator(subscribe
)
461 subscribe_all_slug
= slug_decorator(subscribe_all
)
462 unsubscribe_slug
= slug_decorator(unsubscribe
)
463 unsubscribe_all_slug
= slug_decorator(unsubscribe_all
)
464 add_tag_slug
= slug_decorator(add_tag
)
465 remove_tag_slug
= slug_decorator(remove_tag
)
466 set_public_slug
= slug_decorator(set_public
)
467 all_episodes_slug
= slug_decorator(all_episodes
)
468 flattr_podcast_slug
= slug_decorator(flattr_podcast
)
469 history_podcast_slug
= slug_decorator(history
)
472 show_id
= id_decorator(show
)
473 subscribe_id
= id_decorator(subscribe
)
474 subscribe_all_id
= id_decorator(subscribe_all
)
475 unsubscribe_id
= id_decorator(unsubscribe
)
476 unsubscribe_all_id
= id_decorator(unsubscribe_all
)
477 add_tag_id
= id_decorator(add_tag
)
478 remove_tag_id
= id_decorator(remove_tag
)
479 set_public_id
= id_decorator(set_public
)
480 all_episodes_id
= id_decorator(all_episodes
)
481 flattr_podcast_id
= id_decorator(flattr_podcast
)
482 history_podcast_id
= id_decorator(history
)