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
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
, unsubscribe
29 from mygpo
.db
.couchdb
.episode_state
import get_podcasts_episode_states
, \
30 episode_listener_counts
31 from mygpo
.db
.couchdb
.directory
import tags_for_user
, tags_for_podcast
34 logger
= logging
.getLogger(__name__
)
41 @cache_control(private
=True)
42 @allowed_methods(['GET'])
43 def show(request
, podcast
):
44 """ Shows a podcast detail page """
46 podcast
= check_restrictions(podcast
)
48 current_site
= RequestSite(request
)
50 episodes
= episode_list(podcast
, request
.user
, limit
=num_episodes
)
53 max_listeners
= max([e
.listeners
for e
in episodes
] + [0])
59 episodes
= episodes
[1:]
63 rel_podcasts
= filter(lambda x
: x
!= podcast
, group
.podcasts
)
67 tags
, has_tagged
= get_tags(podcast
, user
)
69 if user
.is_authenticated():
70 state
= podcast_state_for_user_podcast(user
, podcast
)
71 subscribed_devices
= state
.get_subscribed_device_ids()
72 subscribed_devices
= user
.get_devices(subscribed_devices
)
74 subscribe_targets
= podcast
.subscribe_targets(user
)
76 has_history
= bool(state
.actions
)
77 is_public
= state
.settings
.get('public_subscription', True)
78 can_flattr
= request
.user
.get_wksetting(FLATTR_TOKEN
) and podcast
.flattr_url
83 subscribed_devices
= []
84 subscribe_targets
= []
87 is_publisher
= check_publisher_permission(user
, podcast
)
89 episodes_total
= podcast
.episode_count
or 0
90 num_pages
= episodes_total
/ num_episodes
91 page_list
= get_page_list(1, num_pages
, 1, 15)
93 return render(request
, 'podcast.html', {
95 'has_tagged': has_tagged
,
97 'has_history': has_history
,
99 'is_public': is_public
,
100 'devices': subscribed_devices
,
101 'related_podcasts': rel_podcasts
,
102 'can_subscribe': len(subscribe_targets
) > 0,
103 'subscribe_targets': subscribe_targets
,
105 'episodes': episodes
,
106 'max_listeners': max_listeners
,
107 'can_flattr': can_flattr
,
108 'is_publisher': is_publisher
,
109 'page_list': page_list
,
114 def get_tags(podcast
, user
):
116 for t
in tags_for_podcast(podcast
):
118 tags
[tag_str
] = False
120 if not user
.is_anonymous():
121 users_tags
= tags_for_user(user
, podcast
.get_id())
122 for t
in users_tags
.get(podcast
.get_id(), []):
126 tag_list
= [{'tag': key
, 'is_own': value
} for key
, value
in tags
.iteritems()]
127 tag_list
.sort(key
=lambda x
: x
['tag'])
129 if len(tag_list
) > MAX_TAGS_ON_PAGE
:
130 tag_list
= filter(lambda x
: x
['is_own'], tag_list
)
131 tag_list
.append({'tag': '...', 'is_own': False})
133 has_own
= any(t
['is_own'] for t
in tag_list
)
135 return tag_list
, has_own
138 def episode_list(podcast
, user
, offset
=0, limit
=None):
140 Returns a list of episodes, with their action-attribute set to the latest
141 action. The attribute is unsert if there is no episode-action for
145 listeners
= dict(episode_listener_counts(podcast
))
146 episodes
= podcast
.episode_set
.all()
147 episodes
= episodes
.prefetch_related('slugs')[offset
:limit
]
149 if user
.is_authenticated():
151 # prepare pre-populated data for HistoryEntry.fetch_data
152 podcasts_dict
= {podcast
.get_id(): podcast
}
153 episodes_dict
= dict( (episode
.id, episode
) for episode
in episodes
)
155 actions
= get_podcasts_episode_states(podcast
, user
._id
)
156 actions
= map(HistoryEntry
.from_action_dict
, actions
)
158 HistoryEntry
.fetch_data(user
, actions
,
159 podcasts
=podcasts_dict
, episodes
=episodes_dict
)
161 episode_actions
= dict( (action
.episode_id
, action
) for action
in actions
)
165 annotate_episode
= partial(_annotate_episode
, listeners
, episode_actions
)
166 return map(annotate_episode
, episodes
)
172 def history(request
, podcast
):
173 """ shows the subscription history of the user """
176 state
= podcast_state_for_user_podcast(user
, podcast
)
177 history
= list(state
.actions
)
180 dev
= user
.get_device(h
.device
)
181 return proxy_object(h
, device
=dev
)
182 history
= map(_set_objects
, history
)
184 return render(request
, 'podcast-history.html', {
190 def all_episodes(request
, podcast
, page_size
=20):
192 # Make sure page request is an int. If not, deliver first page.
194 page
= int(request
.GET
.get('page', '1'))
200 episodes
= episode_list(podcast
, user
, (page
-1) * 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
, 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
, 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
, 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
, 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
):
423 podcast
= Podcast
.objects
.filter(slugs__slug
=slug
)
424 podcast
= podcast
.prefetch_related('slugs', 'urls').get()
426 # redirect when a non-cannonical slug is used
427 if slug
!= podcast
.slug
:
428 return HttpResponseRedirect(get_podcast_link_target(podcast
))
430 return f(request
, podcast
, *args
, **kwargs
)
437 def _decorator(request
, podcast_id
, *args
, **kwargs
):
440 podcast
= Podcast
.objects
.get(id=podcast_id
)
441 return f(request
, podcast
, *args
, **kwargs
)
442 # TODO: redirect to Slug URL?
444 except Podcast
.DoesNotExist
:
445 podcast
= get_object_or_404(Podcast
, merged_uuids__uuid
=podcast_id
)
446 return HttpResponseRedirect(get_podcast_link_target(podcast
))
451 show_slug
= slug_decorator(show
)
452 subscribe_slug
= slug_decorator(subscribe
)
453 subscribe_all_slug
= slug_decorator(subscribe_all
)
454 unsubscribe_slug
= slug_decorator(unsubscribe
)
455 unsubscribe_all_slug
= slug_decorator(unsubscribe_all
)
456 add_tag_slug
= slug_decorator(add_tag
)
457 remove_tag_slug
= slug_decorator(remove_tag
)
458 set_public_slug
= slug_decorator(set_public
)
459 all_episodes_slug
= slug_decorator(all_episodes
)
460 flattr_podcast_slug
= slug_decorator(flattr_podcast
)
461 history_podcast_slug
= slug_decorator(history
)
464 show_id
= id_decorator(show
)
465 subscribe_id
= id_decorator(subscribe
)
466 subscribe_all_id
= id_decorator(subscribe_all
)
467 unsubscribe_id
= id_decorator(unsubscribe
)
468 unsubscribe_all_id
= id_decorator(unsubscribe_all
)
469 add_tag_id
= id_decorator(add_tag
)
470 remove_tag_id
= id_decorator(remove_tag
)
471 set_public_id
= id_decorator(set_public
)
472 all_episodes_id
= id_decorator(all_episodes
)
473 flattr_podcast_id
= id_decorator(flattr_podcast
)
474 history_podcast_id
= id_decorator(history
)