refactoring well-known settings
[mygpo.git] / mygpo / web / views / podcast.py
blobe8eedf066880965334e7fd9f9736aef5a0e422cf
1 from functools import wraps, partial
3 from django.core.urlresolvers import reverse
4 from django.http import HttpResponseBadRequest, HttpResponseRedirect, Http404
5 from django.shortcuts import render
6 from django.contrib.auth.decorators import login_required
7 from django.contrib.sites.models import RequestSite
8 from django.utils.translation import ugettext as _
9 from django.contrib import messages
10 from django.views.decorators.vary import vary_on_cookie
11 from django.views.decorators.cache import never_cache, cache_control
13 from mygpo.core.models import PodcastGroup, SubscriptionException
14 from mygpo.core.proxy import proxy_object
15 from mygpo.api.sanitizing import sanitize_url
16 from mygpo.users.settings import PUBLIC_SUB_PODCAST
17 from mygpo.users.models import HistoryEntry, DeviceDoesNotExist
18 from mygpo.web.forms import SyncForm
19 from mygpo.decorators import allowed_methods, repeat_on_conflict
20 from mygpo.web.utils import get_podcast_link_target
21 from mygpo.log import log
22 from mygpo.db.couchdb.episode import episodes_for_podcast
23 from mygpo.db.couchdb.podcast import podcast_for_slug, podcast_for_slug_id, \
24 podcast_for_oldid, podcast_for_url
25 from mygpo.db.couchdb.podcast_state import podcast_state_for_user_podcast
26 from mygpo.db.couchdb.episode_state import get_podcasts_episode_states, \
27 episode_listener_counts
28 from mygpo.db.couchdb.directory import tags_for_user, tags_for_podcast
31 MAX_TAGS_ON_PAGE=50
34 @repeat_on_conflict(['state'])
35 def update_podcast_settings(state, is_public):
36 state.settings[PUBLIC_SUB_PODCAST.name] = is_public
37 state.save()
40 @vary_on_cookie
41 @cache_control(private=True)
42 @allowed_methods(['GET'])
43 def show_slug(request, slug):
44 podcast = podcast_for_slug(slug)
46 if slug != podcast.slug:
47 target = reverse('podcast_slug', args=[podcast.slug])
48 return HttpResponseRedirect(target)
50 return show(request, podcast.oldid)
53 @vary_on_cookie
54 @cache_control(private=True)
55 @allowed_methods(['GET'])
56 def show(request, podcast):
58 episodes = episode_list(podcast, request.user, limit=20)
60 max_listeners = max([e.listeners for e in episodes] + [0])
62 episode = None
64 if episodes:
65 episode = episodes[0]
66 episodes = episodes[1:]
68 if podcast.group:
69 group = PodcastGroup.get(podcast.group)
70 rel_podcasts = filter(lambda x: x != podcast, group.podcasts)
71 else:
72 rel_podcasts = []
74 tags = get_tags(podcast, request.user)
76 if request.user.is_authenticated():
77 state = podcast_state_for_user_podcast(request.user, podcast)
78 subscribed_devices = state.get_subscribed_device_ids()
79 subscribed_devices = [request.user.get_device(x) for x in subscribed_devices]
81 subscribe_targets = podcast.subscribe_targets(request.user)
83 history = list(state.actions)
84 def _set_objects(h):
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.get_wksetting(PUBLIC_SUB_PODCAST)
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 tags_for_podcast(podcast):
120 tag_str = t.lower()
121 tags[tag_str] = False
123 if not user.is_anonymous():
124 users_tags = tags_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(episode_listener_counts(podcast))
147 episodes = episodes_for_podcast(podcast, 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 = get_podcasts_episode_states(podcast, 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 return render(request, 'episodes.html', {
177 'podcast': podcast,
178 'episodes': episodes,
179 'max_listeners': max_listeners,
184 def _annotate_episode(listeners, episode_actions, episode):
185 listener_count = listeners.pop(episode._id, None)
186 action = episode_actions.pop(episode._id, None)
187 return proxy_object(episode, listeners=listener_count, action=action)
191 @never_cache
192 @login_required
193 def add_tag(request, podcast):
194 podcast_state = podcast_state_for_user_podcast(request.user, podcast)
196 tag_str = request.GET.get('tag', '')
197 if not tag_str:
198 return HttpResponseBadRequest()
200 tags = tag_str.split(',')
202 @repeat_on_conflict(['state'])
203 def update(state):
204 state.add_tags(tags)
205 state.save()
207 update(state=podcast_state)
209 if request.GET.get('next', '') == 'mytags':
210 return HttpResponseRedirect('/tags/')
212 return HttpResponseRedirect(get_podcast_link_target(podcast))
215 @never_cache
216 @login_required
217 def remove_tag(request, podcast):
218 podcast_state = podcast_state_for_user_podcast(request.user, podcast)
220 tag_str = request.GET.get('tag', '')
221 if not tag_str:
222 return HttpResponseBadRequest()
224 @repeat_on_conflict(['state'])
225 def update(state):
226 tags = list(state.tags)
227 if tag_str in tags:
228 state.tags.remove(tag_str)
229 state.save()
231 update(state=podcast_state)
233 if request.GET.get('next', '') == 'mytags':
234 return HttpResponseRedirect('/tags/')
236 return HttpResponseRedirect(get_podcast_link_target(podcast))
239 @never_cache
240 @login_required
241 @allowed_methods(['GET', 'POST'])
242 def subscribe(request, podcast):
244 if request.method == 'POST':
246 # multiple UIDs from the /podcast/<slug>/subscribe
247 device_uids = [k for (k,v) in request.POST.items() if k==v]
249 # single UID from /podcast/<slug>
250 if 'targets' in request.POST:
251 device_uids.append(request.POST.get('targets'))
253 for uid in device_uids:
254 try:
255 device = request.user.get_device_by_uid(uid)
256 podcast.subscribe(request.user, device)
258 except (SubscriptionException, DeviceDoesNotExist, ValueError) as e:
259 messages.error(request, str(e))
261 return HttpResponseRedirect(get_podcast_link_target(podcast))
263 targets = podcast.subscribe_targets(request.user)
265 return render(request, 'subscribe.html', {
266 'targets': targets,
267 'podcast': podcast,
271 @never_cache
272 @login_required
273 def unsubscribe(request, podcast, device_uid):
275 return_to = request.GET.get('return_to', None)
277 if not return_to:
278 raise Http404('Wrong URL')
280 try:
281 device = request.user.get_device_by_uid(device_uid)
283 except DeviceDoesNotExist as e:
284 messages.error(request, str(e))
286 try:
287 podcast.unsubscribe(request.user, device)
288 except SubscriptionException as e:
289 log('Web: %(username)s: could not unsubscribe from podcast %(podcast_url)s on device %(device_id)s: %(exception)s' %
290 {'username': request.user.username, 'podcast_url': podcast.url, 'device_id': device.id, 'exception': e})
292 return HttpResponseRedirect(return_to)
295 @never_cache
296 @login_required
297 def subscribe_url(request):
298 url = request.GET.get('url', None)
300 if not url:
301 raise Http404('http://my.gpodder.org/subscribe?url=http://www.example.com/podcast.xml')
303 url = sanitize_url(url)
305 if url == '':
306 raise Http404('Please specify a valid url')
308 podcast = podcast_for_url(url, create=True)
310 return HttpResponseRedirect(get_podcast_link_target(podcast, 'subscribe'))
313 @never_cache
314 @allowed_methods(['POST'])
315 def set_public(request, podcast, public):
316 state = podcast_state_for_user_podcast(request.user, podcast)
317 update_podcast_settings(state=state, is_public=public)
318 return HttpResponseRedirect(get_podcast_link_target(podcast))
321 # To make all view accessible via either CouchDB-ID or Slugs
322 # a decorator queries the podcast and passes the Id on to the
323 # regular views
325 def slug_id_decorator(f):
326 @wraps(f)
327 def _decorator(request, slug_id, *args, **kwargs):
328 podcast = podcast_for_slug_id(slug_id)
330 if podcast is None:
331 raise Http404
333 return f(request, podcast, *args, **kwargs)
335 return _decorator
338 def oldid_decorator(f):
339 @wraps(f)
340 def _decorator(request, pid, *args, **kwargs):
341 try:
342 pid = int(pid)
343 except (TypeError, ValueError):
344 raise Http404
346 podcast = podcast_for_oldid(pid)
348 if not podcast:
349 raise Http404
351 return f(request, podcast, *args, **kwargs)
353 return _decorator
356 show_slug_id = slug_id_decorator(show)
357 subscribe_slug_id = slug_id_decorator(subscribe)
358 unsubscribe_slug_id = slug_id_decorator(unsubscribe)
359 add_tag_slug_id = slug_id_decorator(add_tag)
360 remove_tag_slug_id = slug_id_decorator(remove_tag)
361 set_public_slug_id = slug_id_decorator(set_public)
362 all_episodes_slug_id= slug_id_decorator(all_episodes)
365 show_oldid = oldid_decorator(show)
366 subscribe_oldid = oldid_decorator(subscribe)
367 unsubscribe_oldid = oldid_decorator(unsubscribe)
368 add_tag_oldid = oldid_decorator(add_tag)
369 remove_tag_oldid = oldid_decorator(remove_tag)
370 set_public_oldid = oldid_decorator(set_public)
371 all_episodes_oldid = oldid_decorator(all_episodes)