fix display issues on podcast page
[mygpo.git] / mygpo / web / views / podcast.py
blob9f3340119135c611f79d357548bc80948d766a36
1 from datetime import date, timedelta, datetime
2 from functools import wraps, partial
4 from django.core.urlresolvers import reverse
5 from django.http import HttpResponseBadRequest, HttpResponseRedirect, Http404
6 from django.db import IntegrityError
7 from django.shortcuts import render
8 from django.contrib.auth.decorators import login_required
9 from django.contrib.sites.models import RequestSite
10 from django.utils.translation import ugettext as _
11 from django.contrib import messages
12 from django.views.decorators.vary import vary_on_cookie
13 from django.views.decorators.cache import never_cache, cache_control
15 from mygpo.core.models import Podcast, PodcastGroup, SubscriptionException
16 from mygpo.core.proxy import proxy_object
17 from mygpo.api.sanitizing import sanitize_url
18 from mygpo.users.models import HistoryEntry, DeviceDoesNotExist
19 from mygpo.web.forms import PrivacyForm, SyncForm
20 from mygpo.directory.tags import Tag
21 from mygpo.decorators import allowed_methods, repeat_on_conflict
22 from mygpo.utils import daterange
23 from mygpo.web.utils import get_podcast_link_target
24 from mygpo.log import log
27 MAX_TAGS_ON_PAGE=50
30 @repeat_on_conflict(['state'])
31 def update_podcast_settings(state, is_public):
32 state.settings['public_subscription'] = is_public
33 state.save()
36 @vary_on_cookie
37 @cache_control(private=True)
38 @allowed_methods(['GET'])
39 def show_slug(request, slug):
40 podcast = Podcast.for_slug(slug)
42 if slug != podcast.slug:
43 target = reverse('podcast_slug', args=[podcast.slug])
44 return HttpResponseRedirect(target)
46 return show(request, podcast.oldid)
49 @vary_on_cookie
50 @cache_control(private=True)
51 @allowed_methods(['GET'])
52 def show(request, podcast):
54 episodes = episode_list(podcast, request.user, limit=20)
56 max_listeners = max([e.listeners for e in episodes] + [0])
58 episode = None
60 if episodes:
61 episode = episodes[0]
62 episodes = episodes[1:]
64 if podcast.group:
65 group = PodcastGroup.get(podcast.group)
66 rel_podcasts = filter(lambda x: x != podcast, group.podcasts)
67 else:
68 rel_podcasts = []
70 tags = get_tags(podcast, request.user)
72 if request.user.is_authenticated():
74 request.user.sync_all()
76 state = podcast.get_user_state(request.user)
77 subscribed_devices = state.get_subscribed_device_ids()
78 subscribed_devices = [request.user.get_device(x) for x in subscribed_devices]
80 subscribe_targets = podcast.subscribe_targets(request.user)
82 history = list(state.actions)
83 def _set_objects(h):
84 #TODO: optimize by indexing devices by id
85 dev = request.user.get_device(h.device)
86 return proxy_object(h, device=dev)
87 history = map(_set_objects, history)
89 is_public = state.settings.get('public_subscription', True)
91 return render(request, 'podcast.html', {
92 'tags': tags,
93 'history': history,
94 'podcast': podcast,
95 'is_public': is_public,
96 'devices': subscribed_devices,
97 'related_podcasts': rel_podcasts,
98 'can_subscribe': len(subscribe_targets) > 0,
99 'subscribe_targets': subscribe_targets,
100 'episode': episode,
101 'episodes': episodes,
102 'max_listeners': max_listeners,
104 else:
105 current_site = RequestSite(request)
106 return render(request, 'podcast.html', {
107 'podcast': podcast,
108 'related_podcasts': rel_podcasts,
109 'tags': tags,
110 'url': current_site,
111 'episode': episode,
112 'episodes': episodes,
113 'max_listeners': max_listeners,
117 def get_tags(podcast, user):
118 tags = {}
119 for t in Tag.for_podcast(podcast):
120 tag_str = t.lower()
121 tags[tag_str] = False
123 if not user.is_anonymous():
124 users_tags = Tag.for_user(user, podcast.get_id())
125 for t in users_tags.get(podcast.get_id(), []):
126 tag_str = t.lower()
127 tags[tag_str] = True
129 tag_list = [{'tag': key, 'is_own': value} for key, value in tags.iteritems()]
130 tag_list.sort(key=lambda x: x['tag'])
132 if len(tag_list) > MAX_TAGS_ON_PAGE:
133 tag_list = filter(lambda x: x['is_own'], tag_list)
134 tag_list.append({'tag': '...', 'is_own': False})
136 return tag_list
139 def episode_list(podcast, user, limit=None):
141 Returns a list of episodes, with their action-attribute set to the latest
142 action. The attribute is unsert if there is no episode-action for
143 the episode.
146 listeners = dict(podcast.episode_listener_counts())
147 episodes = list(podcast.get_episodes(descending=True, limit=limit))
149 if user.is_authenticated():
151 # prepare pre-populated data for HistoryEntry.fetch_data
152 podcasts_dict = dict( (p_id, podcast) for p_id in podcast.get_ids())
153 episodes_dict = dict( (episode._id, episode) for episode in episodes)
155 actions = podcast.get_episode_states(user._id)
156 actions = map(HistoryEntry.from_action_dict, actions)
158 HistoryEntry.fetch_data(user, actions,
159 podcasts=podcasts_dict, episodes=episodes_dict)
161 episode_actions = dict( (action.episode_id, action) for action in actions)
162 else:
163 episode_actions = {}
165 annotate_episode = partial(_annotate_episode, listeners, episode_actions)
166 return map(annotate_episode, episodes)
170 def all_episodes(request, podcast):
172 episodes = episode_list(podcast, request.user)
174 max_listeners = max([e.listeners for e in episodes] + [0])
176 if request.user.is_authenticated():
178 request.user.sync_all()
180 return render(request, 'episodes.html', {
181 'podcast': podcast,
182 'episodes': episodes,
183 'max_listeners': max_listeners,
188 def _annotate_episode(listeners, episode_actions, episode):
189 listener_count = listeners.pop(episode._id, None)
190 action = episode_actions.pop(episode._id, None)
191 return proxy_object(episode, listeners=listener_count, action=action)
195 @never_cache
196 @login_required
197 def add_tag(request, podcast):
198 podcast_state = podcast.get_user_state(request.user)
200 tag_str = request.GET.get('tag', '')
201 if not tag_str:
202 return HttpResponseBadRequest()
204 tags = tag_str.split(',')
206 @repeat_on_conflict(['state'])
207 def update(state):
208 state.add_tags(tags)
209 state.save()
211 update(state=podcast_state)
213 if request.GET.get('next', '') == 'mytags':
214 return HttpResponseRedirect('/tags/')
216 return HttpResponseRedirect(get_podcast_link_target(podcast))
219 @never_cache
220 @login_required
221 def remove_tag(request, podcast):
222 podcast_state = podcast.get_user_state(request.user)
224 tag_str = request.GET.get('tag', '')
225 if not tag_str:
226 return HttpResponseBadRequest()
228 @repeat_on_conflict(['state'])
229 def update(state):
230 tags = list(state.tags)
231 if tag_str in tags:
232 state.tags.remove(tag_str)
233 state.save()
235 update(state=podcast_state)
237 if request.GET.get('next', '') == 'mytags':
238 return HttpResponseRedirect('/tags/')
240 return HttpResponseRedirect(get_podcast_link_target(podcast))
243 @never_cache
244 @login_required
245 @allowed_methods(['GET', 'POST'])
246 def subscribe(request, podcast):
248 if request.method == 'POST':
249 form = SyncForm(request.POST)
251 try:
252 device = request.user.get_device_by_uid(form.get_target())
253 podcast.subscribe(request.user, device)
255 except (SubscriptionException, DeviceDoesNotExist) as e:
256 messages.error(request, str(e))
258 return HttpResponseRedirect(get_podcast_link_target(podcast))
261 request.user.sync_all()
263 targets = podcast.subscribe_targets(request.user)
265 form = SyncForm()
266 form.set_targets(targets, _('Choose a device:'))
268 return render(request, 'subscribe.html', {
269 'podcast': podcast,
270 'can_subscribe': len(targets) > 0,
271 'form': form
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 try:
285 device = request.user.get_device_by_uid(device_uid)
287 except DeviceDoesNotExist as e:
288 messages.error(request, str(e))
290 try:
291 podcast.unsubscribe(request.user, device)
292 except Exception as e:
293 log('Web: %(username)s: could not unsubscribe from podcast %(podcast_url)s on device %(device_id)s: %(exception)s' %
294 {'username': request.user.username, 'podcast_url': podcast.url, 'device_id': device.id, 'exception': e})
296 return HttpResponseRedirect(return_to)
299 @never_cache
300 @login_required
301 def subscribe_url(request):
302 url = request.GET.get('url', None)
304 if not url:
305 raise Http404('http://my.gpodder.org/subscribe?url=http://www.example.com/podcast.xml')
307 url = sanitize_url(url)
309 if url == '':
310 raise Http404('Please specify a valid url')
312 podcast = Podcast.for_url(url, create=True)
314 return HttpResponseRedirect(get_podcast_link_target(podcast, 'subscribe'))
317 @never_cache
318 @allowed_methods(['POST'])
319 def set_public(request, podcast, public):
320 state = podcast.get_user_state(request.user)
321 update_podcast_settings(state=state, is_public=public)
322 return HttpResponseRedirect(get_podcast_link_target(podcast))
325 # To make all view accessible via either CouchDB-ID or Slugs
326 # a decorator queries the podcast and passes the Id on to the
327 # regular views
329 def slug_id_decorator(f):
330 @wraps(f)
331 def _decorator(request, slug_id, *args, **kwargs):
332 podcast = Podcast.for_slug_id(slug_id)
334 if podcast is None:
335 raise Http404
337 return f(request, podcast, *args, **kwargs)
339 return _decorator
342 def oldid_decorator(f):
343 @wraps(f)
344 def _decorator(request, pid, *args, **kwargs):
345 try:
346 pid = int(pid)
347 except (TypeError, ValueError):
348 raise Http404
350 podcast = Podcast.for_oldid(pid)
352 if not podcast:
353 raise Http404
355 return f(request, podcast, *args, **kwargs)
357 return _decorator
360 show_slug_id = slug_id_decorator(show)
361 subscribe_slug_id = slug_id_decorator(subscribe)
362 unsubscribe_slug_id = slug_id_decorator(unsubscribe)
363 add_tag_slug_id = slug_id_decorator(add_tag)
364 remove_tag_slug_id = slug_id_decorator(remove_tag)
365 set_public_slug_id = slug_id_decorator(set_public)
366 all_episodes_slug_id= slug_id_decorator(all_episodes)
369 show_oldid = oldid_decorator(show)
370 subscribe_oldid = oldid_decorator(subscribe)
371 unsubscribe_oldid = oldid_decorator(unsubscribe)
372 add_tag_oldid = oldid_decorator(add_tag)
373 remove_tag_oldid = oldid_decorator(remove_tag)
374 set_public_oldid = oldid_decorator(set_public)
375 all_episodes_oldid = oldid_decorator(all_episodes)