let podcasts, episodes link to their publisher pages
[mygpo.git] / mygpo / publisher / utils.py
blob374d85443c1d5b5f04fd69566098dde0b63be9d9
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, datetime, time
21 from mygpo.utils import daterange, flatten
22 from mygpo.db.couchdb.podcast import subscriberdata_for_podcast
23 from mygpo.db.couchdb.episode import episodes_for_podcast
24 from mygpo.db.couchdb.episode_state import podcast_listener_count_timespan, \
25 episode_listener_count_timespan
28 def listener_data(podcasts, start_date=datetime(2010, 1, 1), leap=timedelta(days=1)):
29 """ Returns data for the podcast listener timeseries
31 An iterator with data for each day (starting from either the first released
32 episode or the earliest listen-event) is returned, where each day
33 is reresented by a dictionary
35 * date: the day
36 * listeners: the number of listeners on that day
37 * episode: (one of) the episode(s) released on that day
38 """
40 # pre-calculate episode list, make it index-able by release-date
41 episodes = (episodes_for_podcast(podcast, since=start_date) for podcast in podcasts)
42 episodes = flatten(episodes)
43 episodes = dict((e.released.date(), e) for e in episodes)
45 listeners = [ podcast_listener_count_timespan(p, start=start_date)
46 for p in podcasts ]
47 listeners = filter(None, listeners)
49 # we start either at the first episode-release or the first listen-event
50 events = []
52 if episodes.keys():
53 events.append(min(episodes.keys()))
55 if listeners:
56 events.append(min([l[0][0] for l in listeners]))
58 if not events:
59 return
61 start = min(events)
63 for d in daterange(start, leap=leap):
65 listener_sum = 0
66 for l in listeners:
67 if not l:
68 continue
70 day, count = l[0]
71 if day == d:
72 listener_sum += count
73 l.pop(0)
75 episode = episodes[d] if d in episodes else None
77 yield dict(date=d, listeners=listener_sum, episode=episode)
81 def episode_listener_data(episode, start_date=datetime(2010, 1, 1), leap=timedelta(days=1)):
82 """ Returns data for the episode listener timeseries
84 An iterator with data for each day (starting from the first listen-event)
85 is returned, where each day is represented by a dictionary
87 * date: the day
88 * listeners: the number of listeners on that day
89 * episode: the episode, if it was released on that day, otherwise None
90 """
92 listeners = episode_listener_count_timespan(episode, start=start_date)
94 if not listeners:
95 return
97 # we always start at the first listen-event
98 start = listeners[0][0]
99 start = datetime.combine(start, time())
101 for d in daterange(start, leap=leap):
102 next = d + leap
104 if listeners and listeners[0] and listeners[0][0] == d.date():
105 day, l = listeners.pop(0)
106 else:
107 l = 0
109 released = episode.released and episode.released >= d and episode.released <= next
110 released_episode = episode if released else None
112 yield dict(date=d, listeners=l, episode=released_episode)
115 def subscriber_data(podcasts):
116 coll_data = collections.defaultdict(int)
118 for podcast in podcasts:
119 create_entry = lambda r: (r.timestamp.strftime('%y-%m'), r.subscriber_count)
121 subdata = podcast.subscribers + subscriberdata_for_podcast(podcast.get_id()).subscribers
123 data = dict(map(create_entry, subdata))
125 for k in data:
126 coll_data[k] += data[k]
128 # create a list of {'x': label, 'y': value}
129 coll_data = sorted([dict(x=a, y=b) for (a, b) in coll_data.items()], key=lambda x: x['x'])
131 return coll_data
134 def check_publisher_permission(user, podcast):
135 """ Checks if the user has publisher permissions for the given podcast """
137 if not user.is_authenticated():
138 return False
140 if user.is_staff:
141 return True
143 return (podcast.get_id() in user.published_objects)
146 def colour_repr(val, max_val, colours):
148 returns a color representing the given value within a color gradient.
150 The color gradient is given by a list of (r, g, b) tupels. The value
151 is first located within two colors (of the list) and then approximated
152 between these two colors, based on its position within this segment.
154 if len(colours) == 1:
155 return colours[0]
157 if max_val == 0:
158 return colours[0]
160 # calculate position in the gradient; defines the segment
161 pos = float(val) / max_val
162 colour_nr1 = min(len(colours)-1, int(pos * (len(colours)-1)))
163 colour_nr2 = min(len(colours)-1, colour_nr1+1)
164 colour1 = colours[ colour_nr1 ]
165 colour2 = colours[ colour_nr2 ]
167 r1, g1, b1 = colour1
168 r2, g2, b2 = colour2
170 # determine bounds of segment
171 lower_bound = float(max_val) / (len(colours)-1) * colour_nr1
172 upper_bound = min(max_val, lower_bound + float(max_val) / (len(colours)-1))
174 # position within the segment
175 percent = (val - lower_bound) / upper_bound
177 r_step = r2 - r1
178 g_step = g2 - g1
179 b_step = b2 - b1
181 return (r1 + r_step * percent, g1 + g_step * percent, b1 + b_step * percent)