init project

This commit is contained in:
Jonas Heinrich 2025-08-21 09:40:22 +02:00
parent a26fa2c64a
commit dc0440fcef
291 changed files with 124865 additions and 485 deletions

1
council/__init__.py Normal file
View file

@ -0,0 +1 @@

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

36
council/admin.py Normal file
View file

@ -0,0 +1,36 @@
from django.contrib import admin
from .models import PublicBody, Party, Member, Question, Answer
@admin.register(PublicBody)
class PublicBodyAdmin(admin.ModelAdmin):
list_display = ("name", "website")
prepopulated_fields = {"slug": ("name",)}
search_fields = ("name",)
@admin.register(Party)
class PartyAdmin(admin.ModelAdmin):
list_display = ("name", "abbreviation")
search_fields = ("name", "abbreviation")
@admin.register(Member)
class MemberAdmin(admin.ModelAdmin):
list_display = ("first_name", "last_name", "public_body", "party")
list_filter = ("public_body", "party")
search_fields = ("first_name", "last_name")
@admin.register(Question)
class QuestionAdmin(admin.ModelAdmin):
list_display = ("title", "member", "created_at")
list_filter = ("member",)
search_fields = ("title", "body")
@admin.register(Answer)
class AnswerAdmin(admin.ModelAdmin):
list_display = ("question", "answered_by", "created_at")
list_filter = ("answered_by",)
search_fields = ("body",)

7
council/apps.py Normal file
View file

@ -0,0 +1,7 @@
from django.apps import AppConfig
class CouncilConfig(AppConfig):
default_auto_field = "django.db.models.BigAutoField"
name = "council"
verbose_name = "Gemeinderat"

View file

@ -0,0 +1,105 @@
# Generated by Django on initial setup
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
initial = True
dependencies = []
operations = [
migrations.CreateModel(
name="PublicBody",
fields=[
("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")),
("name", models.CharField(max_length=255, unique=True)),
("slug", models.SlugField(max_length=255, unique=True)),
("website", models.URLField(blank=True)),
("description", models.TextField(blank=True)),
("created_at", models.DateTimeField(auto_now_add=True)),
],
options={
"verbose_name": "Gemeinde",
"verbose_name_plural": "Gemeinden",
"ordering": ["name"],
},
),
migrations.CreateModel(
name="Party",
fields=[
("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")),
("name", models.CharField(max_length=255, unique=True)),
("abbreviation", models.CharField(blank=True, max_length=50)),
("color", models.CharField(blank=True, help_text="Hex-Farbe, z. B. #00AAFF", max_length=7)),
],
options={
"verbose_name": "Partei",
"verbose_name_plural": "Parteien",
"ordering": ["name"],
},
),
migrations.CreateModel(
name="Member",
fields=[
("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")),
("first_name", models.CharField(max_length=150)),
("last_name", models.CharField(max_length=150)),
("email", models.EmailField(blank=True, max_length=254)),
(
"party",
models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name="members", to="council.party"),
),
(
"public_body",
models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name="members", to="council.publicbody"),
),
],
options={
"verbose_name": "Stadträt:in",
"verbose_name_plural": "Stadträt:innen",
"ordering": ["last_name", "first_name"],
"unique_together": {("first_name", "last_name", "public_body")},
},
),
migrations.CreateModel(
name="Question",
fields=[
("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")),
("title", models.CharField(max_length=255)),
("body", models.TextField()),
("created_at", models.DateTimeField(auto_now_add=True)),
(
"member",
models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name="questions", to="council.member"),
),
],
options={
"verbose_name": "Frage",
"verbose_name_plural": "Fragen",
"ordering": ["-created_at"],
},
),
migrations.CreateModel(
name="Answer",
fields=[
("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")),
("body", models.TextField()),
("created_at", models.DateTimeField(auto_now_add=True)),
(
"answered_by",
models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name="answers", to="council.member"),
),
(
"question",
models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name="answers", to="council.question"),
),
],
options={
"verbose_name": "Antwort",
"verbose_name_plural": "Antworten",
"ordering": ["created_at"],
},
),
]

View file

@ -0,0 +1 @@

97
council/models.py Normal file
View file

@ -0,0 +1,97 @@
from django.db import models
class PublicBody(models.Model):
name = models.CharField(max_length=255, unique=True)
slug = models.SlugField(max_length=255, unique=True)
website = models.URLField(blank=True)
description = models.TextField(blank=True)
created_at = models.DateTimeField(auto_now_add=True)
class Meta:
verbose_name = "Gemeinde"
verbose_name_plural = "Gemeinden"
ordering = ["name"]
def __str__(self) -> str: # pragma: no cover - trivial
return self.name
class Party(models.Model):
name = models.CharField(max_length=255, unique=True)
abbreviation = models.CharField(max_length=50, blank=True)
color = models.CharField(max_length=7, blank=True, help_text="Hex-Farbe, z. B. #00AAFF")
class Meta:
verbose_name = "Partei"
verbose_name_plural = "Parteien"
ordering = ["name"]
def __str__(self) -> str: # pragma: no cover
return self.abbreviation or self.name
class Member(models.Model):
first_name = models.CharField(max_length=150)
last_name = models.CharField(max_length=150)
email = models.EmailField(blank=True)
public_body = models.ForeignKey(
PublicBody, related_name="members", on_delete=models.SET_NULL, null=True, blank=True
)
party = models.ForeignKey(Party, related_name="members", on_delete=models.SET_NULL, null=True, blank=True)
class Meta:
verbose_name = "Stadträt:in"
verbose_name_plural = "Stadträt:innen"
ordering = ["last_name", "first_name"]
unique_together = ("first_name", "last_name", "public_body")
def __str__(self) -> str: # pragma: no cover
return f"{self.first_name} {self.last_name}"
class Question(models.Model):
title = models.CharField(max_length=255)
body = models.TextField()
member = models.ForeignKey(Member, related_name="questions", on_delete=models.CASCADE)
created_at = models.DateTimeField(auto_now_add=True)
class Meta:
verbose_name = "Frage"
verbose_name_plural = "Fragen"
ordering = ["-created_at"]
def __str__(self) -> str: # pragma: no cover
return self.title
class Answer(models.Model):
question = models.ForeignKey(Question, related_name="answers", on_delete=models.CASCADE)
answered_by = models.ForeignKey(Member, related_name="answers", on_delete=models.SET_NULL, null=True, blank=True)
body = models.TextField()
created_at = models.DateTimeField(auto_now_add=True)
class Meta:
verbose_name = "Antwort"
verbose_name_plural = "Antworten"
ordering = ["created_at"]
def __str__(self) -> str: # pragma: no cover
return f"Antwort zu: {self.question.title}"
class Vote(models.Model):
title = models.CharField(max_length=255)
description = models.TextField(blank=True)
public_body = models.ForeignKey(PublicBody, related_name="votes", on_delete=models.CASCADE)
date = models.DateField(null=True, blank=True)
members = models.ManyToManyField(Member, related_name="votes", blank=True)
created_at = models.DateTimeField(auto_now_add=True)
class Meta:
verbose_name = "Abstimmung"
verbose_name_plural = "Abstimmungen"
ordering = ["-date", "-created_at"]
def __str__(self) -> str: # pragma: no cover
return self.title

13
council/urls.py Normal file
View file

@ -0,0 +1,13 @@
from django.urls import path
from . import views
urlpatterns = [
path("", views.home, name="home"),
path("gemeinden/", views.public_bodies, name="public_bodies"),
path("gemeinden/<slug:slug>/", views.public_body_detail, name="public_body_detail"),
path("mitglieder/", views.members, name="members"),
path("mitglieder/<int:pk>/", views.member_detail, name="member_detail"),
path("parteien/", views.parties, name="parties"),
path("parteien/<int:pk>/", views.party_detail, name="party_detail"),
path("fragen/", views.questions, name="questions"),
]

61
council/views.py Normal file
View file

@ -0,0 +1,61 @@
from django.shortcuts import render, get_object_or_404
from .models import PublicBody, Party, Member, Question, Vote
def home(request):
return render(request, "index.html", {})
def public_bodies(request):
items = PublicBody.objects.all()
return render(request, "council/public_bodies.html", {"items": items})
def public_body_detail(request, slug: str):
body = get_object_or_404(PublicBody, slug=slug)
members = body.members.select_related("party").all()
parties = Party.objects.filter(members__public_body=body).distinct()
votes = body.votes.all()
return render(
request,
"council/public_body_detail.html",
{"body": body, "members": members, "parties": parties, "votes": votes},
)
def members(request):
items = Member.objects.select_related("public_body", "party").all()
return render(request, "council/members.html", {"items": items})
def member_detail(request, pk: int):
member = get_object_or_404(Member.objects.select_related("public_body", "party"), pk=pk)
questions = member.questions.all()
votes = member.votes.select_related("public_body").all()
return render(
request,
"council/member_detail.html",
{"member": member, "questions": questions, "votes": votes},
)
def parties(request):
items = Party.objects.all()
return render(request, "council/parties.html", {"items": items})
def party_detail(request, pk: int):
party = get_object_or_404(Party, pk=pk)
members = party.members.select_related("public_body").all()
public_bodies = PublicBody.objects.filter(members__party=party).distinct()
questions = Question.objects.filter(member__party=party).select_related("member").all()
return render(
request,
"council/party_detail.html",
{"party": party, "members": members, "public_bodies": public_bodies, "questions": questions},
)
def questions(request):
items = Question.objects.select_related("member").all()
return render(request, "council/questions.html", {"items": items})