feat: Erweiterung der Spielerstellung um Elternsuche mit Autocomplete

This commit is contained in:
Matthias Nagel 2025-10-01 13:36:58 +02:00
parent 956d7a45e9
commit 4203a696d9
12 changed files with 103 additions and 11 deletions

View File

@ -28,8 +28,10 @@ class CustomUserChangeForm(forms.ModelForm):
fields = ('username', 'first_name', 'last_name', 'email', 'birth_date', 'player_number', 'team') fields = ('username', 'first_name', 'last_name', 'email', 'birth_date', 'player_number', 'team')
class PlayerCreationForm(forms.ModelForm): class PlayerCreationForm(forms.ModelForm):
parent1_email = forms.EmailField(required=False) parent1_search = forms.CharField(label="Search Parent 1 by Username/Email", required=False)
parent2_email = forms.EmailField(required=False) parent1_new = forms.EmailField(label="Or create new Parent 1 with Email", required=False)
parent2_search = forms.CharField(label="Search Parent 2 by Username/Email", required=False)
parent2_new = forms.EmailField(label="Or create new Parent 2 with Email", required=False)
birth_date = forms.DateField(input_formats=['%d.%m.%Y', '%Y-%m-%d'], widget=forms.DateInput(format='%d.%m.%Y', attrs={'type': 'date'})) birth_date = forms.DateField(input_formats=['%d.%m.%Y', '%Y-%m-%d'], widget=forms.DateInput(format='%d.%m.%Y', attrs={'type': 'date'}))
class Meta: class Meta:

View File

@ -0,0 +1,19 @@
# Generated by Django 5.2.6 on 2025-10-01 10:00
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('accounts', '0003_absenceperiod'),
]
operations = [
migrations.AddField(
model_name='customuser',
name='parents',
field=models.ManyToManyField(blank=True, related_name='children', to=settings.AUTH_USER_MODEL),
),
]

View File

@ -7,6 +7,7 @@ class CustomUser(AbstractUser):
birth_date = models.DateField(null=True, blank=True) birth_date = models.DateField(null=True, blank=True)
player_number = models.IntegerField(default=999) player_number = models.IntegerField(default=999)
team = models.ForeignKey('clubs.Team', on_delete=models.SET_NULL, null=True, blank=True, related_name='players') team = models.ForeignKey('clubs.Team', on_delete=models.SET_NULL, null=True, blank=True, related_name='players')
parents = models.ManyToManyField('self', symmetrical=False, blank=True, related_name='children')
@property @property
def age(self): def age(self):

View File

@ -17,4 +17,39 @@
</div> </div>
</div> </div>
</div> </div>
<script>
document.addEventListener('DOMContentLoaded', function() {
const parent1SearchInput = document.getElementById('id_parent1_search');
const parent2SearchInput = document.getElementById('id_parent2_search');
function setupAutocomplete(inputElement) {
const datalist = document.createElement('datalist');
datalist.id = inputElement.id + '_list';
inputElement.setAttribute('list', datalist.id);
document.body.appendChild(datalist); // Append to body instead of parentNode
inputElement.addEventListener('input', function() {
const query = this.value;
if (query.length < 2) {
datalist.innerHTML = '';
return;
}
fetch(`/accounts/user-search/?q=${query}`)
.then(response => response.json())
.then(data => {
datalist.innerHTML = '';
data.forEach(userString => {
const option = document.createElement('option');
option.value = userString;
datalist.appendChild(option);
});
});
});
}
setupAutocomplete(parent1SearchInput);
setupAutocomplete(parent2SearchInput);
});
</script>
{% endblock %} {% endblock %}

View File

@ -9,4 +9,5 @@ urlpatterns = [
path('logout/', auth_views.LogoutView.as_view(), name='logout'), path('logout/', auth_views.LogoutView.as_view(), name='logout'),
path('profile/', views.edit_profile, name='edit_profile'), path('profile/', views.edit_profile, name='edit_profile'),
path('player/add/', views.PlayerCreateView.as_view(), name='player-add'), path('player/add/', views.PlayerCreateView.as_view(), name='player-add'),
path('user-search/', views.user_search, name='user-search'),
] ]

View File

@ -4,6 +4,8 @@ from django.views.generic.edit import CreateView
from django.contrib.auth.decorators import login_required from django.contrib.auth.decorators import login_required
from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin
from django.contrib.auth import views as auth_views from django.contrib.auth import views as auth_views
from django.db.models import Q
from django.http import JsonResponse
from .forms import InvitationCodeForm, CustomUserCreationForm, CustomUserChangeForm, PlayerCreationForm from .forms import InvitationCodeForm, CustomUserCreationForm, CustomUserChangeForm, PlayerCreationForm
from .models import CustomUser, InvitationCode from .models import CustomUser, InvitationCode
import uuid import uuid
@ -78,14 +80,38 @@ class PlayerCreateView(LoginRequiredMixin, HeadCoachCheckMixin, CreateView):
# Create invitation code for player # Create invitation code for player
InvitationCode.objects.create(code=str(uuid.uuid4()), user=player) InvitationCode.objects.create(code=str(uuid.uuid4()), user=player)
# Create parent users and invitation codes # Handle parents
for email_field in ['parent1_email', 'parent2_email']: for i in ['1', '2']:
email = form.cleaned_data.get(email_field) search_identifier = form.cleaned_data.get(f'parent{i}_search')
if email: new_email = form.cleaned_data.get(f'parent{i}_new')
parent_user = CustomUser.objects.filter(email=email).first()
if not parent_user: if search_identifier:
parent_user = CustomUser.objects.create(email=email, username=email, is_active=False) import re
match = re.search(r'\((\w+)\)', search_identifier)
if match:
username = match.group(1)
try:
parent_user = CustomUser.objects.get(username=username)
player.parents.add(parent_user)
except CustomUser.DoesNotExist:
form.add_error(f'parent{i}_search', 'User not found.')
else:
# if no user is selected from the list, maybe the user typed an email/username directly
try:
parent_user = CustomUser.objects.get(Q(username=search_identifier) | Q(email=search_identifier))
player.parents.add(parent_user)
except CustomUser.DoesNotExist:
form.add_error(f'parent{i}_search', 'User not found.')
except CustomUser.MultipleObjectsReturned:
form.add_error(f'parent{i}_search', 'Multiple users found. Please be more specific.')
elif new_email:
parent_user, created = CustomUser.objects.get_or_create(email=new_email, defaults={'username': new_email, 'is_active': False})
InvitationCode.objects.create(code=str(uuid.uuid4()), user=parent_user) InvitationCode.objects.create(code=str(uuid.uuid4()), user=parent_user)
player.parents.add(parent_user)
if form.errors:
return self.form_invalid(form)
return redirect(self.success_url) return redirect(self.success_url)
@ -94,3 +120,11 @@ class MyLoginView(auth_views.LoginView):
if request.user.is_authenticated: if request.user.is_authenticated:
return redirect('dashboard') return redirect('dashboard')
return super().get(request, *args, **kwargs) return super().get(request, *args, **kwargs)
def user_search(request):
q = request.GET.get('q', '')
users = CustomUser.objects.filter(last_name__istartswith=q).values('username', 'first_name', 'last_name')
results = []
for user in users:
results.append(f"{user['last_name']}, {user['first_name']} ({user['username']})")
return JsonResponse(results, safe=False)

Binary file not shown.