implement chapter support for Advanced API 2
[mygpo.git] / mygpo / web / views.py
blob43a88fd6c0ab87ca0a2b18c5594133e1701c9bab
2 # This file is part of my.gpodder.org.
4 # my.gpodder.org is free software: you can redistribute it and/or modify it
5 # under the terms of the GNU Affero General Public License as published by
6 # the Free Software Foundation, either version 3 of the License, or (at your
7 # option) any later version.
9 # my.gpodder.org is distributed in the hope that it will be useful, but
10 # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
11 # or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
12 # License for more details.
14 # You should have received a copy of the GNU Affero General Public License
15 # along with my.gpodder.org. If not, see <http://www.gnu.org/licenses/>.
18 from django.shortcuts import render_to_response
19 from django.http import HttpResponseRedirect, HttpResponse, HttpResponseBadRequest, HttpResponseNotAllowed, Http404, HttpResponseForbidden
20 from django.contrib.auth import authenticate, login, logout
21 from django.contrib.auth.models import User
22 from django.template import RequestContext
23 from mygpo.api.models import Podcast, Episode, Device, EpisodeAction, SubscriptionAction, ToplistEntry, EpisodeToplistEntry, Subscription, SuggestionEntry, SyncGroup, SUBSCRIBE_ACTION, UNSUBSCRIBE_ACTION, SubscriptionMeta
24 from mygpo.web.models import Rating, SecurityToken
25 from mygpo.web.forms import UserAccountForm, DeviceForm, SyncForm, PrivacyForm, ResendActivationForm
26 from django.forms import ValidationError
27 from mygpo.api.opml import Exporter
28 from django.utils.translation import ugettext as _
29 from mygpo.api.basic_auth import require_valid_user
30 from django.contrib.auth.decorators import login_required
31 from django.shortcuts import get_object_or_404
32 from django.db import IntegrityError
33 from datetime import datetime, date, timedelta
34 from django.contrib.sites.models import Site
35 from django.conf import settings
36 from registration.models import RegistrationProfile
37 from sets import Set
38 from mygpo.api.sanitizing import sanitize_url
39 from mygpo.web.users import get_user
40 from mygpo.log import log
41 from mygpo.utils import daterange
42 import re
43 import random
44 import string
46 def home(request):
47 current_site = Site.objects.get_current()
48 if request.user.is_authenticated():
49 subscriptionlist = create_subscriptionlist(request)
51 return render_to_response('home-user.html', {
52 'subscriptionlist': subscriptionlist,
53 'url': current_site
54 }, context_instance=RequestContext(request))
56 else:
57 podcasts = Podcast.objects.count()
58 return render_to_response('home.html', {
59 'podcast_count': podcasts,
60 'url': current_site
63 def create_subscriptionlist(request):
64 #sync all devices first
65 for d in Device.objects.filter(user=request.user):
66 d.sync()
68 subscriptions = Subscription.objects.filter(user=request.user)
70 l = {}
71 for s in subscriptions:
72 if s.podcast in l:
73 l[s.podcast]['devices'].append(s.device)
74 else:
75 e = Episode.objects.filter(podcast=s.podcast, timestamp__isnull=False).order_by('-timestamp')
76 episode = e[0] if e.count() > 0 else None
77 devices = [s.device]
78 l[s.podcast] = {'podcast': s.podcast, 'episode': episode, 'devices': devices}
80 return l.values()
82 def podcast(request, pid):
83 podcast = get_object_or_404(Podcast, pk=pid)
85 if request.user.is_authenticated():
86 devices = Device.objects.filter(user=request.user)
87 history = SubscriptionAction.objects.filter(podcast=podcast,device__in=devices).order_by('-timestamp')
88 subscribed_devices = [s.device for s in Subscription.objects.filter(podcast=podcast,user=request.user)]
89 subscribe_targets = podcast.subscribe_targets(request.user)
90 episodes = episode_list(podcast, request.user)
91 max_listeners = max([x.listeners for x in episodes]) if len(episodes) else 0
92 success = False
95 qs = Subscription.objects.filter(podcast=podcast, user=request.user)
96 if qs.count()>0 and request.user.get_profile().public_profile:
97 # subscription meta is valid for all subscriptions, so we get one - doesn't matter which
98 subscription = qs[0]
99 subscriptionmeta = subscription.get_meta()
100 if request.method == 'POST':
101 privacy_form = PrivacyForm(request.POST)
102 if privacy_form.is_valid():
103 subscriptionmeta.public = privacy_form.cleaned_data['public']
104 try:
105 subscriptionmeta.save()
106 success = True
107 except IntegrityError, ie:
108 error_message = _('You can\'t use the same UID for two devices.')
109 else:
110 privacy_form = PrivacyForm({
111 'public': subscriptionmeta.public
114 else:
115 privacy_form = None
117 timeline_data = listener_data(podcast)
119 return render_to_response('podcast.html', {
120 'history': history,
121 'timeline_data': timeline_data,
122 'podcast': podcast,
123 'privacy_form': privacy_form,
124 'devices': subscribed_devices,
125 'can_subscribe': len(subscribe_targets) > 0,
126 'episodes': episodes,
127 'max_listeners': max_listeners,
128 'success': success
129 }, context_instance=RequestContext(request))
130 else:
131 current_site = Site.objects.get_current()
132 return render_to_response('podcast.html', {
133 'podcast': podcast,
134 'url': current_site
135 }, context_instance=RequestContext(request))
137 def listener_data(podcast):
138 d = date(2010, 1, 1)
139 day = timedelta(1)
140 episodes = EpisodeAction.objects.filter(episode__podcast=podcast, timestamp__gte=d).order_by('timestamp').values('timestamp')
141 if len(episodes) == 0:
142 return []
144 start = episodes[0]['timestamp']
146 days = []
147 for d in daterange(start):
148 next = d + timedelta(days=1)
149 listeners = EpisodeAction.objects.filter(episode__podcast=podcast, timestamp__gte=d, timestamp__lt=next).values('user_id').distinct().count()
150 e = Episode.objects.filter(podcast=podcast, timestamp__gte=d, timestamp__lt=next)
151 episode = e[0] if e.count() > 0 else None
152 days.append({
153 'date': d,
154 'listeners': listeners,
155 'episode': episode})
157 return days
159 def history(request, len=15, device_id=None):
160 if device_id:
161 devices = Device.objects.filter(id=device_id)
162 else:
163 devices = Device.objects.filter(user=request.user)
165 history = SubscriptionAction.objects.filter(device__in=devices).order_by('-timestamp')[:len]
166 episodehistory = EpisodeAction.objects.filter(device__in=devices).order_by('-timestamp')[:len]
168 generalhistory = []
170 for row in history:
171 generalhistory.append(row)
172 for row in episodehistory:
173 generalhistory.append(row)
175 generalhistory.sort(key=lambda x: x.timestamp,reverse=True)
177 return render_to_response('history.html', {
178 'generalhistory': generalhistory,
179 'singledevice': devices[0] if device_id else None
180 }, context_instance=RequestContext(request))
182 def devices(request):
183 devices = Device.objects.filter(user=request.user,deleted=False).order_by('sync_group')
184 return render_to_response('devicelist.html', {
185 'devices': devices,
186 }, context_instance=RequestContext(request))
188 @login_required
189 def podcast_subscribe(request, pid):
190 podcast = get_object_or_404(Podcast, pk=pid)
191 error_message = None
193 if request.method == 'POST':
194 form = SyncForm(request.POST)
196 try:
197 target = form.get_target()
199 if isinstance(target, SyncGroup):
200 device = target.devices()[0]
201 else:
202 device = target
204 try:
205 SubscriptionAction.objects.create(podcast=podcast, device=device, action=SUBSCRIBE_ACTION)
206 except IntegrityError, e:
207 log('error while subscribing to podcast (device %s, podcast %s)' % (device.id, podcast.id))
209 return HttpResponseRedirect('/podcast/%s' % podcast.id)
211 except ValueError, e:
212 error_message = _('Could not subscribe to the podcast: %s' % e)
214 targets = podcast.subscribe_targets(request.user)
216 form = SyncForm()
217 form.set_targets(targets, _('With which client do you want to subscribe?'))
219 return render_to_response('subscribe.html', {
220 'error_message': error_message,
221 'podcast': podcast,
222 'can_subscribe': len(targets) > 0,
223 'form': form
224 }, context_instance=RequestContext(request))
226 @login_required
227 def podcast_unsubscribe(request, pid, device_id):
229 return_to = request.GET.get('return_to')
231 if return_to == None:
232 raise Http404('Wrong URL')
234 podcast = get_object_or_404(Podcast, pk=pid)
235 device = Device.objects.get(pk=device_id)
236 try:
237 SubscriptionAction.objects.create(podcast=podcast, device=device, action=UNSUBSCRIBE_ACTION, timestamp=datetime.now())
238 except IntegrityError, e:
239 log('error while unsubscribing from podcast (device %s, podcast %s)' % (device.id, podcast.id))
241 return HttpResponseRedirect(return_to)
243 def episode_list(podcast, user):
245 Returns a list of episodes, with their action-attribute set to the latest
246 action. The attribute is unsert if there is no episode-action for
247 the episode.
249 episodes = Episode.objects.filter(podcast=podcast).order_by('-timestamp')
250 for e in episodes:
251 listeners = EpisodeAction.objects.filter(episode=e, action='play').values('user').distinct()
252 e.listeners = listeners.count()
254 if user.is_authenticated():
255 actions = EpisodeAction.objects.filter(episode=e, user=user).order_by('-timestamp')
256 if actions.count() > 0:
257 e.action = actions[0]
259 return episodes
261 def episode(request, id):
262 episode = get_object_or_404(Episode, pk=id)
263 if request.user.is_authenticated():
264 history = EpisodeAction.objects.filter(user=request.user, episode=episode).order_by('-timestamp')
265 else:
266 history = []
268 return render_to_response('episode.html', {
269 'episode': episode,
270 'history': history,
271 }, context_instance=RequestContext(request))
274 @login_required
275 def account(request):
276 success = False
277 error_message = ''
278 site = Site.objects.get_current()
279 token, c = SecurityToken.objects.get_or_create(user=request.user, object='subscriptions', action='r',
280 defaults = {'token': "".join(random.sample(string.letters+string.digits, 32))})
283 if request.method == 'GET':
285 if 'public_subscriptions' in request.GET:
286 token.token = ''
287 token.save()
289 elif 'private_subscriptions' in request.GET:
290 token.token = "".join(random.sample(string.letters+string.digits, 32))
291 token.save()
293 form = UserAccountForm({
294 'email': request.user.email,
295 'public': request.user.get_profile().public_profile
298 return render_to_response('account.html', {
299 'site': site,
300 'token': token.token,
301 'form': form,
302 }, context_instance=RequestContext(request))
304 try:
305 form = UserAccountForm(request.POST)
307 if not form.is_valid():
308 raise ValueError('Invalid data entered.')
310 if form.cleaned_data['password_current']:
311 if not request.user.check_password(form.cleaned_data['password_current']):
312 raise ValueError('Current password is incorrect')
314 request.user.set_password(form.cleaned_data['password1'])
316 request.user.email = form.cleaned_data['email']
317 request.user.save()
318 request.user.get_profile().public_profile = form.cleaned_data['public']
319 request.user.get_profile().save()
321 success = True
323 except ValueError, e:
324 success = False
325 error_message = e
327 except ValidationError, e:
328 success = False
329 error_message = e
331 return render_to_response('account.html', {
332 'site': site,
333 'token': token.token,
334 'form': form,
335 'success': success,
336 'error_message': error_message
337 }, context_instance=RequestContext(request))
340 def toplist(request, len=100):
341 entries = ToplistEntry.objects.all().order_by('-subscriptions')[:len]
342 max_subscribers = max([e.subscriptions for e in entries])
343 current_site = Site.objects.get_current()
344 return render_to_response('toplist.html', {
345 'entries': entries,
346 'max_subscribers': max_subscribers,
347 'url': current_site
348 }, context_instance=RequestContext(request))
351 def episode_toplist(request, len=100):
352 entries = EpisodeToplistEntry.objects.all().order_by('-listeners')[:len]
353 current_site = Site.objects.get_current()
354 max_listeners = max([e.listeners for e in entries])
355 return render_to_response('episode_toplist.html', {
356 'entries': entries,
357 'max_listeners': max_listeners,
358 'url': current_site
359 }, context_instance=RequestContext(request))
362 def toplist_opml(request, count):
363 entries = ToplistEntry.objects.all().order_by('-subscriptions')[:count]
364 exporter = Exporter(_('my.gpodder.org - Top %s') % count)
366 opml = exporter.generate([e.podcast for e in entries])
368 return HttpResponse(opml, mimetype='text/xml')
371 @login_required
372 def suggestions(request):
374 rated = False
376 if 'rate' in request.GET:
377 Rating.objects.create(target='suggestions', user=request.user, rating=request.GET['rate'], timestamp=datetime.now())
378 rated = True
380 entries = SuggestionEntry.forUser(request.user)
381 current_site = Site.objects.get_current()
382 return render_to_response('suggestions.html', {
383 'entries': entries,
384 'rated' : rated,
385 'url': current_site
386 }, context_instance=RequestContext(request))
389 @login_required
390 def device(request, device_id):
391 device = Device.objects.get(pk=device_id)
393 if device.user != request.user:
394 return HttpResponseForbidden(_('You are not allowed to access this device'))
396 subscriptions = device.get_subscriptions()
397 synced_with = list(device.sync_group.devices()) if device.sync_group else []
398 if device in synced_with: synced_with.remove(device)
399 success = False
400 error_message = None
401 sync_form = SyncForm()
402 sync_form.set_targets(device.sync_targets(), _('Synchronize with the following devices'))
404 if request.method == 'POST':
405 device_form = DeviceForm(request.POST)
407 if device_form.is_valid():
408 device.name = device_form.cleaned_data['name']
409 device.type = device_form.cleaned_data['type']
410 device.uid = device_form.cleaned_data['uid']
411 try:
412 device.save()
413 success = True
414 except IntegrityError, ie:
415 device = Device.objects.get(pk=device_id)
416 error_message = _('You can\'t use the same UID for two devices.')
418 else:
419 device_form = DeviceForm({
420 'name': device.name,
421 'type': device.type,
422 'uid' : device.uid
425 return render_to_response('device.html', {
426 'device': device,
427 'device_form': device_form,
428 'sync_form': sync_form,
429 'success': success,
430 'error_message': error_message,
431 'subscriptions': subscriptions,
432 'synced_with': synced_with,
433 'has_sync_targets': len(device.sync_targets()) > 0
434 }, context_instance=RequestContext(request))
437 @login_required
438 def device_delete(request, device_id):
439 if request.method != 'POST':
440 return HttpResponseNotAllowed(['POST'])
442 device = Device.objects.get(pk=device_id)
443 device.deleted = True
444 device.save()
446 current_site = Site.objects.get_current()
447 subscriptionlist = create_subscriptionlist(request)
448 return render_to_response('home-user.html', {
449 'subscriptionlist': subscriptionlist,
450 'url': current_site,
451 'deletedevice_success': True,
452 'device_name': device.name
453 }, context_instance=RequestContext(request))
456 @login_required
457 def device_sync(request, device_id):
459 if request.method != 'POST':
460 return HttpResponseNotAllowed(['POST'])
462 form = SyncForm(request.POST)
463 if not form.is_valid():
464 return HttpResponseBadRequest('invalid')
466 try:
467 target = form.get_target()
469 device = Device.objects.get(pk=device_id)
470 device.sync_with(target)
472 except ValueError, e:
473 log('error while syncing device %s: %s' % (device_id, e))
475 return HttpResponseRedirect('/device/%s' % device_id)
477 @login_required
478 def device_unsync(request, device_id):
479 if request.method != 'GET':
480 return HttpResponseNotAllowed(['GET'])
482 device = Device.objects.get(pk=device_id)
483 device.unsync()
485 return HttpResponseRedirect('/device/%s' % device_id)
487 @login_required
488 def podcast_subscribe_url(request):
489 url = request.GET.get('url')
491 if url == None:
492 raise Http404('http://my.gpodder.org/subscribe?url=http://www.example.com/podcast.xml')
494 url = sanitize_url(url)
496 if url == '':
497 raise Http404('Please specify a valid url')
499 podcast, created = Podcast.objects.get_or_create(url=url)
501 return HttpResponseRedirect('/podcast/%d/subscribe' % podcast.pk)
503 @login_required
504 def delete_account(request):
506 if request.method == 'GET':
507 return render_to_response('delete_account.html')
509 request.user.is_active = False
510 request.user.save()
511 logout(request)
512 return render_to_response('delete_account.html', {
513 'success': True
516 def author(request):
517 current_site = Site.objects.get_current()
518 return render_to_response('authors.html', {
519 'url': current_site
520 }, context_instance=RequestContext(request))
523 def resend_activation(request):
524 error_message = ''
526 if request.method == 'GET':
527 form = ResendActivationForm()
528 return render_to_response('registration/resend_activation.html', {
529 'form': form,
532 site = Site.objects.get_current()
533 form = ResendActivationForm(request.POST)
535 try:
536 if not form.is_valid():
537 raise ValueError(_('Invalid Username entered'))
539 try:
540 user = get_user(form.cleaned_data['username'], form.cleaned_data['email'])
541 except User.DoesNotExist:
542 raise ValueError(_('User does not exist.'))
544 profile = RegistrationProfile.objects.get(user=user)
546 if profile.activation_key == RegistrationProfile.ACTIVATED:
547 raise ValueError(_('Your account already has been activated. Go ahead and log in.'))
549 elif profile.activation_key_expired():
550 raise ValueError(_('Your activation key has expired. Please try another username, or retry with the same one tomorrow.'))
552 except ValueError, e:
553 return render_to_response('registration/resend_activation.html', {
554 'form': form,
555 'error_message' : e
559 try:
560 profile.send_activation_email(site)
562 except AttributeError:
563 #old versions of django-registration send registration mails from RegistrationManager
564 RegistrationProfile.objects.send_activation_email(profile, site)
566 return render_to_response('registration/resent_activation.html')
569 def user_subscriptions(request, username):
570 user = get_object_or_404(User, username=username)
572 token, c = SecurityToken.objects.get_or_create(user=user, object='subscriptions', action='r',
573 defaults = {'token': "".join(random.sample(string.letters+string.digits, 32))})
575 u_token = request.GET.get('token', '')
576 if token.token == '' or token.token == u_token:
577 subscriptions = set([s.podcast for s in Subscription.objects.filter(user=user)])
578 return render_to_response('user_subscriptions.html', {
579 'subscriptions': subscriptions,
580 'other_user': user})
582 else:
583 return render_to_response('user_subscriptions_denied.html', {
584 'other_user': user})