Initial commit

This commit is contained in:
Stephan Porada
2019-02-28 14:09:53 +01:00
commit 96e84d083d
97 changed files with 66293 additions and 0 deletions

0
app/speakers/__init__.py Executable file
View File

3
app/speakers/admin.py Executable file
View File

@ -0,0 +1,3 @@
from django.contrib import admin
# Register your models here.

10
app/speakers/apps.py Executable file
View File

@ -0,0 +1,10 @@
from django.apps import AppConfig
from watson import search as watson
class SpeakersConfig(AppConfig):
name = 'speakers'
def ready(self):
Speaker = self.get_model("Speaker")
watson.register(Speaker, exclude=["short_vita"])

11
app/speakers/forms.py Executable file
View File

@ -0,0 +1,11 @@
from django import forms
class SearchForm(forms.Form):
"""
Configures the html input form for the speaker search.
"""
query = forms.CharField(label="Suche MdB", max_length="200")
query.widget.attrs.update({"class": "autocomplete materialize-textarea",
"id": "icon_prefix2"})

View File

@ -0,0 +1,115 @@
from django.core.management.base import BaseCommand
from speakers.models import Speaker, LegislativeInfo, LegislativeInstitution
import datetime
from lxml import etree
from tqdm import tqdm
class Command(BaseCommand):
help = ("Adds speakers (MdBs) to the database using the django models"
" syntax. Speakers will be added from the official"
" Stammdatenbank.xml. Input is the Stammdatenbank.xml specified"
" by a path.")
def add_arguments(self, parser):
parser.add_argument("input_path",
type=str)
def handle(self, *args, **options):
file_path = options["input_path"]
# self.stdout.write("Reading data from file: " + file_path)
tree = etree.parse(file_path)
speakers = tree.xpath("//MDB")
for speaker_element in tqdm(speakers, desc="Importing speaker data"):
speaker = Speaker()
id = speaker_element.xpath("./ID")[0]
speaker.id = id.text
last_name = speaker_element.xpath("./NAMEN/NAME/NACHNAME")[0]
speaker.last_name = last_name.text
first_name = speaker_element.xpath("./NAMEN/NAME/VORNAME")[0]
speaker.first_name = first_name.text
nobility = speaker_element.xpath("./NAMEN/NAME/ADEL")[0]
speaker.nobility = nobility.text
name_prefix = speaker_element.xpath("./NAMEN/NAME/PRAEFIX")[0]
speaker.name_prefix = name_prefix.text
# self.stdout.write("Reading data for speaker: "
# + str(id.text)
# + " "
# + str(first_name.text)
# + " "
# + str(last_name.text))
title = speaker_element.xpath("./NAMEN/NAME/ANREDE_TITEL")[0]
speaker.title = title.text
birthday = speaker_element.xpath("./BIOGRAFISCHE_ANGABEN/GEBURTSDATUM")[0]
speaker.birthday = birthday.text
birthplace = speaker_element.xpath("./BIOGRAFISCHE_ANGABEN/GEBURTSORT")[0]
speaker.birthplace = birthplace.text
country_of_birth = speaker_element.xpath("./BIOGRAFISCHE_ANGABEN/GEBURTSLAND")[0]
speaker.country_of_birth = country_of_birth.text
day_of_death = speaker_element.xpath("./BIOGRAFISCHE_ANGABEN/STERBEDATUM")[0]
speaker.day_of_death = day_of_death.text
occupation = speaker_element.xpath("./BIOGRAFISCHE_ANGABEN/BERUF")[0]
speaker.occupation = occupation.text
short_vita = speaker_element.xpath("./BIOGRAFISCHE_ANGABEN/VITA_KURZ")[0]
speaker.short_vita = short_vita.text
party = speaker_element.xpath("./BIOGRAFISCHE_ANGABEN/PARTEI_KURZ")[0]
speaker.party = party.text
speaker.save()
legislative_periods = speaker_element.xpath("./WAHLPERIODEN/WAHLPERIODE/WP")
legislative_period_start_dates = speaker_element.xpath("./WAHLPERIODEN/WAHLPERIODE/MDBWP_VON")
legislative_period_end_dates = speaker_element.xpath("./WAHLPERIODEN/WAHLPERIODE/MDBWP_BIS")
mandate_types = speaker_element.xpath("./WAHLPERIODEN/WAHLPERIODE/MANDATSART")
legislative_institutions = speaker_element.xpath("./WAHLPERIODEN/WAHLPERIODE/INSTITUTIONEN/INSTITUTION/INS_LANG")
zipped_infos = zip(legislative_periods,
legislative_period_start_dates,
legislative_period_end_dates,
mandate_types,
legislative_institutions)
for p, sd, ed, m, i in zipped_infos:
legislative_info = LegislativeInfo()
legislative_info.foreign_speaker = speaker
legislative_info.legislative_period = p.text
if(sd.text is not None):
sd = datetime.datetime.strptime(sd.text, "%d.%m.%Y")
sd = datetime.datetime.strftime(sd, "%Y-%m-%d")
legislative_info.legislative_period_start_date = sd
if(ed.text is not None):
ed = datetime.datetime.strptime(ed.text, "%d.%m.%Y")
ed = datetime.datetime.strftime(ed, "%Y-%m-%d")
legislative_info.legislative_period_end_date = ed
legislative_info.mandate_type = m.text
# legislative_info.legislative_institution = i.text
legislative_info.save()
for period in speaker_element.xpath("./WAHLPERIODEN/WAHLPERIODE"):
# print("==============")
legislative_institutions = period.xpath("./INSTITUTIONEN/INSTITUTION/INS_LANG")
# print([e.text for e in legislative_institutions])
instition_start_dates = period.xpath("./INSTITUTIONEN/INSTITUTION/MDBINS_VON")
# print([e.text for e in instition_start_dates])
instition_end_dates = period.xpath("./INSTITUTIONEN/INSTITUTION/MDBINS_BIS")
# print([e.text for e in instition_end_dates])
# print("==============")
zipped_institutions = zip(legislative_institutions,
instition_start_dates,
instition_end_dates)
for institution, start_date, end_date in zipped_institutions:
legislative_institution = LegislativeInstitution()
legislative_institution.foreign_speaker = speaker
current_period = period.xpath("./WP")[0]
legislative_institution.current_period = current_period.text
legislative_institution.institution = institution.text
if(start_date.text is not None):
start_date = datetime.datetime.strptime(start_date.text,
"%d.%m.%Y")
start_date = datetime.datetime.strftime(start_date,
"%Y-%m-%d")
legislative_institution.institution_start_date = start_date
if(end_date.text is not None):
end_date = datetime.datetime.strptime(end_date.text,
"%d.%m.%Y")
end_date = datetime.datetime.strftime(end_date,
"%Y-%m-%d")
legislative_institution.institution_end_date = end_date
legislative_institution.save()

View File

74
app/speakers/models.py Executable file
View File

@ -0,0 +1,74 @@
from django.db import models
class Speaker(models.Model):
"""
This models contains general data about one MdB. Data will be imported from
the Stammdatenbank.xml via the custom django-admin command import_speakers.py.
"""
id = models.IntegerField(verbose_name="MdB ID", primary_key=True)
last_name = models.CharField(verbose_name="Nachname", max_length=50)
first_name = models.CharField(verbose_name="Vorname", max_length=50)
nobility = models.CharField(verbose_name="Adelstitel", max_length=50,
null=True)
name_prefix = models.CharField(verbose_name="Namenspräfix", max_length=50,
null=True)
title = models.CharField(verbose_name="Akademischer Titel", null=True,
blank=True, max_length=50)
birthday = models.IntegerField(verbose_name="Geburtstag", )
birthplace = models.CharField(verbose_name="Geburtsort", null=True,
blank=True, max_length=50)
country_of_birth = models.CharField(verbose_name="Geburtsland", null=True,
blank=True, max_length=50)
day_of_death = models.IntegerField(verbose_name="Todesjahr", null=True,
blank=True)
occupation = models.TextField(verbose_name="Beruf")
short_vita = models.TextField(verbose_name="Kurzbiographie", default=None,
null=True, blank=True)
party = models.CharField(verbose_name="Partei", null=True, blank=True,
max_length=50)
def __str__(self):
return str(self.id) + " " + self.first_name + " " + self.last_name
class LegislativeInfo(models.Model):
"""
This model contains data about the periods an MdB was an active part of the
Deutsche Bundestag. Needs a foreign key which is the coresponding Speaker
entry.
"""
foreign_speaker = models.ForeignKey("Speaker", on_delete=models.CASCADE)
legislative_period = models.IntegerField(verbose_name="Wahlperiode",
null=True)
legislative_period_start_date = models.DateField(verbose_name="MdB von",
null=True)
legislative_period_end_date = models.DateField(verbose_name="MdB bis",
null=True)
mandate_type = models.CharField(verbose_name="Mandatsart", null=True,
blank=True, max_length=50)
def __str__(self):
return str(self.foreign_speaker) + " " + str(self.legislative_period)
class LegislativeInstitution(models.Model):
"""
This model contains data about the instituions an MdB was part of during a
specific legislative period. Needs a foreign key which is the coresponding
Speaker entry.
"""
foreign_speaker = models.ForeignKey("Speaker",
on_delete=models.CASCADE)
current_period = models.IntegerField(verbose_name="Wahlperiode",
null=True)
institution = models.CharField(verbose_name="Institut", null=True,
blank=True, max_length=255)
institution_start_date = models.DateField(verbose_name="Mitglied von",
null=True)
institution_end_date = models.DateField(verbose_name="Mitglied bis",
null=True)
def __str__(self):
return str(self.foreign_legislative_info) + " " + str(self.institution)

19
app/speakers/tables.py Executable file
View File

@ -0,0 +1,19 @@
import django_tables2 as tables
from .models import Speaker
from django_tables2.utils import A # alias for Accessor
class SpeakerTable(tables.Table):
"""
Configures the table showing all speakers. Inserts a column with links to
the profile of one speaker. Also defines all shown columns.
"""
link = tables.LinkColumn("MdB", text="Profil", args=[A("id")],
orderable=False,
attrs={"a": {"class": "waves-effect waves-light btn light-green darken-3"}}) # Adds colum with Link to Profile
class Meta:
model = Speaker
fields = ("last_name", "first_name", "party", "id")
template_name = "speakers/table.html"
empty_text = ("Für den eingegebenen Suchbegriff gibt es leider keine Ergebnisse.")

View File

@ -0,0 +1,120 @@
{% extends "blog/base.html" %}
{% load render_table from django_tables2 %}
{% block content %}
<div class="container">
<div class="row">
<div class="col s12 m4 l4">
<div class="card">
<div class="card-content">
<span class="card-title center-align">
{% if current_speaker.title %}
{{current_speaker.title}}
{% endif %}
{% if current_speaker.nobility %}
{{current_speaker.nobility}}
{% endif %}
{{current_speaker.first_name}}
{% if current_speaker.name_prefix %}
{{current_speaker.name_prefix}}
{% endif %}
{{current_speaker.last_name}}</span>
<p class="center-align"><i class="large material-icons blue-grey-text darken-4">account_circle</i></p>
<span class="card-title">Biographie</span>
<ul>
<li><i class="material-icons blue-grey-text darken-4" style="margin-right: 10px;">cake</i> Geburtstag: {{current_speaker.birthday}}</li>
<br />
{% if current_speaker.day_of_death %}
<li><b style="font-size: 2em;" class="blue-grey-text darken-4"></b><span style="position: relative; right: -20px;"> Todesjahr:
{{ current_speaker.day_of_death}}</span></li>
<br />
{% endif %}
<li><i class="material-icons blue-grey-text darken-4" style="margin-right: 10px;">home</i> Geburtsort: {{current_speaker.birthplace}}</li>
<br />
<li><i class="material-icons blue-grey-text darken-4" style="margin-right: 10px;">work</i> Beruf: {{current_speaker.occupation}}</li>
<br />
<li><i class="material-icons blue-grey-text darken-4" style="margin-right: 10px;">fingerprint</i> Bundestags ID: {{current_speaker.id}}</li>
<br />
<li><i class="material-icons blue-grey-text darken-4" style="margin-right: 10px;">people</i> Partei: {{current_speaker.party}}<a class="tooltipped" data-position="bottom" data-tooltip="Aktuelle eingetragene Partei, bei der die Person Mitglied ist oder war. "><i
class="material-icons blue-grey-text darken-4">info_outline</i></a></li>
<br />
<li><i class="material-icons blue-grey-text darken-4" style="margin-right: 10px;">filter_9_plus</i> Reden/Redebeiträge insgesamt: {{speech_count}}</li>
</ul>
</div>
</div>
<ul class="collapsible expandable hoverable">
{% if current_speaker.short_vita %}
<li>
<div class="collapsible-header">Kurzbiographie</div>
<div class="collapsible-body white"><span>{{current_speaker.short_vita}}</span></div>
</li>
{% endif %}
{% for period in sorted_l_info %}
<li>
<div class="collapsible-header">
<i class="material-icons blue-grey-text darken-4" style="margin-right: 10px;">library_books</i> Wahlperiode {{period.legislative_period}}
</div>
<div class="collapsible-body white">
<ul>
<span class="card-title">Mitglied des Bundestags</span>
<li>Mitglied von {{period.legislative_period_start_date|date:"d.m.Y"}} bis
{% if period.legislative_period_end_date is None %}
heute
{% else %}
{{period.legislative_period_end_date|date:"d.m.Y"}}
{% endif %}
</li>
<br />
<li>Mandatsart: {{period.mandate_type}}
<li>
<br />
<span class="card-title">Institutions und Fraktionszugehörigkeit</span>
{% for institution in sorted_i_info %}
{% if institution.current_period == period.legislative_period %}
<li><b>{{institution.institution}}</b>
<li>
<br />
{% if institution.institution_start_date is not None %}
<li>Von {{institution.institution_start_date|date:"d.m.Y"}} bis
{% if institution.institution_end_date is None %}
heute
{% else %}
{{institution.institution_end_date|date:"d.m.Y"}}
{% endif %}
<li>
<br />
{% endif %}
{% endif %}
{% endfor %}
</ul>
</div>
</li>
{% endfor %}
</ul>
</div>
<div class="col s12 m8">
<div class="card">
<div class="card-content">
<span class="card-title">Reden</span>
<div class="card-content">
<p>Alle Reden, die von {% if current_speaker.title %}
{{current_speaker.title}}
{% endif %}
{% if current_speaker.nobility %}
{{current_speaker.nobility}}
{% endif %}
{{current_speaker.first_name}}
{% if current_speaker.name_prefix %}
{{current_speaker.name_prefix}}
{% endif %}
{{current_speaker.last_name}} als MdB gehalten wurden.
<div style="overflow-x:auto;">
{% render_table speech_table %}
</div>
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock content %}

View File

@ -0,0 +1,34 @@
{% extends "blog/base.html" %}
{% load render_table from django_tables2 %}
{% block content %}
<div class="container">
<div class="row">
<div class="col s12">
<div class="card">
<div class="card-content">
<span class="card-title">Mitglieder des Bundestags</span>
<p>Dies ist eine Liste aller Abgeordneten seit 1949 bis einschließlich der aktuellen Wahlperiode. Die Liste kann sortiert und durchsucht werden.</p>
<p>Ausgangsdaten für diese Liste können auf der <a href="https://www.bundestag.de/service/opendata">offiziellen Seite des Bundestags</a> heruntergeladen werden.</p>
<p>Für jede Person ist ein Profil angelegt, dass Informationen zu dieser bereithält und alle Reden bzw. Redebeiträge dieser gesammelt darstellt.</p>
<br />
<div class="row">
<form method="GET" class="col l4 offset-l8 m6 offset-m6 s12">
{% csrf_token %}
<div class="row">
<div class="input-field">
<i class="material-icons prefix">search</i>
{{form}}
</div>
</div>
</form>
</div>
<div style="overflow-x:auto;">
{% render_table table %}
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock content %}

View File

@ -0,0 +1,95 @@
{% load django_tables2 %}
{% load i18n %}
{% block table-wrapper %}
{% block table %}
<table {% render_attrs table.attrs %} class="highlight">
{% block table.thead %}
{% if table.show_header %}
<thead {{ table.attrs.thead.as_html }}>
<tr>
{% for column in table.columns %}
<th {{ column.attrs.th.as_html }}>
{% if column.orderable %}
<a href="{% querystring table.prefixed_order_by_field=column.order_by_alias.next %}"><i class="material-icons ">sort</i> {{ column.header }}</a>
{% else %}
{{ column.header }}
{% endif %}
</th>
{% endfor %}
</tr>
</thead>
{% endif %}
{% endblock table.thead %}
{% block table.tbody %}
<tbody {{ table.attrs.tbody.as_html }}>
{% for row in table.paginated_rows %}
{% block table.tbody.row %}
<tr {{ row.attrs.as_html }}>
{% for column, cell in row.items %}
<td {{ column.attrs.td.as_html }}>{% if column.localize == None %}{{ cell }}{% else %}{% if column.localize %}{{ cell|localize }}{% else %}{{ cell|unlocalize }}{% endif %}{% endif %}</td>
{% endfor %}
</tr>
{% endblock table.tbody.row %}
{% empty %}
{% if table.empty_text %}
{% block table.tbody.empty_text %}
<tr><td colspan="{{ table.columns|length }}">{{ table.empty_text }}</td></tr>
{% endblock table.tbody.empty_text %}
{% endif %}
{% endfor %}
</tbody>
{% endblock table.tbody %}
{% block table.tfoot %}
{% if table.has_footer %}
<tfoot {{ table.attrs.tfoot.as_html }}>
<tr>
{% for column in table.columns %}
<td {{ column.attrs.tf.as_html }}>{{ column.footer }}</td>
{% endfor %}
</tr>
</tfoot>
{% endif %}
{% endblock table.tfoot %}
</table>
{% endblock table %}
{% block pagination %}
{% if table.page and table.paginator.num_pages > 1 %}
<ul class="pagination">
{% if table.page.has_previous %}
{% block pagination.previous %}
<li class="previous waves-effect">
<a href="{% querystring table.prefixed_page_field=table.page.previous_page_number %}">
{% trans '<i class="material-icons">chevron_left</i>' %}
</a>
</li>
{% endblock pagination.previous %}
{% endif %}
{% if table.page.has_previous or table.page.has_next %}
{% block pagination.range %}
{% for p in table.page|table_page_range:table.paginator %}
<li {% if p == table.page.number %}class="active light-green darken-3"{% endif %} class="waves-effect">
{% if p == '...' %}
<a href="#">{{ p }}</a>
{% else %}
<a href="{% querystring table.prefixed_page_field=p %}">
{{ p }}
</a>
{% endif %}
</li>
{% endfor %}
{% endblock pagination.range %}
{% endif %}
{% if table.page.has_next %}
{% block pagination.next %}
<li class="next waves-effect">
<a href="{% querystring table.prefixed_page_field=table.page.next_page_number %}">
{% trans '<i class="material-icons">chevron_right</i>' %}
</a>
</li>
{% endblock pagination.next %}
{% endif %}
</ul>
{% endif %}
{% endblock pagination %}
{% endblock table-wrapper %}

3
app/speakers/tests.py Executable file
View File

@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

7
app/speakers/urls.py Executable file
View File

@ -0,0 +1,7 @@
from django.urls import path
from . import views
urlpatterns = [
path("", views.speakers, name="MdBs"),
path("mdb/<int:id>", views.speaker, name="MdB"),
]

52
app/speakers/views.py Executable file
View File

@ -0,0 +1,52 @@
from django.shortcuts import render
from django_tables2 import RequestConfig
from .models import Speaker, LegislativeInfo, LegislativeInstitution
from speeches.models import Speech
from .tables import SpeakerTable
from speeches.tables import SpeakerSpeechTable
from django.http import Http404
from watson import search as watson
from .forms import SearchForm
from speeches.forms import SearchFormSpeech
def speakers(request):
if(request.method == "GET"):
form = SearchForm(request.GET)
if(form.is_valid()):
query = form.cleaned_data["query"]
search_results = watson.filter(Speaker, query)
table = SpeakerTable(search_results)
RequestConfig(request, paginate={'per_page': 20}).configure(table)
context = {"title": "Suchergebnisse für " + query,
"form": form, "table": table}
return render(request, "speakers/speakers.html", context)
else:
form = SearchForm()
table = SpeakerTable(Speaker.objects.all().order_by("last_name"))
RequestConfig(request, paginate={'per_page': 20}).configure(table)
context = {"title": "Suche", "table": table, "form": form}
return render(request, "speakers/speakers.html", context)
def speaker(request, id):
try:
current_speaker = Speaker.objects.get(pk=id)
speech_count = len(Speech.objects.filter(foreign_speaker=id))
current_legislative_info = LegislativeInfo.objects.filter(foreign_speaker=id)
sorted_l_info = current_legislative_info.order_by("legislative_period")
institution_info = LegislativeInstitution.objects.filter(foreign_speaker=id)
sorted_i_info = institution_info.order_by("current_period")
table = SpeakerSpeechTable(Speech.objects.filter(foreign_speaker=id))
RequestConfig(request, paginate={'per_page': 20}).configure(table)
except Speaker.DoesNotExist:
raise Http404("Speaker does not exist")
context = {"title": ("MdB "
+ current_speaker.first_name
+ " " + current_speaker.last_name),
"current_speaker": current_speaker,
"sorted_l_info": sorted_l_info,
"sorted_i_info": sorted_i_info,
"speech_table": table,
"speech_count": speech_count}
return render(request, "speakers/speaker.html", context)