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
15 from mygpo
.core
.models
import PodcastGroup
, 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
.episode
import episodes_for_podcast
27 from mygpo
.db
.couchdb
.podcast_state
import podcast_state_for_user_podcast
, \
28 add_subscription_action
, add_podcast_tags
, remove_podcast_tags
, \
29 set_podcast_privacy_settings
, subscribe
, unsubscribe
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:]
63 group
= PodcastGroup
.get(podcast
.group
)
64 rel_podcasts
= filter(lambda x
: x
!= podcast
, group
.podcasts
)
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
= podcast
.episode_set
.all()
148 episodes
= episodes
.prefetch_related('slugs')[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
,
203 episodes_total
= podcast
.episode_count
or 0
204 num_pages
= episodes_total
/ page_size
205 page_list
= get_page_list(1, num_pages
, page
, 15)
207 max_listeners
= max([e
.listeners
for e
in episodes
] + [0])
209 is_publisher
= check_publisher_permission(user
, podcast
)
211 return render(request
, 'episodes.html', {
213 'episodes': episodes
,
214 'max_listeners': max_listeners
,
215 'page_list': page_list
,
216 'current_page': page
,
217 'is_publisher': is_publisher
,
222 def _annotate_episode(listeners
, episode_actions
, episode
):
223 episode
.listener_count
= listeners
.pop(episode
.get_id(), None)
224 episode
.action
= episode_actions
.pop(episode
.get_id(), None)
231 def add_tag(request
, podcast
):
232 podcast_state
= podcast_state_for_user_podcast(request
.user
, podcast
)
234 tag_str
= request
.GET
.get('tag', '')
236 return HttpResponseBadRequest()
238 tags
= tag_str
.split(',')
239 add_podcast_tags(podcast_state
, tags
)
241 if request
.GET
.get('next', '') == 'mytags':
242 return HttpResponseRedirect('/tags/')
244 return HttpResponseRedirect(get_podcast_link_target(podcast
))
249 def remove_tag(request
, podcast
):
250 podcast_state
= podcast_state_for_user_podcast(request
.user
, podcast
)
252 tag_str
= request
.GET
.get('tag', '')
254 return HttpResponseBadRequest()
256 remove_podcast_tags(podcast_state
, tag_str
)
258 if request
.GET
.get('next', '') == 'mytags':
259 return HttpResponseRedirect('/tags/')
261 return HttpResponseRedirect(get_podcast_link_target(podcast
))
266 @allowed_methods(['GET', 'POST'])
267 def subscribe(request
, podcast
):
269 if request
.method
== 'POST':
271 # multiple UIDs from the /podcast/<slug>/subscribe
272 device_uids
= [k
for (k
,v
) in request
.POST
.items() if k
==v
]
274 # single UID from /podcast/<slug>
275 if 'targets' in request
.POST
:
276 devices
= request
.POST
.get('targets')
277 devices
= devices
.split(',')
278 device_uids
.extend(devices
)
280 for uid
in device_uids
:
282 device
= request
.user
.get_device_by_uid(uid
)
283 subscribe(podcast
, request
.user
, device
)
285 except (SubscriptionException
, DeviceDoesNotExist
, ValueError) as e
:
286 messages
.error(request
, str(e
))
288 return HttpResponseRedirect(get_podcast_link_target(podcast
))
290 targets
= podcast
.subscribe_targets(request
.user
)
292 return render(request
, 'subscribe.html', {
300 @allowed_methods(['POST'])
301 def subscribe_all(request
, podcast
):
302 """ subscribe all of the user's devices to the podcast """
305 devs
= podcast
.subscribe_targets(user
)
307 devs
= [dev
[0] if isinstance(dev
, list) else dev
for dev
in devs
]
310 subscribe(podcast
, user
, devs
)
311 except (SubscriptionException
, DeviceDoesNotExist
, ValueError) as e
:
312 messages
.error(request
, str(e
))
314 return HttpResponseRedirect(get_podcast_link_target(podcast
))
319 def unsubscribe(request
, podcast
, device_uid
):
321 return_to
= request
.GET
.get('return_to', None)
324 raise Http404('Wrong URL')
327 device
= request
.user
.get_device_by_uid(device_uid
)
329 except DeviceDoesNotExist
as e
:
330 messages
.error(request
, str(e
))
331 return HttpResponseRedirect(return_to
)
334 unsubscribe(podcast
, request
.user
, device
)
335 except SubscriptionException
as e
:
336 logger
.exception('Web: %(username)s: could not unsubscribe from podcast %(podcast_url)s on device %(device_id)s' %
337 {'username': request
.user
.username
, 'podcast_url': podcast
.url
, 'device_id': device
.id})
339 return HttpResponseRedirect(return_to
)
344 @allowed_methods(['POST'])
345 def unsubscribe_all(request
, podcast
):
346 """ unsubscribe all of the user's devices from the podcast """
349 state
= podcast_state_for_user_podcast(user
, podcast
)
351 dev_ids
= state
.get_subscribed_device_ids()
352 devs
= user
.get_devices(dev_ids
)
354 devs
= [dev
[0] if isinstance(dev
, list) else dev
for dev
in devs
]
357 unsubscribe(podcast
, user
, devs
)
358 except (SubscriptionException
, DeviceDoesNotExist
, ValueError) as e
:
359 messages
.error(request
, str(e
))
361 return HttpResponseRedirect(get_podcast_link_target(podcast
))
366 def subscribe_url(request
):
367 url
= request
.GET
.get('url', None)
370 raise Http404('http://my.gpodder.org/subscribe?url=http://www.example.com/podcast.xml')
372 url
= normalize_feed_url(url
)
375 raise Http404('Please specify a valid url')
377 podcast
= Podcasts
.objects
.get_or_create_for_url(url
)
379 return HttpResponseRedirect(get_podcast_link_target(podcast
, 'subscribe'))
383 @allowed_methods(['POST'])
384 def set_public(request
, podcast
, public
):
385 state
= podcast_state_for_user_podcast(request
.user
, podcast
)
386 set_podcast_privacy_settings(state
, public
)
387 return HttpResponseRedirect(get_podcast_link_target(podcast
))
392 def flattr_podcast(request
, podcast
):
393 """ Flattrs a podcast, records an event and redirects to the podcast """
396 site
= RequestSite(request
)
398 # do flattring via the tasks queue, but wait for the result
399 task
= flattr_thing
.delay(user
, podcast
.get_id(), site
.domain
,
400 request
.is_secure(), 'Podcast')
401 success
, msg
= task
.get()
404 action
= SubscriptionAction()
405 action
.action
= 'flattr'
406 state
= podcast_state_for_user_podcast(request
.user
, podcast
)
407 add_subscription_action(state
, action
)
408 messages
.success(request
, _("Flattr\'d"))
411 messages
.error(request
, msg
)
413 return HttpResponseRedirect(get_podcast_link_target(podcast
))
416 # To make all view accessible via either IDs or Slugs
417 # a decorator queries the podcast and passes the Id on to the
420 def slug_decorator(f
):
422 def _decorator(request
, slug
, *args
, **kwargs
):
424 podcast
= Podcast
.objects
.filter(slugs__slug
=slug
)
425 podcast
= podcast
.prefetch_related('slugs', 'urls').get()
427 # redirect when a non-cannonical slug is used
428 if slug
!= podcast
.slug
:
429 return HttpResponseRedirect(get_podcast_link_target(podcast
))
431 return f(request
, podcast
, *args
, **kwargs
)
438 def _decorator(request
, podcast_id
, *args
, **kwargs
):
441 podcast
= Podcast
.objects
.get(id=podcast_id
)
442 return f(request
, podcast
, *args
, **kwargs
)
443 # TODO: redirect to Slug URL?
445 except Podcast
.DoesNotExist
:
446 podcast
= get_object_or_404(Podcast
, merged_uuids__uuid
=podcast_id
)
447 return HttpResponseRedirect(get_podcast_link_target(podcast
))
452 show_slug
= slug_decorator(show
)
453 subscribe_slug
= slug_decorator(subscribe
)
454 subscribe_all_slug
= slug_decorator(subscribe_all
)
455 unsubscribe_slug
= slug_decorator(unsubscribe
)
456 unsubscribe_all_slug
= slug_decorator(unsubscribe_all
)
457 add_tag_slug
= slug_decorator(add_tag
)
458 remove_tag_slug
= slug_decorator(remove_tag
)
459 set_public_slug
= slug_decorator(set_public
)
460 all_episodes_slug
= slug_decorator(all_episodes
)
461 flattr_podcast_slug
= slug_decorator(flattr_podcast
)
462 history_podcast_slug
= slug_decorator(history
)
465 show_id
= id_decorator(show
)
466 subscribe_id
= id_decorator(subscribe
)
467 subscribe_all_id
= id_decorator(subscribe_all
)
468 unsubscribe_id
= id_decorator(unsubscribe
)
469 unsubscribe_all_id
= id_decorator(unsubscribe_all
)
470 add_tag_id
= id_decorator(add_tag
)
471 remove_tag_id
= id_decorator(remove_tag
)
472 set_public_id
= id_decorator(set_public
)
473 all_episodes_id
= id_decorator(all_episodes
)
474 flattr_podcast_id
= id_decorator(flattr_podcast
)
475 history_podcast_id
= id_decorator(history
)