Erste Datenstruktur angelegt. CSV-Import vorbereitet.

This commit is contained in:
2026-07-25 13:25:32 +02:00
parent bf70ac323a
commit 0aa0bb2eeb
18 changed files with 575 additions and 0 deletions
+61
View File
@@ -0,0 +1,61 @@
from django.contrib.auth.models import AbstractUser
from django.db import models
from django.utils.translation import gettext_lazy as _
class User(AbstractUser):
"""
Custom User Model mit expliziter Rollenunterstützung.
Erweitert das Standard-Django-User-Modell um ein 'role'-Feld,
um Admins und Spieler klar zu trennen.
"""
class Role(models.TextChoices):
ADMIN = 'admin', _('Administrator')
PLAYER = 'player', _('Spieler')
role = models.CharField(
max_length=10,
choices=Role.choices,
default=Role.PLAYER,
verbose_name=_('Rolle')
)
def is_admin(self):
return self.role == self.Role.ADMIN
class Player(models.Model):
"""
Das reine Spielerprofil.
Hinweis: Ein Spieler muss nicht unbedingt einen Account (Login) haben,
da wir auch per CSV importierte Gäste unterstützen.
Falls doch ein Account existiert, verknüpfen wir ihn hier 1-zu-1.
"""
user = models.OneToOneField(
User,
on_delete=models.SET_NULL,
null=True,
blank=True,
related_name='player_profile',
verbose_name=_('Benutzeraccount')
)
first_name = models.CharField(_('Vorname'), max_length=100)
last_name = models.CharField(_('Nachname'), max_length=100)
club = models.CharField(
_('Verein/Klasse'),
max_length=150,
blank=True,
default=''
)
class Meta:
ordering = ['last_name', 'first_name']
verbose_name = 'Spieler'
verbose_name_plural = 'Spieler'
def __str__(self):
return f"{self.first_name} {self.last_name} ({self.club})"
+12
View File
@@ -0,0 +1,12 @@
from rest_framework import serializers
from .models import Player
class PlayerSerializer(serializers.ModelSerializer):
class Meta:
model = Player
fields = ['id', 'first_name', 'last_name', 'club']
class PlayerImportDataSerializer(serializers.Serializer):
"""Validiert die Eingabe für den CSV-Import."""
csv_data = serializers.CharField() # Der Raw-CSV Text (Base64 oder Plain)
tournament_id = serializers.IntegerField() # Die ID des Zieltourniers
+7
View File
@@ -0,0 +1,7 @@
from django.urls import path
from .views import PlayerListView, PlayerImportView
urlpatterns = [
path('players/', PlayerListView.as_view(), name='player-list'),
path('players/import/', PlayerImportView.as_view(), name='player-import'),
]
+91
View File
@@ -0,0 +1,91 @@
import csv
import io
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework.permissions import IsAuthenticated
from .models import Player, User
from tournaments.models import Tournament, Registration
from .serializers import PlayerSerializer, PlayerImportDataSerializer
class PlayerListView(APIView):
"""Listet alle Spieler auf."""
def get(self, request):
players = Player.objects.all()
serializer = PlayerSerializer(players, many=True)
return Response(serializer.data)
class PlayerImportView(APIView):
"""
Importiert Spieler aus Moodle-CSV.
Erwartet ein POST mit JSON: { "csv_data": "...", "tournament_id": 1 }
"""
def post(self, request):
serializer = PlayerImportDataSerializer(data=request.data)
if not serializer.is_valid():
return Response(serializer.errors, status=400)
csv_string = serializer.validated_data['csv_data']
tournament_id = serializer.validated_data['tournament_id']
# Ziel-Turnier finden
try:
tournament = Tournament.objects.get(id=tournament_id)
except Tournament.DoesNotExist:
return Response({"error": "Turnier nicht gefunden"}, status=404)
created_count = 0
existing_count = 0
# CSV parsen (Moodle nutzt Semikolon als Separator)
csv_file = io.StringIO(csv_string)
reader = csv.reader(csv_file, delimiter=';')
next(reader, None) # Überspringe Header-Zeile
for row in reader:
if len(row) < 5:
continue
last_name = row[0].strip()
first_name = row[1].strip()
email = row[2].strip()
# city = row[3] (optional)
club = row[4].strip() # Die Moodle-Gruppe/Klasse als "Verein"
if not first_name or not last_name:
continue
# 1. Spieler anlegen oder aktualisieren
player, created = Player.objects.update_or_create(
defaults={'club': club},
first_name=first_name,
last_name=last_name
)
if created:
created_count += 1
else:
existing_count += 1
# 2. Spieler ins Turnier registrieren (Registration anlegen)
Registration.objects.get_or_create(
tournament=tournament,
player=player
)
# Optional: Falls ein Login-Wunsch besteht und keine User existiert:
if email and not User.objects.filter(username=email).exists():
User.objects.create_user(
username=email,
email=email,
password='schach1234' # Default-Passwort für Moodle-Imports
)
return Response({
"message": "Import abgeschlossen",
"created_players": created_count,
"updated_players": existing_count
}, status=201)