assign episode slugs in separate management cmd
[mygpo.git] / mygpo / publisher / utils.py
blobec428b00f2a498705c98f16852e575860436ef0a
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 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 Podcast as OldPodcast
27 from mygpo.api.constants import DEVICE_TYPES
28 from mygpo import migrate
31 def listener_data(podcasts, start_date=datetime(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 = (podcast.get_episodes(since=start_date) for podcast in podcasts)
45 episodes = flatten(episodes)
46 episodes = dict((e.released.date(), e) for e in episodes)
48 listeners = [ list(p.listener_count_timespan(start=start_date))
49 for p in podcasts ]
50 listeners = filter(None, listeners)
52 # we start either at the first episode-release or the first listen-event
53 events = []
55 if episodes.keys():
56 events.append(min(episodes.keys()))
58 if listeners:
59 events.append(min([l[0][0] for l in listeners]))
61 if not events:
62 return
64 start = min(events)
66 for d in daterange(start, leap=leap):
68 listener_sum = 0
69 for l in listeners:
70 if not l:
71 continue
73 day, count = l[0]
74 if day == d:
75 listener_sum += count
76 l.pop(0)
78 episode = episodes[d] if d in episodes else None
80 yield dict(date=d, listeners=listener_sum, episode=episode)
84 def episode_listener_data(episode, start_date=datetime(2010, 1, 1), leap=timedelta(days=1)):
85 """ Returns data for the episode listener timeseries
87 An iterator with data for each day (starting from the first listen-event)
88 is returned, where each day is represented by a dictionary
90 * date: the day
91 * listeners: the number of listeners on that day
92 * episode: the episode, if it was released on that day, otherwise None
93 """
95 listeners = list(episode.listener_count_timespan(start=start_date))
97 if not listeners:
98 return
100 # we always start at the first listen-event
101 start = listeners[0][0]
102 start = datetime.combine(start, time())
104 for d in daterange(start, leap=leap):
105 next = d + leap
107 if listeners and listeners[0] and listeners[0][0] == d.date():
108 day, l = listeners.pop()
109 else:
110 l = 0
112 released = episode.released and episode.released >= d and episode.released <= next
113 released_episode = episode if released else None
115 yield dict(date=d, listeners=l, episode=released_episode)
118 def subscriber_data(podcasts):
119 coll_data = collections.defaultdict(int)
121 for podcast in podcasts:
122 create_entry = lambda r: (r.timestamp.strftime('%y-%m'), r.subscriber_count)
123 data = dict(map(create_entry, podcast.get_all_subscriber_data()))
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 if user.is_staff:
136 return True
138 if isinstance(podcast, OldPodcast):
139 podcast = migrate.get_or_migrate_podcast(podcast)
141 u = migrate.get_or_migrate_user(user)
142 return (podcast.get_id() in u.published_objects)
145 def colour_repr(val, max_val, colours):
147 returns a color representing the given value within a color gradient.
149 The color gradient is given by a list of (r, g, b) tupels. The value
150 is first located within two colors (of the list) and then approximated
151 between these two colors, based on its position within this segment.
153 if len(colours) == 1:
154 return colours[0]
156 # calculate position in the gradient; defines the segment
157 pos = float(val) / max_val
158 colour_nr1 = min(len(colours)-1, int(pos * (len(colours)-1)))
159 colour_nr2 = min(len(colours)-1, colour_nr1+1)
160 colour1 = colours[ colour_nr1 ]
161 colour2 = colours[ colour_nr2 ]
163 r1, g1, b1 = colour1
164 r2, g2, b2 = colour2
166 # determine bounds of segment
167 lower_bound = float(max_val) / (len(colours)-1) * colour_nr1
168 upper_bound = min(max_val, lower_bound + float(max_val) / (len(colours)-1))
170 # position within the segment
171 percent = (val - lower_bound) / upper_bound
173 r_step = r2 - r1
174 g_step = g2 - g1
175 b_step = b2 - b1
177 return (r1 + r_step * percent, g1 + g_step * percent, b1 + b_step * percent)