Compare commits

..

6 Commits

15 changed files with 296 additions and 21 deletions

View File

@ -106,6 +106,7 @@ AUTH_PASSWORD_VALIDATORS = [
},
]
AUTH_USER_MODEL = 'member.UserAccount'
# Internationalization
# https://docs.djangoproject.com/en/5.2/topics/i18n/

View File

@ -1,7 +1,7 @@
# forms.py
from django import forms
from .models import Person
from .models import Person, UserAccount
class PersonForm(forms.ModelForm):
class Meta:
@ -10,3 +10,21 @@ class PersonForm(forms.ModelForm):
widgets = {
'geburtsdatum': forms.DateInput(attrs={'type': 'date'}),
}
class AccountForm(forms.ModelForm):
password=forms.CharField(widget=forms.PasswordInput())
confirm_password=forms.CharField(widget=forms.PasswordInput())
class Meta:
model = UserAccount
fields = ['username', 'password']
def clean(self):
cleaned_data = super(AccountForm, self).clean()
passwordStr = cleaned_data.get("password")
confirm_passwordStr = cleaned_data.get("confirm_password")
if passwordStr != confirm_passwordStr:
raise forms.ValidateError("password and confirm_password does not match")

View File

@ -26,24 +26,24 @@ class Command(BaseCommand):
self.stdout.write("🔐 Erstelle Benutzerkonten...")
UserAccount.objects.create(
person=p0,
userName="admin",
passwort_hash=make_password("adminpass1234"),
username="admin",
password=make_password("adminpass1234"),
role="superadmin",
isActive=True,
is_active=True,
)
UserAccount.objects.create(
person=p1,
userName="maxadmin",
passwort_hash=make_password("adminpass123"),
username="maxadmin",
password=make_password("adminpass123"),
role="admin",
isActive=True,
is_active=True,
)
UserAccount.objects.create(
person=p2,
userName="erikam",
passwort_hash=make_password("mitglied456"),
username="erikam",
password=make_password("mitglied456"),
role="mitglied",
isActive=True,
is_active=True,
)
self.stdout.write(self.style.SUCCESS("✅ Testdaten erfolgreich eingespielt."))

View File

@ -0,0 +1,105 @@
# Generated by Django 5.2.1 on 2025-06-15 15:41
import django.contrib.auth.models
import django.contrib.auth.validators
import django.utils.timezone
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('auth', '0012_alter_user_first_name_max_length'),
('member', '0001_initial'),
]
operations = [
migrations.AlterModelOptions(
name='useraccount',
options={'verbose_name': 'user', 'verbose_name_plural': 'users'},
),
migrations.AlterModelManagers(
name='useraccount',
managers=[
('objects', django.contrib.auth.models.UserManager()),
],
),
migrations.RemoveField(
model_name='useraccount',
name='isActive',
),
migrations.RemoveField(
model_name='useraccount',
name='lastLogin',
),
migrations.RemoveField(
model_name='useraccount',
name='passwort_hash',
),
migrations.RemoveField(
model_name='useraccount',
name='userName',
),
migrations.AddField(
model_name='useraccount',
name='date_joined',
field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined'),
),
migrations.AddField(
model_name='useraccount',
name='email',
field=models.EmailField(blank=True, max_length=254, verbose_name='email address'),
),
migrations.AddField(
model_name='useraccount',
name='first_name',
field=models.CharField(blank=True, max_length=150, verbose_name='first name'),
),
migrations.AddField(
model_name='useraccount',
name='groups',
field=models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.group', verbose_name='groups'),
),
migrations.AddField(
model_name='useraccount',
name='is_active',
field=models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active'),
),
migrations.AddField(
model_name='useraccount',
name='is_staff',
field=models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status'),
),
migrations.AddField(
model_name='useraccount',
name='is_superuser',
field=models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status'),
),
migrations.AddField(
model_name='useraccount',
name='last_login',
field=models.DateTimeField(blank=True, null=True, verbose_name='last login'),
),
migrations.AddField(
model_name='useraccount',
name='last_name',
field=models.CharField(blank=True, max_length=150, verbose_name='last name'),
),
migrations.AddField(
model_name='useraccount',
name='password',
field=models.CharField(default='changeme123', max_length=128, verbose_name='password'),
preserve_default=False,
),
migrations.AddField(
model_name='useraccount',
name='user_permissions',
field=models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.permission', verbose_name='user permissions'),
),
migrations.AddField(
model_name='useraccount',
name='username',
field=models.CharField(default='username', error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=150, unique=True, validators=[django.contrib.auth.validators.UnicodeUsernameValidator()], verbose_name='username'),
preserve_default=False,
),
]

View File

@ -1,20 +1,17 @@
from django.db import models
from django.contrib.auth.models import AbstractUser
from django.utils import timezone
from .Person import Person
class UserAccount(models.Model):
class UserAccount(AbstractUser):
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,3 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
<path d="M7.5 5l5 5-5 5V5z"/>
</svg>

After

Width:  |  Height:  |  Size: 119 B

View File

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" class="size-4">
<path fill-rule="evenodd" d="M5 3.25V4H2.75a.75.75 0 0 0 0 1.5h.3l.815 8.15A1.5 1.5 0 0 0 5.357 15h5.285a1.5 1.5 0 0 0 1.493-1.35l.815-8.15h.3a.75.75 0 0 0 0-1.5H11v-.75A2.25 2.25 0 0 0 8.75 1h-1.5A2.25 2.25 0 0 0 5 3.25Zm2.25-.75a.75.75 0 0 0-.75.75V4h3v-.75a.75.75 0 0 0-.75-.75h-1.5ZM6.05 6a.75.75 0 0 1 .787.713l.275 5.5a.75.75 0 0 1-1.498.075l-.275-5.5A.75.75 0 0 1 6.05 6Zm3.9 0a.75.75 0 0 1 .712.787l-.275 5.5a.75.75 0 0 1-1.498-.075l.275-5.5a.75.75 0 0 1 .786-.711Z" clip-rule="evenodd" />
</svg>

After

Width:  |  Height:  |  Size: 603 B

View File

@ -0,0 +1,26 @@
{% extends "master.html" %}
{% block content %}
<div class="members-table">
<h3>{{ action }} Person</h3>
<form method="post">
{% csrf_token %}
<div class="form-group">
<label for="{{form.username.id_for_label}}">Username</label>
{{form.username}}
</div>
<div class="form-group">
<label for="{{form.password.id_for_label}}">Password</label>
{{form.password}}
</div>
<div class="form-group">
<label for="{{form.password.id_for_label}}">Password (confirmation)</label>
{{form.confirm_password}}
</div>
<button class="button" type="submit">Speichern</button>
<a href="{% url 'details' id %}" class="button" style="background:#444;">Abbrechen</a>
</form>
</div>
{% endblock %}
{% block title %}
{{ action }} Account
{% endblock %}

View File

@ -0,0 +1,15 @@
{% extends "master.html" %}
{% block content %}
<div class="members-table">
<h3>Account löschen</h3>
<p>Möchtest du den Useraccount <strong>{{account.username}}</strong> der {{ account.person.vorname }} {{account.person.nachname}} zugeordnet ist, wirklich löschen?</p>
<form method="post">
{% csrf_token %}
<button class="button" type="submit">Ja, löschen</button>
<a href="{% url 'details' id %}" class="button" style="background:#444;">Abbrechen</a>
</form>
</div>
{% endblock %}
{% block title %}
Account löschen
{% endblock %}

View File

@ -0,0 +1,15 @@
{% extends "master.html" %}
{% block content %}
<div class="members-table">
<h3>Mitglied löschen</h3>
<p>Möchtest du <strong>{{ person.nachname }} {{person.vorname}}</strong> wirklich löschen?</p>
<form method="post">
{% csrf_token %}
<button class="button" type="submit">Ja, löschen</button>
<a href="{% url 'members' %}" class="button" style="background:#444;">Abbrechen</a>
</form>
</div>
{% endblock %}
{% block title %}
Mitglied löschen
{% endblock %}

View File

@ -1,4 +1,7 @@
{% extends "master.html" %}
{% load static %}
{% load svg %}
{% block title %}
Details zu {{ mymember.firstname }} {{ mymember.lastname }}
@ -10,6 +13,33 @@
<p>Geburtsdatum: {{ mymember.geburtsdatum }}</p>
<table>
<thead>
<tr>
<th>Name</th>
<th>Rolle</th>
<th>Aktiv</th>
<th>Aktionen</th>
</tr>
</thead>
<tbody>
{% for x in accounts %}
<tr>
<td>{{ x.username }}</td>
<td>{{ x.rolle }}</td>
<td>{% if x.aktiv %}{% inline_svg 'icons/uxwing/check.svg' 'icon' %}{% endif %}</td>
<td><a href="{% url 'edit_account' x.id %}" title="Bearbeiten"><img src="{% static 'icons/heroicons/pencil.svg'%}" class="icon"></a> <a href="{% url 'delete_account' x.id %}" title="Löschen"><img src="{% static 'icons/heroicons/trash.svg'%}" class="icon"></a></td>
</tr>
{% endfor %}
<tr>
<td colspan="4">
<a href="{% url 'create_account' mymember.id %}" class="button"><img src="{% static 'icons/heroicons/plus.svg'%}" class="icon"> Konto hinzufügen</a>
</td>
</tr>
</tbody>
</table>
<p>Back to <a href="/members">Members</a></p>
</div>
{% endblock %}

View File

@ -14,6 +14,7 @@
<thead>
<tr>
<th>Name</th>
<th>Accounts</th>
<th>Aktiv</th>
<th>Aktionen</th>
</tr>
@ -22,8 +23,9 @@
{% for x in mymembers %}
<tr>
<td><a href="{% url 'details' x.id %}"><img src="{% static 'icons/arrow-right.svg' %}" class="icon">{{ x.nachname }} {{ x.vorname}}</a> </td>
<td>{{x.accounts}}</td>
<td>{% if x.aktiv %}{% inline_svg 'icons/uxwing/check.svg' 'icon' %}{% endif %}</td>
<td><a href="{% url 'edit' x.id %}" title="Bearbeiten"><img src="{% static 'icons/heroicons/pencil.svg'%}" class="icon"></a></td>
<td><a href="{% url 'edit' x.id %}" title="Bearbeiten"><img src="{% static 'icons/heroicons/pencil.svg'%}" class="icon"></a> <a href="{% url 'delete' x.id %}" title="Löschen"><img src="{% static 'icons/heroicons/trash.svg'%}" class="icon"></a></td>
</tr>
{% endfor %}
</tbody>

View File

@ -25,3 +25,6 @@
</form>
</div>
{% endblock %}
{% block title %}
{{ action }} Person
{% endblock %}

View File

@ -6,4 +6,10 @@ urlpatterns = [
path('members/details/<int:id>', views.details, name="details"),
path('members/create/', views.create, name="create"),
path('members/edit/<int:id>', views.edit, name="edit"),
path('members/delete/<int:id>', views.delete, name="delete"),
path('members/account/details/<int:id>', views.details_account, name="details_account"),
path('members/account/edit/<int:id>', views.edit_account, name="edit_account"),
path('members/account/create/<int:id>', views.create_account, name="create_account"),
path('members/account/edit/<int:id>', views.edit_account, name="edit_account"),
path('members/account/delete/<int:id>', views.delete_account, name="delete_account"),
]

View File

@ -1,11 +1,12 @@
from django.http import HttpResponse
from django.shortcuts import render, redirect, get_object_or_404
from django.template import loader
from .models import Person
from .forms import PersonForm
from django.db.models import Count
from .models import Person, UserAccount
from .forms import PersonForm, AccountForm
def members(request):
mymembers=Person.objects.all().values()
mymembers=Person.objects.annotate(accounts=Count('benutzerkonten'))
template=loader.get_template("memberlist.html")
context = {
'mymembers': mymembers
@ -14,9 +15,11 @@ def members(request):
def details(request, id):
mymember = Person.objects.get(id=id)
accounts = UserAccount.objects.filter(person_id=id)
template = loader.get_template("details.html")
context = {
'mymember': mymember
'mymember': mymember,
'accounts': accounts
}
return HttpResponse(template.render(context, request))
@ -40,3 +43,51 @@ def edit(request, id):
else:
form = PersonForm(instance=person)
return render(request, 'person_form.html', {'form': form, 'action': 'Bearbeiten'})
def delete(request, id):
person = get_object_or_404(Person, id=id)
if request.method == 'POST':
person.delete()
return redirect('members')
return render(request, 'confirm_delete.html', { 'person': person})
def details_account(request, id):
account = get_object_or_404(Account, id=id)
template = loader.get_template("details_account.html")
context = {
'account': account,
}
return HttpResponse(template.render(context, request))
def create_account(request, id):
person = get_object_or_404(Person, id=id)
if request.method == "POST":
form = AccountForm(request.POST)
if form.is_valid():
account=form.save(commit=False)
account.person = person
account.save()
return redirect("details", person.id)
else:
form = AccountForm()
return render(request, "account_form.html", {'form': form, 'action': 'Erstellen', 'id': id})
def edit_account(request, id):
account = get_object_or_404(UserAccount, id=id)
if request.method == 'POST':
form = AccountForm(request.POST, instance=account)
if form.is_valid():
form.save()
return redirect('details', account.person.id)
else:
form = AccountForm(instance=account)
return render(request, 'account_form.html', {'form': form, 'action': "Bearbeiten" , 'id': account.person.id})
def delete_account(request, id):
account=get_object_or_404(UserAccount, id=id)
person_id=account.person.id
if (request.method=='POST'):
account.delete()
return redirect("details", person_id)
return render(request, "confirm_account_delete.html", {'account': account, 'id': account.person.id})