1 from functools
import wraps
, partial
2 from datetime
import datetime
4 from django
.core
.urlresolvers
import reverse
5 from django
.http
import HttpResponseBadRequest
, HttpResponseRedirect
, Http404
6 from django
.shortcuts
import render
7 from django
.contrib
.auth
.decorators
import login_required
8 from django
.contrib
.sites
.models
import RequestSite
9 from django
.utils
.translation
import ugettext
as _
10 from django
.contrib
import messages
11 from django
.views
.decorators
.vary
import vary_on_cookie
12 from django
.views
.decorators
.cache
import never_cache
, cache_control
13 from django
.shortcuts
import get_object_or_404
14 from django
.contrib
.contenttypes
.models
import ContentType
16 from mygpo
.podcasts
.models
import Podcast
, PodcastGroup
, Episode
, Tag
17 from mygpo
.users
.models
import SubscriptionException
18 from mygpo
.subscriptions
.models
import Subscription
19 from mygpo
.subscriptions
import (
20 subscribe
as subscribe_podcast
,
21 unsubscribe
as unsubscribe_podcast
,
22 subscribe_all
as subscribe_podcast_all
,
23 unsubscribe_all
as unsubscribe_podcast_all
,
26 from mygpo
.history
.models
import HistoryEntry
27 from mygpo
.core
.tasks
import flattr_thing
28 from mygpo
.utils
import normalize_feed_url
29 from mygpo
.users
.settings
import PUBLIC_SUB_PODCAST
, FLATTR_TOKEN
30 from mygpo
.publisher
.utils
import check_publisher_permission
31 from mygpo
.usersettings
.models
import UserSettings
32 from mygpo
.users
.models
import Client
33 from mygpo
.web
.forms
import SyncForm
34 from mygpo
.decorators
import allowed_methods
35 from mygpo
.web
.utils
import get_podcast_link_target
, get_page_list
, \
39 logger
= logging
.getLogger(__name__
)
43 @cache_control(private
=True)
44 @allowed_methods(['GET'])
45 def show(request
, podcast
):
46 """ Shows a podcast detail page """
48 podcast
= check_restrictions(podcast
)
50 current_site
= RequestSite(request
)
52 episodes
= episode_list(podcast
, request
.user
, limit
=num_episodes
)
55 max_listeners
= max([e
.listeners
for e
in episodes
] + [0])
61 episodes
= episodes
[1:]
65 rel_podcasts
= group
.podcast_set
.exclude(pk
=podcast
.pk
)
69 tags
= get_tags(podcast
, user
)
70 has_tagged
= any(t
['is_own'] for t
in tags
)
72 if user
.is_authenticated():
73 subscribed_devices
= Client
.objects
.filter(
74 subscription__user
=user
,
75 subscription__podcast
=podcast
,
78 subscribe_targets
= get_subscribe_targets(podcast
, user
)
80 has_history
= HistoryEntry
.objects
.filter(user
=user
, podcast
=podcast
)\
82 can_flattr
= (user
.profile
.settings
.get_wksetting(FLATTR_TOKEN
) and
87 subscribed_devices
= []
88 subscribe_targets
= []
91 is_publisher
= check_publisher_permission(user
, podcast
)
93 episodes_total
= podcast
.episode_count
or 0
94 num_pages
= episodes_total
/ num_episodes
95 page_list
= get_page_list(1, num_pages
, 1, 15)
97 return render(request
, 'podcast.html', {
99 'has_tagged': has_tagged
,
101 'has_history': has_history
,
103 'devices': subscribed_devices
,
104 'related_podcasts': rel_podcasts
,
105 'can_subscribe': len(subscribe_targets
) > 0,
106 'subscribe_targets': subscribe_targets
,
108 'episodes': episodes
,
109 'max_listeners': max_listeners
,
110 'can_flattr': can_flattr
,
111 'is_publisher': is_publisher
,
112 'page_list': page_list
,
117 def get_tags(podcast
, user
, max_tags
=50):
118 """ Returns all tags that user sees for the given podcast
120 The tag list is a list of dicts in the form of {'tag': 'tech', 'is_own':
121 True}. "is_own" indicates if the tag was created by the given user. """
124 for tag
in podcast
.tags
.all():
127 tags
[t
] = {'tag': t
, 'is_own': False}
130 tags
[t
]['is_own'] = True
135 def episode_list(podcast
, user
, offset
=0, limit
=20):
136 """ Returns a list of episodes """
137 # fast pagination by using Episode.order instead of offset/limit
138 page_start
= podcast
.max_episode_order
- offset
139 page_end
= page_start
- limit
140 return Episode
.objects
.filter(podcast
=podcast
,
141 order__lte
=page_start
,
146 def all_episodes(request
, podcast
, page_size
=20):
148 # Make sure page request is an int. If not, deliver first page.
150 page
= int(request
.GET
.get('page', '1'))
156 episodes
= episode_list(podcast
, user
, (page
-1) * page_size
, page_size
)
157 episodes_total
= podcast
.episode_count
or 0
158 num_pages
= episodes_total
/ page_size
159 page_list
= get_page_list(1, num_pages
, page
, 15)
161 max_listeners
= max([e
.listeners
for e
in episodes
] + [0])
163 is_publisher
= check_publisher_permission(user
, podcast
)
165 return render(request
, 'episodes.html', {
167 'episodes': episodes
,
168 'max_listeners': max_listeners
,
169 'page_list': page_list
,
170 'current_page': page
,
171 'is_publisher': is_publisher
,
177 def add_tag(request
, podcast
):
179 tag_str
= request
.GET
.get('tag', '')
181 return HttpResponseBadRequest()
185 tags
= tag_str
.split(',')
186 tags
= map(unicode.strip
, tags
)
188 ContentType
.objects
.get_for_model(podcast
)
191 Tag
.objects
.get_or_create(
195 content_type
=ContentType
.objects
.get_for_model(podcast
),
196 object_id
=podcast
.id,
199 if request
.GET
.get('next', '') == 'mytags':
200 return HttpResponseRedirect('/tags/')
202 return HttpResponseRedirect(get_podcast_link_target(podcast
))
207 def remove_tag(request
, podcast
):
209 tag_str
= request
.GET
.get('tag', '')
211 return HttpResponseBadRequest()
215 tags
= tag_str
.split(',')
216 tags
= map(unicode.strip
, tags
)
218 ContentType
.objects
.get_for_model(podcast
)
224 content_type
=ContentType
.objects
.get_for_model(podcast
),
225 object_id
=podcast
.id,
228 if request
.GET
.get('next', '') == 'mytags':
229 return HttpResponseRedirect('/tags/')
231 return HttpResponseRedirect(get_podcast_link_target(podcast
))
236 @allowed_methods(['GET', 'POST'])
237 def subscribe(request
, podcast
):
239 if request
.method
== 'POST':
241 # multiple UIDs from the /podcast/<slug>/subscribe
242 device_uids
= [k
for (k
,v
) in request
.POST
.items() if k
==v
]
244 # single UID from /podcast/<slug>
245 if 'targets' in request
.POST
:
246 devices
= request
.POST
.get('targets')
247 devices
= devices
.split(',')
248 device_uids
.extend(devices
)
250 for uid
in device_uids
:
252 device
= request
.user
.client_set
.get(uid
=uid
)
253 subscribe_podcast(podcast
, request
.user
, device
)
255 except Client
.DoesNotExist
as e
:
256 messages
.error(request
, str(e
))
258 return HttpResponseRedirect(get_podcast_link_target(podcast
))
260 targets
= get_subscribe_targets(podcast
, request
.user
)
262 return render(request
, 'subscribe.html', {
270 @allowed_methods(['POST'])
271 def subscribe_all(request
, podcast
):
272 """ subscribe all of the user's devices to the podcast """
274 subscribe_podcast_all(podcast
, user
)
275 return HttpResponseRedirect(get_podcast_link_target(podcast
))
280 def unsubscribe(request
, podcast
, device_uid
):
282 return_to
= request
.GET
.get('return_to', None)
285 raise Http404('Wrong URL')
289 device
= user
.client_set
.get(uid
=device_uid
)
291 except Client
.DoesNotExist
as e
:
292 messages
.error(request
, str(e
))
293 return HttpResponseRedirect(return_to
)
296 unsubscribe_podcast(podcast
, user
, device
)
297 except SubscriptionException
as e
:
298 logger
.exception('Web: %(username)s: could not unsubscribe from podcast %(podcast_url)s on device %(device_id)s' %
299 {'username': request
.user
.username
, 'podcast_url': podcast
.url
, 'device_id': device
.id})
301 return HttpResponseRedirect(return_to
)
306 @allowed_methods(['POST'])
307 def unsubscribe_all(request
, podcast
):
308 """ unsubscribe all of the user's devices from the podcast """
310 unsubscribe_podcast_all(podcast
, user
)
311 return HttpResponseRedirect(get_podcast_link_target(podcast
))
316 def subscribe_url(request
):
317 url
= request
.GET
.get('url', None)
320 raise Http404('http://my.gpodder.org/subscribe?url=http://www.example.com/podcast.xml')
322 url
= normalize_feed_url(url
)
325 raise Http404('Please specify a valid url')
327 podcast
= Podcast
.objects
.get_or_create_for_url(url
)
329 return HttpResponseRedirect(get_podcast_link_target(podcast
, 'subscribe'))
333 @allowed_methods(['POST'])
334 def set_public(request
, podcast
, public
):
335 settings
, created
= UserSettings
.objects
.get_or_create(
337 content_type
=ContentType
.objects
.get_for_model(podcast
),
338 object_id
=podcast
.pk
,
340 settings
.set_wksetting(PUBLIC_SUB_PODCAST
, public
)
342 return HttpResponseRedirect(get_podcast_link_target(podcast
))
347 def flattr_podcast(request
, podcast
):
348 """ Flattrs a podcast, records an event and redirects to the podcast """
351 site
= RequestSite(request
)
352 now
= datetime
.utcnow()
354 # do flattring via the tasks queue, but wait for the result
355 task
= flattr_thing
.delay(user
, podcast
.get_id(), site
.domain
,
356 request
.is_secure(), 'Podcast')
357 success
, msg
= task
.get()
360 messages
.success(request
, _("Flattr\'d"))
363 messages
.error(request
, msg
)
365 return HttpResponseRedirect(get_podcast_link_target(podcast
))
368 # To make all view accessible via either IDs or Slugs
369 # a decorator queries the podcast and passes the Id on to the
372 def slug_decorator(f
):
374 def _decorator(request
, slug
, *args
, **kwargs
):
377 podcast
= Podcast
.objects
.filter(
379 slugs__content_type
=ContentType
.objects
.get_for_model(Podcast
),
381 podcast
= podcast
.prefetch_related('slugs', 'urls').get()
382 except Podcast
.DoesNotExist
:
385 # redirect when a non-cannonical slug is used
386 if slug
!= podcast
.slug
:
387 return HttpResponseRedirect(get_podcast_link_target(podcast
))
389 return f(request
, podcast
, *args
, **kwargs
)
396 def _decorator(request
, podcast_id
, *args
, **kwargs
):
399 podcast
= Podcast
.objects
.filter(id=podcast_id
)
400 podcast
= podcast
.prefetch_related('slugs', 'urls').get()
402 # if the podcast has a slug, redirect to its canonical URL
404 return HttpResponseRedirect(get_podcast_link_target(podcast
))
406 return f(request
, podcast
, *args
, **kwargs
)
408 except Podcast
.DoesNotExist
:
409 podcast
= get_object_or_404(Podcast
, merged_uuids__uuid
=podcast_id
)
410 return HttpResponseRedirect(get_podcast_link_target(podcast
))
415 show_slug
= slug_decorator(show
)
416 subscribe_slug
= slug_decorator(subscribe
)
417 subscribe_all_slug
= slug_decorator(subscribe_all
)
418 unsubscribe_slug
= slug_decorator(unsubscribe
)
419 unsubscribe_all_slug
= slug_decorator(unsubscribe_all
)
420 add_tag_slug
= slug_decorator(add_tag
)
421 remove_tag_slug
= slug_decorator(remove_tag
)
422 set_public_slug
= slug_decorator(set_public
)
423 all_episodes_slug
= slug_decorator(all_episodes
)
424 flattr_podcast_slug
= slug_decorator(flattr_podcast
)
427 show_id
= id_decorator(show
)
428 subscribe_id
= id_decorator(subscribe
)
429 subscribe_all_id
= id_decorator(subscribe_all
)
430 unsubscribe_id
= id_decorator(unsubscribe
)
431 unsubscribe_all_id
= id_decorator(unsubscribe_all
)
432 add_tag_id
= id_decorator(add_tag
)
433 remove_tag_id
= id_decorator(remove_tag
)
434 set_public_id
= id_decorator(set_public
)
435 all_episodes_id
= id_decorator(all_episodes
)
436 flattr_podcast_id
= id_decorator(flattr_podcast
)