Compare commits

...

16 Commits

23 changed files with 498 additions and 5 deletions

138
.gitignore vendored Normal file
View File

@ -0,0 +1,138 @@
# Django #
*.log
*.pot
*.pyc
__pycache__
db.sqlite3
media
# Backup files #
*.bak
# If you are using PyCharm #
# User-specific stuff
.idea/**/workspace.xml
.idea/**/tasks.xml
.idea/**/usage.statistics.xml
.idea/**/dictionaries
.idea/**/shelf
# AWS User-specific
.idea/**/aws.xml
# Generated files
.idea/**/contentModel.xml
# Sensitive or high-churn files
.idea/**/dataSources/
.idea/**/dataSources.ids
.idea/**/dataSources.local.xml
.idea/**/sqlDataSources.xml
.idea/**/dynamic.xml
.idea/**/uiDesigner.xml
.idea/**/dbnavigator.xml
# Gradle
.idea/**/gradle.xml
.idea/**/libraries
# File-based project format
*.iws
# IntelliJ
out/
# JIRA plugin
atlassian-ide-plugin.xml
# Python #
*.py[cod]
*$py.class
# Distribution / packaging
.Python build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
*.whl
*.egg-info/
.installed.cfg
*.egg
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.coverage
.coverage.*
.cache
.pytest_cache/
nosetests.xml
coverage.xml
*.cover
.hypothesis/
# Jupyter Notebook
.ipynb_checkpoints
# pyenv
.python-version
# celery
celerybeat-schedule.*
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# mkdocs documentation
/site
# mypy
.mypy_cache/
# Sublime Text #
*.tmlanguage.cache
*.tmPreferences.cache
*.stTheme.cache
*.sublime-workspace
*.sublime-project
# sftp configuration file
sftp-config.json
# Package control specific files Package
Control.last-run
Control.ca-list
Control.ca-bundle
Control.system-ca-bundle
GitHub.sublime-settings
# Visual Studio Code #
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
.history

View File

@ -9,8 +9,10 @@ https://docs.djangoproject.com/en/5.2/topics/settings/
For the full list of settings and their values, see
https://docs.djangoproject.com/en/5.2/ref/settings/
"""
import pymysql
pymysql.install_as_MySQLdb()
from pathlib import Path
from decouple import config
# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent
@ -25,7 +27,7 @@ SECRET_KEY = 'django-insecure-tulz*odc=l1x(g^-=ne!y7^@lg1uce)=ha^!0wi5qkifq&#^sg
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True
ALLOWED_HOSTS = []
ALLOWED_HOSTS = [ "albani.sternbauer.de" ]
# Application definition
@ -37,6 +39,7 @@ INSTALLED_APPS = [
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'member',
]
MIDDLEWARE = [
@ -74,8 +77,12 @@ WSGI_APPLICATION = 'FloriCore.wsgi.application'
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': BASE_DIR / 'db.sqlite3',
'ENGINE': 'django.db.backends.mysql',
'NAME': config('DB_NAME'),
'USER': config('DB_USER'),
'PASSWORD': config('DB_PASSWORD'),
'HOST': config('DB_HOST', default='localhost'),
'PORT': config('DB_PORT', default='3306'),
}
}

View File

@ -15,8 +15,9 @@ Including another URLconf
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
"""
from django.contrib import admin
from django.urls import path
from django.urls import path,include
urlpatterns = [
path('', include('member.urls')),
path('admin/', admin.site.urls),
]

0
member/__init__.py Normal file
View File

3
member/admin.py Normal file
View File

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

6
member/apps.py Normal file
View File

@ -0,0 +1,6 @@
from django.apps import AppConfig
class MemberConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'member'

View File

View File

View File

@ -0,0 +1,49 @@
from django.conf import settings
from django.core.management.base import BaseCommand
from member.models import Person, UserAccount
from django.contrib.auth.hashers import make_password
class Command(BaseCommand):
help = "Füllt die DB mit Beispieldaten"
def handle(self, *args, **kwargs):
if not settings.DEBUG:
raise CommandError("❌ Dieses Kommando darf nur in DEBUG-Umgebungen verwendet werden.")
# Bestehende Daten löschen
self.stdout.write("🧹 Lösche bestehende Personen und Benutzerkonten...")
UserAccount.objects.all().delete()
Person.objects.all().delete()
# Personen anlegen
self.stdout.write("👤 Erstelle Personen...")
p0 = Person.objects.create(vorname="Florian", nachname="von Lorch", geburtsdatum="0304-05-04", aktiv=True)
p1 = Person.objects.create(vorname="Max", nachname="Mustermann", geburtsdatum="1980-05-01", aktiv=True)
p2 = Person.objects.create(vorname="Erika", nachname="Beispiel", geburtsdatum="1992-03-15", aktiv=True)
p3 = Person.objects.create(vorname="Thomas", nachname="Feuer", geburtsdatum=None, aktiv=True)
p4 = Person.objects.create(vorname="Julia", nachname="Wehr", geburtsdatum="1999-11-20", aktiv=False)
# Benutzerkonten anlegen
self.stdout.write("🔐 Erstelle Benutzerkonten...")
UserAccount.objects.create(
person=p0,
userName="admin",
passwort_hash=make_password("adminpass1234"),
role="superadmin",
isActive=True,
)
UserAccount.objects.create(
person=p1,
userName="maxadmin",
passwort_hash=make_password("adminpass123"),
role="admin",
isActive=True,
)
UserAccount.objects.create(
person=p2,
userName="erikam",
passwort_hash=make_password("mitglied456"),
role="mitglied",
isActive=True,
)
self.stdout.write(self.style.SUCCESS("✅ Testdaten erfolgreich eingespielt."))

View File

@ -0,0 +1,37 @@
# Generated by Django 5.2.1 on 2025-05-10 12:49
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
]
operations = [
migrations.CreateModel(
name='Person',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('vorname', models.CharField(max_length=100)),
('nachname', models.CharField(max_length=100)),
('geburtsdatum', models.DateField(blank=True, null=True)),
('aktiv', models.BooleanField(default=True)),
],
),
migrations.CreateModel(
name='UserAccount',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('userName', models.CharField(max_length=150, unique=True)),
('passwort_hash', models.CharField(max_length=128)),
('role', models.CharField(choices=[('mitglied', 'Mitglied'), ('geraetewart', 'Gerätewart'), ('kommandant', 'Kommandant'), ('admin', 'Administrator')], max_length=50)),
('isActive', models.BooleanField(default=True)),
('lastLogin', models.DateTimeField(blank=True, null=True)),
('person', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='benutzerkonten', to='member.person')),
],
),
]

View File

11
member/models/Person.py Normal file
View File

@ -0,0 +1,11 @@
from django.db import models
from django.utils import timezone
class Person(models.Model):
vorname = models.CharField(max_length=100)
nachname = models.CharField(max_length=100)
geburtsdatum = models.DateField(null=True, blank=True)
aktiv = models.BooleanField(default=True)
def __str__(self):
return f"{self.vorname} {self.nachname}"

View File

@ -0,0 +1,20 @@
from django.db import models
from django.utils import timezone
from .Person import Person
class UserAccount(models.Model):
person = models.ForeignKey(Person, on_delete=models.CASCADE, related_name='benutzerkonten')
userName = models.CharField(max_length=150, unique=True)
passwort_hash = models.CharField(max_length=128)
role = models.CharField(max_length=50, choices=[
('mitglied', 'Mitglied'),
('geraetewart', 'Gerätewart'),
('kommandant', 'Kommandant'),
('admin', 'Administrator'),
])
isActive = models.BooleanField(default=True)
lastLogin = models.DateTimeField(null=True, blank=True)
def __str__(self):
return f"{self.benutzername} ({self.rolle})"

View File

@ -0,0 +1,2 @@
from .Person import Person
from .UserAccount import UserAccount

View File

@ -0,0 +1,131 @@
:root {
--bg-dark: #121212;
--bg-sidebar: #2a0000;
--bg-panel: #1e1e1e;
--bg-accent: #b22222;
--text-light: #f1f1f1;
--text-muted: #999;
--highlight: #f5a623;
--font-sans: 'Inter', 'Roboto', sans-serif;
}
body {
margin: 0;
font-family: var(--font-sans);
background-color: var(--bg-dark);
color: var(--text-light);
display: flex;
}
.sidebar {
width: 220px;
height: 100vh;
background-color: var(--bg-sidebar);
color: var(--text-light);
display: flex;
flex-direction: column;
padding: 20px;
}
.sidebar h2 {
font-size: 1.2rem;
margin-bottom: 2rem;
}
.sidebar nav a {
color: var(--text-light);
text-decoration: none;
margin: 10px 0;
display: flex;
align-items: center;
padding: 8px 12px;
border-radius: 4px;
transition: background 0.2s ease;
}
.sidebar nav a:hover,
.sidebar nav a.active {
background-color: var(--bg-accent);
}
.main {
padding: 20px;
display: flex;
height: 100vh;
box-sizing: border-box;
background-color: var(--bg-dark);
overflow-y: auto;
}
.members-table {
flex: 2;
margin-right: 20px;
background-color: var(--bg-panel);
padding: 20px;
border-radius: 8px;
overflow-y: auto;
}
.members-table table {
width: 100%;
border-collapse: collapse;
}
.members-table th,
.members-table td {
text-align: left;
padding: 10px;
border-bottom: 1px solid #333;
}
.members-table th {
color: var(--highlight);
font-weight: 600;
}
.members-table tr:hover {
background-color: rgba(255, 255, 255, 0.05);
cursor: pointer;
}
.details-panel {
flex: 1;
background-color: var(--bg-panel);
padding: 20px;
border-radius: 8px;
}
.details-panel h3 {
margin-top: 0;
}
.details-panel .info {
margin-bottom: 10px;
color: var(--text-muted);
}
.badge {
display: inline-block;
background-color: var(--bg-accent);
padding: 4px 8px;
border-radius: 4px;
font-size: 0.8rem;
color: #fff;
margin-right: 5px;
}
.button {
background-color: var(--bg-accent);
color: #fff;
border: none;
padding: 8px 16px;
font-size: 0.9rem;
border-radius: 4px;
cursor: pointer;
margin-top: 10px;
}
.button:hover {
background-color: #d63031;
}

View File

@ -0,0 +1,13 @@
{% extends "master.html" %}
{% block title %}
Details zu {{ mymember.firstname }} {{ mymember.lastname }}
{% endblock %}
{% block content %}
<h1>{{ mymember.vorname }} {{ mymember.nachname }}</h1>
<p>Geburtsdatum: {{ mymember.geburtsdatum }}</p>
<p>Back to <a href="/members">Members</a></p>
{% endblock %}

View File

@ -0,0 +1,14 @@
{% load static %}
<!DOCTYPE html>
<html>
<head>
<title>{% block title %}{% endblock %}</title>
<link rel="stylesheet" href="{% static 'member/css/styles.css' %}">
</head>
<body>
{% include "partials/sidebar.html" %}
<div class="main">
{% block content %}{% endblock %}
</div>
</body>
</html>

View File

@ -0,0 +1,19 @@
{% extends "master.html" %}
{% block title %}
List of all persons
{% endblock %}
{% block content %}
<div class="members-table">
<h3>Members</h3>
<ul>
{% for x in mymembers %}
<li><a href="details/{{x.id}}"> {{ x.vorname }} {{ x.nachname }}</a></li>
{% empty %}
<li>Keine Datensaetze gefunden</li>
{% endfor %}
</ul>
</div>
{% endblock %}

View File

@ -0,0 +1,7 @@
<div class="sidebar">
<h2>🧯 VERWALTUNG</h2>
<nav>
<a href={% url 'members' %} class="{% if request.path == '/members/' %}active{% endif %}">Mitglieder</a>
</nav>
</div>

3
member/tests.py Normal file
View File

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

7
member/urls.py Normal file
View File

@ -0,0 +1,7 @@
from django.urls import path
from . import views
urlpatterns = [
path('members/', views.members, name='members'),
path('members/details/<int:id>', views.details, name="details"),
]

19
member/views.py Normal file
View File

@ -0,0 +1,19 @@
from django.http import HttpResponse
from django.template import loader
from .models import Person
def members(request):
mymembers=Person.objects.all().values()
template=loader.get_template("memberlist.html")
context = {
'mymembers': mymembers
}
return HttpResponse(template.render(context, request))
def details(request, id):
mymember = Person.objects.get(id=id)
template = loader.get_template("details.html")
context = {
'mymember': mymember
}
return HttpResponse(template.render(context, request))

6
requirements.txt Normal file
View File

@ -0,0 +1,6 @@
asgiref==3.8.1
Django==5.2.1
mysql-connector-python==9.3.0
PyMySQL==1.1.1
python-decouple==3.8
sqlparse==0.5.3