class Conversations(SavannahFilterView): def __init__(self, request, community_id): super().__init__(request, community_id) self.active_tab = "conversations" self.charts = set() self._membersChart = None self._channelsChart = None self._tagsChart = None self._rolesChart = None self._responseTimes = None self.RESULTS_PER_PAGE = 25 try: self.page = int(request.GET.get('page', 1)) except: self.page = 1 if 'conversation_search' in request.GET: self.conversation_search = request.GET.get('conversation_search', "").lower() else: self.conversation_search = None self.result_count = 0 @property def all_conversations(self): conversations = Conversation.objects.filter( channel__source__community=self.community) conversations = conversations.filter(timestamp__gte=self.rangestart, timestamp__lte=self.rangeend) if self.tag: conversations = conversations.filter(tags=self.tag) if self.member_tag: conversations = conversations.filter(speaker__tags=self.member_tag) if self.role: conversations = conversations.filter(speaker__role=self.role) if self.conversation_search: conversations = conversations.filter( content__icontains=self.conversation_search) self.result_count = conversations.count() conversations = conversations.select_related( 'channel', 'channel__source', 'speaker').prefetch_related('tags').order_by('-timestamp') start = (self.page - 1) * self.RESULTS_PER_PAGE return conversations[start:start + self.RESULTS_PER_PAGE] @property def has_pages(self): return self.result_count > self.RESULTS_PER_PAGE @property def last_page(self): pages = int(self.result_count / self.RESULTS_PER_PAGE) + 1 return pages @property def page_links(self): pages = int(self.result_count / self.RESULTS_PER_PAGE) + 1 offset = 1 if self.page > 5: offset = self.page - 5 if offset + 9 > pages: offset = pages - 9 if offset < 1: offset = 1 return [page + offset for page in range(min(10, pages))] def getResponseTimes(self): if not self._responseTimes: replies = Conversation.objects.filter( speaker__community_id=self.community, thread_start__isnull=True, timestamp__gte=self.rangestart, timestamp__lte=self.rangeend) if self.tag: replies = replies.filter( Q(tags=self.tag) | Q(replies__tags=self.tag)) if self.member_tag: replies = replies.filter( replies__speaker__tags=self.member_tag) if self.role: replies = replies.filter(replies__speaker__role=self.role) if self.conversation_search: replies = replies.filter( Q(content__icontains=self.conversation_search) | Q(replies__content__icontains=self.conversation_search)) replies = replies.annotate( first_response=Min('replies__timestamp')) replies = replies.filter(first_response__isnull=False, first_response__gt=F('timestamp')) response_time = ExpressionWrapper( F('first_response') - F('timestamp'), output_field=fields.DurationField()) replies = replies.annotate(response_time=response_time) self._responseTimes = replies.aggregate(avg=Avg('response_time'), min=Min('response_time'), max=Max('response_time')) return self._responseTimes @property def min_response_time(self): response_times = self.getResponseTimes() if response_times['min'] is None: return None return response_times['min'] - datetime.timedelta( microseconds=response_times['min'].microseconds) @property def max_response_time(self): response_times = self.getResponseTimes() if response_times['max'] is None: return None return response_times['max'] - datetime.timedelta( microseconds=response_times['max'].microseconds) @property def avg_response_time(self): response_times = self.getResponseTimes() if response_times['avg'] is None: return None return response_times['avg'] - datetime.timedelta( microseconds=response_times['avg'].microseconds) def getConversationsChart(self): if not self._membersChart: months = list() counts = dict() conversations = Conversation.objects.filter( channel__source__community=self.community, timestamp__gte=self.rangestart, timestamp__lte=self.rangeend) if self.tag: conversations = conversations.filter(tags=self.tag) if self.member_tag: conversations = conversations.filter( speaker__tags=self.member_tag) if self.role: conversations = conversations.filter(speaker__role=self.role) if self.conversation_search: conversations = conversations.filter( content__icontains=self.conversation_search) conversations = conversations.order_by("timestamp") for m in conversations: month = self.trunc_date(m.timestamp) if month not in months: months.append(month) if month not in counts: counts[month] = 1 else: counts[month] += 1 self._membersChart = (months, counts) return self._membersChart @property def conversations_chart_months(self): (months, counts) = self.getConversationsChart() return self.timespan_chart_keys(months) @property def conversations_chart_counts(self): (months, counts) = self.getConversationsChart() return [ counts.get(month, 0) for month in self.timespan_chart_keys(months) ] def channelsChart(self): if not self._channelsChart: channels = list() counts = dict() channels = Channel.objects.filter(source__community=self.community) convo_filter = Q(conversation__timestamp__gte=self.rangestart, conversation__timestamp__lte=self.rangeend) if self.tag: convo_filter = convo_filter & Q(conversation__tags=self.tag) if self.member_tag: convo_filter = convo_filter & Q( conversation__speaker__tags=self.member_tag) if self.role: convo_filter = convo_filter & Q( conversation__speaker__role=self.role) if self.conversation_search: convo_filter = convo_filter & Q( conversation__content__icontains=self.conversation_search) channels = channels.annotate( conversation_count=Count('conversation', filter=convo_filter)) channels = channels.annotate( source_connector=F('source__connector'), source_icon=F('source__icon_name'), color=F('tag__color')) for c in channels.order_by("-conversation_count"): if c.conversation_count == 0: continue counts[c] = c.conversation_count self._channelsChart = PieChart("channelsChart", title="Conversations by Channel", limit=8) for channel, count in sorted(counts.items(), key=operator.itemgetter(1), reverse=True): self._channelsChart.add( "%s (%s)" % (channel.name, ConnectionManager.display_name(channel.source_connector)), count, channel.color) self.charts.add(self._channelsChart) return self._channelsChart def tagsChart(self): if not self._tagsChart: counts = dict() tags = Tag.objects.filter(community=self.community) convo_filter = Q(conversation__timestamp__gte=self.rangestart, conversation__timestamp__lte=self.rangeend) if self.tag: convo_filter = convo_filter & Q(conversation__tags=self.tag) if self.member_tag: convo_filter = convo_filter & Q( conversation__speaker__tags=self.member_tag) if self.role: convo_filter = convo_filter & Q( conversation__speaker__role=self.role) if self.conversation_search: convo_filter = convo_filter & Q( conversation__content__icontains=self.conversation_search) tags = tags.annotate( conversation_count=Count('conversation', filter=convo_filter)) for t in tags: counts[t] = t.conversation_count self._tagsChart = PieChart("tagsChart", title="Conversations by Tag", limit=12) for tag, count in sorted(counts.items(), key=operator.itemgetter(1), reverse=True): if count > 0: self._tagsChart.add(tag.name, count, tag.color) self.charts.add(self._tagsChart) return self._tagsChart def rolesChart(self): if not self._rolesChart: counts = dict() colors = { Member.COMMUNITY: savannah_colors.MEMBER.COMMUNITY, Member.STAFF: savannah_colors.MEMBER.STAFF, Member.BOT: savannah_colors.MEMBER.BOT } members = Member.objects.filter(community=self.community) convo_filter = Q(speaker_in__timestamp__gte=self.rangestart, speaker_in__timestamp__lte=self.rangeend) if self.tag: convo_filter = convo_filter & Q(speaker_in__tags=self.tag) if self.member_tag: members = members.filter(tags=self.member_tag) if self.role: members = members.filter(role=self.role) if self.conversation_search: convo_filter = convo_filter & Q( speaker_in__content__icontains=self.conversation_search) #convo_filter = convo_filter & Q(speaker_in__speaker_id=F('id')) members = members.annotate(conversation_count=Count( 'speaker_in', filter=convo_filter)).filter( conversation_count__gt=0) for m in members: if m.role in counts: counts[m.role] += m.conversation_count else: counts[m.role] = m.conversation_count self._rolesChart = PieChart("rolesChart", title="Conversations by Role") for role, count in sorted(counts.items(), key=operator.itemgetter(1), reverse=True): self._rolesChart.add(Member.ROLE_NAME[role], count, colors[role]) self.charts.add(self._rolesChart) return self._rolesChart @property def most_active(self): activity_counts = dict() members = Member.objects.filter(community=self.community) convo_filter = Q(speaker_in__timestamp__gte=self.rangestart, speaker_in__timestamp__lte=self.rangeend) if self.tag: convo_filter = convo_filter & Q(speaker_in__tags=self.tag) if self.member_tag: members = members.filter(tags=self.member_tag) if self.role: members = members.filter(role=self.role) if self.conversation_search: convo_filter = convo_filter & Q( speaker_in__content__icontains=self.conversation_search) members = members.annotate(conversation_count=Count( 'speaker_in', filter=convo_filter)).filter( conversation_count__gt=0).prefetch_related('tags').order_by( '-conversation_count') return members[:20] @property def most_connected(self): if self.conversation_search: return [] members = Member.objects.filter(community=self.community) connection_filter = Q( memberconnection__last_connected__gte=self.rangestart, memberconnection__last_connected__lte=self.rangeend) if self.tag: connection_filter = connection_filter & Q( connections__tags=self.tag) if self.member_tag: members = members.filter(tags=self.member_tag) if self.role: members = members.filter(role=self.role) members = members.annotate(connection_count=Count( 'connections', filter=connection_filter)).filter( connection_count__gt=0).prefetch_related('tags').order_by( '-connection_count') return members[:20] @login_required def as_view(request, community_id): view = Conversations(request, community_id) return render(request, 'savannahv2/conversations.html', view.context)
class MemberProfile(SavannahView): def __init__(self, request, member_id): self.member = get_object_or_404(Member, id=member_id) super().__init__(request, self.member.community_id) self.active_tab = "members" self.RESULTS_PER_PAGE = 100 self._engagementChart = None self._channelsChart = None try: self.page = int(request.GET.get('page', 1)) except: self.page = 1 self.tag = None self.member_tage = None self.role = None @property def is_watched(self): return MemberWatch.objects.filter(manager=self.request.user, member=self.member).count() > 0 @property def member_levels(self): return MemberLevel.objects.filter(community=self.community, member=self.member).order_by('-project__default_project', '-level', 'timestamp') def open_tasks(self): return Task.objects.filter(stakeholders=self.member, done__isnull=True) @property def all_gifts(self): return Gift.objects.filter(community=self.community, member=self.member) @property def all_conversations(self): conversations = Conversation.objects.filter(channel__source__community=self.member.community, speaker=self.member) if self.tag: conversations = conversations.filter(tags=self.tag) if self.role: conversations = conversations.filter(participants__role=self.role) conversations = conversations.annotate(tag_count=Count('tags'), channel_name=F('channel__name'), channel_icon=F('channel__source__icon_name')).order_by('-timestamp') return conversations[:20] @property def all_contributions(self): contributions = Contribution.objects.filter(community=self.member.community, author=self.member).annotate(tag_count=Count('tags'), channel_name=F('channel__name'), channel_icon=F('channel__source__icon_name')).order_by('-timestamp') if self.tag: contributions = contributions.filter(tags=self.tag) if self.role: contributions = contributions.filter(author__role=self.role) contributions = contributions.annotate(tag_count=Count('tags'), channel_name=F('channel__name'), channel_icon=F('channel__source__icon_name')).order_by('-timestamp') return contributions[:10] def getEngagementChart(self): if not self._engagementChart: conversations_counts = dict() activity_counts = dict() conversations = conversations = Conversation.objects.filter(channel__source__community=self.member.community, speaker=self.member, timestamp__gte=datetime.datetime.now() - datetime.timedelta(days=90)) if self.tag: conversations = conversations.filter(tags=self.tag) if self.role: conversations = conversations.filter(speaker__role=self.role) conversations = conversations.order_by("timestamp") for c in conversations: month = str(c.timestamp)[:10] if month not in conversations_counts: conversations_counts[month] = 1 else: conversations_counts[month] += 1 activity = Contribution.objects.filter(community=self.member.community, author=self.member, timestamp__gte=datetime.datetime.now() - datetime.timedelta(days=90)) if self.tag: activity = activity.filter(tags=self.tag) if self.role: activity = activity.filter(author__role=self.role) activity = activity.order_by("timestamp") for a in activity: month = str(a.timestamp)[:10] if month not in activity_counts: activity_counts[month] = 1 else: activity_counts[month] += 1 self._engagementChart = (conversations_counts, activity_counts) return self._engagementChart @property def engagement_chart_months(self): base = datetime.datetime.today() date_list = [base - datetime.timedelta(days=x) for x in range(90)] date_list.reverse() return [str(day)[:10] for day in date_list] @property def engagement_chart_conversations(self): (conversations_counts, activity_counts) = self.getEngagementChart() base = datetime.datetime.today() date_list = [base - datetime.timedelta(days=x) for x in range(90)] date_list.reverse() return [conversations_counts.get(str(day)[:10], 0) for day in date_list] @property def engagement_chart_activities(self): (conversations_counts, activity_counts) = self.getEngagementChart() base = datetime.datetime.today() date_list = [base - datetime.timedelta(days=x) for x in range(90)] date_list.reverse() return [activity_counts.get(str(day)[:10], 0) for day in date_list] def channels_chart(self): channel_names = dict() if not self._channelsChart: channels = list() counts = dict() from_colors = ['4e73df', '1cc88a', '36b9cc', '7dc5fe', 'cceecc'] next_color = 0 channels = Channel.objects.filter(source__community=self.member.community) convo_filter = Q(conversation__speaker=self.member, conversation__timestamp__gte=datetime.datetime.now() - datetime.timedelta(days=180)) if self.tag: convo_filter = convo_filter & Q(conversation__tags=self.tag) if self.role: convo_filter = convo_filter & Q(conversation__speaker__role=self.role) channels = channels.annotate(conversation_count=Count('conversation', filter=convo_filter)) channels = channels.annotate(source_icon=F('source__icon_name'), source_connector=F('source__connector'), color=F('tag__color')) for c in channels: if c.conversation_count == 0: continue counts[c] = c.conversation_count self._channelsChart = PieChart("channelsChart", title="Conversations by Channel", limit=8) for channel, count in sorted(counts.items(), key=operator.itemgetter(1), reverse=True): self._channelsChart.add("%s (%s)" % (channel.name, ConnectionManager.display_name(channel.source_connector)), count, channel.color) self.charts.add(self._channelsChart) return self._channelsChart @property def channel_names(self): chart = self.getChannelsChart() return str([channel[0] for channel in chart]) @property def channel_counts(self): chart = self.getChannelsChart() return [channel[1] for channel in chart] @property def channel_colors(self): chart = self.getChannelsChart() return ['#'+channel[2] for channel in chart] @login_required def as_view(request, member_id): view = MemberProfile(request, member_id) if request.method == 'POST': if 'delete_note' in request.POST: note = get_object_or_404(Note, id=request.POST.get('delete_note')) context = view.context context.update({ 'object_type':"Note", 'object_name': str(note), 'object_id': note.id, }) return render(request, "savannahv2/delete_confirm.html", context) elif 'delete_confirm' in request.POST: note = get_object_or_404(Note, id=request.POST.get('object_id')) note.delete() messages.success(request, "Note deleted") return redirect('member_profile', member_id=member_id) return render(request, 'savannahv2/member_profile.html', view.context)
class Connections(SavannahFilterView): def __init__(self, request, community_id, json=False): self._is_json = json super().__init__(request, community_id) self.active_tab = "connections" self._connectionsChart = None self._sourcesChart = None def _add_sources_message(self): if self._is_json: pass else: super()._add_sources_message() def getConnectionsChart(self): if not self._connectionsChart: months = list() counts = dict() connections = MemberConnection.objects.filter( via__community=self.community) if self.member_tag: connections = connections.filter( Q(from_member__tags=self.member_tag) | Q(to_member__tags=self.member_tag)) if self.role: connections = connections.filter( Q(from_member__role=self.role) & Q(from_member__role=self.role)) counts['prev'] = connections.filter( first_connected__lt=self.rangestart).count() connections = connections.filter( first_connected__gte=self.rangestart, first_connected__lte=self.rangeend) for c in connections: month = self.trunc_date(c.first_connected) if month not in months: months.append(month) if month not in counts: counts[month] = 1 else: counts[month] += 1 self._connectionsChart = (months, counts) return self._connectionsChart @property def connections_chart_months(self): (months, counts) = self.getConnectionsChart() return self.timespan_chart_keys(months) @property def connections_chart_counts(self): (months, counts) = self.getConnectionsChart() cumulative_counts = [] previous = counts['prev'] for month in self.timespan_chart_keys(months): cumulative_counts.append(counts.get(month, 0) + previous) previous = cumulative_counts[-1] return cumulative_counts def sources_chart(self): channel_names = dict() if not self._sourcesChart: counts = dict() connections = MemberConnection.objects.filter( via__community=self.community, first_connected__gte=self.rangestart, first_connected__lte=self.rangeend) if self.member_tag: connections = connections.filter( Q(from_member__tags=self.member_tag) | Q(to_member__tags=self.member_tag)) if self.role: connections = connections.filter( Q(from_member__role=self.role) & Q(from_member__role=self.role)) connections = connections.annotate( source_name=F('via__name'), source_connector=F('via__connector'), source_icon=F('via__icon_name')) for c in connections: source_name = "%s (%s)" % (c.source_name, ConnectionManager.display_name( c.source_connector)) if source_name not in counts: counts[source_name] = 1 else: counts[source_name] += 1 self._sourcesChart = PieChart("sourcesChart", title="Connections by Source", limit=8) for source_name, count in sorted(counts.items(), key=operator.itemgetter(1), reverse=True): self._sourcesChart.add(source_name, count) self.charts.add(self._sourcesChart) return self._sourcesChart @login_required def as_view(request, community_id): view = Connections(request, community_id) return render(request, 'savannahv2/connections.html', view.context) @login_required def as_json(request, community_id): view = Connections(request, community_id, json=True) nodes = list() links = list() member_map = dict() connection_counts = dict() connected = set() if view.timespan <= 31: timespan = view.timespan else: timespan = 30 connections = MemberConnection.objects.filter( from_member__community=view.community, last_connected__gte=view.rangeend - datetime.timedelta(days=timespan), last_connected__lte=view.rangeend) if view.member_tag: connections = connections.filter( Q(to_member__tags=view.member_tag) | Q(from_member__tags=view.member_tag)) if view.role: connections = connections.filter( Q(to_member__role=view.role) & Q(from_member__role=view.role)) connections = connections.select_related( 'from_member').prefetch_related('from_member__tags').order_by( '-last_connected') for connection in connections: if connection.from_member_id != connection.to_member_id: if connection.to_member_id > connection.from_member_id: connection_id = str(connection.to_member_id) + ":" + str( connection.from_member_id) else: connection_id = str(connection.from_member_id) + ":" + str( connection.to_member_id) links.append({ "source": connection.from_member_id, "target": connection.to_member_id }) member_map[connection.from_member_id] = connection.from_member if not connection_id in connected: connected.add(connection_id) if connection.from_member_id not in connection_counts: connection_counts[connection.from_member_id] = 1 else: connection_counts[connection.from_member_id] += 1 if len(connected) >= 100000: break for member_id, member in member_map.items(): tag_color = None tags = member.tags.all() if len(tags) > 0: tag_color = tags[0].color if tag_color is None and member.role == Member.BOT: tag_color = "aeaeae" elif tag_color is None and member.role == Member.STAFF: tag_color = "36b9cc" if tag_color is None: tag_color = "1f77b4" nodes.append({ "id": member_id, "name": member.name, "color": tag_color, "connections": connection_counts.get(member_id, 0) }) return JsonResponse({"nodes": nodes, "links": links})
class Members(SavannahFilterView): def __init__(self, request, community_id): super().__init__(request, community_id) self.active_tab = "members" self._membersChart = None self._tagsChart = None self._sourcesChart = None @property def all_members(self): members = Member.objects.filter(community=self.community) if self.member_tag: members =members.filter(tags=self.member_tag) if self.role: members =members.filter(role=self.role) members = members.annotate(note_count=Count('note'), tag_count=Count('tags')) return members @property def new_members(self): members = Member.objects.filter(community=self.community) if self.member_tag: members = members.filter(tags=self.member_tag) if self.role: members = members.filter(role=self.role) members = members.filter(first_seen__gte=self.rangestart, first_seen__lte=self.rangeend) return members.order_by("-first_seen")[:10] @property def recently_active(self): members = Member.objects.filter(community=self.community) if self.member_tag: members = members.filter(tags=self.member_tag) if self.role: members = members.filter(role=self.role) members = members.annotate(last_active=Max('speaker_in__timestamp', filter=Q(speaker_in__timestamp__isnull=False))) members = members.filter(last_active__gte=self.rangestart, last_active__lte=self.rangeend) actives = dict() for m in members: if m.last_active is not None: actives[m] = m.last_active recently_active = [(member, tstamp) for member, tstamp in sorted(actives.items(), key=operator.itemgetter(1), reverse=True)] return recently_active[:10] def getMembersChart(self): if not self._membersChart: months = list() counts = dict() monthly_active = dict() total = 0 members = Member.objects.filter(community=self.community) if self.member_tag: members = members.filter(tags=self.member_tag) if self.role: members = members.filter(role=self.role) seen = members.annotate(month=Trunc('first_seen', self.trunc_span)).values('month').annotate(member_count=Count('id', distinct=True)).order_by('month') for m in seen: total += 1 month = self.trunc_date(m['month']) if month not in months: months.append(month) counts[month] = m['member_count'] active = members.annotate(month=Trunc('speaker_in__timestamp', self.trunc_span)).values('month').annotate(member_count=Count('id', distinct=True)).order_by('month') for a in active: if a['month'] is not None: month = self.trunc_date(a['month']) if month not in months: months.append(month) monthly_active[month] = a['member_count'] self._membersChart = (sorted(months), counts, monthly_active) return self._membersChart @property def members_chart_months(self): (months, counts, monthly_active) = self.getMembersChart() return self.timespan_chart_keys(months) @property def members_chart_counts(self): (months, counts, monthly_active) = self.getMembersChart() return [counts.get(month, 0) for month in self.timespan_chart_keys(months)] @property def members_chart_monthly_active(self): (months, counts, monthly_active) = self.getMembersChart() return [monthly_active.get(month, 0) for month in self.timespan_chart_keys(months)] def sources_chart(self): if not self._sourcesChart: counts = dict() other_count = 0 identity_filter = Q(contact__member__first_seen__gte=self.rangestart, contact__member__last_seen__lte=self.rangeend) if self.member_tag: identity_filter = identity_filter & Q(contact__member__tags=self.member_tag) if self.role: identity_filter = identity_filter & Q(contact__member__role=self.role) sources = Source.objects.filter(community=self.community).annotate(identity_count=Count('contact', filter=identity_filter)) for source in sources: if source.identity_count == 0: continue counts[source] = source.identity_count self._sourcesChart = PieChart("sourcesChart", title="Member Sources", limit=8) for source, count in sorted(counts.items(), key=operator.itemgetter(1), reverse=True): self._sourcesChart.add("%s (%s)" % (source.name, ConnectionManager.display_name(source.connector)), count) self.charts.add(self._sourcesChart) return self._sourcesChart @login_required def as_view(request, community_id): members = Members(request, community_id) return render(request, 'savannahv2/members.html', members.context)
class Contributions(SavannahFilterView): def __init__(self, request, community_id): super().__init__(request, community_id) self.active_tab = "contributions" self._membersChart = None self._channelsChart = None self.RESULTS_PER_PAGE = 25 try: self.page = int(request.GET.get('page', 1)) except: self.page = 1 if 'search' in request.GET: self.search = request.GET.get('search', "").lower() else: self.search = None self.result_count = 0 @property def all_contributions(self): contributions = Contribution.objects.filter(community=self.community) contributions = contributions.filter(timestamp__gte=self.rangestart, timestamp__lte=self.rangeend) if self.tag: contributions = contributions.filter(tags=self.tag) if self.member_tag: contributions = contributions.filter(author__tags=self.member_tag) if self.role: contributions = contributions.filter(author__role=self.role) contributions = contributions.annotate( author_name=F('author__name'), channel_name=F('channel__name'), source_name=F('contribution_type__source__name'), source_icon=F('contribution_type__source__icon_name' )).prefetch_related('tags').order_by('-timestamp') self.result_count = contributions.count() start = (self.page - 1) * self.RESULTS_PER_PAGE return contributions[start:start + self.RESULTS_PER_PAGE] @property def has_pages(self): return self.result_count > self.RESULTS_PER_PAGE @property def last_page(self): pages = int(self.result_count / self.RESULTS_PER_PAGE) + 1 return pages @property def page_links(self): pages = int(self.result_count / self.RESULTS_PER_PAGE) + 1 offset = 1 if self.page > 5: offset = self.page - 5 if offset + 9 > pages: offset = pages - 9 if offset < 1: offset = 1 return [page + offset for page in range(min(10, pages))] @property def new_contributors(self): members = Member.objects.filter(community=self.community) contrib_filter = None if self.tag: contrib_filter = Q(contribution__tags=self.tag) if self.member_tag: members = members.filter(tags=self.member_tag) if self.role: members = members.filter(role=self.role) members = members.annotate(first_contrib=Min('contribution__timestamp', filter=contrib_filter)) members = members.filter(first_contrib__gte=self.rangestart, first_contrib__lte=self.rangeend) members = members.prefetch_related('tags') actives = dict() for m in members: if m.first_contrib is not None: actives[m] = m.first_contrib recently_active = [(member, tstamp) for member, tstamp in sorted( actives.items(), key=operator.itemgetter(1), reverse=True)] return recently_active[:10] @property def recent_contributors(self): members = Member.objects.filter(community=self.community) contrib_filter = Q(contribution__timestamp__gte=self.rangestart, contribution__timestamp__lte=self.rangeend) if self.tag: contrib_filter = contrib_filter & Q(contribution__tags=self.tag) if self.member_tag: members = members.filter(tags=self.member_tag) if self.role: members = members.filter(role=self.role) members = members.annotate(last_active=Max( 'contribution__timestamp', filter=contrib_filter)).filter( last_active__isnull=False).prefetch_related('tags') actives = dict() for m in members: if m.last_active is not None: actives[m] = m.last_active recently_active = [(member, tstamp) for member, tstamp in sorted( actives.items(), key=operator.itemgetter(1), reverse=True)] return recently_active[:10] @property def top_contributors(self): activity_counts = dict() members = Member.objects.filter(community=self.community) contrib_filter = Q(contribution__timestamp__gte=self.rangestart, contribution__timestamp__lte=self.rangeend) if self.tag: contrib_filter = contrib_filter & Q(contribution__tags=self.tag) if self.member_tag: members = members.filter(tags=self.member_tag) if self.role: members = members.filter(role=self.role) members = members.annotate(contribution_count=Count( 'contribution', filter=contrib_filter)).filter( contribution_count__gt=0).prefetch_related('tags') for m in members: if m.contribution_count > 0: activity_counts[m] = m.contribution_count most_active = [(member, count) for member, count in sorted(activity_counts.items(), key=operator.itemgetter(1))] most_active.reverse() return most_active[:10] @property def top_supporters(self): activity_counts = dict() contributor_ids = set() contributors = Member.objects.filter(community=self.community) contrib_filter = Q(contribution__timestamp__gte=self.rangestart, contribution__timestamp__lte=self.rangeend) if self.tag: contrib_filter = contrib_filter & Q(contribution__tags=self.tag) if self.member_tag: contributors = contributors.filter(tags=self.member_tag) if self.role: contributors = contributors.filter(role=self.role) contributors = contributors.annotate( contribution_count=Count('contribution', filter=contrib_filter)) contributors = contributors.filter( contribution_count__gt=0).order_by('-contribution_count') for c in contributors: if c.contribution_count > 0: contributor_ids.add(c.id) members = Member.objects.filter(community=self.community) members = members.annotate(conversation_count=Count( 'speaker_in', filter=Q(speaker_in__participants__in=contributor_ids, speaker_in__timestamp__gte=datetime.datetime.now() - datetime.timedelta(days=30)))) members = members.order_by('-conversation_count').filter( conversation_count__gt=0).prefetch_related('tags') for m in members[:10]: if m.conversation_count > 0: activity_counts[m] = m.conversation_count most_active = [(member, count) for member, count in sorted(activity_counts.items(), key=operator.itemgetter(1))] most_active.reverse() return most_active[:10] @property def top_enablers(self): activity_counts = dict() contributor_ids = set() contributors = Member.objects.filter(community=self.community) contrib_filter = Q(contribution__timestamp__gte=self.rangestart, contribution__timestamp__lte=self.rangeend) if self.tag: contrib_filter = contrib_filter & Q(contribution__tags=self.tag) if self.member_tag: contributors = contributors.filter(tags=self.member_tag) if self.role: contributors = contributors.filter(role=self.role) contributors = contributors.annotate(contribution_count=Count( 'contribution', filter=contrib_filter)).filter( contribution_count__gt=0) for c in contributors: if c.contribution_count > 0: contributor_ids.add(c.id) members = Member.objects.filter(community=self.community) members = members.annotate(connection_count=Count( 'memberconnection__id', filter=Q(memberconnection__to_member__in=contributor_ids, memberconnection__first_connected__lte=self.rangeend, memberconnection__last_connected__gte=self.rangestart))) members = members.order_by('-connection_count').filter( connection_count__gt=0).prefetch_related('tags') for m in members: if m.connection_count > 0: activity_counts[m] = m.connection_count most_active = [(member, count) for member, count in sorted(activity_counts.items(), key=operator.itemgetter(1))] most_active.reverse() return most_active[:10] def getContributionsChart(self): if not self._membersChart: months = list() counts = dict() contributions = Contribution.objects.filter( community=self.community, timestamp__gte=self.rangestart, timestamp__lte=self.rangeend) if self.tag: contributions = contributions.filter(tags=self.tag) if self.member_tag: contributions = contributions.filter( author__tags=self.member_tag) if self.role: contributions = contributions.filter(author__role=self.role) contributions = contributions.order_by("timestamp") for m in contributions: month = self.trunc_date(m.timestamp) if month not in months: months.append(month) if month not in counts: counts[month] = 1 else: counts[month] += 1 self._membersChart = (months, counts) return self._membersChart @property def contributions_chart_months(self): (months, counts) = self.getContributionsChart() return self.timespan_chart_keys(months) @property def contributions_chart_counts(self): (months, counts) = self.getContributionsChart() return [ counts.get(month, 0) for month in self.timespan_chart_keys(months) ] def channels_chart(self): if not self._channelsChart: channels = list() counts = dict() channels = Channel.objects.filter(source__community=self.community) contrib_filter = Q(contribution__timestamp__gte=self.rangestart, contribution__timestamp__lte=self.rangeend) if self.tag: contrib_filter = contrib_filter & Q( contribution__tags=self.tag) if self.member_tag: contrib_filter = contrib_filter & Q( contribution__author__tags=self.member_tag) if self.role: contrib_filter = contrib_filter & Q( contribution__author__role=self.role) channels = channels.annotate(contribution_count=Count( 'contribution', filter=contrib_filter)) channels = channels.annotate( source_icon=F('source__icon_name'), source_connector=F('source__connector'), color=F('tag__color')) for c in channels: if c.contribution_count == 0: continue counts[c] = c.contribution_count self._channelsChart = PieChart("channelsChart", title="Contributions by Channel", limit=8) for channel, count in sorted(counts.items(), key=operator.itemgetter(1), reverse=True): self._channelsChart.add( "%s (%s)" % (channel.name, ConnectionManager.display_name(channel.source_connector)), count, channel.color) self.charts.add(self._channelsChart) return self._channelsChart @login_required def as_view(request, community_id): view = Contributions(request, community_id) return render(request, 'savannahv2/contributions.html', view.context)