Author SHA1 Message Date
Martin Bley 3322649f05 Code optimizsations 2026-09-19 09:04:26 +02:00
Martin Bley 6b99dd5250 venv is running 2026-09-19 07:58:45 +02:00
Martin Bley d4dbd3d9da Updatet venv 2026-09-19 07:32:04 +02:00
Martin Bley 5b7f847bc6 Models optimiert 2026-09-13 08:51:38 +02:00
7 changed files with 627 additions and 439 deletions
+45 -9
View File
@@ -1,10 +1,46 @@
*.pyc
*.swp
bin/
manage.py
# Virtuelle Umgebung
venv/
env/
ENV/
.venv
# Python
__pycache__/
*.py[cod]
*$py.class
*.so
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
*.egg-info/
.installed.cfg
*.egg
# Django
*.log
db.sqlite3
media/
old_stuff/
static/
templates/blogindex.html-media
tippy/static/
warnings.txt
staticfiles/
# IDE
.vscode/
.idea/
*.swp
*.swo
*~
# Umgebungsvariablen
.env
.env.local
+2 -2
View File
@@ -76,7 +76,7 @@ WSGI_APPLICATION = 'TipPy.wsgi.application'
# https://docs.djangoproject.com/en/4.0/ref/settings/#databases
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql_psycopg2',
'ENGINE': 'django.db.backends.postgresql',
'NAME': 'tippy',
'USER': 'tippy',
'PASSWORD': 'tippy',
@@ -143,4 +143,4 @@ STATICFILES_DIRS = [
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
# Redirect on logout
LOGOUT_REDIRECT_URL = "home"
LOGOUT_REDIRECT_URL = "home"
Executable
+23
View File
@@ -0,0 +1,23 @@
#!/usr/bin/env python
"""Django's command-line utility for administrative tasks."""
import os
import sys
def main():
"""Run administrative tasks."""
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "TipPy.settings")
try:
from django.core.management import execute_from_command_line
except ImportError as exc:
raise ImportError(
"Couldn't import Django. Are you sure it's installed and "
"available on your PYTHONPATH environment variable? Did you "
"forget to activate a virtual environment?"
) from exc
execute_from_command_line(sys.argv)
if __name__ == "__main__":
main()
+8 -6
View File
@@ -1,6 +1,8 @@
Django~=4.1.7
psycopg2~=2.9.5
suds~=1.1.2
Pillow~=9.4.0
pytz~=2023.2
urllib3~=1.26.15
asgiref==3.12.1
Django==6.1.1
pillow==12.3.0
psycopg2==2.9.13
pytz==2026.3.post1
sqlparse==0.6.0
suds-community==1.2.0
urllib3==2.8.0
+11 -3
View File
@@ -37,6 +37,9 @@ class Competition(models.Model):
leagueName = models.CharField(max_length=128)
season = models.CharField(max_length=4)
class Meta:
unique_together = ("leagueShortcut", "season")
def __unicode__(self):
return (self.leagueShortcut)
@@ -62,6 +65,12 @@ class Match(models.Model):
group = models.IntegerField()
matchday = models.IntegerField()
matchday_name = models.CharField(max_length=128)
competition = models.ForeignKey(
Competition,
on_delete=models.CASCADE
)
idTeam1 = models.ForeignKey(
Team, related_name='+',
on_delete=models.CASCADE
@@ -70,11 +79,10 @@ class Match(models.Model):
Team, related_name='+',
on_delete=models.CASCADE
)
pointsTeam1 = models.SmallIntegerField()
pointsTeam2 = models.SmallIntegerField()
finished = models.BooleanField()
season = models.CharField(max_length=4)
leagueShortcut = models.CharField(max_length=12)
def __unicode__(self):
return (str(self.matchID))
@@ -121,7 +129,7 @@ class Score(models.Model):
return str(self.client)
class chart(models.Model):
class Chart(models.Model):
team = models.ForeignKey(
Team,
on_delete=models.CASCADE
+26 -34
View File
@@ -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 <http://www.gnu.org/licenses/>.
#
#
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)
)
+512 -385
View File
@@ -21,474 +21,601 @@ along with TipPy. If not, see <http://www.gnu.org/licenses/>.
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'):