From 3322649f0539cb97e6d89537d2ab934af9ef880c Mon Sep 17 00:00:00 2001 From: Martin Bley Date: Sat, 19 Sep 2026 09:04:26 +0200 Subject: [PATCH] Code optimizsations --- tipp/openliga.py | 60 ++-- tipp/views.py | 897 +++++++++++++++++++++++++++-------------------- 2 files changed, 538 insertions(+), 419 deletions(-) diff --git a/tipp/openliga.py b/tipp/openliga.py index 03b1403..8a3ce99 100644 --- a/tipp/openliga.py +++ b/tipp/openliga.py @@ -2,28 +2,30 @@ # -*- coding: utf-8 -*- # # Copyright (c) 2015-2016 by Martin Bley (martin@mb-oss.de) -# +# # This file is part of TipPy. -# +# # TipPy is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. -# +# # TipPy is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. -# +# # You should have received a copy of the GNU General Public License # along with TipPy. If not, see . -# +# import locale import urllib.error import urllib.parse import urllib.request -from suds.client import Client +from zeep import Client +from zeep.transports import Transport +from requests import Session class OpenLiga(object): @@ -40,54 +42,44 @@ class OpenLiga(object): self.debug = debug - # get a SUDS client object - if self.proxyurl is None: - try: - self.client = Client( - 'http://www.openligadb.de/' - + 'Webservices/Sportsdata.asmx?WSDL') - except (urllib.error.URLError): - self.error += "Connect to webservice failed." - else: - try: - t = suds.transport.http.HttpTransport() - proxy = urllib.request.ProxyHandler({'http': proxyurl}) - opener = urllib.request.build_opener(proxy) - t.urlopener = opener - self.client = suds.client.Client( - 'http://www.openligadb.de/Webservices/' - + 'Sportsdata.asmx?WSDL', transport=t) - except urllib.error.URLError as e: - self.error += "Connect to webservice failed " \ - + "(via proxy " + proxyurl + "): " + str(e) + "\n" + # get a SOAP client object + wsdl_url = 'http://www.openligadb.de/Webservices/Sportsdata.asmx?WSDL' + + try: + if proxyurl is None: + self.client = Client(wsdl=wsdl_url) + else: + session = Session() + session.proxies = {'http': proxyurl, 'https': proxyurl} + transport = Transport(session=session) + self.client = Client(wsdl=wsdl_url, transport=transport) + except (urllib.error.URLError, Exception) as e: + self.error += f"Connect to webservice failed: {str(e)}\n" def getSeason(self, season, league='bl1'): """ Get the whole season. Args: season and league shortcut (optional) """ - return (self.client.service.GetMatchdataByLeagueSaison( + return self.client.service.GetMatchdataByLeagueSaison( leagueShortcut=league, leagueSaison=season) - ) def getMatchday(self, season, matchdaynumber, league='bl1'): """ Get matchday. Args: season, matchdaynumber and league shortcut (optional) """ - return (self.client.service.GetMatchdataByGroupLeagueSaison( + return self.client.service.GetMatchdataByGroupLeagueSaison( groupOrderID=matchdaynumber, leagueShortcut=league, leagueSaison=season) - ) def getTeams(self, season, league='bl1'): - return (self.client.service.GetTeamsByLeagueSaison( + return self.client.service.GetTeamsByLeagueSaison( leagueShortcut=league, leagueSaison=season) - ) def getCurrentGroup(self, league): - return (self.client.service.GetCurrentGroup( + return self.client.service.GetCurrentGroup( leagueShortcut=league) - ) + diff --git a/tipp/views.py b/tipp/views.py index 6217d0f..7da8563 100644 --- a/tipp/views.py +++ b/tipp/views.py @@ -21,474 +21,601 @@ along with TipPy. If not, see . import os from os.path import join as pjoin -import django.shortcuts +import logging import pytz from PIL import Image as PImage from django.conf import settings from django.contrib.admin.views.decorators import staff_member_required from django.contrib.auth.decorators import login_required +from django.contrib.auth.models import User from django.core.exceptions import ObjectDoesNotExist -from django.core.paginator import Paginator, EmptyPage, \ - PageNotAnInteger +from django.core.files.storage import default_storage +from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger from django.db.models import Sum from django.http import HttpResponseRedirect +from django.shortcuts import render, redirect, get_object_or_404 from django.utils import timezone +from django.views.decorators.http import require_http_methods from tipp.forms import * from tipp.models import * from tipp.openliga import * -timezoneLocal = pytz.timezone('Europe/Berlin') +TIMEZONE_LOCAL = pytz.timezone('Europe/Berlin') +THUMBNAIL_SIZE = (50, 50) +logger = logging.getLogger(__name__) def about(request): - return django.shortcuts.render(request, 'about.html') + """Render about page.""" + return render(request, 'about.html') @login_required def home(request): + """Redirect to current matchday.""" ls = get_current_ls() season = get_current_season() md = get_current_md(ls) - return django.shortcuts.redirect("matchday", - ls=ls, - season=season, - matchday=str(md).zfill(2) - ) + return redirect( + 'matchday', + ls=ls, + season=season, + matchday=str(md).zfill(2) + ) + +def _create_thumbnail(image_path): + """Create a 50x50 thumbnail from the original image.""" + try: + im = PImage.open(image_path) + im.thumbnail(THUMBNAIL_SIZE, PImage.Resampling.LANCZOS) + im.save(image_path) + except Exception as e: + logger.error(f"Failed to create thumbnail: {str(e)}") + raise + + +def _delete_avatar(image_path): + """Delete avatar image file safely.""" + if os.path.isfile(image_path): + try: + os.remove(image_path) + except OSError as e: + logger.error(f"Failed to delete avatar: {str(e)}") @login_required +@require_http_methods(["GET", "POST"]) def profile(request, pk): - user = User.objects.get(id=pk) + """Display and edit user profile.""" + user = get_object_or_404(User, id=pk) profile, created = UserProfile.objects.get_or_create(user=user) - if profile.avatar: - img = profile.avatar.name - else: - img = None - - debug = [] - debug.append('avatar: ' + str(img)) - - # If it's a HTTP POST, we're interested in processing form data. if request.method == 'POST': - user_form = UserForm(data=request.POST, instance=user) - profile_form = UserProfileForm( - data=request.POST, instance=profile - ) - - if user_form.is_valid() and profile_form.is_valid(): - - user = user_form.save() - - profile = profile_form.save(commit=False) - profile.user = user - - # Did the user provide a profile picture? - if 'avatar' in request.FILES: - profile.avatar = request.FILES['avatar'] - # Now we save the UserProfile model instance. - profile.save() - - # create thumbnail - imfn = pjoin(settings.MEDIA_ROOT, profile.avatar.name) - im = PImage.open(imfn) - im.thumbnail((50, 50), PImage.ANTIALIAS) - im.save(settings.MEDIA_ROOT + profile.avatar.name) - if 'avatar-clear' in request.POST: - if 'on' in request.POST['avatar-clear']: - image = pjoin(settings.MEDIA_ROOT, img) - debug.append('Is the image ' + str(image) - + ' a regular file? ' - + str(os.path.isfile(image)) - ) - if os.path.isfile(image): - os.remove(image) - profile.save() - - for item in request.POST: - debug.append(str(item) + " -> " - + str(request.POST[item]) - ) - - # Invalid form or forms - mistakes or something else? - # Print problems to the terminal. - # They'll also be shown to the user. - else: - print( - user_form.errors, - profile_form.errors - ) - - return django.shortcuts.redirect('/accounts/profile/' + str(user.id)) - - # Not a HTTP POST, so we render our form using two ModelForm - # instances. These forms will be blank, ready for user input. + return _handle_profile_post(request, user, profile) else: - user_form = UserForm( - initial={'last_name': user.last_name, - 'first_name': user.first_name, - 'email': user.email}, - instance=user - ) - profile_form = UserProfileForm(instance=profile) + return _handle_profile_get(request, user, profile) - return django.shortcuts.render(request, 'registration/profile.html', { + +def _handle_profile_post(request, user, profile): + """Handle POST request for profile update.""" + user_form = UserForm(data=request.POST, instance=user) + profile_form = UserProfileForm(data=request.POST, instance=profile) + + if user_form.is_valid() and profile_form.is_valid(): + user = user_form.save() + profile = profile_form.save(commit=False) + profile.user = user + + # Handle avatar upload + if 'avatar' in request.FILES: + profile.avatar = request.FILES['avatar'] + profile.save() + + image_path = os.path.join(settings.MEDIA_ROOT, profile.avatar.name) + try: + _create_thumbnail(image_path) + except Exception as e: + logger.error(f"Thumbnail creation failed: {str(e)}") + + # Handle avatar deletion + elif 'avatar-clear' in request.POST and request.POST['avatar-clear'] == 'on': + if profile.avatar: + old_image_path = os.path.join(settings.MEDIA_ROOT, profile.avatar.name) + _delete_avatar(old_image_path) + profile.avatar = None + + profile.save() + return redirect('profile', pk=user.id) + else: + logger.warning(f"Form validation failed for user {user.id}: " + f"User errors: {user_form.errors}, " + f"Profile errors: {profile_form.errors}") + + return _render_profile_form(request, user_form, profile_form, profile) + + +def _handle_profile_get(request, user, profile): + """Handle GET request for profile display.""" + user_form = UserForm(instance=user) + profile_form = UserProfileForm(instance=profile) + + return _render_profile_form(request, user_form, profile_form, profile) + + +def _render_profile_form(request, user_form, profile_form, profile): + """Render profile form template.""" + context = { 'user_form': user_form, 'profile_form': profile_form, - 'debug': debug, - 'img': img} - ) + 'avatar': profile.avatar.name if profile.avatar else None, + } + return render(request, 'registration/profile.html', context) @staff_member_required def staff(request): - season = get_current_season(), - """ get available competitions """ - competitions = Competition.objects.filter(season=season).order_by('leagueName') + """Display available competitions for current season.""" + season = get_current_season() + competitions = Competition.objects.filter( + season=season + ).order_by('leagueName') - return django.shortcuts.render(request, 'staff.html', {'competitions': competitions}) + return render(request, 'staff.html', {'competitions': competitions}) @staff_member_required def getSeason(request, ls, season): + """Fetch and import teams and matches for a season from OpenLiga.""" ol = OpenLiga() - teams = ol.getTeams(str(season), ls) - season = ol.getSeason(str(season), ls) + teams_data = ol.getTeams(str(season), ls) + season_data = ol.getSeason(str(season), ls) - return_vals = [] - for team in teams[0]: - t = Team( - teamID=team['teamID'], - name=team['teamName'], - iconURL=team['teamIconURL'] + # Bulk import teams + _import_teams(teams_data[0]) + + # Bulk import matches + _import_season_matches(season_data[0], ls) + + return redirect( + 'matchday', + ls=ls, + season=get_current_season(), + matchday=str(get_current_md(ls)).zfill(2) + ) + + +def _import_teams(teams_data): + """Import teams, update existing ones with icon URL if missing.""" + teams_to_create = [] + teams_to_update = [] + + for team_data in teams_data: + team_id = int(team_data['teamID']) + + try: + existing_team = Team.objects.get(teamID=team_id) + if existing_team.iconURL is None: + existing_team.iconURL = team_data['teamIconURL'] + teams_to_update.append(existing_team) + except Team.DoesNotExist: + teams_to_create.append( + Team( + teamID=team_id, + name=team_data['teamName'], + iconURL=team_data['teamIconURL'] + ) + ) + + if teams_to_create: + Team.objects.bulk_create(teams_to_create, ignore_conflicts=True) + + if teams_to_update: + Team.objects.bulk_update(teams_to_update, ['iconURL'], batch_size=100) + + +def _import_season_matches(season_data, ls): + """Import all matches from season data.""" + matches = [] + + for match_data in season_data: + try: + team1 = Team.objects.get(teamID=int(match_data['idTeam1'])) + team2 = Team.objects.get(teamID=int(match_data['idTeam2'])) + except Team.DoesNotExist: + logger = logging.getLogger(__name__) + logger.warning(f"Team not found for match {match_data['matchID']}") + continue + + matches.append( + Match( + matchID=match_data['matchID'], + matchDateTime=match_data['matchDateTime'], + group=int(match_data['groupID']), + matchday=int(match_data['groupOrderID']), + matchday_name=match_data['groupName'], + idTeam1=team1, + idTeam2=team2, + pointsTeam1=match_data['pointsTeam1'], + pointsTeam2=match_data['pointsTeam2'], + finished=bool(match_data['matchIsFinished']), + season=match_data['leagueSaison'], + leagueShortcut=match_data['leagueShortcut'] + ) ) - """ - check if object exits and do insert if not - """ - try: - to = Team.objects.get(teamID=int(t.teamID)) - if to.iconURL is None: - t.save(update_fields=['iconURL']) - except Team.DoesNotExist: - t.save() - - for match in season[0]: - m = Match(matchID=match['matchID'], - matchDateTime=match['matchDateTime'], - group=int(match['groupID']), - matchday=int(match['groupOrderID']), - matchday_name=match['groupName'], - idTeam1=Team.objects.get(teamID=int(match['idTeam1'])), - idTeam2=Team.objects.get(teamID=int(match['idTeam2'])), - pointsTeam1=match['pointsTeam1'], - pointsTeam2=match['pointsTeam2'], - finished=bool(match['matchIsFinished']), - season=match['leagueSaison'], - leagueShortcut=match['leagueShortcut'] - ) - m.save() - - return django.shortcuts.redirect("matchday", - ls=ls, - season=get_current_season(), - matchday=str(get_current_md(ls)).zfill(2) - ) + if matches: + Match.objects.bulk_create(matches, ignore_conflicts=True) @login_required def update(request, ls, season, cur_md): - """ - To do: - - update table tipp_competition - """ - + """Update match results and calculate user scores for current matchday.""" ol = OpenLiga() md_matches = ol.getMatchday(str(season), int(cur_md), ls) - # Achtung: DateTimeField prüfen - for match in md_matches[0]: - # Endergebnis holen - matchResults = match['matchResults'] - try: - for result in matchResults[0]: - if result['resultName'] == "Halbzeit": - pointsTeam1 = int(result['pointsTeam1']) - pointsTeam2 = int(result['pointsTeam2']) - elif result['resultName'] == "Endergebnis": - pointsTeam1 = int(result['pointsTeam1']) - pointsTeam2 = int(result['pointsTeam2']) - break - elif str(result['resultName']) == u"nach Verlängerung": - pointsTeam1 = int(result['pointsTeam1']) - pointsTeam2 = int(result['pointsTeam2']) - else: - pointsTeam1 = -1 - pointsTeam2 = -1 + # Update match results + _update_match_results(md_matches[0]) - except: - pointsTeam1 = -1 - pointsTeam2 = -1 + # Calculate scores for all tips + _calculate_matchday_scores(request.user, ls, season, cur_md) - m = Match(matchID=match['matchID'], - matchDateTime=match['matchDateTime'], - pointsTeam1=pointsTeam1, - pointsTeam2=pointsTeam2, - finished=bool(match['matchIsFinished']), - ) - m.save(update_fields=['matchDateTime', 'pointsTeam1', 'pointsTeam2', 'finished']) - pointsTeam1 = -1 - pointsTeam2 = -1 + return redirect('matchday', ls=ls, season=season, matchday=cur_md) - # evaluate scores - """ get mandants current user is related """ - mandants = RelUserMandant.objects.filter(user=request.user). \ - values_list('mandant', flat=True) - # scores = Score.objects.get(client=Mandant.objects.get(name='huse')) - scores = Score.objects.get(client=mandants[0]) +def _update_match_results(matches_data): + """Update match results from OpenLiga data.""" + matches_to_update = [] + + for match_data in matches_data: + points_team1, points_team2 = _extract_match_result(match_data['matchResults']) + + matches_to_update.append( + Match( + matchID=match_data['matchID'], + matchDateTime=match_data['matchDateTime'], + pointsTeam1=points_team1, + pointsTeam2=points_team2, + finished=bool(match_data['matchIsFinished']), + ) + ) + + if matches_to_update: + Match.objects.bulk_update( + matches_to_update, + ['matchDateTime', 'pointsTeam1', 'pointsTeam2', 'finished'], + batch_size=100 + ) + + +def _extract_match_result(match_results): + """Extract final match result from match results data. + + Returns: + tuple: (pointsTeam1, pointsTeam2), defaults to (-1, -1) if not found. + """ + if not match_results or not match_results[0]: + return -1, -1 + + try: + for result in match_results[0]: + result_name = result['resultName'] + + # Prefer final result, but accept extra time if available + if result_name in ("Endergebnis", "nach Verlängerung"): + return int(result['pointsTeam1']), int(result['pointsTeam2']) + + # Fallback: half-time result if final not found + for result in match_results[0]: + if result['resultName'] == "Halbzeit": + return int(result['pointsTeam1']), int(result['pointsTeam2']) + + except (KeyError, ValueError, TypeError) as e: + logger = logging.getLogger(__name__) + logger.warning(f"Error extracting match result: {str(e)}") + + return -1, -1 + + +def _calculate_matchday_scores(user, ls, season, cur_md): + """Calculate scores for all user tips on finished matches.""" + try: + mandant = RelUserMandant.objects.filter(user=user).values_list( + 'mandant', flat=True + ).first() + + if not mandant: + logger = logging.getLogger(__name__) + logger.warning(f"No mandant found for user {user.id}") + return + + scores = Score.objects.get(client=mandant) + except Score.DoesNotExist: + logger = logging.getLogger(__name__) + logger.warning(f"No score configuration found for mandant {mandant}") + return finished_matches = Match.objects.filter( finished=True, leagueShortcut=ls, season=season, matchday=cur_md - ) + ).prefetch_related('tipp_set') + + tipps_to_update = [] + for match in finished_matches: - tipps = Tipp.objects.filter(matchID=match.matchID) - for tipp in tipps: - score = 0 - goals_total = match.pointsTeam1 + match.pointsTeam2 - - # Tipp is acurate - if (tipp.pointsTeam1 == match.pointsTeam1 - and tipp.pointsTeam2 == match.pointsTeam2): - if goals_total >= 5: - score = scores.exact_high - else: - score = scores.exact - - tipp.score = score - tipp.save() - continue - - # who won? - winnerReal = None - if match.pointsTeam1 > match.pointsTeam2: - winnerReal = 1 - elif match.pointsTeam2 > match.pointsTeam1: - winnerReal = 2 - elif match.pointsTeam1 == match.pointsTeam2: - winnerReal = 0 - - winnerTipp = None - if tipp.pointsTeam1 > tipp.pointsTeam2: - winnerTipp = 1 - elif tipp.pointsTeam2 > tipp.pointsTeam1: - winnerTipp = 2 - elif tipp.pointsTeam1 == tipp.pointsTeam2: - winnerTipp = 0 - - diffReal = match.pointsTeam1 - match.pointsTeam2 - diffTipp = tipp.pointsTeam1 - tipp.pointsTeam2 - - if winnerTipp == winnerReal: - if diffTipp == diffReal: - score = scores.diff - else: - score = scores.tendency - - tipp.score = score - tipp.save() - continue - + for tipp in match.tipp_set.all(): + score = _calculate_tipp_score(tipp, match, scores) tipp.score = score - tipp.save() - continue + tipps_to_update.append(tipp) - return django.shortcuts.redirect("matchday", - ls=ls, - season=season, - matchday=cur_md - ) + if tipps_to_update: + Tipp.objects.bulk_update(tipps_to_update, ['score'], batch_size=100) +def _calculate_tipp_score(tipp, match, scores): + """Calculate score for a single tip based on match result. + + Returns: + int: Points earned for this tip. + """ + # Exact result + if tipp.pointsTeam1 == match.pointsTeam1 and tipp.pointsTeam2 == match.pointsTeam2: + goals_total = match.pointsTeam1 + match.pointsTeam2 + return scores.exact_high if goals_total >= 5 else scores.exact + + # Winner and difference + winner_real = _get_winner(match.pointsTeam1, match.pointsTeam2) + winner_tipp = _get_winner(tipp.pointsTeam1, tipp.pointsTeam2) + + if winner_tipp == winner_real: + diff_real = match.pointsTeam1 - match.pointsTeam2 + diff_tipp = tipp.pointsTeam1 - tipp.pointsTeam2 + + if diff_tipp == diff_real: + return scores.diff + else: + return scores.tendency + + return 0 + + +def _get_winner(points_team1, points_team2): + """Determine match winner. + + Returns: + int: 1 for team1 win, 2 for team2 win, 0 for draw, -1 for invalid. + """ + if points_team1 > points_team2: + return 1 + elif points_team2 > points_team1: + return 2 + elif points_team1 == points_team2: + return 0 + return -1 + @login_required +@require_http_methods(["GET", "POST"]) def matchday(request, ls, season, matchday, template_name='md.html'): - update(request, ls, season, matchday) - debug = '' - debug += "Debugging: " - has_refresh = True + """Display matchday with user tips, match results, and mandant scores.""" + # Process form submissions first + _process_matchday_tips(request, ls, season, matchday) + + user = request.user + matches = _build_matchday_matches(request, user, ls, season, matchday) + mandant_dict = _build_mandant_scores(user, matches) + posts = _get_mandant_posts(user) + + cur_group = matches[0]['md_name'] if matches else "N/A" - """ get matches of the matchday """ - md_matches = Match.objects.filter(leagueShortcut=ls, season=season, - matchday=int(matchday)).order_by('matchDateTime') - - """ get the current user """ - user = User.objects.get(username=request.user.username) - - """ get mandants current user is related """ - mandants = RelUserMandant.objects.filter(user=request.user). \ - values_list('mandant', flat=True) - - """ get mates """ - mandant_dict = {} - for m in mandants: - mandant = Mandant.objects.get(id=m) - mandant_dict[mandant.description] = [] - rs = RelUserMandant.objects.filter(mandant=m) - - for r in rs: - mandant_dict[mandant.description].append({ - 'mate': r.user, - 'tipps': [], - 'sum_score': 0 - }) - - matches = [] - for match in md_matches: - """ - create a form for each tipp - """ - form = TippForm(request.POST or None, prefix=str(match.matchID)) - - """ - check if Tipp already exists - """ - try: - tipp = Tipp.objects.get(tipperID=user.id, - matchID=match.matchID) - except ObjectDoesNotExist: - tipp = None - - """ - if request is a POST, process form data, else just show form - """ - if form.is_valid(): - data = form.cleaned_data - - if tipp is not None: - """ Tipp exists an needs an UPDATE""" - tipp = Tipp(id=tipp.pk, - matchID=match, - tipperID=user, - pointsTeam1=data['tippTeam1'], - pointsTeam2=data['tippTeam2'] - ) - tipp.save() - - else: - """ do INSERT """ - tipp = Tipp(matchID=match, - tipperID=user, - pointsTeam1=data['tippTeam1'], - pointsTeam2=data['tippTeam2']) - tipp.save() - - match_started = timezone.now() >= match.matchDateTime - - if tipp is not None: - f = TippForm(prefix=str(match.matchID), - initial={'tippTeam1': tipp.pointsTeam1, - 'tippTeam2': tipp.pointsTeam2} - ) - else: - f = TippForm(prefix=str(match.matchID)) - - item = {} - item['matchDateTime'] = (match.matchDateTime. - astimezone(timezoneLocal).strftime('%a %d.%m, %H:%M')) - - item['iconTeam1'] = str(match.idTeam1.icon) - item['iconURLTeam1'] = str(match.idTeam1.iconURL) - item['nameTeam1'] = str(match.idTeam1.name) - item['abbrTeam1'] = str(match.idTeam1.abbr) - item['iconTeam2'] = str(match.idTeam2.icon) - item['iconURLTeam2'] = str(match.idTeam2.iconURL) - item['nameTeam2'] = str(match.idTeam2.name) - item['abbrTeam2'] = str(match.idTeam2.abbr) - item['started'] = match_started - item['finished'] = match.finished - item['md_name'] = match.matchday_name - - if (match.pointsTeam1 == -1 or match.pointsTeam2 == -1): - item['matchResult'] = "-:-" - else: - item['matchResult'] = str(match.pointsTeam1) + ":" \ - + str(match.pointsTeam2) - - item['tippFormTeam1'] = f['tippTeam1'] - item['tippFormTeam2'] = f['tippTeam2'] - - if match_started is True: - f.fields['tippTeam1'].widget.attrs['disabled'] = True - f.fields['tippTeam2'].widget.attrs['disabled'] = True - - """ get tipps from mates """ - for mandant in mandant_dict: - for mate in mandant_dict[mandant]: - try: - matetipp = Tipp.objects.get(tipperID=mate['mate'], - matchID=match.matchID) - if match_started is True: - if matetipp.score is None: - score = u'\u231B' - else: - score = matetipp.score - - mate['tipps'].append(str(matetipp.pointsTeam1) - + ":" + str(matetipp.pointsTeam2) - + "(" + str(score) + ")") - if match.finished is True: - mate['sum_score'] += matetipp.score - else: - mate['tipps'].append(u'\u2714') - except Exception as e: - mate['tipps'].append(u'\u2717') - - """ sort items in mandant_dict by score """ - mandant_dict[mandant] = sorted(mandant_dict[mandant], - key=lambda k: k['sum_score'], reverse=True) - - matches.append(item) - - # get the newest blogposts - pm = RelPostMandant.objects.filter(mandant__in=mandants). \ - values_list('post', flat=True) - posts = [] - for post in Post.objects.filter(id__in=pm, published=True)[:5]: - try: - avatar = UserProfile.objects.get(user_id=post.author_id). \ - avatar.name - except: - avatar = None - posts.append((post, avatar)) - - try: - cur_group = matches[0]['md_name'] - except: - cur_group = "N/A" - - debug += str(mandant_dict) - - return django.shortcuts.render(request, 'md.html', { - 'debug': debug, - # 'mds_season': mds_in_season, + return render(request, template_name, { 'cur_md': matchday, 'md_name': cur_group, 'matches': matches, 'ls': ls, 'season': season, - 'username': user, + 'username': user.username, 'mandant_dict': mandant_dict, - # 'tipp_mates': sorted(tipp_mates, key=lambda k: k['sum_score'], - # reverse=True), 'posts': posts, - 'has_refresh': has_refresh + 'has_refresh': True }) +def _process_matchday_tips(request, ls, season, matchday): + """Process and save tip form submissions.""" + if request.method != 'POST': + return + + md_matches = Match.objects.filter( + leagueShortcut=ls, + season=season, + matchday=int(matchday) + ) + + for match in md_matches: + form = TippForm(request.POST, prefix=str(match.matchID)) + + if form.is_valid(): + data = form.cleaned_data + Tipp.objects.update_or_create( + tipperID=request.user, + matchID=match, + defaults={ + 'pointsTeam1': data['tippTeam1'], + 'pointsTeam2': data['tippTeam2'] + } + ) + + +def _build_matchday_matches(request, user, ls, season, matchday): + """Build match data with forms and results for template.""" + md_matches = Match.objects.filter( + leagueShortcut=ls, + season=season, + matchday=int(matchday) + ).select_related('idTeam1', 'idTeam2').order_by('matchDateTime') + + # Fetch all user tips in one query + user_tipps = { + tipp.matchID_id: tipp + for tipp in Tipp.objects.filter(tipperID=user, matchID__in=md_matches) + } + + matches = [] + for match in md_matches: + match_started = timezone.now() >= match.matchDateTime + tipp = user_tipps.get(match.id) + + # Build form with initial data + initial = {} + if tipp: + initial = { + 'tippTeam1': tipp.pointsTeam1, + 'tippTeam2': tipp.pointsTeam2 + } + + form = TippForm(prefix=str(match.matchID), initial=initial) + + if match_started: + form.fields['tippTeam1'].widget.attrs['disabled'] = True + form.fields['tippTeam2'].widget.attrs['disabled'] = True + + match_result = _format_match_result(match.pointsTeam1, match.pointsTeam2) + + item = { + 'matchDateTime': match.matchDateTime.astimezone(TIMEZONE_LOCAL).strftime( + '%a %d.%m, %H:%M' + ), + 'iconTeam1': str(match.idTeam1.icon), + 'iconURLTeam1': str(match.idTeam1.iconURL), + 'nameTeam1': str(match.idTeam1.name), + 'abbrTeam1': str(match.idTeam1.abbr), + 'iconTeam2': str(match.idTeam2.icon), + 'iconURLTeam2': str(match.idTeam2.iconURL), + 'nameTeam2': str(match.idTeam2.name), + 'abbrTeam2': str(match.idTeam2.abbr), + 'started': match_started, + 'finished': match.finished, + 'md_name': match.matchday_name, + 'matchResult': match_result, + 'tippFormTeam1': form['tippTeam1'], + 'tippFormTeam2': form['tippTeam2'], + 'match_id': match.id, + } + + matches.append(item) + + return matches + + +def _format_match_result(points_team1, points_team2): + """Format match result for display.""" + if points_team1 == -1 or points_team2 == -1: + return "-:-" + return f"{points_team1}:{points_team2}" + + +def _build_mandant_scores(user, matches): + """Build mandant dict with user scores and tips.""" + mandants = RelUserMandant.objects.filter(user=user).values_list( + 'mandant', flat=True + ) + + if not mandants: + return {} + + mandant_dict = {} + + # Fetch all mandants and related users + mandant_users = {} + for mandant in mandants: + mandant_obj = get_object_or_404(Mandant, id=mandant) + mandant_users[mandant] = RelUserMandant.objects.filter( + mandant=mandant + ).select_related('user') + + # Get all tips for all matches and users + match_ids = [m['match_id'] for m in matches] + user_ids = [] + for mandant in mandant_users: + user_ids.extend([rel.user_id for rel in mandant_users[mandant]]) + + all_tipps = { + (tipp.tipperID_id, tipp.matchID_id): tipp + for tipp in Tipp.objects.filter( + tipperID_id__in=user_ids, + matchID_id__in=match_ids + ) + } + + # Build mandant dict + for mandant in mandants: + mandant_obj = Mandant.objects.get(id=mandant) + mandant_dict[mandant_obj.description] = [] + + for rel in mandant_users[mandant]: + mate_data = { + 'mate': rel.user, + 'tipps': [], + 'sum_score': 0 + } + + # Get tips for this mate across all matches + for match in matches: + tipp = all_tipps.get((rel.user_id, match['match_id'])) + + if tipp: + if match['started']: + score = tipp.score if tipp.score is not None else '\u231B' + tip_str = f"{tipp.pointsTeam1}:{tipp.pointsTeam2}({score})" + mate_data['tipps'].append(tip_str) + + if match['finished']: + mate_data['sum_score'] += tipp.score or 0 + else: + mate_data['tipps'].append('\u2714') + else: + mate_data['tipps'].append('\u2717') + + mandant_dict[mandant_obj.description].append(mate_data) + + # Sort by score + mandant_dict[mandant_obj.description] = sorted( + mandant_dict[mandant_obj.description], + key=lambda k: k['sum_score'], + reverse=True + ) + + return mandant_dict + + +def _get_mandant_posts(user): + """Get latest published blog posts for user's mandants.""" + mandants = RelUserMandant.objects.filter(user=user).values_list( + 'mandant', flat=True + ) + + post_ids = RelPostMandant.objects.filter( + mandant__in=mandants + ).values_list('post', flat=True) + + posts = [] + for post in Post.objects.filter( + id__in=post_ids, + published=True + ).select_related('author__userprofile')[:5]: + avatar = None + try: + avatar = post.author.userprofile.avatar.name + except (ObjectDoesNotExist, AttributeError): + pass + + posts.append((post, avatar)) + + return posts + + @login_required def charts(request, ls, season, pos='default', template_name='charts.html'):