[UserSettings] refactor, add tests
[mygpo.git] / mygpo / web / views / podcast.py
blob045a49d9fbbe95e9e27b4093bf8689f8784b3154
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).all().by_released()
138 episodes = list(episodes.prefetch_related('slugs')[offset:offset+limit])
139 return episodes
142 def all_episodes(request, podcast, page_size=20):
144 # Make sure page request is an int. If not, deliver first page.
145 try:
146 page = int(request.GET.get('page', '1'))
147 except ValueError:
148 page = 1
150 user = request.user
152 episodes = episode_list(podcast, user, (page-1) * page_size, page_size)
153 episodes_total = podcast.episode_count or 0
154 num_pages = episodes_total / page_size
155 page_list = get_page_list(1, num_pages, page, 15)
157 max_listeners = max([e.listeners for e in episodes] + [0])
159 is_publisher = check_publisher_permission(user, podcast)
161 return render(request, 'episodes.html', {
162 'podcast': podcast,
163 'episodes': episodes,
164 'max_listeners': max_listeners,
165 'page_list': page_list,
166 'current_page': page,
167 'is_publisher': is_publisher,
171 @never_cache
172 @login_required
173 def add_tag(request, podcast):
175 tag_str = request.GET.get('tag', '')
176 if not tag_str:
177 return HttpResponseBadRequest()
179 user = request.user
181 tags = tag_str.split(',')
182 tags = map(unicode.strip, tags)
184 ContentType.objects.get_for_model(podcast)
186 for tag in tags:
187 Tag.objects.get_or_create(
188 tag=tag,
189 source=Tag.USER,
190 user=user,
191 content_type=ContentType.objects.get_for_model(podcast),
192 object_id=podcast.id,
195 if request.GET.get('next', '') == 'mytags':
196 return HttpResponseRedirect('/tags/')
198 return HttpResponseRedirect(get_podcast_link_target(podcast))
201 @never_cache
202 @login_required
203 def remove_tag(request, podcast):
205 tag_str = request.GET.get('tag', '')
206 if not tag_str:
207 return HttpResponseBadRequest()
209 user = request.user
211 tags = tag_str.split(',')
212 tags = map(unicode.strip, tags)
214 ContentType.objects.get_for_model(podcast)
216 Tag.objects.filter(
217 tag__in=tags,
218 source=Tag.USER,
219 user=user,
220 content_type=ContentType.objects.get_for_model(podcast),
221 object_id=podcast.id,
222 ).delete()
224 if request.GET.get('next', '') == 'mytags':
225 return HttpResponseRedirect('/tags/')
227 return HttpResponseRedirect(get_podcast_link_target(podcast))
230 @never_cache
231 @login_required
232 @allowed_methods(['GET', 'POST'])
233 def subscribe(request, podcast):
235 if request.method == 'POST':
237 # multiple UIDs from the /podcast/<slug>/subscribe
238 device_uids = [k for (k,v) in request.POST.items() if k==v]
240 # single UID from /podcast/<slug>
241 if 'targets' in request.POST:
242 devices = request.POST.get('targets')
243 devices = devices.split(',')
244 device_uids.extend(devices)
246 for uid in device_uids:
247 try:
248 device = request.user.client_set.get(uid=uid)
249 subscribe_podcast(podcast, request.user, device)
251 except Client.DoesNotExist as e:
252 messages.error(request, str(e))
254 return HttpResponseRedirect(get_podcast_link_target(podcast))
256 targets = get_subscribe_targets(podcast, request.user)
258 return render(request, 'subscribe.html', {
259 'targets': targets,
260 'podcast': podcast,
264 @never_cache
265 @login_required
266 @allowed_methods(['POST'])
267 def subscribe_all(request, podcast):
268 """ subscribe all of the user's devices to the podcast """
269 user = request.user
270 subscribe_podcast_all(podcast, user)
271 return HttpResponseRedirect(get_podcast_link_target(podcast))
274 @never_cache
275 @login_required
276 def unsubscribe(request, podcast, device_uid):
278 return_to = request.GET.get('return_to', None)
280 if not return_to:
281 raise Http404('Wrong URL')
283 user = request.user
284 try:
285 device = user.client_set.get(uid=device_uid)
287 except Client.DoesNotExist as e:
288 messages.error(request, str(e))
289 return HttpResponseRedirect(return_to)
291 try:
292 unsubscribe_podcast(podcast, user, device)
293 except SubscriptionException as e:
294 logger.exception('Web: %(username)s: could not unsubscribe from podcast %(podcast_url)s on device %(device_id)s' %
295 {'username': request.user.username, 'podcast_url': podcast.url, 'device_id': device.id})
297 return HttpResponseRedirect(return_to)
300 @never_cache
301 @login_required
302 @allowed_methods(['POST'])
303 def unsubscribe_all(request, podcast):
304 """ unsubscribe all of the user's devices from the podcast """
305 user = request.user
306 unsubscribe_podcast_all(podcast, user)
307 return HttpResponseRedirect(get_podcast_link_target(podcast))
310 @never_cache
311 @login_required
312 def subscribe_url(request):
313 url = request.GET.get('url', None)
315 if not url:
316 raise Http404('http://my.gpodder.org/subscribe?url=http://www.example.com/podcast.xml')
318 url = normalize_feed_url(url)
320 if not url:
321 raise Http404('Please specify a valid url')
323 podcast = Podcast.objects.get_or_create_for_url(url)
325 return HttpResponseRedirect(get_podcast_link_target(podcast, 'subscribe'))
328 @never_cache
329 @allowed_methods(['POST'])
330 def set_public(request, podcast, public):
331 settings, created = UserSettings.objects.get_or_create(
332 user=request.user,
333 content_type=ContentType.objects.get_for_model(podcast),
334 object_id=podcast.pk,
336 settings.set_wksetting(PUBLIC_SUB_PODCAST, public)
337 settings.save()
338 return HttpResponseRedirect(get_podcast_link_target(podcast))
341 @never_cache
342 @login_required
343 def flattr_podcast(request, podcast):
344 """ Flattrs a podcast, records an event and redirects to the podcast """
346 user = request.user
347 site = RequestSite(request)
348 now = datetime.utcnow()
350 # do flattring via the tasks queue, but wait for the result
351 task = flattr_thing.delay(user, podcast.get_id(), site.domain,
352 request.is_secure(), 'Podcast')
353 success, msg = task.get()
355 if success:
356 messages.success(request, _("Flattr\'d"))
358 else:
359 messages.error(request, msg)
361 return HttpResponseRedirect(get_podcast_link_target(podcast))
364 # To make all view accessible via either IDs or Slugs
365 # a decorator queries the podcast and passes the Id on to the
366 # regular views
368 def slug_decorator(f):
369 @wraps(f)
370 def _decorator(request, slug, *args, **kwargs):
372 try:
373 podcast = Podcast.objects.filter(
374 slugs__slug=slug,
375 slugs__content_type=ContentType.objects.get_for_model(Podcast),
377 podcast = podcast.prefetch_related('slugs', 'urls').get()
378 except Podcast.DoesNotExist:
379 raise Http404
381 # redirect when a non-cannonical slug is used
382 if slug != podcast.slug:
383 return HttpResponseRedirect(get_podcast_link_target(podcast))
385 return f(request, podcast, *args, **kwargs)
387 return _decorator
390 def id_decorator(f):
391 @wraps(f)
392 def _decorator(request, podcast_id, *args, **kwargs):
394 try:
395 podcast = Podcast.objects.filter(id=podcast_id)
396 podcast = podcast.prefetch_related('slugs', 'urls').get()
398 # if the podcast has a slug, redirect to its canonical URL
399 if podcast.slug:
400 return HttpResponseRedirect(get_podcast_link_target(podcast))
402 return f(request, podcast, *args, **kwargs)
404 except Podcast.DoesNotExist:
405 podcast = get_object_or_404(Podcast, merged_uuids__uuid=podcast_id)
406 return HttpResponseRedirect(get_podcast_link_target(podcast))
408 return _decorator
411 show_slug = slug_decorator(show)
412 subscribe_slug = slug_decorator(subscribe)
413 subscribe_all_slug = slug_decorator(subscribe_all)
414 unsubscribe_slug = slug_decorator(unsubscribe)
415 unsubscribe_all_slug = slug_decorator(unsubscribe_all)
416 add_tag_slug = slug_decorator(add_tag)
417 remove_tag_slug = slug_decorator(remove_tag)
418 set_public_slug = slug_decorator(set_public)
419 all_episodes_slug = slug_decorator(all_episodes)
420 flattr_podcast_slug = slug_decorator(flattr_podcast)
423 show_id = id_decorator(show)
424 subscribe_id = id_decorator(subscribe)
425 subscribe_all_id = id_decorator(subscribe_all)
426 unsubscribe_id = id_decorator(unsubscribe)
427 unsubscribe_all_id = id_decorator(unsubscribe_all)
428 add_tag_id = id_decorator(add_tag)
429 remove_tag_id = id_decorator(remove_tag)
430 set_public_id = id_decorator(set_public)
431 all_episodes_id = id_decorator(all_episodes)
432 flattr_podcast_id = id_decorator(flattr_podcast)