[Podcasts] order episodes by "order"
[mygpo.git] / mygpo / web / views / podcast.py
blobc4e75cb797d5a6a6c5459c6d026bd1b1eb814079
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,
24 get_subscribe_targets
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, \
36 check_restrictions
38 import logging
39 logger = logging.getLogger(__name__)
42 @vary_on_cookie
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)
51 num_episodes = 20
52 episodes = episode_list(podcast, request.user, limit=num_episodes)
53 user = request.user
55 max_listeners = max([e.listeners for e in episodes] + [0])
57 episode = None
59 if episodes:
60 episode = episodes[0]
61 episodes = episodes[1:]
63 if podcast.group:
64 group = podcast.group
65 rel_podcasts = group.podcast_set.exclude(pk=podcast.pk)
66 else:
67 rel_podcasts = []
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)\
81 .exists()
82 can_flattr = (user.profile.settings.get_wksetting(FLATTR_TOKEN) and
83 podcast.flattr_url)
85 else:
86 has_history = False
87 subscribed_devices = []
88 subscribe_targets = []
89 can_flattr = False
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', {
98 'tags': tags,
99 'has_tagged': has_tagged,
100 'url': current_site,
101 'has_history': has_history,
102 'podcast': podcast,
103 'devices': subscribed_devices,
104 'related_podcasts': rel_podcasts,
105 'can_subscribe': len(subscribe_targets) > 0,
106 'subscribe_targets': subscribe_targets,
107 'episode': episode,
108 'episodes': episodes,
109 'max_listeners': max_listeners,
110 'can_flattr': can_flattr,
111 'is_publisher': is_publisher,
112 'page_list': page_list,
113 'current_page': 1,
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. """
122 tags = {}
124 for tag in podcast.tags.all():
125 t = tag.tag.lower()
126 if not t in tags:
127 tags[t] = {'tag': t, 'is_own': False}
129 if tag.user == user:
130 tags[t]['is_own'] = True
132 return tags.values()
135 def episode_list(podcast, user, offset=0, limit=None):
136 """ Returns a list of episodes """
137 episodes = Episode.objects.filter(podcast=podcast)\
138 .order_by('-order', '-released')
139 episodes = list(episodes.prefetch_related('slugs')[offset:offset+limit])
140 return episodes
143 def all_episodes(request, podcast, page_size=20):
145 # Make sure page request is an int. If not, deliver first page.
146 try:
147 page = int(request.GET.get('page', '1'))
148 except ValueError:
149 page = 1
151 user = request.user
153 episodes = episode_list(podcast, user, (page-1) * page_size, page_size)
154 episodes_total = podcast.episode_count or 0
155 num_pages = episodes_total / page_size
156 page_list = get_page_list(1, num_pages, page, 15)
158 max_listeners = max([e.listeners for e in episodes] + [0])
160 is_publisher = check_publisher_permission(user, podcast)
162 return render(request, 'episodes.html', {
163 'podcast': podcast,
164 'episodes': episodes,
165 'max_listeners': max_listeners,
166 'page_list': page_list,
167 'current_page': page,
168 'is_publisher': is_publisher,
172 @never_cache
173 @login_required
174 def add_tag(request, podcast):
176 tag_str = request.GET.get('tag', '')
177 if not tag_str:
178 return HttpResponseBadRequest()
180 user = request.user
182 tags = tag_str.split(',')
183 tags = map(unicode.strip, tags)
185 ContentType.objects.get_for_model(podcast)
187 for tag in tags:
188 Tag.objects.get_or_create(
189 tag=tag,
190 source=Tag.USER,
191 user=user,
192 content_type=ContentType.objects.get_for_model(podcast),
193 object_id=podcast.id,
196 if request.GET.get('next', '') == 'mytags':
197 return HttpResponseRedirect('/tags/')
199 return HttpResponseRedirect(get_podcast_link_target(podcast))
202 @never_cache
203 @login_required
204 def remove_tag(request, podcast):
206 tag_str = request.GET.get('tag', '')
207 if not tag_str:
208 return HttpResponseBadRequest()
210 user = request.user
212 tags = tag_str.split(',')
213 tags = map(unicode.strip, tags)
215 ContentType.objects.get_for_model(podcast)
217 Tag.objects.filter(
218 tag__in=tags,
219 source=Tag.USER,
220 user=user,
221 content_type=ContentType.objects.get_for_model(podcast),
222 object_id=podcast.id,
223 ).delete()
225 if request.GET.get('next', '') == 'mytags':
226 return HttpResponseRedirect('/tags/')
228 return HttpResponseRedirect(get_podcast_link_target(podcast))
231 @never_cache
232 @login_required
233 @allowed_methods(['GET', 'POST'])
234 def subscribe(request, podcast):
236 if request.method == 'POST':
238 # multiple UIDs from the /podcast/<slug>/subscribe
239 device_uids = [k for (k,v) in request.POST.items() if k==v]
241 # single UID from /podcast/<slug>
242 if 'targets' in request.POST:
243 devices = request.POST.get('targets')
244 devices = devices.split(',')
245 device_uids.extend(devices)
247 for uid in device_uids:
248 try:
249 device = request.user.client_set.get(uid=uid)
250 subscribe_podcast(podcast, request.user, device)
252 except Client.DoesNotExist as e:
253 messages.error(request, str(e))
255 return HttpResponseRedirect(get_podcast_link_target(podcast))
257 targets = get_subscribe_targets(podcast, request.user)
259 return render(request, 'subscribe.html', {
260 'targets': targets,
261 'podcast': podcast,
265 @never_cache
266 @login_required
267 @allowed_methods(['POST'])
268 def subscribe_all(request, podcast):
269 """ subscribe all of the user's devices to the podcast """
270 user = request.user
271 subscribe_podcast_all(podcast, user)
272 return HttpResponseRedirect(get_podcast_link_target(podcast))
275 @never_cache
276 @login_required
277 def unsubscribe(request, podcast, device_uid):
279 return_to = request.GET.get('return_to', None)
281 if not return_to:
282 raise Http404('Wrong URL')
284 user = request.user
285 try:
286 device = user.client_set.get(uid=device_uid)
288 except Client.DoesNotExist as e:
289 messages.error(request, str(e))
290 return HttpResponseRedirect(return_to)
292 try:
293 unsubscribe_podcast(podcast, user, device)
294 except SubscriptionException as e:
295 logger.exception('Web: %(username)s: could not unsubscribe from podcast %(podcast_url)s on device %(device_id)s' %
296 {'username': request.user.username, 'podcast_url': podcast.url, 'device_id': device.id})
298 return HttpResponseRedirect(return_to)
301 @never_cache
302 @login_required
303 @allowed_methods(['POST'])
304 def unsubscribe_all(request, podcast):
305 """ unsubscribe all of the user's devices from the podcast """
306 user = request.user
307 unsubscribe_podcast_all(podcast, user)
308 return HttpResponseRedirect(get_podcast_link_target(podcast))
311 @never_cache
312 @login_required
313 def subscribe_url(request):
314 url = request.GET.get('url', None)
316 if not url:
317 raise Http404('http://my.gpodder.org/subscribe?url=http://www.example.com/podcast.xml')
319 url = normalize_feed_url(url)
321 if not url:
322 raise Http404('Please specify a valid url')
324 podcast = Podcast.objects.get_or_create_for_url(url)
326 return HttpResponseRedirect(get_podcast_link_target(podcast, 'subscribe'))
329 @never_cache
330 @allowed_methods(['POST'])
331 def set_public(request, podcast, public):
332 settings, created = UserSettings.objects.get_or_create(
333 user=request.user,
334 content_type=ContentType.objects.get_for_model(podcast),
335 object_id=podcast.pk,
337 settings.set_wksetting(PUBLIC_SUB_PODCAST, public)
338 settings.save()
339 return HttpResponseRedirect(get_podcast_link_target(podcast))
342 @never_cache
343 @login_required
344 def flattr_podcast(request, podcast):
345 """ Flattrs a podcast, records an event and redirects to the podcast """
347 user = request.user
348 site = RequestSite(request)
349 now = datetime.utcnow()
351 # do flattring via the tasks queue, but wait for the result
352 task = flattr_thing.delay(user, podcast.get_id(), site.domain,
353 request.is_secure(), 'Podcast')
354 success, msg = task.get()
356 if success:
357 messages.success(request, _("Flattr\'d"))
359 else:
360 messages.error(request, msg)
362 return HttpResponseRedirect(get_podcast_link_target(podcast))
365 # To make all view accessible via either IDs or Slugs
366 # a decorator queries the podcast and passes the Id on to the
367 # regular views
369 def slug_decorator(f):
370 @wraps(f)
371 def _decorator(request, slug, *args, **kwargs):
373 try:
374 podcast = Podcast.objects.filter(
375 slugs__slug=slug,
376 slugs__content_type=ContentType.objects.get_for_model(Podcast),
378 podcast = podcast.prefetch_related('slugs', 'urls').get()
379 except Podcast.DoesNotExist:
380 raise Http404
382 # redirect when a non-cannonical slug is used
383 if slug != podcast.slug:
384 return HttpResponseRedirect(get_podcast_link_target(podcast))
386 return f(request, podcast, *args, **kwargs)
388 return _decorator
391 def id_decorator(f):
392 @wraps(f)
393 def _decorator(request, podcast_id, *args, **kwargs):
395 try:
396 podcast = Podcast.objects.filter(id=podcast_id)
397 podcast = podcast.prefetch_related('slugs', 'urls').get()
399 # if the podcast has a slug, redirect to its canonical URL
400 if podcast.slug:
401 return HttpResponseRedirect(get_podcast_link_target(podcast))
403 return f(request, podcast, *args, **kwargs)
405 except Podcast.DoesNotExist:
406 podcast = get_object_or_404(Podcast, merged_uuids__uuid=podcast_id)
407 return HttpResponseRedirect(get_podcast_link_target(podcast))
409 return _decorator
412 show_slug = slug_decorator(show)
413 subscribe_slug = slug_decorator(subscribe)
414 subscribe_all_slug = slug_decorator(subscribe_all)
415 unsubscribe_slug = slug_decorator(unsubscribe)
416 unsubscribe_all_slug = slug_decorator(unsubscribe_all)
417 add_tag_slug = slug_decorator(add_tag)
418 remove_tag_slug = slug_decorator(remove_tag)
419 set_public_slug = slug_decorator(set_public)
420 all_episodes_slug = slug_decorator(all_episodes)
421 flattr_podcast_slug = slug_decorator(flattr_podcast)
424 show_id = id_decorator(show)
425 subscribe_id = id_decorator(subscribe)
426 subscribe_all_id = id_decorator(subscribe_all)
427 unsubscribe_id = id_decorator(unsubscribe)
428 unsubscribe_all_id = id_decorator(unsubscribe_all)
429 add_tag_id = id_decorator(add_tag)
430 remove_tag_id = id_decorator(remove_tag)
431 set_public_id = id_decorator(set_public)
432 all_episodes_id = id_decorator(all_episodes)
433 flattr_podcast_id = id_decorator(flattr_podcast)