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 mygpo
.podcasts
.models
import Podcast
, Episode
24 from mygpo
.directory
.search
import search_podcasts
25 from mygpo
.web
.utils
import process_lang_params
, get_language_names
, \
26 get_page_list
, get_podcast_link_target
, sanitize_language_codes
27 from mygpo
.directory
.tags
import Topics
28 from mygpo
.users
.settings
import FLATTR_TOKEN
29 from mygpo
.categories
.models
import Category
30 from mygpo
.podcastlists
.models
import PodcastList
31 from mygpo
.data
.feeddownloader
import (verify_podcast_url
, NoEpisodesException
,
32 UpdatePodcastException
)
33 from mygpo
.data
.tasks
import update_podcasts
36 class ToplistView(TemplateView
):
37 """ Generic Top List view """
39 @method_decorator(vary_on_cookie
)
40 @method_decorator(cache_control(private
=True))
41 def dispatch(self
, *args
, **kwargs
):
42 """ Only used for applying decorators """
43 return super(ToplistView
, self
).dispatch(*args
, **kwargs
)
45 def all_languages(self
):
46 """ Returns all 2-letter language codes that are used by podcasts.
48 It filters obviously invalid strings, but does not check if any
49 of these codes is contained in ISO 639. """
51 query
= Podcast
.objects
.exclude(language__isnull
=True)
52 query
= query
.distinct('language').values('language')
54 langs
= [o
['language'] for o
in query
]
55 langs
= sorted(sanitize_language_codes(langs
))
57 return get_language_names(langs
)
60 """ Currently selected language """
61 return process_lang_params(self
.request
)
64 """ Current site for constructing absolute links """
65 return RequestSite(self
.request
)
68 class PodcastToplistView(ToplistView
):
69 """ Most subscribed podcasts """
71 template_name
= 'toplist.html'
73 def get_context_data(self
, num
=100):
74 context
= super(PodcastToplistView
, self
).get_context_data()
76 entries
= Podcast
.objects
.all()\
77 .prefetch_related('slugs')\
78 .toplist(self
.language())[:num
]
79 context
['entries'] = entries
81 context
['max_subscribers'] = max([0] + [p
.subscriber_count() for p
in entries
])
86 class EpisodeToplistView(ToplistView
):
87 """ Most listened-to episodes """
89 template_name
= 'episode_toplist.html'
91 def get_context_data(self
, num
=100):
92 context
= super(EpisodeToplistView
, self
).get_context_data()
94 entries
= Episode
.objects
.all()\
95 .select_related('podcast')\
96 .prefetch_related('slugs', 'podcast__slugs')\
97 .toplist(self
.language())[:num
]
98 context
['entries'] = entries
100 # Determine maximum listener amount (or 0 if no entries exist)
101 context
['max_listeners'] = max([0]+[e
.listeners
for e
in entries
])
106 class Carousel(View
):
107 """ A carousel demo """
109 @method_decorator(cache_control(private
=True))
110 @method_decorator(vary_on_cookie
)
111 def get(self
, request
):
113 return render(request
, 'carousel.html', {
114 # evaluated lazyly, cached by template
119 class Directory(View
):
120 """ The main directory page """
122 @method_decorator(cache_control(private
=True))
123 @method_decorator(vary_on_cookie
)
124 def get(self
, request
):
126 return render(request
, 'directory.html', {
128 # evaluated lazyly, cached by template
130 'podcastlists': self
.get_random_list(),
131 'random_podcast': Podcast
.objects
.all().random().first(),
135 def get_random_list(self
, podcasts_per_list
=5):
136 random_list
= PodcastList
.objects
.order_by('?').first()
140 @cache_control(private
=True)
142 def category(request
, category
, page_size
=20):
144 category
= Category
.objects
.get(tags__tag
=category
)
145 except Category
.DoesNotExist
:
146 return HttpResponseNotFound()
148 podcasts
= category
.entries
.all()\
149 .prefetch_related('podcast', 'podcast__slugs')
151 paginator
= Paginator(podcasts
, page_size
)
153 page
= request
.GET
.get('page')
155 podcasts
= paginator
.page(page
)
156 except PageNotAnInteger
:
157 # If page is not an integer, deliver first page.
158 podcasts
= paginator
.page(1)
160 # If page is out of range (e.g. 9999), deliver last page of results.
161 podcasts
= paginator
.page(paginator
.num_pages
)
163 page_list
= get_page_list(1, paginator
.num_pages
, podcasts
.number
, 15)
165 return render(request
, 'category.html', {
167 'category': category
.title
,
168 'page_list': page_list
,
175 @cache_control(private
=True)
177 def search(request
, template
='search.html', args
={}):
179 if 'q' in request
.GET
:
180 q
= request
.GET
.get('q', '')
183 page
= int(request
.GET
.get('page', 1))
187 start
= RESULTS_PER_PAGE
*(page
-1)
188 results
= search_podcasts(q
)
190 num_pages
= int(ceil(total
/ RESULTS_PER_PAGE
))
191 results
= results
[start
:start
+RESULTS_PER_PAGE
]
193 page_list
= get_page_list(1, num_pages
, page
, 15)
200 max_subscribers
= max([p
.subscribers
for p
in results
] + [0])
202 current_site
= RequestSite(request
)
204 return render(request
, template
, dict(
207 page_list
= page_list
,
208 max_subscribers
= max_subscribers
,
209 domain
= current_site
.domain
,
215 @cache_control(private
=True)
217 def podcast_lists(request
, page_size
=20):
219 lists
= PodcastList
.objects
.all()\
220 .annotate(num_votes
=Count('votes'))\
221 .order_by('-num_votes')
223 paginator
= Paginator(lists
, page_size
)
225 page
= request
.GET
.get('page')
227 lists
= paginator
.page(page
)
228 except PageNotAnInteger
:
229 lists
= paginator
.page(1)
232 lists
= paginator
.page(paginator
.num_pages
)
233 page
= paginator
.num_pages
235 num_pages
= int(ceil(PodcastList
.objects
.count() / float(page_size
)))
236 page_list
= get_page_list(1, num_pages
, int(page
), 15)
238 return render(request
, 'podcast_lists.html', {
240 'page_list': page_list
,
245 class MissingPodcast(View
):
246 """ Check if a podcast is missing """
248 @method_decorator(login_required
)
249 def get(self
, request
):
251 site
= RequestSite(request
)
253 # check if we're doing a query
254 url
= request
.GET
.get('q', None)
262 podcast
= Podcast
.objects
.get(urls__url
=url
)
265 except Podcast
.DoesNotExist
:
266 # check if we could add a podcast for the given URL
269 can_add
= verify_podcast_url(url
)
271 except (UpdatePodcastException
, NoEpisodesException
) as ex
:
273 messages
.error(request
, str(ex
))
275 return render(request
, 'missing.html', {
283 class AddPodcast(View
):
284 """ Add a missing podcast"""
286 @method_decorator(login_required
)
287 @method_decorator(cache_control(private
=True))
288 @method_decorator(vary_on_cookie
)
289 def post(self
, request
):
291 url
= request
.POST
.get('url', None)
296 res
= update_podcasts
.delay([url
])
298 return HttpResponseRedirect(reverse('add-podcast-status',
302 class AddPodcastStatus(TemplateView
):
303 """ Status of adding a podcast """
305 template_name
= 'directory/add-podcast-status.html'
307 def get(self
, request
, task_id
):
308 result
= update_podcasts
.AsyncResult(task_id
)
310 if not result
.ready():
311 return self
.render_to_response({
316 podcasts
= result
.get()
317 messages
.success(request
, _('%d podcasts added' % len(podcasts
)))
319 except (UpdatePodcastException
, NoEpisodesException
) as ex
:
320 messages
.error(request
, str(ex
))
323 return self
.render_to_response({
325 'podcasts': podcasts
,
329 class PodcastListView(ListView
):
330 """ A generic podcast list view """
333 context_object_name
= 'podcasts'
335 @method_decorator(vary_on_cookie
)
336 @method_decorator(cache_control(private
=True))
337 def dispatch(self
, *args
, **kwargs
):
338 """ Only used for applying decorators """
339 return super(PodcastListView
, self
).dispatch(*args
, **kwargs
)
345 There seems to be no other pre-defined method for getting the current
347 https://docs.djangoproject.com/en/dev/ref/class-based-views/mixins-multiple-object/#multipleobjectmixin
349 return self
.get_context_data()['page_obj']
351 def page_list(self
, page_size
=15):
352 """ Return a list of pages, eg [1, 2, 3, '...', 6, 7, 8] """
354 return get_page_list(1,
355 page
.paginator
.num_pages
,
357 page
.paginator
.per_page
,
360 def max_subscribers(self
):
361 """ Maximum subscribers of the podcasts on this page """
363 podcasts
= page
.object_list
364 return max([p
.subscriber_count() for p
in podcasts
] + [0])
367 class FlattrPodcastList(PodcastListView
):
368 """ Lists podcasts that have Flattr payment URLs """
370 template_name
= 'flattr-podcasts.html'
372 def get_queryset(self
):
373 return Podcast
.objects
.all().flattr()
375 def get_context_data(self
, num
=100):
376 context
= super(FlattrPodcastList
, self
).get_context_data()
377 context
['flattr_auth'] = (self
.request
.user
.is_authenticated()
378 # and bool(self.request.user.get_wksetting(FLATTR_TOKEN))
383 class LicensePodcastList(PodcastListView
):
384 """ Lists podcasts with a given license """
386 template_name
= 'directory/license-podcasts.html'
388 def get_queryset(self
):
389 return Podcast
.objects
.all().license(self
.license_url
)
392 def license_url(self
):
393 return self
.kwargs
['license_url']
396 class LicenseList(TemplateView
):
397 """ Lists all podcast licenses """
399 template_name
= 'directory/licenses.html'
402 """ Returns all podcast licenses """
403 query
= Podcast
.objects
.exclude(license__isnull
=True)
404 values
= query
.values("license").annotate(Count("id")).order_by()
406 counter
= Counter({l
['license']: l
['id__count'] for l
in values
})
407 return counter
.most_common()