1 from __future__
import division
3 from itertools
import imap
as map
5 from collections
import Counter
7 from django
.http
import HttpResponseNotFound
, Http404
, HttpResponseRedirect
8 from django
.core
.urlresolvers
import reverse
9 from django
.shortcuts
import render
10 from django
.db
.models
import Count
11 from django
.contrib
.sites
.models
import RequestSite
12 from django
.views
.decorators
.cache
import cache_control
13 from django
.views
.decorators
.vary
import vary_on_cookie
14 from django
.views
.generic
import ListView
15 from django
.utils
.decorators
import method_decorator
16 from django
.views
.generic
.base
import View
, TemplateView
17 from django
.contrib
.auth
.decorators
import login_required
18 from django
.contrib
import messages
19 from django
.utils
.translation
import ugettext
as _
20 from django
.contrib
.auth
import get_user_model
22 from feedservice
.parse
.models
import ParserException
23 from feedservice
.parse
import FetchFeedException
25 from mygpo
.core
.proxy
import proxy_object
26 from mygpo
.podcasts
.models
import Podcast
, Episode
27 from mygpo
.directory
.search
import search_podcasts
28 from mygpo
.web
.utils
import process_lang_params
, get_language_names
, \
29 get_page_list
, get_podcast_link_target
, sanitize_language_codes
30 from mygpo
.directory
.tags
import Topics
31 from mygpo
.users
.settings
import FLATTR_TOKEN
32 from mygpo
.data
.feeddownloader
import PodcastUpdater
, NoEpisodesException
33 from mygpo
.data
.tasks
import update_podcasts
34 from mygpo
.db
.couchdb
.directory
import category_for_tag
35 from mygpo
.db
.couchdb
.podcastlist
import random_podcastlists
, \
36 podcastlist_count
, podcastlists_by_rating
39 class ToplistView(TemplateView
):
40 """ Generic Top List view """
42 @method_decorator(vary_on_cookie
)
43 @method_decorator(cache_control(private
=True))
44 def dispatch(self
, *args
, **kwargs
):
45 """ Only used for applying decorators """
46 return super(ToplistView
, self
).dispatch(*args
, **kwargs
)
48 def all_languages(self
):
49 """ Returns all 2-letter language codes that are used by podcasts.
51 It filters obviously invalid strings, but does not check if any
52 of these codes is contained in ISO 639. """
54 query
= Podcast
.objects
.exclude(language__isnull
=True)
55 query
= query
.distinct('language').values('language')
57 langs
= [o
['language'] for o
in query
]
58 langs
= sorted(sanitize_language_codes(langs
))
60 return get_language_names(langs
)
63 """ Currently selected language """
64 return process_lang_params(self
.request
)
67 """ Current site for constructing absolute links """
68 return RequestSite(self
.request
)
71 class PodcastToplistView(ToplistView
):
72 """ Most subscribed podcasts """
74 template_name
= 'toplist.html'
76 def get_context_data(self
, num
=100):
77 context
= super(PodcastToplistView
, self
).get_context_data()
79 entries
= Podcast
.objects
.all()\
80 .prefetch_related('slugs')\
81 .toplist(self
.language())[:num
]
82 context
['entries'] = entries
84 context
['max_subscribers'] = max([0] + [p
.subscriber_count() for p
in entries
])
89 class EpisodeToplistView(ToplistView
):
90 """ Most listened-to episodes """
92 template_name
= 'episode_toplist.html'
94 def get_context_data(self
, num
=100):
95 context
= super(EpisodeToplistView
, self
).get_context_data()
97 entries
= Episode
.objects
.all()\
98 .select_related('podcast')\
99 .prefetch_related('slugs', 'podcast__slugs')\
100 .toplist(self
.language())[:num
]
101 context
['entries'] = entries
103 # Determine maximum listener amount (or 0 if no entries exist)
104 context
['max_listeners'] = max([0]+[e
.listeners
for e
in entries
])
109 class Carousel(View
):
110 """ A carousel demo """
112 @method_decorator(cache_control(private
=True))
113 @method_decorator(vary_on_cookie
)
114 def get(self
, request
):
116 return render(request
, 'carousel.html', {
117 # evaluated lazyly, cached by template
122 class Directory(View
):
123 """ The main directory page """
125 @method_decorator(cache_control(private
=True))
126 @method_decorator(vary_on_cookie
)
127 def get(self
, request
):
129 return render(request
, 'directory.html', {
131 # evaluated lazyly, cached by template
133 'podcastlists': self
.get_random_list(),
134 'random_podcast': Podcast
.objects
.all().random().first(),
138 def get_random_list(self
, podcasts_per_list
=5):
139 User
= get_user_model()
140 random_list
= next(random_podcastlists(), None)
143 podcast_ids
= random_list
.podcasts
[:podcasts_per_list
]
144 random_list
= proxy_object(random_list
)
145 random_list
.more_podcasts
= max(0, len(random_list
.podcasts
) - podcasts_per_list
)
146 random_list
.podcasts
= Podcast
.objects
.filter(id__in
=podcast_ids
)
149 random_list
.user
= User
.objects
.get(profile__uuid
=random_list
.user
)
150 except User
.DoesNotExist
:
156 @cache_control(private
=True)
158 def category(request
, category
, page_size
=20):
159 category
= category_for_tag(category
)
161 return HttpResponseNotFound()
163 # Make sure page request is an int. If not, deliver first page.
165 page
= int(request
.GET
.get('page', '1'))
169 entries
= category
.get_podcasts( (page
-1) * page_size
, page
*page_size
)
170 podcasts
= filter(None, entries
)
171 num_pages
= int(ceil(len(category
.podcasts
) / page_size
))
173 page_list
= get_page_list(1, num_pages
, page
, 15)
175 return render(request
, 'category.html', {
177 'category': category
.label
,
178 'page_list': page_list
,
185 @cache_control(private
=True)
187 def search(request
, template
='search.html', args
={}):
189 if 'q' in request
.GET
:
190 q
= request
.GET
.get('q', '').encode('utf-8')
193 page
= int(request
.GET
.get('page', 1))
197 start
= RESULTS_PER_PAGE
*(page
-1)
198 results
= search_podcasts(q
)
200 num_pages
= int(ceil(total
/ RESULTS_PER_PAGE
))
201 results
= results
[start
:start
+RESULTS_PER_PAGE
]
203 page_list
= get_page_list(1, num_pages
, page
, 15)
210 max_subscribers
= max([p
.subscribers
for p
in results
] + [0])
212 current_site
= RequestSite(request
)
214 return render(request
, template
, dict(
217 page_list
= page_list
,
218 max_subscribers
= max_subscribers
,
219 domain
= current_site
.domain
,
225 @cache_control(private
=True)
227 def podcast_lists(request
, page_size
=20):
229 # Make sure page request is an int. If not, deliver first page.
231 page
= int(request
.GET
.get('page', '1'))
235 lists
= podcastlists_by_rating(skip
=(page
-1) * page_size
, limit
=page_size
)
238 def _prepare_list(l
):
239 User
= get_user_model()
241 user
= User
.objects
.get(profile__uuid
=l
.user
)
242 except User
.DoesNotExist
:
246 l
.username
= user
.username
if user
else ''
249 lists
= filter(None, map(_prepare_list
, lists
))
251 num_pages
= int(ceil(podcastlist_count() / float(page_size
)))
253 page_list
= get_page_list(1, num_pages
, page
, 15)
255 return render(request
, 'podcast_lists.html', {
257 'page_list': page_list
,
262 class MissingPodcast(View
):
263 """ Check if a podcast is missing """
265 @method_decorator(login_required
)
266 def get(self
, request
):
268 site
= RequestSite(request
)
270 # check if we're doing a query
271 url
= request
.GET
.get('q', None)
279 podcast
= Podcast
.objects
.get(urls__url
=url
)
282 except Podcast
.DoesNotExist
:
283 # check if we could add a podcast for the given URL
285 updater
= PodcastUpdater()
288 can_add
= updater
.verify_podcast_url(url
)
290 except (ParserException
, FetchFeedException
,
291 NoEpisodesException
) as ex
:
293 messages
.error(request
, unicode(ex
))
295 return render(request
, 'missing.html', {
303 class AddPodcast(View
):
304 """ Add a missing podcast"""
306 @method_decorator(login_required
)
307 @method_decorator(cache_control(private
=True))
308 @method_decorator(vary_on_cookie
)
309 def post(self
, request
):
311 url
= request
.POST
.get('url', None)
316 res
= update_podcasts
.delay([url
])
318 return HttpResponseRedirect(reverse('add-podcast-status',
322 class AddPodcastStatus(TemplateView
):
323 """ Status of adding a podcast """
325 template_name
= 'directory/add-podcast-status.html'
327 def get(self
, request
, task_id
):
328 result
= update_podcasts
.AsyncResult(task_id
)
330 if not result
.ready():
331 return self
.render_to_response({
336 podcasts
= result
.get()
337 messages
.success(request
, _('%d podcasts added' % len(podcasts
)))
339 except (ParserException
, FetchFeedException
,
340 NoEpisodesException
) as ex
:
341 messages
.error(request
, str(ex
))
344 return self
.render_to_response({
346 'podcasts': podcasts
,
350 class PodcastListView(ListView
):
351 """ A generic podcast list view """
354 context_object_name
= 'podcasts'
356 @method_decorator(vary_on_cookie
)
357 @method_decorator(cache_control(private
=True))
358 def dispatch(self
, *args
, **kwargs
):
359 """ Only used for applying decorators """
360 return super(PodcastListView
, self
).dispatch(*args
, **kwargs
)
366 There seems to be no other pre-defined method for getting the current
368 https://docs.djangoproject.com/en/dev/ref/class-based-views/mixins-multiple-object/#multipleobjectmixin
370 return self
.get_context_data()['page_obj']
372 def page_list(self
, page_size
=15):
373 """ Return a list of pages, eg [1, 2, 3, '...', 6, 7, 8] """
375 return get_page_list(1,
376 page
.paginator
.num_pages
,
378 page
.paginator
.per_page
,
381 def max_subscribers(self
):
382 """ Maximum subscribers of the podcasts on this page """
384 podcasts
= page
.object_list
385 return max([p
.subscriber_count() for p
in podcasts
] + [0])
388 class FlattrPodcastList(PodcastListView
):
389 """ Lists podcasts that have Flattr payment URLs """
391 template_name
= 'flattr-podcasts.html'
393 def get_queryset(self
):
394 return Podcast
.objects
.all().flattr()
396 def get_context_data(self
, num
=100):
397 context
= super(FlattrPodcastList
, self
).get_context_data()
398 context
['flattr_auth'] = (self
.request
.user
.is_authenticated()
399 # and bool(self.request.user.get_wksetting(FLATTR_TOKEN))
404 class LicensePodcastList(PodcastListView
):
405 """ Lists podcasts with a given license """
407 template_name
= 'directory/license-podcasts.html'
409 def get_queryset(self
):
410 return Podcast
.objects
.all().license(self
.license_url
)
413 def license_url(self
):
414 return self
.kwargs
['license_url']
417 class LicenseList(TemplateView
):
418 """ Lists all podcast licenses """
420 template_name
= 'directory/licenses.html'
423 """ Returns all podcast licenses """
424 query
= Podcast
.objects
.exclude(license__isnull
=True)
425 values
= query
.values("license").annotate(Count("id")).order_by()
427 counter
= Counter({l
['license']: l
['id__count'] for l
in values
})
428 return counter
.most_common()