Skip to content

Commit

Permalink
feat: Added first version of Instagram politics report.
Browse files Browse the repository at this point in the history
  • Loading branch information
Nico-AP committed Jul 30, 2024
1 parent 173b136 commit 51b210d
Show file tree
Hide file tree
Showing 20 changed files with 861 additions and 248 deletions.
6 changes: 5 additions & 1 deletion config/settings/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -194,4 +194,8 @@

# Reports
# ------------------------------------------------------------------------------
POLITICS_KEY = os.getenv('POLITICS_KEY', None)
POLITICS_KEY_INSTAGRAM = os.getenv('POLITICS_KEY_INSTAGRAM', None)
POLITICS_KEY_FACEBOOK = os.getenv('POLITICS_KEY_FACEBOOK', None)
SEARCH_KEY = os.getenv('SEARCH_KEY', None)
DIGITAL_MEAL_KEY = os.getenv('DIGITAL_MEAL_KEY', None)
CHATGPT_KEY = os.getenv('CHATGPT_KEY', None)
2 changes: 1 addition & 1 deletion config/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
path('ddm/logout/', auth_views.LogoutView.as_view(), name='ddm-logout'),
path('ddm/contact/', TemplateView.as_view(template_name='ddl/custom-ddm/contact.html'), name='ddm-contact'),
path('ckeditor/', include('ckeditor_uploader.urls')),
path('reports/', include('reports.urls'))
path('reports/', include('reports.urls')),
path('dm-api/<int:pk>/class-data', dm_apis.ClassReportAPI.as_view(), name='class_data_api'), # int:pk relates to ID of DDM project (must be named 'pk' due to ddm authentication scheme).
path('dm-api/<int:pk>/class-overview', dm_apis.ClassOverviewAPI.as_view(), name='class_overview_api'), # int:pk relates to ID of DDM project.
path('dm-api/<int:pk>/individual-data', dm_apis.IndividualReportAPI.as_view(), name='individual_data_api') # int:pk relates to ID of DDM project.
Expand Down
2 changes: 1 addition & 1 deletion ddl/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from django.http import HttpResponse

from .models import User
from ddm.models.core import DataDonation
from ddm.models.core import DataDonation, FileUploader, DonationBlueprint


class ExportCsvMixin:
Expand Down
6 changes: 5 additions & 1 deletion env.example
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,8 @@ DJANGO_DB_USER=''
DJANGO_DB_PW=''

# Reports
POLITICS_KEY=''
POLITICS_KEY_INSTAGRAM=''
POLITICS_KEY_FACEBOOK=''
SEARCH_KEY=''
DIGITAL_MEAL_KEY=''
CHATGPT_KEY=''
301 changes: 301 additions & 0 deletions reports/graphs.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,301 @@
import random

from bokeh.embed import components
from bokeh.models import Legend, Span, FactorRange, ColumnDataSource
from bokeh.plotting import figure
from bokeh.transform import factor_cmap


INSTA_CATEGORIES = ['Parteien', 'Politiker:innen', 'Medien', 'Organisationen']
FIRE_PALETTE = ['#de0c1c', '#fe2d2d', '#fb7830', '#fecf02'] #, '#ffeea3']


def get_custom_legend(p):
label_colors = {
'Parteien': FIRE_PALETTE[0],
'Politiker:innen': FIRE_PALETTE[1],
'Medien': FIRE_PALETTE[2],
'Organisationen': FIRE_PALETTE[3]
}
legend_items = [(label, [p.square(-10, -10, legend_label=label, color=label_colors[label])]) for label in
INSTA_CATEGORIES]
legend = Legend(items=legend_items, location='center',
background_fill_color=None, border_line_color=None,
orientation='horizontal')
return legend


def get_insta_follows_plot(followed_accounts):
n_total = sum([len(followed_accounts[key]) for key in followed_accounts.keys()])
n_relevant = n_total - len(followed_accounts['other'])

dimension = ['Gefolgte Accounts']
categories = INSTA_CATEGORIES
palette = FIRE_PALETTE

data = {
'Gefolgte Accounts': dimension,
'Parteien': [len(followed_accounts['parties'])],
'Politiker:innen': [len(followed_accounts['politicians'])],
'Medien': [len(followed_accounts['media'])],
'Organisationen': [len(followed_accounts['organisations'])],
# 'andere (nicht politisch)': [len(followed_accounts['other'])],
}

p = figure(y_range=dimension, x_range=(0, n_relevant), height=140, width=800, toolbar_location=None,
tools='hover', tooltips='$name: @$name',
background_fill_color=None)

p.hbar_stack(categories, y='Gefolgte Accounts', height=0.9, color=palette, source=ColumnDataSource(data),
legend_label=categories)

p.border_fill_color = None
p.y_range.range_padding = 0.1
p.yaxis.visible = False
p.ygrid.visible = False
p.xgrid.visible = False
p.axis.minor_tick_line_color = None
p.outline_line_color = None

p.legend.visible = False
legend = get_custom_legend(p)
p.add_layout(legend, 'below')

script, div = components(p)
return {'script': script, 'div': div}


def get_insta_line_plot(followed_accounts, n_followed):
def draw_line(plot, y, x_a, x_b, color):
plot.add_layout(Span(location=y, dimension='width', line_width=2, line_color='lightgray', level='underlay'))
plot.square(x=x_a, y=y, size=15, color=color)
plot.triangle(x=x_b, y=y, size=15, color=color)

x_max = n_followed
p = figure(
tools="",
toolbar_location=None,
background_fill_color='#fff',
x_range=(0, x_max),
height=300, width=800,
)
p.grid.grid_line_color = None

# x_a = person, x_b = reference group
draw_line(p, 4, 10, 20, '#de0c1c')
draw_line(p, 3, 12, 90, '#fe2d2d')
draw_line(p, 2, 20, 42, '#fb7830')
draw_line(p, 1, 5, 50, '#fecf02')

# Customize plot appearance
p.yaxis.major_label_overrides = {4: 'Parteien', 3.5: '', 3: 'Politiker', 2.5: '', 2: 'Medien', 1.5: '',
1: 'Organisationen'}

p.axis.minor_tick_line_color = None
p.axis.major_tick_line_color = None
p.outline_line_color = None

p.yaxis.major_label_text_font_size = '12pt'

p.xaxis.axis_line_color = None
p.xaxis.axis_label = 'Anzahl gefolgte Accounts'

# Add custom legend
legend_items = [
('Sie', [p.square(x=-5, y=2, size=15, color='#000', legend_label='Sie')]),
('Andere', [p.triangle(x=-5, y=2, size=15, color='#000', legend_label='Andere')])
]
p.legend.visible = False
legend = Legend(items=legend_items, location='right',
background_fill_color=None, border_line_color=None,
orientation='horizontal')
p.add_layout(legend, 'above')

script, div = components(p)
return {'script': script, 'div': div}


def get_interaction_plot(interactions):
def get_list_for_plot(category, interactions):
l = []
for interaction in ['likes_posts', 'likes_stories', 'comments_general', 'comments_reels']:
l.append(len(interactions[interaction][category]))
return l

interaction_types = [
'Likes Posts', # 'likes_posts',
'Likes Stories', # 'likes_stories',
'Kommentare allgemein', # 'comments_general',
'Kommentare Reels' # 'comments_reels'
]
categories = INSTA_CATEGORIES
data = {
'interactions': interaction_types,
'Parteien': get_list_for_plot('parties', interactions),
'Politiker:innen': get_list_for_plot('politicians', interactions),
'Medien': get_list_for_plot('media', interactions),
'Organisationen': get_list_for_plot('organisations', interactions),
# 'andere (nicht politisch)': get_list_for_plot('other', interactions),
}

palette = FIRE_PALETTE
x = [(interaction, category) for interaction in interaction_types for category in categories]
counts = sum(zip(data['Parteien'], data['Politiker:innen'], data['Medien'], data['Organisationen']), ())
source = ColumnDataSource(data=dict(x=x, counts=counts))

p = figure(x_range=FactorRange(*x), height=350, width=800,
toolbar_location=None, tools="", background_fill_color=None)

p.vbar(x='x', top='counts', width=0.9, source=source, line_color='white',
fill_color=factor_cmap('x', palette=palette, factors=categories, start=1, end=2))

p.border_fill_color = None
p.y_range.start = 0
p.x_range.range_padding = 0.1
p.xaxis.major_label_orientation = 1
p.axis.minor_tick_line_color = None
p.xgrid.grid_line_color = None

p.xaxis.group_text_color = '#000'
p.xaxis.group_text_font_size = '10pt'

legend = get_custom_legend(p)
p.legend.visible = False
p.add_layout(legend, 'below')

script, div = components(p)
return {'script': script, 'div': div}


def get_content_plot(content):
def get_list_for_plot(category, interactions):
l = []
for interaction in ['seen_ads', 'recommended_profiles', 'seen_posts', 'seen_videos']:
l.append(len(interactions[interaction][category]))
return l

interaction_types = [
'Geschaute Werbung',
'Vorgeschlagene Profile',
'Geschaute Posts',
'Geschaute Videos'
]
categories = INSTA_CATEGORIES
data = {
'interactions': interaction_types,
'Parteien': get_list_for_plot('parties', content),
'Politiker:innen': get_list_for_plot('politicians', content),
'Medien': get_list_for_plot('media', content),
'Organisationen': get_list_for_plot('organisations', content),
# 'andere (nicht politisch)': get_list_for_plot('other', content),
}

palette = FIRE_PALETTE

x = [(interaction, category) for interaction in interaction_types for category in categories]
counts = sum(zip(data['Parteien'], data['Politiker:innen'], data['Medien'], data['Organisationen']), ())

source = ColumnDataSource(data=dict(x=x, counts=counts))

p = figure(x_range=FactorRange(*x), height=350, width=800,
toolbar_location=None, tools="", background_fill_color=None)

p.vbar(x='x', top='counts', width=1, source=source, line_color='white',
fill_color=factor_cmap('x', palette=palette, factors=categories, start=1, end=2))

p.border_fill_color = None
p.y_range.start = 0
p.x_range.range_padding = 0.1
p.xaxis.major_label_orientation = 1
p.xgrid.grid_line_color = None
p.axis.minor_tick_line_color = None

p.xaxis.group_text_color = '#000'
p.xaxis.group_text_font_size = '10pt'

legend = get_custom_legend(p)
p.legend.visible = False
p.add_layout(legend, 'below')

script, div = components(p)
return {'script': script, 'div': div}


def get_vote_graph(data, color='1'):
colors = {
'1': '#fbe122',
'2': '#002b79'
}
categories = ['ja', 'nein', 'leer', 'nicht teilgenommen']
counts = [data[cat] for cat in categories]
p = figure(x_range=categories, height=250, width=500,
toolbar_location=None, tools="")
p.vbar(x=categories, top=counts, width=0.9,
fill_color=colors[color], line_color='white')

p.border_fill_color = None
p.y_range.start = 0
p.x_range.range_padding = 0.1
p.axis.minor_tick_line_color = None
p.xgrid.grid_line_color = None

p.xaxis.group_text_color = '#000'
p.xaxis.group_text_font_size = '10pt'

p.xaxis.axis_label = 'Abstimmungsverhalten'
p.yaxis.axis_label = 'Anzahl Personen'
p.xaxis.axis_label_text_font_size = '11pt'
p.yaxis.axis_label_text_font_size = '11pt'
p.xaxis.axis_label_text_font_style = 'normal'
p.yaxis.axis_label_text_font_style = 'normal'
p.xaxis.major_label_text_font_size = '10pt'
p.xaxis.major_tick_line_color = None

script, div = components(p)
return {'script': script, 'div': div}


def get_party_graph(data, key):
colors = {
'SP': '#e4002b',
'SVP': '#009f4e',
'Mitte': '#ff9b00',
'FDP': '#074ea1'
}
data = {
'SP': {str(i): random.randint(1, 100) for i in range(1, 11)},
'SVP': {str(i): random.randint(1, 100) for i in range(1, 11)},
'Mitte': {str(i): random.randint(1, 100) for i in range(1, 11)},
'FDP': {str(i): random.randint(1, 100) for i in range(1, 11)}
}

categories = [str(i) for i in range(1, 11)]
counts = [data[key][cat] for cat in categories]

p = figure(x_range=categories, height=400, width=400,
toolbar_location=None, tools="")
p.vbar(x=categories, top=counts, width=0.9,
fill_color='white', line_color='white')

p.background_fill_color = colors[key]
p.border_fill_color = None
p.y_range.start = 0
p.x_range.range_padding = 0.1
p.axis.minor_tick_line_color = None
p.xgrid.grid_line_color = None
p.ygrid.grid_line_color = None

p.xaxis.group_text_color = '#000'
p.xaxis.group_text_font_size = '10pt'

p.xaxis.axis_label = 'Politische Ausrichtung (von 1=links bis 10=rechts)'
p.yaxis.axis_label = f'Anzahl Personen, die einem {key}-Account folgen'
p.xaxis.axis_label_text_font_size = '11pt'
p.yaxis.axis_label_text_font_size = '9pt'
p.xaxis.axis_label_text_font_style = 'normal'
p.yaxis.axis_label_text_font_style = 'normal'
p.xaxis.major_label_text_font_size = '10pt'
p.xaxis.major_tick_line_color = None

script, div = components(p)
return {'script': script, 'div': div}
Empty file added reports/jobs.py
Empty file.
22 changes: 22 additions & 0 deletions reports/management/commands/update_politics_stats.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# see: https://docs.djangoproject.com/en/3.2/howto/custom-management-commands/

from django.core.management.base import BaseCommand, CommandError


class Command(BaseCommand):
help = 'Closes the specified poll for voting'

def add_arguments(self, parser):
parser.add_argument('poll_ids', nargs='+', type=int)

def handle(self, *args, **options):
for poll_id in options['poll_ids']:
# try:
# poll = Poll.objects.get(pk=poll_id)
# except Poll.DoesNotExist:
# raise CommandError('Poll "%s" does not exist' % poll_id)
#
# poll.opened = False
# poll.save()

self.stdout.write(self.style.SUCCESS('Successfully closed poll "%s"' % poll_id))
Empty file added reports/migrations/__init__.py
Empty file.
Loading

0 comments on commit 51b210d

Please sign in to comment.