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
.search
.index
import search_podcasts
27 from mygpo
.web
.utils
import process_lang_params
, get_language_names
, \
28 get_page_list
, get_podcast_link_target
, sanitize_language_codes
29 from mygpo
.directory
.tags
import Topics
30 from mygpo
.users
.settings
import FLATTR_TOKEN
31 from mygpo
.data
.feeddownloader
import PodcastUpdater
, NoEpisodesException
32 from mygpo
.data
.tasks
import update_podcasts
33 from mygpo
.db
.couchdb
.user
import get_user_by_id
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().toplist(self
.language())[:num
]
80 context
['entries'] = entries
82 context
['max_subscribers'] = max([0] + [p
.subscriber_count() for p
in entries
])
87 class EpisodeToplistView(ToplistView
):
88 """ Most listened-to episodes """
90 template_name
= 'episode_toplist.html'
92 def get_context_data(self
, num
=100):
93 context
= super(EpisodeToplistView
, self
).get_context_data()
95 entries
= Episode
.objects
.all().toplist(self
.language())[:num
]
97 # load podcast objects
98 podcast_ids
= [e
.podcast
for e
in entries
]
99 podcasts
= Podcast
.objects
.get(id__in
=podcast_ids
)
100 podcasts
= {podcast
.id: podcast
for podcast
in podcasts
}
101 for entry
in entries
:
102 entry
.podcast
= podcasts
.get(entry
.podcast
, None)
104 context
['entries'] = entries
106 # Determine maximum listener amount (or 0 if no entries exist)
107 context
['max_listeners'] = max([0]+[e
.listeners
for e
in entries
])
112 class Carousel(View
):
113 """ A carousel demo """
115 @method_decorator(cache_control(private
=True))
116 @method_decorator(vary_on_cookie
)
117 def get(self
, request
):
119 return render(request
, 'carousel.html', {
120 # evaluated lazyly, cached by template
125 class Directory(View
):
126 """ The main directory page """
128 @method_decorator(cache_control(private
=True))
129 @method_decorator(vary_on_cookie
)
130 def get(self
, request
):
132 return render(request
, 'directory.html', {
134 # evaluated lazyly, cached by template
136 'podcastlists': self
.get_random_list(),
137 'random_podcast': Podcast
.objects
.random().first(),
141 def get_random_list(self
, podcasts_per_list
=5):
142 random_list
= next(random_podcastlists(), None)
145 random_list
= proxy_object(random_list
)
146 random_list
.more_podcasts
= max(0, len(random_list
.podcasts
) - podcasts_per_list
)
147 random_list
.podcasts
= Podcast
.objects
.filter(id__in
=random_list
.podcasts
[:podcasts_per_list
])
148 random_list
.user
= get_user_by_id(random_list
.user
)
153 @cache_control(private
=True)
155 def category(request
, category
, page_size
=20):
156 category
= category_for_tag(category
)
158 return HttpResponseNotFound()
160 # Make sure page request is an int. If not, deliver first page.
162 page
= int(request
.GET
.get('page', '1'))
166 entries
= category
.get_podcasts( (page
-1) * page_size
, page
*page_size
)
167 podcasts
= filter(None, entries
)
168 num_pages
= int(ceil(len(category
.podcasts
) / page_size
))
170 page_list
= get_page_list(1, num_pages
, page
, 15)
172 return render(request
, 'category.html', {
174 'category': category
.label
,
175 'page_list': page_list
,
182 @cache_control(private
=True)
184 def search(request
, template
='search.html', args
={}):
186 if 'q' in request
.GET
:
187 q
= request
.GET
.get('q', '').encode('utf-8')
190 page
= int(request
.GET
.get('page', 1))
194 start
= RESULTS_PER_PAGE
*(page
-1)
195 results
= search_podcasts(q
)
196 num_pages
= int(ceil(results
.total
/ RESULTS_PER_PAGE
))
197 results
= results
[start
:start
+RESULTS_PER_PAGE
]
199 page_list
= get_page_list(1, num_pages
, page
, 15)
206 max_subscribers
= max([p
.subscribers
for p
in results
] + [0])
208 current_site
= RequestSite(request
)
210 return render(request
, template
, dict(
213 page_list
= page_list
,
214 max_subscribers
= max_subscribers
,
215 domain
= current_site
.domain
,
221 @cache_control(private
=True)
223 def podcast_lists(request
, page_size
=20):
225 # Make sure page request is an int. If not, deliver first page.
227 page
= int(request
.GET
.get('page', '1'))
231 lists
= podcastlists_by_rating(skip
=(page
-1) * page_size
, limit
=page_size
)
234 def _prepare_list(l
):
235 user
= get_user_by_id(l
.user
)
237 l
.username
= user
.username
if user
else ''
240 lists
= map(_prepare_list
, lists
)
242 num_pages
= int(ceil(podcastlist_count() / float(page_size
)))
244 page_list
= get_page_list(1, num_pages
, page
, 15)
246 return render(request
, 'podcast_lists.html', {
248 'page_list': page_list
,
253 class MissingPodcast(View
):
254 """ Check if a podcast is missing """
256 @method_decorator(login_required
)
257 def get(self
, request
):
259 site
= RequestSite(request
)
261 # check if we're doing a query
262 url
= request
.GET
.get('q', None)
270 podcast
= Podcast
.objects
.get(urls__url
=url
)
273 except Podcast
.DoesNotExist
:
274 # check if we could add a podcast for the given URL
276 updater
= PodcastUpdater()
279 can_add
= updater
.verify_podcast_url(url
)
281 except (ParserException
, FetchFeedException
,
282 NoEpisodesException
) as ex
:
284 messages
.error(request
, unicode(ex
))
286 return render(request
, 'missing.html', {
294 class AddPodcast(View
):
295 """ Add a missing podcast"""
297 @method_decorator(login_required
)
298 @method_decorator(cache_control(private
=True))
299 @method_decorator(vary_on_cookie
)
300 def post(self
, request
):
302 url
= request
.POST
.get('url', None)
307 res
= update_podcasts
.delay([url
])
309 return HttpResponseRedirect(reverse('add-podcast-status',
313 class AddPodcastStatus(TemplateView
):
314 """ Status of adding a podcast """
316 template_name
= 'directory/add-podcast-status.html'
318 def get(self
, request
, task_id
):
319 result
= update_podcasts
.AsyncResult(task_id
)
321 if not result
.ready():
322 return self
.render_to_response({
327 podcasts
= result
.get()
328 messages
.success(request
, _('%d podcasts added' % len(podcasts
)))
330 except (ParserException
, FetchFeedException
,
331 NoEpisodesException
) as ex
:
332 messages
.error(request
, str(ex
))
335 return self
.render_to_response({
337 'podcasts': podcasts
,
341 class PodcastListView(ListView
):
342 """ A generic podcast list view """
345 context_object_name
= 'podcasts'
347 @method_decorator(vary_on_cookie
)
348 @method_decorator(cache_control(private
=True))
349 def dispatch(self
, *args
, **kwargs
):
350 """ Only used for applying decorators """
351 return super(PodcastListView
, self
).dispatch(*args
, **kwargs
)
357 There seems to be no other pre-defined method for getting the current
359 https://docs.djangoproject.com/en/dev/ref/class-based-views/mixins-multiple-object/#multipleobjectmixin
361 return self
.get_context_data()['page_obj']
363 def page_list(self
, page_size
=15):
364 """ Return a list of pages, eg [1, 2, 3, '...', 6, 7, 8] """
366 return get_page_list(1,
367 page
.paginator
.num_pages
,
369 page
.paginator
.per_page
,
372 def max_subscribers(self
):
373 """ Maximum subscribers of the podcasts on this page """
375 podcasts
= page
.object_list
376 return max([p
.subscriber_count() for p
in podcasts
] + [0])
379 class FlattrPodcastList(PodcastListView
):
380 """ Lists podcasts that have Flattr payment URLs """
382 template_name
= 'flattr-podcasts.html'
384 def get_queryset(self
):
385 return Podcast
.objects
.flattr()
387 def get_context_data(self
, num
=100):
388 context
= super(FlattrPodcastList
, self
).get_context_data()
389 context
['flattr_auth'] = (self
.request
.user
.is_authenticated()
390 # and bool(self.request.user.get_wksetting(FLATTR_TOKEN))
395 class LicensePodcastList(PodcastListView
):
396 """ Lists podcasts with a given license """
398 template_name
= 'directory/license-podcasts.html'
400 def get_queryset(self
):
401 return Podcast
.objects
.license(self
.license_url
)
404 def license_url(self
):
405 return self
.kwargs
['license_url']
408 class LicenseList(TemplateView
):
409 """ Lists all podcast licenses """
411 template_name
= 'directory/licenses.html'
414 """ Returns all podcast licenses """
415 query
= Podcast
.objects
.exclude(license__isnull
=True)
416 values
= query
.values("license").annotate(Count("id")).order_by()
418 counter
= Counter({l
['license']: l
['id__count'] for l
in values
})
419 return counter
.most_common()