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
30 @repeat_on_conflict(['state'])
31 def update_podcast_settings(state
, is_public
):
32 state
.settings
['public_subscription'] = is_public
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
)
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])
62 episodes
= episodes
[1:]
65 group
= PodcastGroup
.get(podcast
.group
)
66 rel_podcasts
= filter(lambda x
: x
!= podcast
, group
.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
)
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', {
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
,
101 'episodes': episodes
,
102 'max_listeners': max_listeners
,
105 current_site
= RequestSite(request
)
106 return render(request
, 'podcast.html', {
108 'related_podcasts': rel_podcasts
,
112 'episodes': episodes
,
113 'max_listeners': max_listeners
,
117 def get_tags(podcast
, user
):
119 for t
in Tag
.for_podcast(podcast
):
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(), []):
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})
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
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
)
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', {
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
)
197 def add_tag(request
, podcast
):
198 podcast_state
= podcast
.get_user_state(request
.user
)
200 tag_str
= request
.GET
.get('tag', '')
202 return HttpResponseBadRequest()
204 tags
= tag_str
.split(',')
206 @repeat_on_conflict(['state'])
211 update(state
=podcast_state
)
213 if request
.GET
.get('next', '') == 'mytags':
214 return HttpResponseRedirect('/tags/')
216 return HttpResponseRedirect(get_podcast_link_target(podcast
))
221 def remove_tag(request
, podcast
):
222 podcast_state
= podcast
.get_user_state(request
.user
)
224 tag_str
= request
.GET
.get('tag', '')
226 return HttpResponseBadRequest()
228 @repeat_on_conflict(['state'])
230 tags
= list(state
.tags
)
232 state
.tags
.remove(tag_str
)
235 update(state
=podcast_state
)
237 if request
.GET
.get('next', '') == 'mytags':
238 return HttpResponseRedirect('/tags/')
240 return HttpResponseRedirect(get_podcast_link_target(podcast
))
245 @allowed_methods(['GET', 'POST'])
246 def subscribe(request
, podcast
):
248 if request
.method
== 'POST':
249 form
= SyncForm(request
.POST
)
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
)
266 form
.set_targets(targets
, _('Choose a device:'))
268 return render(request
, 'subscribe.html', {
270 'can_subscribe': len(targets
) > 0,
277 def unsubscribe(request
, podcast
, device_uid
):
279 return_to
= request
.GET
.get('return_to', None)
282 raise Http404('Wrong URL')
285 device
= request
.user
.get_device_by_uid(device_uid
)
287 except DeviceDoesNotExist
as e
:
288 messages
.error(request
, str(e
))
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
)
301 def subscribe_url(request
):
302 url
= request
.GET
.get('url', None)
305 raise Http404('http://my.gpodder.org/subscribe?url=http://www.example.com/podcast.xml')
307 url
= sanitize_url(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'))
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
329 def slug_id_decorator(f
):
331 def _decorator(request
, slug_id
, *args
, **kwargs
):
332 podcast
= Podcast
.for_slug_id(slug_id
)
337 return f(request
, podcast
, *args
, **kwargs
)
342 def oldid_decorator(f
):
344 def _decorator(request
, pid
, *args
, **kwargs
):
347 except (TypeError, ValueError):
350 podcast
= Podcast
.for_oldid(pid
)
355 return f(request
, podcast
, *args
, **kwargs
)
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
)