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 _
21 from feedservice
.parse
.models
import ParserException
22 from feedservice
.parse
import FetchFeedException
24 from mygpo
.core
.proxy
import proxy_object
25 from mygpo
.podcasts
.models
import Podcast
26 from mygpo
.directory
.toplist
import PodcastToplist
, EpisodeToplist
, \
28 from mygpo
.directory
.search
import search_podcasts
29 from mygpo
.web
.utils
import process_lang_params
, get_language_names
, \
30 get_page_list
, get_podcast_link_target
, sanitize_language_codes
31 from mygpo
.directory
.tags
import Topics
32 from mygpo
.users
.settings
import FLATTR_TOKEN
33 from mygpo
.data
.feeddownloader
import PodcastUpdater
, NoEpisodesException
34 from mygpo
.data
.tasks
import update_podcasts
35 from mygpo
.db
.couchdb
.user
import get_user_by_id
36 from mygpo
.db
.couchdb
.podcast
import podcasts_by_id
, \
37 podcasts_to_dict
, podcast_for_url
38 from mygpo
.db
.couchdb
.directory
import category_for_tag
39 from mygpo
.db
.couchdb
.podcastlist
import random_podcastlists
, \
40 podcastlist_count
, podcastlists_by_rating
43 class ToplistView(TemplateView
):
44 """ Generic Top List view """
46 @method_decorator(vary_on_cookie
)
47 @method_decorator(cache_control(private
=True))
48 def dispatch(self
, *args
, **kwargs
):
49 """ Only used for applying decorators """
50 return super(ToplistView
, self
).dispatch(*args
, **kwargs
)
52 def all_languages(self
):
53 """ Returns all 2-letter language codes that are used by podcasts.
55 It filters obviously invalid strings, but does not check if any
56 of these codes is contained in ISO 639. """
58 query
= Podcast
.objects
.exclude(language__isnull
=True)
59 query
= query
.distinct('language').values('language')
61 langs
= [o
['language'] for o
in query
]
62 langs
= sorted(sanitize_language_codes(langs
))
64 return get_language_names(langs
)
67 """ Currently selected language """
68 return process_lang_params(self
.request
)
71 """ Current site for constructing absolute links """
72 return RequestSite(self
.request
)
75 class PodcastToplistView(ToplistView
):
76 """ Most subscribed podcasts """
78 template_name
= 'toplist.html'
80 def get_context_data(self
, num
=100):
81 context
= super(PodcastToplistView
, self
).get_context_data()
83 toplist
= PodcastToplist(self
.language())
84 entries
= toplist
[:num
]
85 context
['entries'] = entries
87 context
['max_subscribers'] = max([0] + [p
.subscriber_count() for (oldp
, p
) in entries
])
92 class EpisodeToplistView(ToplistView
):
93 """ Most listened-to episodes """
95 template_name
= 'episode_toplist.html'
97 def get_context_data(self
, num
=100):
98 context
= super(EpisodeToplistView
, self
).get_context_data()
100 toplist
= EpisodeToplist(language
=self
.language())
101 entries
= list(map(proxy_object
, toplist
[:num
]))
103 # load podcast objects
104 podcast_ids
= [e
.podcast
for e
in entries
]
105 podcasts
= podcasts_to_dict(podcast_ids
, True)
106 for entry
in entries
:
107 entry
.podcast
= podcasts
.get(entry
.podcast
, None)
109 context
['entries'] = entries
111 # Determine maximum listener amount (or 0 if no entries exist)
112 context
['max_listeners'] = max([0]+[e
.listeners
for e
in entries
])
117 class Carousel(View
):
118 """ A carousel demo """
120 @method_decorator(cache_control(private
=True))
121 @method_decorator(vary_on_cookie
)
122 def get(self
, request
):
124 return render(request
, 'carousel.html', {
125 # evaluated lazyly, cached by template
130 class Directory(View
):
131 """ The main directory page """
133 @method_decorator(cache_control(private
=True))
134 @method_decorator(vary_on_cookie
)
135 def get(self
, request
):
137 return render(request
, 'directory.html', {
139 # evaluated lazyly, cached by template
141 'trending_podcasts': TrendingPodcasts(''),
142 'podcastlists': self
.get_random_list(),
143 'random_podcast': Podcast
.objects
.random().first(),
147 def get_random_list(self
, podcasts_per_list
=5):
148 random_list
= next(random_podcastlists(), None)
151 random_list
= proxy_object(random_list
)
152 random_list
.more_podcasts
= max(0, len(random_list
.podcasts
) - podcasts_per_list
)
153 random_list
.podcasts
= podcasts_by_id(random_list
.podcasts
[:podcasts_per_list
])
154 random_list
.user
= get_user_by_id(random_list
.user
)
159 @cache_control(private
=True)
161 def category(request
, category
, page_size
=20):
162 category
= category_for_tag(category
)
164 return HttpResponseNotFound()
166 # Make sure page request is an int. If not, deliver first page.
168 page
= int(request
.GET
.get('page', '1'))
172 entries
= category
.get_podcasts( (page
-1) * page_size
, page
*page_size
)
173 podcasts
= filter(None, entries
)
174 num_pages
= int(ceil(len(category
.podcasts
) / page_size
))
176 page_list
= get_page_list(1, num_pages
, page
, 15)
178 return render(request
, 'category.html', {
180 'category': category
.label
,
181 'page_list': page_list
,
188 @cache_control(private
=True)
190 def search(request
, template
='search.html', args
={}):
192 if 'q' in request
.GET
:
193 q
= request
.GET
.get('q', '').encode('utf-8')
196 page
= int(request
.GET
.get('page', 1))
200 results
, total
= search_podcasts(q
=q
, skip
=RESULTS_PER_PAGE
*(page
-1))
201 num_pages
= int(ceil(total
/ RESULTS_PER_PAGE
))
203 page_list
= get_page_list(1, num_pages
, page
, 15)
210 max_subscribers
= max([p
.subscriber_count() for p
in results
] + [0])
211 current_site
= RequestSite(request
)
213 return render(request
, template
, dict(
216 page_list
= page_list
,
217 max_subscribers
= max_subscribers
,
218 domain
= current_site
.domain
,
224 @cache_control(private
=True)
226 def podcast_lists(request
, page_size
=20):
228 # Make sure page request is an int. If not, deliver first page.
230 page
= int(request
.GET
.get('page', '1'))
234 lists
= podcastlists_by_rating(skip
=(page
-1) * page_size
, limit
=page_size
)
237 def _prepare_list(l
):
238 user
= get_user_by_id(l
.user
)
240 l
.username
= user
.username
if user
else ''
243 lists
= map(_prepare_list
, lists
)
245 num_pages
= int(ceil(podcastlist_count() / float(page_size
)))
247 page_list
= get_page_list(1, num_pages
, page
, 15)
249 return render(request
, 'podcast_lists.html', {
251 'page_list': page_list
,
256 class MissingPodcast(View
):
257 """ Check if a podcast is missing """
259 @method_decorator(login_required
)
260 def get(self
, request
):
262 site
= RequestSite(request
)
264 # check if we're doing a query
265 url
= request
.GET
.get('q', None)
272 podcast
= podcast_for_url(url
)
274 # if the podcast does already exist, there's nothing more to do
278 # check if we could add a podcast for the given URL
281 updater
= PodcastUpdater()
284 can_add
= updater
.verify_podcast_url(url
)
286 except (ParserException
, FetchFeedException
,
287 NoEpisodesException
) as ex
:
289 messages
.error(request
, unicode(ex
))
291 return render(request
, 'missing.html', {
299 class AddPodcast(View
):
300 """ Add a missing podcast"""
302 @method_decorator(login_required
)
303 @method_decorator(cache_control(private
=True))
304 @method_decorator(vary_on_cookie
)
305 def post(self
, request
):
307 url
= request
.POST
.get('url', None)
312 res
= update_podcasts
.delay([url
])
314 return HttpResponseRedirect(reverse('add-podcast-status',
318 class AddPodcastStatus(TemplateView
):
319 """ Status of adding a podcast """
321 template_name
= 'directory/add-podcast-status.html'
323 def get(self
, request
, task_id
):
324 result
= update_podcasts
.AsyncResult(task_id
)
326 if not result
.ready():
327 return self
.render_to_response({
332 podcasts
= result
.get()
333 messages
.success(request
, _('%d podcasts added' % len(podcasts
)))
335 except (ParserException
, FetchFeedException
,
336 NoEpisodesException
) as ex
:
337 messages
.error(request
, str(ex
))
340 return self
.render_to_response({
342 'podcasts': podcasts
,
346 class PodcastListView(ListView
):
347 """ A generic podcast list view """
350 context_object_name
= 'podcasts'
352 @method_decorator(vary_on_cookie
)
353 @method_decorator(cache_control(private
=True))
354 def dispatch(self
, *args
, **kwargs
):
355 """ Only used for applying decorators """
356 return super(PodcastListView
, self
).dispatch(*args
, **kwargs
)
362 There seems to be no other pre-defined method for getting the current
364 https://docs.djangoproject.com/en/dev/ref/class-based-views/mixins-multiple-object/#multipleobjectmixin
366 return self
.get_context_data()['page_obj']
368 def page_list(self
, page_size
=15):
369 """ Return a list of pages, eg [1, 2, 3, '...', 6, 7, 8] """
371 return get_page_list(1,
372 page
.paginator
.num_pages
,
374 page
.paginator
.per_page
,
377 def max_subscribers(self
):
378 """ Maximum subscribers of the podcasts on this page """
380 podcasts
= page
.object_list
381 return max([p
.subscriber_count() for p
in podcasts
] + [0])
384 class FlattrPodcastList(PodcastListView
):
385 """ Lists podcasts that have Flattr payment URLs """
387 template_name
= 'flattr-podcasts.html'
389 def get_queryset(self
):
390 return Podcast
.objects
.flattr()
392 def get_context_data(self
, num
=100):
393 context
= super(FlattrPodcastList
, self
).get_context_data()
394 context
['flattr_auth'] = (self
.request
.user
.is_authenticated()
395 # and bool(self.request.user.get_wksetting(FLATTR_TOKEN))
400 class LicensePodcastList(PodcastListView
):
401 """ Lists podcasts with a given license """
403 template_name
= 'directory/license-podcasts.html'
405 def get_queryset(self
):
406 return Podcast
.objects
.license(self
.license_url
)
409 def license_url(self
):
410 return self
.kwargs
['license_url']
413 class LicenseList(TemplateView
):
414 """ Lists all podcast licenses """
416 template_name
= 'directory/licenses.html'
419 """ Returns all podcast licenses """
420 query
= Podcast
.objects
.exclude(license__isnull
=True)
421 values
= query
.values("license").annotate(Count("id")).order_by()
423 counter
= Counter({l
['license']: l
['id__count'] for l
in values
})
424 return counter
.most_common()