af5e94b5fea9684734657350ca6bd4aa21588353
1 from hashlib
import sha1
2 from random
import random
3 from datetime
import datetime
5 from restkit
import RequestFailed
7 from django
.core
.cache
import cache
9 from mygpo
.core
.models
import Podcast
, PodcastGroup
, PodcastSubscriberData
10 from mygpo
.core
.signals
import incomplete_obj
11 from mygpo
.decorators
import repeat_on_conflict
12 from mygpo
.cache
import cache_result
13 from mygpo
.utils
import get_timestamp
14 from mygpo
.db
.couchdb
import get_main_database
15 from mygpo
.db
import QueryParameterMissing
16 from mygpo
.db
.couchdb
.utils
import multi_request_view
, is_couchdb_id
19 def podcast_slugs(base_slug
):
20 res
= Podcast
.view('podcasts/by_slug',
21 startkey
= [base_slug
, None],
22 endkey
= [base_slug
+ 'ZZZZZ', None],
25 return [r
['key'][0] for r
in res
]
28 @cache_result(timeout
=60*60)
30 return Podcast
.view('podcasts/by_id',
32 stale
= 'update_after',
36 @cache_result(timeout
=60*60)
37 def podcasts_for_tag(tag
):
38 """ Returns the podcasts with the current tag.
40 Some podcasts might be returned twice """
43 raise QueryParameterMissing('tag')
45 res
= multi_request_view(Podcast
, 'podcasts/by_tag',
47 startkey
= [tag
, None],
55 yield (r
['key'][1], r
['value'])
57 res
= multi_request_view(Podcast
, 'usertags/podcasts',
59 startkey
= [tag
, None],
67 yield (r
['key'][1], r
['value'])
70 @cache_result(timeout
=60*60)
71 def get_podcast_languages():
72 """ Returns all 2-letter language codes that are used by podcasts.
74 It filters obviously invalid strings, but does not check if any
75 of these codes is contained in ISO 639. """
77 from mygpo
.web
.utils
import sanitize_language_codes
79 res
= Podcast
.view('podcasts/by_language',
84 langs
= [r
['key'][0] for r
in res
]
85 sane_lang
= sanitize_language_codes(langs
)
90 @cache_result(timeout
=60*60)
91 def podcast_by_id(podcast_id
, current_id
=False):
94 raise QueryParameterMissing('podcast_id')
96 r
= Podcast
.view('podcasts/by_id',
98 classes
= [Podcast
, PodcastGroup
],
105 podcast_group
= r
.first()
107 podcast
= podcast_group
.get_podcast_by_id(podcast_id
, current_id
)
109 if podcast
.needs_update
:
110 incomplete_obj
.send_robust(sender
=podcast
)
116 @cache_result(timeout
=60*60)
117 def podcastgroup_by_id(group_id
):
120 raise QueryParameterMissing('group_id')
122 pg
= PodcastGroup
.get(group_id
)
125 incomplete_obj
.send_robust(sender
=pg
)
131 @cache_result(timeout
=60*60)
132 def podcast_for_slug(slug
):
135 raise QueryParameterMissing('slug')
137 r
= Podcast
.view('podcasts/by_slug',
138 startkey
= [slug
, None],
149 if doc
['doc_type'] == 'Podcast':
150 obj
= Podcast
.wrap(doc
)
153 pg
= PodcastGroup
.wrap(doc
)
154 obj
= pg
.get_podcast_by_id(pid
)
157 incomplete_obj
.send_robust(sender
=obj
)
162 @cache_result(timeout
=60*60)
163 def podcast_for_slug_id(slug_id
):
164 """ Returns the Podcast for either an CouchDB-ID for a Slug """
166 if is_couchdb_id(slug_id
):
167 return podcast_by_id(slug_id
)
169 return podcast_for_slug(slug_id
)
172 @cache_result(timeout
=60*60)
173 def podcastgroup_for_slug_id(slug_id
):
174 """ Returns the Podcast for either an CouchDB-ID for a Slug """
177 raise QueryParameterMissing('slug_id')
179 if is_couchdb_id(slug_id
):
180 return podcastgroup_by_id(slug_id
)
184 return PodcastGroup
.for_slug(slug_id
)
188 def podcasts_by_id(ids
):
191 raise QueryParameterMissing('ids')
196 r
= Podcast
.view('podcasts/by_id',
202 podcasts
= map(_wrap_podcast_group
, r
)
204 for podcast
in podcasts
:
205 if podcast
.needs_update
:
206 incomplete_obj
.send_robust(sender
=podcast
)
212 @cache_result(timeout
=60*60)
213 def podcast_for_oldid(oldid
):
216 raise QueryParameterMissing('oldid')
218 r
= Podcast
.view('podcasts/by_oldid',
220 classes
= [Podcast
, PodcastGroup
],
227 podcast_group
= r
.first()
228 podcast
= podcast_group
.get_podcast_by_oldid(oldid
)
230 if podcast
.needs_update
:
231 incomplete_obj
.send_robust(sender
=podcast
)
236 @cache_result(timeout
=60*60)
237 def podcastgroup_for_oldid(oldid
):
240 raise QueryParameterMissing('oldid')
242 r
= PodcastGroup
.view('podcasts/groups_by_oldid',
253 incomplete_obj
.send_robust(sender
=pg
)
258 def podcast_for_url(url
, create
=False):
261 raise QueryParameterMissing('url')
263 key
= 'podcast-by-url-%s' % sha1(url
.encode('utf-8')).hexdigest()
265 podcast
= cache
.get(key
)
269 r
= Podcast
.view('podcasts/by_url',
271 classes
=[Podcast
, PodcastGroup
],
276 podcast_group
= r
.first()
277 podcast
= podcast_group
.get_podcast_by_url(url
)
279 if podcast
.needs_update
:
280 incomplete_obj
.send_robust(sender
=podcast
)
282 cache
.set(key
, podcast
)
288 podcast
.created_timestamp
= get_timestamp(datetime
.utcnow())
291 incomplete_obj
.send_robust(sender
=podcast
)
299 def random_podcasts(language
='', chunk_size
=5):
300 """ Returns an iterator of random podcasts
302 optionaly a language code can be specified. If given the podcasts will
303 be restricted to this language. chunk_size determines how many podcasts
304 will be fetched at once """
308 res
= Podcast
.view('podcasts/random',
309 startkey
= [language
, rnd
],
321 # The view podcasts/random does not include incomplete podcasts,
322 # so we don't need to send any 'incomplete_obj' signals here
325 if obj
['doc_type'] == 'Podcast':
326 yield Podcast
.wrap(obj
)
328 elif obj
['doc_type'] == 'PodcastGroup':
329 yield PodcastGroup
.wrap(obj
)
333 def podcasts_by_last_update():
334 res
= Podcast
.view('podcasts/by_last_update',
336 stale
= 'update_after',
340 # TODO: this method is only used for retrieving podcasts to update;
341 # should we really send 'incomplete_obj' signals here?
343 return map(_wrap_podcast_group_key1
, res
)
349 from mygpo
.db
.couchdb
.utils
import multi_request_view
350 res
= multi_request_view(Podcast
,'podcasts/by_id',
353 stale
= 'update_after',
356 # TODO: this method is only used for maintenance purposes; should we
357 # really send 'incomplete_obj' signals here?
361 if obj
['doc_type'] == 'Podcast':
362 yield Podcast
.wrap(obj
)
365 pg
= PodcastGroup
.wrap(obj
)
366 podcast
= pg
.get_podcast_by_id(pid
)
370 def all_podcasts_groups(cls
):
371 return cls
.view('podcasts/podcasts_groups', include_docs
=True,
372 classes
=[Podcast
, PodcastGroup
]).iterator()
376 def podcasts_to_dict(ids
, use_cache
=False):
379 raise QueryParameterMissing('ids')
390 res
= cache
.get_many(ids
)
391 cache_objs
.extend(res
.values())
392 ids
= [x
for x
in ids
if x
not in res
.keys()]
394 db_objs
= podcasts_by_id(ids
)
396 for obj
in (cache_objs
+ db_objs
):
398 # get_multi returns dict {'key': _id, 'error': 'not found'}
399 # for non-existing objects
400 if isinstance(obj
, dict) and 'error' in obj
:
405 for i
in obj
.get_ids():
409 cache
.set_many(dict( (obj
.get_id(), obj
) for obj
in db_objs
))
415 def podcasts_need_update():
416 db
= get_main_database()
417 res
= db
.view('episodes/need_update',
422 # TODO: this method is only used for retrieving podcasts to update;
423 # should we really send 'incomplete_obj' signals here?
426 podcast_id
= r
['key']
427 podcast
= podcast_by_id(podcast_id
)
432 @cache_result(timeout
=60*60)
433 def get_flattr_podcasts(offset
=0, limit
=20):
434 """ returns all podcasts that contain Flattr payment URLs """
436 r
= Podcast
.view('podcasts/flattr',
439 classes
= [Podcast
, PodcastGroup
],
446 for podcast
in podcasts
:
447 if podcast
.needs_update
:
448 incomplete_obj
.send_robust(sender
=podcast
)
453 @cache_result(timeout
=60*60)
454 def get_flattr_podcast_count():
455 """ returns the number of podcasts that contain Flattr payment URLs """
456 r
= list(Podcast
.view('podcasts/flattr'))
460 def subscriberdata_for_podcast(podcast_id
):
463 raise QueryParameterMissing('podcast_id')
465 r
= PodcastSubscriberData
.view('podcasts/subscriber_data',
473 data
= PodcastSubscriberData()
474 data
.podcast
= podcast_id
479 def _wrap_podcast_group(res
):
480 if res
['doc']['doc_type'] == 'Podcast':
481 return Podcast
.wrap(res
['doc'])
483 pg
= PodcastGroup
.wrap(res
['doc'])
485 return pg
.get_podcast_by_id(id)
488 def _wrap_podcast_group_key1(res
):
490 if obj
['doc_type'] == 'Podcast':
491 return Podcast
.wrap(obj
)
495 pg
= PodcastGroup
.wrap(obj
)
496 podcast
= pg
.get_podcast_by_id(pid
)
501 def search_wrapper(result
):
503 if doc
['doc_type'] == 'Podcast':
504 p
= Podcast
.wrap(doc
)
505 elif doc
['doc_type'] == 'PodcastGroup':
506 p
= PodcastGroup
.wrap(doc
)
511 @cache_result(timeout
=60*60)
512 def search(q
, offset
=0, num_results
=20):
517 db
= get_main_database()
519 #FIXME current couchdbkit can't parse responses for multi-query searches
520 q
= q
.replace(',', '')
523 res
= db
.search('podcasts/search',
524 wrapper
= search_wrapper
,
527 stale
= 'update_after',
530 sort
='\\subscribers<int>')
534 for podcast
in podcasts
:
535 if podcast
.needs_update
:
536 incomplete_obj
.send_robust(sender
=podcast
)
538 return podcasts
, res
.total_rows
540 except RequestFailed
:
544 @repeat_on_conflict(['podcast'])
545 def update_additional_data(podcast
, twitter
):
546 podcast
.twitter
= twitter
549 # clear the whole cache until we have a better invalidation mechanism