don't compare episode actions by their user_oldid
[mygpo.git] / mygpo / directory / toplist.py
blob65437e469fc3a82a93d82f3bdd5f657d74b9212c
1 from itertools import product
3 from datetime import date, timedelta
5 from django.core.cache import cache
7 from mygpo.core.models import Episode, Podcast, PodcastGroup
8 from mygpo.data.mimetype import get_type, CONTENT_TYPES
9 from mygpo.utils import daterange
12 CACHE_SECONDS = 60*60
14 class Toplist(object):
15 """ Base class for Episode and Podcast toplists """
17 def __init__(self, cls, view, languages=[], types=[], view_args={}):
18 self.cls = cls
19 self.languages = languages
20 self.view = view
21 self.view_args = view_args
23 if len(types) == len(CONTENT_TYPES):
24 self.types = []
25 else:
26 self.types = types
29 def _get_query_keys(self):
30 """ Returns an iterator of query keys that are passed to the view """
32 if not self.languages and not self.types:
33 yield ["none"]
35 elif self.languages and not self.types:
36 for lang in self.languages:
37 yield ["language", lang]
39 elif not self.languages and self.types:
40 for type in self.types:
41 yield ["type", type]
43 else:
44 for typ, lang in product(self.types, self.languages):
45 yield ["type-language", typ, lang]
48 def _query(self, skip, limit):
49 """ Queries the database and returns the sorted results """
51 results = []
52 for key in self._get_query_keys():
53 r = self._cache_or_query(skip, limit, key)
54 results.extend(r)
56 results = list(set(results))
57 results = self._sort(results)
58 return results[skip:skip+limit]
61 def _cache_or_query(self, skip, limit, key):
62 cache_str = '{cls}-{skip}-{limit}-{key}'.format(
63 skip=skip,
64 limit=limit,
65 cls=self.__class__.__name__,
66 key='-'.join(key)
69 res = cache.get(cache_str)
70 if not res:
71 r = self.cls.view(self.view,
72 startkey = key + [{}],
73 endkey = key + [None],
74 include_docs = True,
75 descending = True,
76 limit = limit + skip,
77 stale = 'update_after',
78 **self.view_args
80 res = list(r)
81 cache.set(cache_str, res, CACHE_SECONDS)
83 return res
86 def _sort(self, results):
87 return results
90 def __getitem__(self, key):
91 if isinstance(key, slice):
92 start = key.start or 0
93 length = key.stop - start
94 else:
95 start = key
96 length = 1
98 return self._query(start, length)
102 class EpisodeToplist(Toplist):
103 """ Retrieves the episode toplist for a certain date """
105 def __init__(self, languages=[], types=[], startdate=None):
106 super(EpisodeToplist, self).__init__(Episode,
107 'toplist/episodes', languages, types)
108 self.date = startdate or date.today()
111 def _sort(self, results):
112 results.sort(key=lambda episode: episode.listeners, reverse=True)
113 return results
116 def _get_query_keys(self):
117 """ Returns the query keys based on instance variables """
119 date_str = self.date.strftime('%Y-%m-%d')
121 for criteria in super(EpisodeToplist, self)._get_query_keys():
122 yield [date_str] + criteria
126 class PodcastToplist(Toplist):
127 """ Podcast toplist based on number of subscribers """
129 # FIXME: podcast and episode toplist are separated now, so we could
130 # get rid of the type field
131 TYPE = 'Podcast'
133 def __init__(self, languages=[], types=[]):
134 super(PodcastToplist, self).__init__(Podcast, 'toplist/podcasts',
135 languages, types,
136 view_args=dict(classes=[Podcast, PodcastGroup]))
139 def _get_query_keys(self):
140 for criteria in super(PodcastToplist, self)._get_query_keys():
141 yield [self.TYPE] + criteria
144 def _sort(self, results):
145 # sort by subscriber_count and id to ensure same order when subscriber_count is equal
146 cur = sorted(results, key=lambda p: (p.subscriber_count(), p.get_id()), reverse=True)
147 prev = sorted(results, key=lambda p: (p.prev_subscriber_count(), p.get_id()), reverse=True)
149 res = dict( (p, n) for n, p in enumerate(cur))
151 for old, p in enumerate(prev):
152 new = res.get(p, 0)
153 res[p] = (new, old)
155 return [(old+1, p) for p, (new, old) in sorted(res.items(), key=lambda i: i[1][0])]