5 from collections
import Counter
7 from django
.http
import HttpResponseNotFound
, Http404
, HttpResponseRedirect
8 from django
.urls
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
.requests
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
import View
18 from django
.views
.generic
import TemplateView
19 from django
.contrib
.auth
.decorators
import login_required
20 from django
.contrib
import messages
21 from django
.utils
.translation
import ugettext
as _
22 from django
.contrib
.auth
import get_user_model
24 from mygpo
.podcasts
.models
import Podcast
, Episode
25 from mygpo
.directory
.search
import search_podcasts
26 from mygpo
.web
.utils
import process_lang_params
, get_language_names
, \
27 get_page_list
, get_podcast_link_target
, sanitize_language_codes
28 from mygpo
.directory
.tags
import Topics
29 from mygpo
.users
.settings
import FLATTR_TOKEN
30 from mygpo
.categories
.models
import Category
31 from mygpo
.podcastlists
.models
import PodcastList
32 from mygpo
.data
.feeddownloader
import (verify_podcast_url
, NoEpisodesException
,
33 UpdatePodcastException
)
34 from mygpo
.data
.tasks
import update_podcasts
37 class ToplistView(TemplateView
):
38 """ Generic Top List view """
40 @method_decorator(vary_on_cookie
)
41 @method_decorator(cache_control(private
=True))
42 def dispatch(self
, *args
, **kwargs
):
43 """ Only used for applying decorators """
44 return super(ToplistView
, self
).dispatch(*args
, **kwargs
)
46 def all_languages(self
):
47 """ Returns all 2-letter language codes that are used by podcasts.
49 It filters obviously invalid strings, but does not check if any
50 of these codes is contained in ISO 639. """
52 query
= Podcast
.objects
.exclude(language__isnull
=True)
53 query
= query
.distinct('language').values('language')
55 langs
= [o
['language'] for o
in query
]
56 langs
= sorted(sanitize_language_codes(langs
))
58 return get_language_names(langs
)
61 """ Currently selected language """
62 return process_lang_params(self
.request
)
65 """ Current site for constructing absolute links """
66 return RequestSite(self
.request
)
69 class PodcastToplistView(ToplistView
):
70 """ Most subscribed podcasts """
72 template_name
= 'toplist.html'
74 def get_context_data(self
, num
=100):
75 context
= super(PodcastToplistView
, self
).get_context_data()
77 entries
= Podcast
.objects
.all()\
78 .prefetch_related('slugs')\
79 .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()\
96 .select_related('podcast')\
97 .prefetch_related('slugs', 'podcast__slugs')\
98 .toplist(self
.language())[:num
]
99 context
['entries'] = entries
101 # Determine maximum listener amount (or 0 if no entries exist)
102 listeners
= [e
.listeners
for e
in entries
if e
.listeners
is not None]
103 max_listeners
= max(listeners
, default
=0)
104 context
['max_listeners'] = max_listeners
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(),
135 'podcast_ad': Podcast
.objects
.get_advertised_podcast(),
139 def get_random_list(self
, podcasts_per_list
=5):
140 random_list
= PodcastList
.objects
.order_by('?').first()
144 @cache_control(private
=True)
146 def category(request
, category
, page_size
=20):
148 category
= Category
.objects
.get(tags__tag
=category
)
149 except Category
.DoesNotExist
:
150 return HttpResponseNotFound()
152 podcasts
= category
.entries
.all()\
153 .prefetch_related('podcast', 'podcast__slugs')
155 paginator
= Paginator(podcasts
, page_size
)
157 page
= request
.GET
.get('page')
159 podcasts
= paginator
.page(page
)
160 except PageNotAnInteger
:
161 # If page is not an integer, deliver first page.
162 podcasts
= paginator
.page(1)
164 # If page is out of range (e.g. 9999), deliver last page of results.
165 podcasts
= paginator
.page(paginator
.num_pages
)
167 page_list
= get_page_list(1, paginator
.num_pages
, podcasts
.number
, 15)
169 return render(request
, 'category.html', {
171 'category': category
.title
,
172 'page_list': page_list
,
179 @cache_control(private
=True)
181 def search(request
, template
='search.html', args
={}):
183 if 'q' in request
.GET
:
184 q
= request
.GET
.get('q', '')
187 page
= int(request
.GET
.get('page', 1))
191 start
= RESULTS_PER_PAGE
*(page
-1)
192 results
= search_podcasts(q
)
194 num_pages
= int(ceil(total
/ RESULTS_PER_PAGE
))
195 results
= results
[start
:start
+RESULTS_PER_PAGE
]
197 page_list
= get_page_list(1, num_pages
, page
, 15)
204 max_subscribers
= max([p
.subscribers
for p
in results
] + [0])
206 current_site
= RequestSite(request
)
208 return render(request
, template
, dict(
211 page_list
= page_list
,
212 max_subscribers
= max_subscribers
,
213 domain
= current_site
.domain
,
219 @cache_control(private
=True)
221 def podcast_lists(request
, page_size
=20):
223 lists
= PodcastList
.objects
.all()\
224 .annotate(num_votes
=Count('votes'))\
225 .order_by('-num_votes')
227 paginator
= Paginator(lists
, page_size
)
229 page
= request
.GET
.get('page')
231 lists
= paginator
.page(page
)
232 except PageNotAnInteger
:
233 lists
= paginator
.page(1)
236 lists
= paginator
.page(paginator
.num_pages
)
237 page
= paginator
.num_pages
239 num_pages
= int(ceil(PodcastList
.objects
.count() / float(page_size
)))
240 page_list
= get_page_list(1, num_pages
, int(page
), 15)
242 return render(request
, 'podcast_lists.html', {
244 'page_list': page_list
,
249 class MissingPodcast(View
):
250 """ Check if a podcast is missing """
252 @method_decorator(login_required
)
253 def get(self
, request
):
255 site
= RequestSite(request
)
257 # check if we're doing a query
258 url
= request
.GET
.get('q', None)
266 podcast
= Podcast
.objects
.get(urls__url
=url
)
269 except Podcast
.DoesNotExist
:
270 # check if we could add a podcast for the given URL
273 can_add
= verify_podcast_url(url
)
275 except (UpdatePodcastException
, NoEpisodesException
) as ex
:
277 messages
.error(request
, str(ex
))
279 return render(request
, 'missing.html', {
287 class AddPodcast(View
):
288 """ Add a missing podcast"""
290 @method_decorator(login_required
)
291 @method_decorator(cache_control(private
=True))
292 @method_decorator(vary_on_cookie
)
293 def post(self
, request
):
295 url
= request
.POST
.get('url', None)
300 res
= update_podcasts
.delay([url
])
302 return HttpResponseRedirect(reverse('add-podcast-status',
306 class AddPodcastStatus(TemplateView
):
307 """ Status of adding a podcast """
309 template_name
= 'directory/add-podcast-status.html'
311 def get(self
, request
, task_id
):
312 result
= update_podcasts
.AsyncResult(task_id
)
314 if not result
.ready():
315 return self
.render_to_response({
320 podcasts
= result
.get()
321 messages
.success(request
, _('%d podcasts added' % len(podcasts
)))
323 except (UpdatePodcastException
, NoEpisodesException
) as ex
:
324 messages
.error(request
, str(ex
))
327 return self
.render_to_response({
329 'podcasts': podcasts
,
333 class PodcastListView(ListView
):
334 """ A generic podcast list view """
337 context_object_name
= 'podcasts'
339 @method_decorator(vary_on_cookie
)
340 @method_decorator(cache_control(private
=True))
341 def dispatch(self
, *args
, **kwargs
):
342 """ Only used for applying decorators """
343 return super(PodcastListView
, self
).dispatch(*args
, **kwargs
)
349 There seems to be no other pre-defined method for getting the current
351 https://docs.djangoproject.com/en/dev/ref/class-based-views/mixins-multiple-object/#multipleobjectmixin
353 return self
.get_context_data()['page_obj']
355 def page_list(self
, page_size
=15):
356 """ Return a list of pages, eg [1, 2, 3, '...', 6, 7, 8] """
358 return get_page_list(1,
359 page
.paginator
.num_pages
,
361 page
.paginator
.per_page
,
364 def max_subscribers(self
):
365 """ Maximum subscribers of the podcasts on this page """
367 podcasts
= page
.object_list
368 return max([p
.subscriber_count() for p
in podcasts
] + [0])
371 class FlattrPodcastList(PodcastListView
):
372 """ Lists podcasts that have Flattr payment URLs """
374 template_name
= 'flattr-podcasts.html'
376 def get_queryset(self
):
377 return Podcast
.objects
.all().flattr()
379 def get_context_data(self
, num
=100):
380 context
= super(FlattrPodcastList
, self
).get_context_data()
381 context
['flattr_auth'] = (self
.request
.user
.is_authenticated
382 # and bool(self.request.user.get_wksetting(FLATTR_TOKEN))
387 class LicensePodcastList(PodcastListView
):
388 """ Lists podcasts with a given license """
390 template_name
= 'directory/license-podcasts.html'
392 def get_queryset(self
):
393 return Podcast
.objects
.all().license(self
.license_url
)
396 def license_url(self
):
397 return self
.kwargs
['license_url']
400 class LicenseList(TemplateView
):
401 """ Lists all podcast licenses """
403 template_name
= 'directory/licenses.html'
406 """ Returns all podcast licenses """
407 query
= Podcast
.objects
.exclude(license__isnull
=True)
408 values
= query
.values("license").annotate(Count("id")).order_by()
410 counter
= Counter({l
['license']: l
['id__count'] for l
in values
})
411 return counter
.most_common()