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
.utils
import normalize_feed_url
17 from mygpo
.users
.settings
import PUBLIC_SUB_PODCAST
, FLATTR_TOKEN
18 from mygpo
.publisher
.utils
import check_publisher_permission
19 from mygpo
.users
.models
import HistoryEntry
, DeviceDoesNotExist
, SubscriptionAction
20 from mygpo
.web
.forms
import SyncForm
21 from mygpo
.decorators
import allowed_methods
, repeat_on_conflict
22 from mygpo
.web
.utils
import get_podcast_link_target
, get_page_list
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 logger
= logging
.getLogger(__name__
)
39 @repeat_on_conflict(['state'])
40 def update_podcast_settings(state
, is_public
):
41 state
.settings
[PUBLIC_SUB_PODCAST
.name
] = is_public
46 @cache_control(private
=True)
47 @allowed_methods(['GET'])
48 def show_slug(request
, slug
):
49 podcast
= podcast_for_slug(slug
)
51 if slug
!= podcast
.slug
:
52 target
= reverse('podcast_slug', args
=[podcast
.slug
])
53 return HttpResponseRedirect(target
)
55 return show(request
, podcast
.oldid
)
59 @cache_control(private
=True)
60 @allowed_methods(['GET'])
61 def show(request
, podcast
):
62 """ Shows a podcast detail page """
64 current_site
= RequestSite(request
)
65 episodes
= episode_list(podcast
, request
.user
, limit
=20)
68 max_listeners
= max([e
.listeners
for e
in episodes
] + [0])
74 episodes
= episodes
[1:]
77 group
= PodcastGroup
.get(podcast
.group
)
78 rel_podcasts
= filter(lambda x
: x
!= podcast
, group
.podcasts
)
82 tags
= get_tags(podcast
, user
)
84 if user
.is_authenticated():
85 state
= podcast_state_for_user_podcast(user
, podcast
)
86 subscribed_devices
= state
.get_subscribed_device_ids()
87 subscribed_devices
= [user
.get_device(x
) for x
in subscribed_devices
]
89 subscribe_targets
= podcast
.subscribe_targets(user
)
91 history
= list(state
.actions
)
92 is_public
= state
.settings
.get('public_subscription', True)
93 can_flattr
= request
.user
.get_wksetting(FLATTR_TOKEN
) and podcast
.flattr_url
98 subscribed_devices
= []
99 subscribe_targets
= []
102 is_publisher
= check_publisher_permission(user
, podcast
)
105 dev
= user
.get_device(h
.device
)
106 return proxy_object(h
, device
=dev
)
107 history
= map(_set_objects
, history
)
109 return render(request
, 'podcast.html', {
114 'is_public': is_public
,
115 'devices': subscribed_devices
,
116 'related_podcasts': rel_podcasts
,
117 'can_subscribe': len(subscribe_targets
) > 0,
118 'subscribe_targets': subscribe_targets
,
120 'episodes': episodes
,
121 'max_listeners': max_listeners
,
122 'can_flattr': can_flattr
,
123 'is_publisher': is_publisher
,
127 def get_tags(podcast
, user
):
129 for t
in tags_for_podcast(podcast
):
131 tags
[tag_str
] = False
133 if not user
.is_anonymous():
134 users_tags
= tags_for_user(user
, podcast
.get_id())
135 for t
in users_tags
.get(podcast
.get_id(), []):
139 tag_list
= [{'tag': key
, 'is_own': value
} for key
, value
in tags
.iteritems()]
140 tag_list
.sort(key
=lambda x
: x
['tag'])
142 if len(tag_list
) > MAX_TAGS_ON_PAGE
:
143 tag_list
= filter(lambda x
: x
['is_own'], tag_list
)
144 tag_list
.append({'tag': '...', 'is_own': False})
149 def episode_list(podcast
, user
, offset
=0, limit
=None):
151 Returns a list of episodes, with their action-attribute set to the latest
152 action. The attribute is unsert if there is no episode-action for
156 listeners
= dict(episode_listener_counts(podcast
))
157 episodes
= episodes_for_podcast(podcast
, descending
=True, skip
=offset
, limit
=limit
)
159 if user
.is_authenticated():
161 # prepare pre-populated data for HistoryEntry.fetch_data
162 podcasts_dict
= dict( (p_id
, podcast
) for p_id
in podcast
.get_ids())
163 episodes_dict
= dict( (episode
._id
, episode
) for episode
in episodes
)
165 actions
= get_podcasts_episode_states(podcast
, user
._id
)
166 actions
= map(HistoryEntry
.from_action_dict
, actions
)
168 HistoryEntry
.fetch_data(user
, actions
,
169 podcasts
=podcasts_dict
, episodes
=episodes_dict
)
171 episode_actions
= dict( (action
.episode_id
, action
) for action
in actions
)
175 annotate_episode
= partial(_annotate_episode
, listeners
, episode_actions
)
176 return map(annotate_episode
, episodes
)
180 def all_episodes(request
, podcast
, page_size
=20):
182 # Make sure page request is an int. If not, deliver first page.
184 page
= int(request
.GET
.get('page', '1'))
190 episodes
= episode_list(podcast
, user
, (page
-1) * page_size
,
192 episodes_total
= podcast
.episode_count
or 0
193 num_pages
= episodes_total
/ page_size
194 page_list
= get_page_list(1, num_pages
, page
, 15)
196 max_listeners
= max([e
.listeners
for e
in episodes
] + [0])
198 is_publisher
= check_publisher_permission(user
, podcast
)
200 return render(request
, 'episodes.html', {
202 'episodes': episodes
,
203 'max_listeners': max_listeners
,
204 'page_list': page_list
,
205 'current_page': page
,
206 'is_publisher': is_publisher
,
211 def _annotate_episode(listeners
, episode_actions
, episode
):
212 listener_count
= listeners
.pop(episode
._id
, None)
213 action
= episode_actions
.pop(episode
._id
, None)
214 return proxy_object(episode
, listeners
=listener_count
, action
=action
)
220 def add_tag(request
, podcast
):
221 podcast_state
= podcast_state_for_user_podcast(request
.user
, podcast
)
223 tag_str
= request
.GET
.get('tag', '')
225 return HttpResponseBadRequest()
227 tags
= tag_str
.split(',')
229 @repeat_on_conflict(['state'])
234 update(state
=podcast_state
)
236 if request
.GET
.get('next', '') == 'mytags':
237 return HttpResponseRedirect('/tags/')
239 return HttpResponseRedirect(get_podcast_link_target(podcast
))
244 def remove_tag(request
, podcast
):
245 podcast_state
= podcast_state_for_user_podcast(request
.user
, podcast
)
247 tag_str
= request
.GET
.get('tag', '')
249 return HttpResponseBadRequest()
251 @repeat_on_conflict(['state'])
253 tags
= list(state
.tags
)
255 state
.tags
.remove(tag_str
)
258 update(state
=podcast_state
)
260 if request
.GET
.get('next', '') == 'mytags':
261 return HttpResponseRedirect('/tags/')
263 return HttpResponseRedirect(get_podcast_link_target(podcast
))
268 @allowed_methods(['GET', 'POST'])
269 def subscribe(request
, podcast
):
271 if request
.method
== 'POST':
273 # multiple UIDs from the /podcast/<slug>/subscribe
274 device_uids
= [k
for (k
,v
) in request
.POST
.items() if k
==v
]
276 # single UID from /podcast/<slug>
277 if 'targets' in request
.POST
:
278 device_uids
.append(request
.POST
.get('targets'))
280 for uid
in device_uids
:
282 device
= request
.user
.get_device_by_uid(uid
)
283 podcast
.subscribe(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 def unsubscribe(request
, podcast
, device_uid
):
302 return_to
= request
.GET
.get('return_to', None)
305 raise Http404('Wrong URL')
308 device
= request
.user
.get_device_by_uid(device_uid
)
310 except DeviceDoesNotExist
as e
:
311 messages
.error(request
, str(e
))
312 return HttpResponseRedirect(return_to
)
315 podcast
.unsubscribe(request
.user
, device
)
316 except SubscriptionException
as e
:
317 logger
.exception('Web: %(username)s: could not unsubscribe from podcast %(podcast_url)s on device %(device_id)s' %
318 {'username': request
.user
.username
, 'podcast_url': podcast
.url
, 'device_id': device
.id})
320 return HttpResponseRedirect(return_to
)
325 def subscribe_url(request
):
326 url
= request
.GET
.get('url', None)
329 raise Http404('http://my.gpodder.org/subscribe?url=http://www.example.com/podcast.xml')
331 url
= normalize_feed_url(url
)
334 raise Http404('Please specify a valid url')
336 podcast
= podcast_for_url(url
, create
=True)
338 return HttpResponseRedirect(get_podcast_link_target(podcast
, 'subscribe'))
342 @allowed_methods(['POST'])
343 def set_public(request
, podcast
, public
):
344 state
= podcast_state_for_user_podcast(request
.user
, podcast
)
345 update_podcast_settings(state
=state
, is_public
=public
)
346 return HttpResponseRedirect(get_podcast_link_target(podcast
))
351 def flattr_podcast(request
, podcast
):
352 """ Flattrs a podcast, records an event and redirects to the podcast """
355 site
= RequestSite(request
)
357 # do flattring via the tasks queue, but wait for the result
358 task
= flattr_thing
.delay(user
, podcast
.get_id(), site
.domain
,
359 request
.is_secure(), 'Podcast')
360 success
, msg
= task
.get()
363 action
= SubscriptionAction()
364 action
.action
= 'flattr'
365 state
= podcast_state_for_user_podcast(request
.user
, podcast
)
366 add_subscription_action(state
, action
)
367 messages
.success(request
, _("Flattr\'d"))
370 messages
.error(request
, msg
)
372 return HttpResponseRedirect(get_podcast_link_target(podcast
))
375 # To make all view accessible via either CouchDB-ID or Slugs
376 # a decorator queries the podcast and passes the Id on to the
379 def slug_id_decorator(f
):
381 def _decorator(request
, slug_id
, *args
, **kwargs
):
382 podcast
= podcast_for_slug_id(slug_id
)
387 # redirect when Id or a merged (non-cannonical) slug is used
388 if podcast
.slug
and slug_id
!= podcast
.slug
:
389 return HttpResponseRedirect(get_podcast_link_target(podcast
))
391 return f(request
, podcast
, *args
, **kwargs
)
396 def oldid_decorator(f
):
398 def _decorator(request
, pid
, *args
, **kwargs
):
401 except (TypeError, ValueError):
404 podcast
= podcast_for_oldid(pid
)
409 # redirect to Id or slug URL
410 return HttpResponseRedirect(get_podcast_link_target(podcast
))
415 show_slug_id
= slug_id_decorator(show
)
416 subscribe_slug_id
= slug_id_decorator(subscribe
)
417 unsubscribe_slug_id
= slug_id_decorator(unsubscribe
)
418 add_tag_slug_id
= slug_id_decorator(add_tag
)
419 remove_tag_slug_id
= slug_id_decorator(remove_tag
)
420 set_public_slug_id
= slug_id_decorator(set_public
)
421 all_episodes_slug_id
= slug_id_decorator(all_episodes
)
422 flattr_podcast_slug_id
=slug_id_decorator(flattr_podcast
)
425 show_oldid
= oldid_decorator(show
)
426 subscribe_oldid
= oldid_decorator(subscribe
)
427 unsubscribe_oldid
= oldid_decorator(unsubscribe
)
428 add_tag_oldid
= oldid_decorator(add_tag
)
429 remove_tag_oldid
= oldid_decorator(remove_tag
)
430 set_public_oldid
= oldid_decorator(set_public
)
431 all_episodes_oldid
= oldid_decorator(all_episodes
)
432 flattr_podcast_oldid
= oldid_decorator(flattr_podcast
)