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
.core
.proxy
import proxy_object
27 from mygpo
.podcasts
.models
import Podcast
, Episode
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
.categories
.models
import Category
34 from mygpo
.podcastlists
.models
import PodcastList
35 from mygpo
.data
.feeddownloader
import PodcastUpdater
, NoEpisodesException
36 from mygpo
.data
.tasks
import update_podcasts
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()\
80 .prefetch_related('slugs')\
81 .toplist(self
.language())[:num
]
82 context
['entries'] = entries
84 context
['max_subscribers'] = max([0] + [p
.subscriber_count() for p
in entries
])
89 class EpisodeToplistView(ToplistView
):
90 """ Most listened-to episodes """
92 template_name
= 'episode_toplist.html'
94 def get_context_data(self
, num
=100):
95 context
= super(EpisodeToplistView
, self
).get_context_data()
97 entries
= Episode
.objects
.all()\
98 .select_related('podcast')\
99 .prefetch_related('slugs', 'podcast__slugs')\
100 .toplist(self
.language())[:num
]
101 context
['entries'] = entries
103 # Determine maximum listener amount (or 0 if no entries exist)
104 context
['max_listeners'] = max([0]+[e
.listeners
for e
in entries
])
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(),
138 def get_random_list(self
, podcasts_per_list
=5):
139 random_list
= PodcastList
.objects
.order_by('?').first()
143 @cache_control(private
=True)
145 def category(request
, category
, page_size
=20):
147 category
= Category
.objects
.get(tags__tag
=category
)
148 except Category
.DoesNotExist
:
149 return HttpResponseNotFound()
151 podcasts
= category
.entries
.all()\
152 .prefetch_related('podcast', 'podcast__slugs')
154 paginator
= Paginator(podcasts
, page_size
)
156 page
= request
.GET
.get('page')
158 podcasts
= paginator
.page(page
)
159 except PageNotAnInteger
:
160 # If page is not an integer, deliver first page.
161 podcasts
= paginator
.page(1)
163 # If page is out of range (e.g. 9999), deliver last page of results.
164 podcasts
= paginator
.page(paginator
.num_pages
)
166 page_list
= get_page_list(1, paginator
.num_pages
, podcasts
.number
, 15)
168 return render(request
, 'category.html', {
170 'category': category
.title
,
171 'page_list': page_list
,
178 @cache_control(private
=True)
180 def search(request
, template
='search.html', args
={}):
182 if 'q' in request
.GET
:
183 q
= request
.GET
.get('q', '').encode('utf-8')
186 page
= int(request
.GET
.get('page', 1))
190 start
= RESULTS_PER_PAGE
*(page
-1)
191 results
= search_podcasts(q
)
193 num_pages
= int(ceil(total
/ RESULTS_PER_PAGE
))
194 results
= results
[start
:start
+RESULTS_PER_PAGE
]
196 page_list
= get_page_list(1, num_pages
, page
, 15)
203 max_subscribers
= max([p
.subscribers
for p
in results
] + [0])
205 current_site
= RequestSite(request
)
207 return render(request
, template
, dict(
210 page_list
= page_list
,
211 max_subscribers
= max_subscribers
,
212 domain
= current_site
.domain
,
218 @cache_control(private
=True)
220 def podcast_lists(request
, page_size
=20):
222 lists
= PodcastList
.objects
.all()\
223 .annotate(num_votes
=Count('votes'))\
224 .order_by('-num_votes')
226 paginator
= Paginator(lists
, page_size
)
228 page
= request
.GET
.get('page')
230 lists
= paginator
.page(page
)
231 except PageNotAnInteger
:
232 lists
= paginator
.page(1)
235 lists
= paginator
.page(paginator
.num_pages
)
236 page
= paginator
.num_pages
238 num_pages
= int(ceil(PodcastList
.objects
.count() / float(page_size
)))
239 page_list
= get_page_list(1, num_pages
, page
, 15)
241 return render(request
, 'podcast_lists.html', {
243 'page_list': page_list
,
248 class MissingPodcast(View
):
249 """ Check if a podcast is missing """
251 @method_decorator(login_required
)
252 def get(self
, request
):
254 site
= RequestSite(request
)
256 # check if we're doing a query
257 url
= request
.GET
.get('q', None)
265 podcast
= Podcast
.objects
.get(urls__url
=url
)
268 except Podcast
.DoesNotExist
:
269 # check if we could add a podcast for the given URL
271 updater
= PodcastUpdater()
274 can_add
= updater
.verify_podcast_url(url
)
276 except (ParserException
, FetchFeedException
,
277 NoEpisodesException
) as ex
:
279 messages
.error(request
, unicode(ex
))
281 return render(request
, 'missing.html', {
289 class AddPodcast(View
):
290 """ Add a missing podcast"""
292 @method_decorator(login_required
)
293 @method_decorator(cache_control(private
=True))
294 @method_decorator(vary_on_cookie
)
295 def post(self
, request
):
297 url
= request
.POST
.get('url', None)
302 res
= update_podcasts
.delay([url
])
304 return HttpResponseRedirect(reverse('add-podcast-status',
308 class AddPodcastStatus(TemplateView
):
309 """ Status of adding a podcast """
311 template_name
= 'directory/add-podcast-status.html'
313 def get(self
, request
, task_id
):
314 result
= update_podcasts
.AsyncResult(task_id
)
316 if not result
.ready():
317 return self
.render_to_response({
322 podcasts
= result
.get()
323 messages
.success(request
, _('%d podcasts added' % len(podcasts
)))
325 except (ParserException
, FetchFeedException
,
326 NoEpisodesException
) as ex
:
327 messages
.error(request
, str(ex
))
330 return self
.render_to_response({
332 'podcasts': podcasts
,
336 class PodcastListView(ListView
):
337 """ A generic podcast list view """
340 context_object_name
= 'podcasts'
342 @method_decorator(vary_on_cookie
)
343 @method_decorator(cache_control(private
=True))
344 def dispatch(self
, *args
, **kwargs
):
345 """ Only used for applying decorators """
346 return super(PodcastListView
, self
).dispatch(*args
, **kwargs
)
352 There seems to be no other pre-defined method for getting the current
354 https://docs.djangoproject.com/en/dev/ref/class-based-views/mixins-multiple-object/#multipleobjectmixin
356 return self
.get_context_data()['page_obj']
358 def page_list(self
, page_size
=15):
359 """ Return a list of pages, eg [1, 2, 3, '...', 6, 7, 8] """
361 return get_page_list(1,
362 page
.paginator
.num_pages
,
364 page
.paginator
.per_page
,
367 def max_subscribers(self
):
368 """ Maximum subscribers of the podcasts on this page """
370 podcasts
= page
.object_list
371 return max([p
.subscriber_count() for p
in podcasts
] + [0])
374 class FlattrPodcastList(PodcastListView
):
375 """ Lists podcasts that have Flattr payment URLs """
377 template_name
= 'flattr-podcasts.html'
379 def get_queryset(self
):
380 return Podcast
.objects
.all().flattr()
382 def get_context_data(self
, num
=100):
383 context
= super(FlattrPodcastList
, self
).get_context_data()
384 context
['flattr_auth'] = (self
.request
.user
.is_authenticated()
385 # and bool(self.request.user.get_wksetting(FLATTR_TOKEN))
390 class LicensePodcastList(PodcastListView
):
391 """ Lists podcasts with a given license """
393 template_name
= 'directory/license-podcasts.html'
395 def get_queryset(self
):
396 return Podcast
.objects
.all().license(self
.license_url
)
399 def license_url(self
):
400 return self
.kwargs
['license_url']
403 class LicenseList(TemplateView
):
404 """ Lists all podcast licenses """
406 template_name
= 'directory/licenses.html'
409 """ Returns all podcast licenses """
410 query
= Podcast
.objects
.exclude(license__isnull
=True)
411 values
= query
.values("license").annotate(Count("id")).order_by()
413 counter
= Counter({l
['license']: l
['id__count'] for l
in values
})
414 return counter
.most_common()