1 from collections
import namedtuple
, defaultdict
2 from datetime
import timedelta
, datetime
, time
4 from mygpo
.podcasts
.models
import Episode
5 from mygpo
.utils
import daterange
, flatten
6 from mygpo
.history
.models
import EpisodeHistoryEntry
7 from mygpo
.history
.stats
import playcounts_timerange
8 from mygpo
.publisher
.models
import PublishedPodcast
11 ListenerData
= namedtuple('ListenerData', 'date playcount episode')
13 def listener_data(podcasts
, start_date
=datetime(2010, 1, 1),
14 leap
=timedelta(days
=1)):
15 """ Returns data for the podcast listener timeseries
17 An iterator with data for each day (starting from either the first released
18 episode or the earliest play-event) is returned, where each day is
19 reresented by a ListenerData tuple. """
20 # index episodes by releaes-date
21 episodes
= Episode
.objects
.filter(podcast__in
=podcasts
,
22 released__gt
=start_date
)
23 episodes
= {e
.released
.date(): e
for e
in episodes
}
25 history
= EpisodeHistoryEntry
.objects\
26 .filter(episode__podcast__in
=podcasts
,
27 timestamp__gte
=start_date
)\
28 # contains play-counts, indexed by date {date: play-count}
29 play_counts
= playcounts_timerange(history
)
31 # we start either at the first episode-release or the first listen-event
32 events
= list(episodes
.keys()) + list(play_counts
.keys())
35 # if we don't have any events, stop
39 for date
in daterange(start
, leap
=leap
):
40 playcount
= play_counts
.get(date
, 0)
41 episode
= episodes
.get(date
, None)
42 yield ListenerData(date
, playcount
, episode
)
45 def episode_listener_data(episode
, start_date
=datetime(2010, 1, 1),
46 leap
=timedelta(days
=1)):
47 """ Returns data for the episode listener timeseries
49 An iterator with data for each day (starting from the first event
50 is returned, where each day is represented by a ListenerData tuple """
51 history
= EpisodeHistoryEntry
.objects\
52 .filter(episode
=episode
,
53 timestamp__gte
=start_date
)\
54 # contains play-counts, indexed by date {date: play-count}
55 play_counts
= playcounts_timerange(history
)
57 # we start either at the episode-release or the first listen-event
58 events
= list(play_counts
.keys()) + \
59 [episode
.released
.date()] if episode
.released
else []
64 # we always start at the first listen-event
66 for date
in daterange(start
, leap
=leap
):
67 playcount
= play_counts
.get(date
, 0)
68 e
= episode
if (episode
.released
.date() == date
) else None
69 yield ListenerData(date
, playcount
, e
)
72 def subscriber_data(podcasts
):
73 coll_data
= defaultdict(int)
80 for podcast
in podcasts
:
81 create_entry
= lambda r
: (r
.timestamp
.strftime('%y-%m'), r
.subscriber_count
)
83 subdata
= [podcast
.subscribers
]
85 data
= dict(map(create_entry
, subdata
))
88 coll_data
[k
] += data
[k
]
90 # create a list of {'x': label, 'y': value}
91 coll_data
= sorted([dict(x
=a
, y
=b
) for (a
, b
) in coll_data
.items()], key
=lambda x
: x
['x'])
96 def check_publisher_permission(user
, podcast
):
97 """ Checks if the user has publisher permissions for the given podcast """
99 if not user
.is_authenticated
:
105 return PublishedPodcast
.objects
.filter(publisher
=user
, podcast
=podcast
).exists()
108 def colour_repr(val
, max_val
, colours
):
110 returns a color representing the given value within a color gradient.
112 The color gradient is given by a list of (r, g, b) tupels. The value
113 is first located within two colors (of the list) and then approximated
114 between these two colors, based on its position within this segment.
116 if len(colours
) == 1:
122 # calculate position in the gradient; defines the segment
123 pos
= float(val
) / max_val
124 colour_nr1
= min(len(colours
)-1, int(pos
* (len(colours
)-1)))
125 colour_nr2
= min(len(colours
)-1, colour_nr1
+1)
126 colour1
= colours
[ colour_nr1
]
127 colour2
= colours
[ colour_nr2
]
132 # determine bounds of segment
133 lower_bound
= float(max_val
) / (len(colours
)-1) * colour_nr1
134 upper_bound
= min(max_val
, lower_bound
+ float(max_val
) / (len(colours
)-1))
136 # position within the segment
137 percent
= (val
- lower_bound
) / upper_bound
143 return (r1
+ r_step
* percent
, g1
+ g_step
* percent
, b1
+ b_step
* percent
)