move mygpo.web.views into own subdirectory
[mygpo.git] / mygpo / web / views / __init__.py
blob892fbac6c4f35070246069c9cd548278bd0ccda5
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
262 @login_required
263 def account(request):
264 success = False
265 error_message = ''
266 site = Site.objects.get_current()
267 token, c = SecurityToken.objects.get_or_create(user=request.user, object='subscriptions', action='r',
268 defaults = {'token': "".join(random.sample(string.letters+string.digits, 32))})
271 if request.method == 'GET':
273 if 'public_subscriptions' in request.GET:
274 token.token = ''
275 token.save()
277 elif 'private_subscriptions' in request.GET:
278 token.token = "".join(random.sample(string.letters+string.digits, 32))
279 token.save()
281 form = UserAccountForm({
282 'email': request.user.email,
283 'public': request.user.get_profile().public_profile
286 return render_to_response('account.html', {
287 'site': site,
288 'token': token.token,
289 'form': form,
290 }, context_instance=RequestContext(request))
292 try:
293 form = UserAccountForm(request.POST)
295 if not form.is_valid():
296 raise ValueError('Invalid data entered.')
298 if form.cleaned_data['password_current']:
299 if not request.user.check_password(form.cleaned_data['password_current']):
300 raise ValueError('Current password is incorrect')
302 request.user.set_password(form.cleaned_data['password1'])
304 request.user.email = form.cleaned_data['email']
305 request.user.save()
306 request.user.get_profile().public_profile = form.cleaned_data['public']
307 request.user.get_profile().save()
309 success = True
311 except ValueError, e:
312 success = False
313 error_message = e
315 except ValidationError, e:
316 success = False
317 error_message = e
319 return render_to_response('account.html', {
320 'site': site,
321 'token': token.token,
322 'form': form,
323 'success': success,
324 'error_message': error_message
325 }, context_instance=RequestContext(request))
328 def toplist(request, len=100):
329 entries = ToplistEntry.objects.all().order_by('-subscriptions')[:len]
330 max_subscribers = max([e.subscriptions for e in entries])
331 current_site = Site.objects.get_current()
332 return render_to_response('toplist.html', {
333 'entries': entries,
334 'max_subscribers': max_subscribers,
335 'url': current_site
336 }, context_instance=RequestContext(request))
339 def episode_toplist(request, len=100):
340 entries = EpisodeToplistEntry.objects.all().order_by('-listeners')[:len]
341 current_site = Site.objects.get_current()
342 max_listeners = max([e.listeners for e in entries])
343 return render_to_response('episode_toplist.html', {
344 'entries': entries,
345 'max_listeners': max_listeners,
346 'url': current_site
347 }, context_instance=RequestContext(request))
350 def toplist_opml(request, count):
351 entries = ToplistEntry.objects.all().order_by('-subscriptions')[:count]
352 exporter = Exporter(_('my.gpodder.org - Top %s') % count)
354 opml = exporter.generate([e.podcast for e in entries])
356 return HttpResponse(opml, mimetype='text/xml')
359 @login_required
360 def suggestions(request):
362 rated = False
364 if 'rate' in request.GET:
365 Rating.objects.create(target='suggestions', user=request.user, rating=request.GET['rate'], timestamp=datetime.now())
366 rated = True
368 entries = SuggestionEntry.forUser(request.user)
369 current_site = Site.objects.get_current()
370 return render_to_response('suggestions.html', {
371 'entries': entries,
372 'rated' : rated,
373 'url': current_site
374 }, context_instance=RequestContext(request))
377 @login_required
378 def device(request, device_id):
379 device = Device.objects.get(pk=device_id)
381 if device.user != request.user:
382 return HttpResponseForbidden(_('You are not allowed to access this device'))
384 subscriptions = device.get_subscriptions()
385 synced_with = list(device.sync_group.devices()) if device.sync_group else []
386 if device in synced_with: synced_with.remove(device)
387 success = False
388 error_message = None
389 sync_form = SyncForm()
390 sync_form.set_targets(device.sync_targets(), _('Synchronize with the following devices'))
392 if request.method == 'POST':
393 device_form = DeviceForm(request.POST)
395 if device_form.is_valid():
396 device.name = device_form.cleaned_data['name']
397 device.type = device_form.cleaned_data['type']
398 device.uid = device_form.cleaned_data['uid']
399 try:
400 device.save()
401 success = True
402 except IntegrityError, ie:
403 device = Device.objects.get(pk=device_id)
404 error_message = _('You can\'t use the same UID for two devices.')
406 else:
407 device_form = DeviceForm({
408 'name': device.name,
409 'type': device.type,
410 'uid' : device.uid
413 return render_to_response('device.html', {
414 'device': device,
415 'device_form': device_form,
416 'sync_form': sync_form,
417 'success': success,
418 'error_message': error_message,
419 'subscriptions': subscriptions,
420 'synced_with': synced_with,
421 'has_sync_targets': len(device.sync_targets()) > 0
422 }, context_instance=RequestContext(request))
425 @login_required
426 def device_delete(request, device_id):
427 if request.method != 'POST':
428 return HttpResponseNotAllowed(['POST'])
430 device = Device.objects.get(pk=device_id)
431 device.deleted = True
432 device.save()
434 current_site = Site.objects.get_current()
435 subscriptionlist = create_subscriptionlist(request)
436 return render_to_response('home-user.html', {
437 'subscriptionlist': subscriptionlist,
438 'url': current_site,
439 'deletedevice_success': True,
440 'device_name': device.name
441 }, context_instance=RequestContext(request))
444 @login_required
445 def device_sync(request, device_id):
447 if request.method != 'POST':
448 return HttpResponseNotAllowed(['POST'])
450 form = SyncForm(request.POST)
451 if not form.is_valid():
452 return HttpResponseBadRequest('invalid')
454 try:
455 target = form.get_target()
457 device = Device.objects.get(pk=device_id)
458 device.sync_with(target)
460 except ValueError, e:
461 log('error while syncing device %s: %s' % (device_id, e))
463 return HttpResponseRedirect('/device/%s' % device_id)
465 @login_required
466 def device_unsync(request, device_id):
467 if request.method != 'GET':
468 return HttpResponseNotAllowed(['GET'])
470 device = Device.objects.get(pk=device_id)
471 device.unsync()
473 return HttpResponseRedirect('/device/%s' % device_id)
475 @login_required
476 def podcast_subscribe_url(request):
477 url = request.GET.get('url')
479 if url == None:
480 raise Http404('http://my.gpodder.org/subscribe?url=http://www.example.com/podcast.xml')
482 url = sanitize_url(url)
484 if url == '':
485 raise Http404('Please specify a valid url')
487 podcast, created = Podcast.objects.get_or_create(url=url)
489 return HttpResponseRedirect('/podcast/%d/subscribe' % podcast.pk)
491 @login_required
492 def delete_account(request):
494 if request.method == 'GET':
495 return render_to_response('delete_account.html')
497 request.user.is_active = False
498 request.user.save()
499 logout(request)
500 return render_to_response('delete_account.html', {
501 'success': True
504 def author(request):
505 current_site = Site.objects.get_current()
506 return render_to_response('authors.html', {
507 'url': current_site
508 }, context_instance=RequestContext(request))
511 def resend_activation(request):
512 error_message = ''
514 if request.method == 'GET':
515 form = ResendActivationForm()
516 return render_to_response('registration/resend_activation.html', {
517 'form': form,
520 site = Site.objects.get_current()
521 form = ResendActivationForm(request.POST)
523 try:
524 if not form.is_valid():
525 raise ValueError(_('Invalid Username entered'))
527 try:
528 user = get_user(form.cleaned_data['username'], form.cleaned_data['email'])
529 except User.DoesNotExist:
530 raise ValueError(_('User does not exist.'))
532 profile = RegistrationProfile.objects.get(user=user)
534 if profile.activation_key == RegistrationProfile.ACTIVATED:
535 raise ValueError(_('Your account already has been activated. Go ahead and log in.'))
537 elif profile.activation_key_expired():
538 raise ValueError(_('Your activation key has expired. Please try another username, or retry with the same one tomorrow.'))
540 except ValueError, e:
541 return render_to_response('registration/resend_activation.html', {
542 'form': form,
543 'error_message' : e
547 try:
548 profile.send_activation_email(site)
550 except AttributeError:
551 #old versions of django-registration send registration mails from RegistrationManager
552 RegistrationProfile.objects.send_activation_email(profile, site)
554 return render_to_response('registration/resent_activation.html')
557 def user_subscriptions(request, username):
558 user = get_object_or_404(User, username=username)
560 token, c = SecurityToken.objects.get_or_create(user=user, object='subscriptions', action='r',
561 defaults = {'token': "".join(random.sample(string.letters+string.digits, 32))})
563 u_token = request.GET.get('token', '')
564 if token.token == '' or token.token == u_token:
565 subscriptions = set([s.podcast for s in Subscription.objects.filter(user=user)])
566 return render_to_response('user_subscriptions.html', {
567 'subscriptions': subscriptions,
568 'other_user': user})
570 else:
571 return render_to_response('user_subscriptions_denied.html', {
572 'other_user': user})