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
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
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
,
54 }, context_instance
=RequestContext(request
))
57 podcasts
= Podcast
.objects
.count()
58 return render_to_response('home.html', {
59 'podcast_count': podcasts
,
63 def create_subscriptionlist(request
):
64 #sync all devices first
65 for d
in Device
.objects
.filter(user
=request
.user
):
68 subscriptions
= Subscription
.objects
.filter(user
=request
.user
)
71 for s
in subscriptions
:
73 l
[s
.podcast
]['devices'].append(s
.device
)
75 e
= Episode
.objects
.filter(podcast
=s
.podcast
, timestamp__isnull
=False).order_by('-timestamp')
76 episode
= e
[0] if e
.count() > 0 else None
78 l
[s
.podcast
] = {'podcast': s
.podcast
, 'episode': episode
, 'devices': devices
}
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
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
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']
105 subscriptionmeta
.save()
107 except IntegrityError
, ie
:
108 error_message
= _('You can\'t use the same UID for two devices.')
110 privacy_form
= PrivacyForm({
111 'public': subscriptionmeta
.public
117 timeline_data
= listener_data(podcast
)
119 return render_to_response('podcast.html', {
121 'timeline_data': timeline_data
,
123 'privacy_form': privacy_form
,
124 'devices': subscribed_devices
,
125 'can_subscribe': len(subscribe_targets
) > 0,
126 'episodes': episodes
,
127 'max_listeners': max_listeners
,
129 }, context_instance
=RequestContext(request
))
131 current_site
= Site
.objects
.get_current()
132 return render_to_response('podcast.html', {
135 }, context_instance
=RequestContext(request
))
137 def listener_data(podcast
):
140 episodes
= EpisodeAction
.objects
.filter(episode__podcast
=podcast
, timestamp__gte
=d
).order_by('timestamp').values('timestamp')
141 if len(episodes
) == 0:
144 start
= episodes
[0]['timestamp']
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
154 'listeners': listeners
,
159 def history(request
, len=15, device_id
=None):
161 devices
= Device
.objects
.filter(id=device_id
)
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]
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', {
186 }, context_instance
=RequestContext(request
))
189 def podcast_subscribe(request
, pid
):
190 podcast
= get_object_or_404(Podcast
, pk
=pid
)
193 if request
.method
== 'POST':
194 form
= SyncForm(request
.POST
)
197 target
= form
.get_target()
199 if isinstance(target
, SyncGroup
):
200 device
= target
.devices()[0]
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
)
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
,
222 'can_subscribe': len(targets
) > 0,
224 }, context_instance
=RequestContext(request
))
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
)
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
249 episodes
= Episode
.objects
.filter(podcast
=podcast
).order_by('-timestamp')
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]
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')
268 return render_to_response('episode.html', {
271 }, context_instance
=RequestContext(request
))
275 def account(request
):
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
:
289 elif 'private_subscriptions' in request
.GET
:
290 token
.token
= "".join(random
.sample(string
.letters
+string
.digits
, 32))
293 form
= UserAccountForm({
294 'email': request
.user
.email
,
295 'public': request
.user
.get_profile().public_profile
298 return render_to_response('account.html', {
300 'token': token
.token
,
302 }, context_instance
=RequestContext(request
))
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']
318 request
.user
.get_profile().public_profile
= form
.cleaned_data
['public']
319 request
.user
.get_profile().save()
323 except ValueError, e
:
327 except ValidationError
, e
:
331 return render_to_response('account.html', {
333 'token': token
.token
,
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', {
346 'max_subscribers': max_subscribers
,
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', {
357 'max_listeners': max_listeners
,
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')
372 def suggestions(request
):
376 if 'rate' in request
.GET
:
377 Rating
.objects
.create(target
='suggestions', user
=request
.user
, rating
=request
.GET
['rate'], timestamp
=datetime
.now())
380 entries
= SuggestionEntry
.forUser(request
.user
)
381 current_site
= Site
.objects
.get_current()
382 return render_to_response('suggestions.html', {
386 }, context_instance
=RequestContext(request
))
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
)
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']
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.')
419 device_form
= DeviceForm({
425 return render_to_response('device.html', {
427 'device_form': device_form
,
428 'sync_form': sync_form
,
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
))
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
446 current_site
= Site
.objects
.get_current()
447 subscriptionlist
= create_subscriptionlist(request
)
448 return render_to_response('home-user.html', {
449 'subscriptionlist': subscriptionlist
,
451 'deletedevice_success': True,
452 'device_name': device
.name
453 }, context_instance
=RequestContext(request
))
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')
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
)
478 def device_unsync(request
, device_id
):
479 if request
.method
!= 'GET':
480 return HttpResponseNotAllowed(['GET'])
482 device
= Device
.objects
.get(pk
=device_id
)
485 return HttpResponseRedirect('/device/%s' % device_id
)
488 def podcast_subscribe_url(request
):
489 url
= request
.GET
.get('url')
492 raise Http404('http://my.gpodder.org/subscribe?url=http://www.example.com/podcast.xml')
494 url
= sanitize_url(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
)
504 def delete_account(request
):
506 if request
.method
== 'GET':
507 return render_to_response('delete_account.html')
509 request
.user
.is_active
= False
512 return render_to_response('delete_account.html', {
517 current_site
= Site
.objects
.get_current()
518 return render_to_response('authors.html', {
520 }, context_instance
=RequestContext(request
))
523 def resend_activation(request
):
526 if request
.method
== 'GET':
527 form
= ResendActivationForm()
528 return render_to_response('registration/resend_activation.html', {
532 site
= Site
.objects
.get_current()
533 form
= ResendActivationForm(request
.POST
)
536 if not form
.is_valid():
537 raise ValueError(_('Invalid Username entered'))
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', {
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
,
583 return render_to_response('user_subscriptions_denied.html', {