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
.core
.paginator
import Paginator
, EmptyPage
, PageNotAnInteger
10 from django
.shortcuts
import render
11 from django
.db
.models
import Count
12 from django
.contrib
.sites
.models
import RequestSite
13 from django
.views
.decorators
.cache
import cache_control
14 from django
.views
.decorators
.vary
import vary_on_cookie
15 from django
.views
.generic
import ListView
16 from django
.utils
.decorators
import method_decorator
17 from django
.views
.generic
.base
import View
, TemplateView
18 from django
.contrib
.auth
.decorators
import login_required
19 from django
.contrib
import messages
20 from django
.utils
.translation
import ugettext
as _
21 from django
.contrib
.auth
import get_user_model
23 from feedservice
.parse
.models
import ParserException
24 from feedservice
.parse
import FetchFeedException
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
.categories
.models
import Category
33 from mygpo
.podcastlists
.models
import PodcastList
34 from mygpo
.data
.feeddownloader
import PodcastUpdater
, NoEpisodesException
35 from mygpo
.data
.tasks
import update_podcasts
38 class ToplistView(TemplateView
):
39 """ Generic Top List view """
41 @method_decorator(vary_on_cookie
)
42 @method_decorator(cache_control(private
=True))
43 def dispatch(self
, *args
, **kwargs
):
44 """ Only used for applying decorators """
45 return super(ToplistView
, self
).dispatch(*args
, **kwargs
)
47 def all_languages(self
):
48 """ Returns all 2-letter language codes that are used by podcasts.
50 It filters obviously invalid strings, but does not check if any
51 of these codes is contained in ISO 639. """
53 query
= Podcast
.objects
.exclude(language__isnull
=True)
54 query
= query
.distinct('language').values('language')
56 langs
= [o
['language'] for o
in query
]
57 langs
= sorted(sanitize_language_codes(langs
))
59 return get_language_names(langs
)
62 """ Currently selected language """
63 return process_lang_params(self
.request
)
66 """ Current site for constructing absolute links """
67 return RequestSite(self
.request
)
70 class PodcastToplistView(ToplistView
):
71 """ Most subscribed podcasts """
73 template_name
= 'toplist.html'
75 def get_context_data(self
, num
=100):
76 context
= super(PodcastToplistView
, self
).get_context_data()
78 entries
= Podcast
.objects
.all()\
79 .prefetch_related('slugs')\
80 .toplist(self
.language())[:num
]
81 context
['entries'] = entries
83 context
['max_subscribers'] = max([0] + [p
.subscriber_count() for p
in entries
])
88 class EpisodeToplistView(ToplistView
):
89 """ Most listened-to episodes """
91 template_name
= 'episode_toplist.html'
93 def get_context_data(self
, num
=100):
94 context
= super(EpisodeToplistView
, self
).get_context_data()
96 entries
= Episode
.objects
.all()\
97 .select_related('podcast')\
98 .prefetch_related('slugs', 'podcast__slugs')\
99 .toplist(self
.language())[:num
]
100 context
['entries'] = entries
102 # Determine maximum listener amount (or 0 if no entries exist)
103 context
['max_listeners'] = max([0]+[e
.listeners
for e
in entries
])
108 class Carousel(View
):
109 """ A carousel demo """
111 @method_decorator(cache_control(private
=True))
112 @method_decorator(vary_on_cookie
)
113 def get(self
, request
):
115 return render(request
, 'carousel.html', {
116 # evaluated lazyly, cached by template
121 class Directory(View
):
122 """ The main directory page """
124 @method_decorator(cache_control(private
=True))
125 @method_decorator(vary_on_cookie
)
126 def get(self
, request
):
128 return render(request
, 'directory.html', {
130 # evaluated lazyly, cached by template
132 'podcastlists': self
.get_random_list(),
133 'random_podcast': Podcast
.objects
.all().random().first(),
137 def get_random_list(self
, podcasts_per_list
=5):
138 random_list
= PodcastList
.objects
.order_by('?').first()
142 @cache_control(private
=True)
144 def category(request
, category
, page_size
=20):
146 category
= Category
.objects
.get(tags__tag
=category
)
147 except Category
.DoesNotExist
:
148 return HttpResponseNotFound()
150 podcasts
= category
.entries
.all()\
151 .prefetch_related('podcast', 'podcast__slugs')
153 paginator
= Paginator(podcasts
, page_size
)
155 page
= request
.GET
.get('page')
157 podcasts
= paginator
.page(page
)
158 except PageNotAnInteger
:
159 # If page is not an integer, deliver first page.
160 podcasts
= paginator
.page(1)
162 # If page is out of range (e.g. 9999), deliver last page of results.
163 podcasts
= paginator
.page(paginator
.num_pages
)
165 page_list
= get_page_list(1, paginator
.num_pages
, podcasts
.number
, 15)
167 return render(request
, 'category.html', {
169 'category': category
.title
,
170 'page_list': page_list
,
177 @cache_control(private
=True)
179 def search(request
, template
='search.html', args
={}):
181 if 'q' in request
.GET
:
182 q
= request
.GET
.get('q', '').encode('utf-8')
185 page
= int(request
.GET
.get('page', 1))
189 start
= RESULTS_PER_PAGE
*(page
-1)
190 results
= search_podcasts(q
)
192 num_pages
= int(ceil(total
/ RESULTS_PER_PAGE
))
193 results
= results
[start
:start
+RESULTS_PER_PAGE
]
195 page_list
= get_page_list(1, num_pages
, page
, 15)
202 max_subscribers
= max([p
.subscribers
for p
in results
] + [0])
204 current_site
= RequestSite(request
)
206 return render(request
, template
, dict(
209 page_list
= page_list
,
210 max_subscribers
= max_subscribers
,
211 domain
= current_site
.domain
,
217 @cache_control(private
=True)
219 def podcast_lists(request
, page_size
=20):
221 lists
= PodcastList
.objects
.all()\
222 .annotate(num_votes
=Count('votes'))\
223 .order_by('-num_votes')
225 paginator
= Paginator(lists
, page_size
)
227 page
= request
.GET
.get('page')
229 lists
= paginator
.page(page
)
230 except PageNotAnInteger
:
231 lists
= paginator
.page(1)
234 lists
= paginator
.page(paginator
.num_pages
)
235 page
= paginator
.num_pages
237 num_pages
= int(ceil(PodcastList
.objects
.count() / float(page_size
)))
238 page_list
= get_page_list(1, num_pages
, page
, 15)
240 return render(request
, 'podcast_lists.html', {
242 'page_list': page_list
,
247 class MissingPodcast(View
):
248 """ Check if a podcast is missing """
250 @method_decorator(login_required
)
251 def get(self
, request
):
253 site
= RequestSite(request
)
255 # check if we're doing a query
256 url
= request
.GET
.get('q', None)
264 podcast
= Podcast
.objects
.get(urls__url
=url
)
267 except Podcast
.DoesNotExist
:
268 # check if we could add a podcast for the given URL
270 updater
= PodcastUpdater()
273 can_add
= updater
.verify_podcast_url(url
)
275 except (ParserException
, FetchFeedException
,
276 NoEpisodesException
) as ex
:
278 messages
.error(request
, unicode(ex
))
280 return render(request
, 'missing.html', {
288 class AddPodcast(View
):
289 """ Add a missing podcast"""
291 @method_decorator(login_required
)
292 @method_decorator(cache_control(private
=True))
293 @method_decorator(vary_on_cookie
)
294 def post(self
, request
):
296 url
= request
.POST
.get('url', None)
301 res
= update_podcasts
.delay([url
])
303 return HttpResponseRedirect(reverse('add-podcast-status',
307 class AddPodcastStatus(TemplateView
):
308 """ Status of adding a podcast """
310 template_name
= 'directory/add-podcast-status.html'
312 def get(self
, request
, task_id
):
313 result
= update_podcasts
.AsyncResult(task_id
)
315 if not result
.ready():
316 return self
.render_to_response({
321 podcasts
= result
.get()
322 messages
.success(request
, _('%d podcasts added' % len(podcasts
)))
324 except (ParserException
, FetchFeedException
,
325 NoEpisodesException
) as ex
:
326 messages
.error(request
, str(ex
))
329 return self
.render_to_response({
331 'podcasts': podcasts
,
335 class PodcastListView(ListView
):
336 """ A generic podcast list view """
339 context_object_name
= 'podcasts'
341 @method_decorator(vary_on_cookie
)
342 @method_decorator(cache_control(private
=True))
343 def dispatch(self
, *args
, **kwargs
):
344 """ Only used for applying decorators """
345 return super(PodcastListView
, self
).dispatch(*args
, **kwargs
)
351 There seems to be no other pre-defined method for getting the current
353 https://docs.djangoproject.com/en/dev/ref/class-based-views/mixins-multiple-object/#multipleobjectmixin
355 return self
.get_context_data()['page_obj']
357 def page_list(self
, page_size
=15):
358 """ Return a list of pages, eg [1, 2, 3, '...', 6, 7, 8] """
360 return get_page_list(1,
361 page
.paginator
.num_pages
,
363 page
.paginator
.per_page
,
366 def max_subscribers(self
):
367 """ Maximum subscribers of the podcasts on this page """
369 podcasts
= page
.object_list
370 return max([p
.subscriber_count() for p
in podcasts
] + [0])
373 class FlattrPodcastList(PodcastListView
):
374 """ Lists podcasts that have Flattr payment URLs """
376 template_name
= 'flattr-podcasts.html'
378 def get_queryset(self
):
379 return Podcast
.objects
.all().flattr()
381 def get_context_data(self
, num
=100):
382 context
= super(FlattrPodcastList
, self
).get_context_data()
383 context
['flattr_auth'] = (self
.request
.user
.is_authenticated()
384 # and bool(self.request.user.get_wksetting(FLATTR_TOKEN))
389 class LicensePodcastList(PodcastListView
):
390 """ Lists podcasts with a given license """
392 template_name
= 'directory/license-podcasts.html'
394 def get_queryset(self
):
395 return Podcast
.objects
.all().license(self
.license_url
)
398 def license_url(self
):
399 return self
.kwargs
['license_url']
402 class LicenseList(TemplateView
):
403 """ Lists all podcast licenses """
405 template_name
= 'directory/licenses.html'
408 """ Returns all podcast licenses """
409 query
= Podcast
.objects
.exclude(license__isnull
=True)
410 values
= query
.values("license").annotate(Count("id")).order_by()
412 counter
= Counter({l
['license']: l
['id__count'] for l
in values
})
413 return counter
.most_common()