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 listeners
= list(filter(None, [e
.listeners
for e
in episodes
]))
56 max_listeners
= max(listeners
+ [0])
62 episodes
= episodes
[1:]
66 rel_podcasts
= group
.podcast_set
.exclude(pk
=podcast
.pk
)
70 tags
= get_tags(podcast
, user
)
71 has_tagged
= any(t
['is_own'] for t
in tags
)
73 if user
.is_authenticated():
74 subscribed_devices
= Client
.objects
.filter(
75 subscription__user
=user
,
76 subscription__podcast
=podcast
,
79 subscribe_targets
= get_subscribe_targets(podcast
, user
)
81 has_history
= HistoryEntry
.objects
.filter(user
=user
, podcast
=podcast
)\
83 can_flattr
= (user
.profile
.settings
.get_wksetting(FLATTR_TOKEN
) and
88 subscribed_devices
= []
89 subscribe_targets
= []
92 is_publisher
= check_publisher_permission(user
, podcast
)
94 episodes_total
= podcast
.episode_count
or 0
95 num_pages
= episodes_total
/ num_episodes
96 page_list
= get_page_list(1, num_pages
, 1, 15)
98 return render(request
, 'podcast.html', {
100 'has_tagged': has_tagged
,
102 'has_history': has_history
,
104 'devices': subscribed_devices
,
105 'related_podcasts': rel_podcasts
,
106 'can_subscribe': len(subscribe_targets
) > 0,
107 'subscribe_targets': subscribe_targets
,
109 'episodes': episodes
,
110 'max_listeners': max_listeners
,
111 'can_flattr': can_flattr
,
112 'is_publisher': is_publisher
,
113 'page_list': page_list
,
118 def get_tags(podcast
, user
, max_tags
=50):
119 """ Returns all tags that user sees for the given podcast
121 The tag list is a list of dicts in the form of {'tag': 'tech', 'is_own':
122 True}. "is_own" indicates if the tag was created by the given user. """
125 for tag
in podcast
.tags
.all():
128 tags
[t
] = {'tag': t
, 'is_own': False}
131 tags
[t
]['is_own'] = True
133 return list(tags
.values())
136 def episode_list(podcast
, user
, offset
=0, limit
=20):
137 """ Returns a list of episodes """
138 # fast pagination by using Episode.order instead of offset/limit
139 page_start
= podcast
.max_episode_order
- offset
140 page_end
= page_start
- limit
141 return Episode
.objects
.filter(podcast
=podcast
,
142 order__lte
=page_start
,
144 .prefetch_related('slugs')\
148 def all_episodes(request
, podcast
, page_size
=20):
150 # Make sure page request is an int. If not, deliver first page.
152 page
= int(request
.GET
.get('page', '1'))
158 episodes
= episode_list(podcast
, user
, (page
-1) * page_size
, page_size
)
159 episodes_total
= podcast
.episode_count
or 0
160 num_pages
= episodes_total
/ page_size
161 page_list
= get_page_list(1, num_pages
, page
, 15)
163 max_listeners
= max([e
.listeners
for e
in episodes
] + [0])
165 is_publisher
= check_publisher_permission(user
, podcast
)
167 return render(request
, 'episodes.html', {
169 'episodes': episodes
,
170 'max_listeners': max_listeners
,
171 'page_list': page_list
,
172 'current_page': page
,
173 'is_publisher': is_publisher
,
179 def add_tag(request
, podcast
):
181 tag_str
= request
.GET
.get('tag', '')
183 return HttpResponseBadRequest()
187 tags
= tag_str
.split(',')
188 tags
= list(map(str.strip
, tags
))
190 ContentType
.objects
.get_for_model(podcast
)
193 Tag
.objects
.get_or_create(
197 content_type
=ContentType
.objects
.get_for_model(podcast
),
198 object_id
=podcast
.id,
201 if request
.GET
.get('next', '') == 'mytags':
202 return HttpResponseRedirect('/tags/')
204 return HttpResponseRedirect(get_podcast_link_target(podcast
))
209 def remove_tag(request
, podcast
):
211 tag_str
= request
.GET
.get('tag', '')
213 return HttpResponseBadRequest()
217 tags
= tag_str
.split(',')
218 tags
= list(map(str.strip
, tags
))
220 ContentType
.objects
.get_for_model(podcast
)
226 content_type
=ContentType
.objects
.get_for_model(podcast
),
227 object_id
=podcast
.id,
230 if request
.GET
.get('next', '') == 'mytags':
231 return HttpResponseRedirect('/tags/')
233 return HttpResponseRedirect(get_podcast_link_target(podcast
))
238 @allowed_methods(['GET', 'POST'])
239 def subscribe(request
, podcast
):
241 if request
.method
== 'POST':
243 # multiple UIDs from the /podcast/<slug>/subscribe
244 device_uids
= [k
for (k
,v
) in list(request
.POST
.items()) if k
==v
]
246 # single UID from /podcast/<slug>
247 if 'targets' in request
.POST
:
248 devices
= request
.POST
.get('targets')
249 devices
= devices
.split(',')
250 device_uids
.extend(devices
)
252 for uid
in device_uids
:
254 device
= request
.user
.client_set
.get(uid
=uid
)
255 subscribe_podcast(podcast
, request
.user
, device
)
257 except Client
.DoesNotExist
as e
:
258 messages
.error(request
, str(e
))
260 return HttpResponseRedirect(get_podcast_link_target(podcast
))
262 targets
= get_subscribe_targets(podcast
, request
.user
)
264 return render(request
, 'subscribe.html', {
272 @allowed_methods(['POST'])
273 def subscribe_all(request
, podcast
):
274 """ subscribe all of the user's devices to the podcast """
276 subscribe_podcast_all(podcast
, user
)
277 return HttpResponseRedirect(get_podcast_link_target(podcast
))
282 def unsubscribe(request
, podcast
, device_uid
):
284 return_to
= request
.GET
.get('return_to', None)
287 raise Http404('Wrong URL')
291 device
= user
.client_set
.get(uid
=device_uid
)
293 except Client
.DoesNotExist
as e
:
294 messages
.error(request
, str(e
))
295 return HttpResponseRedirect(return_to
)
298 unsubscribe_podcast(podcast
, user
, device
)
299 except SubscriptionException
as e
:
300 logger
.exception('Web: %(username)s: could not unsubscribe from podcast %(podcast_url)s on device %(device_id)s' %
301 {'username': request
.user
.username
, 'podcast_url': podcast
.url
, 'device_id': device
.id})
303 return HttpResponseRedirect(return_to
)
308 @allowed_methods(['POST'])
309 def unsubscribe_all(request
, podcast
):
310 """ unsubscribe all of the user's devices from the podcast """
312 unsubscribe_podcast_all(podcast
, user
)
313 return HttpResponseRedirect(get_podcast_link_target(podcast
))
318 def subscribe_url(request
):
319 url
= request
.GET
.get('url', None)
322 raise Http404('http://my.gpodder.org/subscribe?url=http://www.example.com/podcast.xml')
324 url
= normalize_feed_url(url
)
327 raise Http404('Please specify a valid url')
329 podcast
= Podcast
.objects
.get_or_create_for_url(url
)
331 return HttpResponseRedirect(get_podcast_link_target(podcast
, 'subscribe'))
335 @allowed_methods(['POST'])
336 def set_public(request
, podcast
, public
):
337 settings
, created
= UserSettings
.objects
.get_or_create(
339 content_type
=ContentType
.objects
.get_for_model(podcast
),
340 object_id
=podcast
.pk
,
342 settings
.set_wksetting(PUBLIC_SUB_PODCAST
, public
)
344 return HttpResponseRedirect(get_podcast_link_target(podcast
))
349 def flattr_podcast(request
, podcast
):
350 """ Flattrs a podcast, records an event and redirects to the podcast """
353 site
= RequestSite(request
)
354 now
= datetime
.utcnow()
356 # do flattring via the tasks queue, but wait for the result
357 task
= flattr_thing
.delay(user
, podcast
.get_id(), site
.domain
,
358 request
.is_secure(), 'Podcast')
359 success
, msg
= task
.get()
362 messages
.success(request
, _("Flattr\'d"))
365 messages
.error(request
, msg
)
367 return HttpResponseRedirect(get_podcast_link_target(podcast
))
370 # To make all view accessible via either IDs or Slugs
371 # a decorator queries the podcast and passes the Id on to the
374 def slug_decorator(f
):
376 def _decorator(request
, slug
, *args
, **kwargs
):
379 podcast
= Podcast
.objects
.filter(
381 slugs__content_type
=ContentType
.objects
.get_for_model(Podcast
),
383 podcast
= podcast
.prefetch_related('slugs', 'urls').get()
384 except Podcast
.DoesNotExist
:
387 # redirect when a non-cannonical slug is used
388 if slug
!= podcast
.slug
:
389 return HttpResponseRedirect(get_podcast_link_target(podcast
))
391 return f(request
, podcast
, *args
, **kwargs
)
398 def _decorator(request
, podcast_id
, *args
, **kwargs
):
401 podcast
= Podcast
.objects
.filter(id=podcast_id
)
402 podcast
= podcast
.prefetch_related('slugs', 'urls').get()
404 # if the podcast has a slug, redirect to its canonical URL
406 return HttpResponseRedirect(get_podcast_link_target(podcast
))
408 return f(request
, podcast
, *args
, **kwargs
)
410 except Podcast
.DoesNotExist
:
411 podcast
= get_object_or_404(Podcast
, merged_uuids__uuid
=podcast_id
)
412 return HttpResponseRedirect(get_podcast_link_target(podcast
))
417 show_slug
= slug_decorator(show
)
418 subscribe_slug
= slug_decorator(subscribe
)
419 subscribe_all_slug
= slug_decorator(subscribe_all
)
420 unsubscribe_slug
= slug_decorator(unsubscribe
)
421 unsubscribe_all_slug
= slug_decorator(unsubscribe_all
)
422 add_tag_slug
= slug_decorator(add_tag
)
423 remove_tag_slug
= slug_decorator(remove_tag
)
424 set_public_slug
= slug_decorator(set_public
)
425 all_episodes_slug
= slug_decorator(all_episodes
)
426 flattr_podcast_slug
= slug_decorator(flattr_podcast
)
429 show_id
= id_decorator(show
)
430 subscribe_id
= id_decorator(subscribe
)
431 subscribe_all_id
= id_decorator(subscribe_all
)
432 unsubscribe_id
= id_decorator(unsubscribe
)
433 unsubscribe_all_id
= id_decorator(unsubscribe_all
)
434 add_tag_id
= id_decorator(add_tag
)
435 remove_tag_id
= id_decorator(remove_tag
)
436 set_public_id
= id_decorator(set_public
)
437 all_episodes_id
= id_decorator(all_episodes
)
438 flattr_podcast_id
= id_decorator(flattr_podcast
)