migrate episode heatmap to CouchDB
[mygpo.git] / mygpo / publisher / utils.py
blobb511f11e2aeea98656a5fb859eb90fbc858ba994
2 # This file is part of my.gpodder.org.
4 # my.gpodder.org is free software: you can redistribute it and/or modify it
5 # under the terms of the GNU Affero General Public License as published by
6 # the Free Software Foundation, either version 3 of the License, or (at your
7 # option) any later version.
9 # my.gpodder.org is distributed in the hope that it will be useful, but
10 # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
11 # or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
12 # License for more details.
14 # You should have received a copy of the GNU Affero General Public License
15 # along with my.gpodder.org. If not, see <http://www.gnu.org/licenses/>.
18 import collections
19 from datetime import timedelta, date
21 from django.db.models import Avg, Count
22 from django.contrib.auth.models import User
24 from mygpo.utils import daterange, flatten
25 from mygpo.core.models import Podcast
26 from mygpo.api.models import Episode, EpisodeAction
27 from mygpo.api.constants import DEVICE_TYPES
28 from mygpo import migrate
31 def listener_data(podcasts, start_date=date(2010, 1, 1), leap=timedelta(days=1)):
32 """ Returns data for the podcast listener timeseries
34 An iterator with data for each day (starting from either the first released
35 episode or the earliest listen-event) is returned, where each day
36 is reresented by a dictionary
38 * date: the day
39 * listeners: the number of listeners on that day
40 * episode: (one of) the episode(s) released on that day
41 """
43 # pre-calculate episode list, make it index-able by release-date
44 episodes = flatten([podcast.get_episodes() for podcast in podcasts])
45 episodes = filter(lambda e: e.released, episodes)
46 episodes = dict([(e.released.date(), e) for e in episodes])
48 listeners = [ list(p.listener_count_timespan()) for p in podcasts ]
50 # we start either at the first episode-release or the first listen-event
51 start = min( min(episodes.keys()), min([l[0][0] for l in listeners]))
53 for d in daterange(start, leap=leap):
55 listener_sum = 0
56 for l in listeners:
57 if not l:
58 continue
60 day, count = l[0]
61 if day == d:
62 listener_sum += count
63 l.pop(0)
65 episode = episodes[d] if d in episodes else None
67 yield dict(date=d, listeners=listener_sum, episode=episode)
71 def episode_listener_data(episode, start_date=date(2010, 1, 1), leap=timedelta(days=1)):
72 """ Returns data for the episode listener timeseries
74 An iterator with data for each day (starting from the first listen-event)
75 is returned, where each day is represented by a dictionary
77 * date: the day
78 * listeners: the number of listeners on that day
79 * episode: the episode, if it was released on that day, otherwise None
80 """
82 listeners = list(episode.listener_count_timespan())
84 # we always start at the first listen-event
85 start = listeners[0][0]
87 for d in daterange(start, leap=leap):
88 next = d + leap
90 if listeners and listeners[0] and listeners[0][0] == d:
91 day, l = listeners.pop()
92 else:
93 l = 0
95 released = episode.released and episode.released >= d and episode.released <= next
96 released_episode = episode if released else None
98 yield dict(date=d, listeners=l, episode=released_episode)
101 def subscriber_data(podcasts):
102 coll_data = collections.defaultdict(int)
104 for podcast in podcasts:
105 create_entry = lambda r: (r.timestamp.strftime('%y-%m'), r.subscriber_count)
106 data = dict(map(create_entry, podcast.subscribers))
108 for k in data:
109 coll_data[k] += data[k]
111 # create a list of {'x': label, 'y': value}
112 coll_data = sorted([dict(x=a, y=b) for (a, b) in coll_data.items()], key=lambda x: x['x'])
114 return coll_data
117 def check_publisher_permission(user, podcast):
118 if user.is_staff:
119 return True
121 p = migrate.get_or_migrate_podcast(podcast)
122 u = migrate.get_or_migrate_user(user)
123 if p.get_id() in u.published_objects:
124 return True
126 return False
129 def colour_repr(val, max_val, colours):
131 returns a color representing the given value within a color gradient.
133 The color gradient is given by a list of (r, g, b) tupels. The value
134 is first located within two colors (of the list) and then approximated
135 between these two colors, based on its position within this segment.
137 if len(colours) == 1:
138 return colours[0]
140 # calculate position in the gradient; defines the segment
141 pos = float(val) / max_val
142 colour_nr1 = min(len(colours)-1, int(pos * (len(colours)-1)))
143 colour_nr2 = min(len(colours)-1, colour_nr1+1)
144 colour1 = colours[ colour_nr1 ]
145 colour2 = colours[ colour_nr2 ]
147 r1, g1, b1 = colour1
148 r2, g2, b2 = colour2
150 # determine bounds of segment
151 lower_bound = float(max_val) / (len(colours)-1) * colour_nr1
152 upper_bound = min(max_val, lower_bound + float(max_val) / (len(colours)-1))
154 # position within the segment
155 percent = (val - lower_bound) / upper_bound
157 r_step = r2 - r1
158 g_step = g2 - g1
159 b_step = b2 - b1
161 return (r1 + r_step * percent, g1 + g_step * percent, b1 + b_step * percent)