Compare commits
5
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3322649f05 | ||
|
|
6b99dd5250 | ||
|
|
d4dbd3d9da | ||
|
|
5b7f847bc6 | ||
|
|
99eaafff00 |
+45
-9
@@ -1,10 +1,46 @@
|
|||||||
*.pyc
|
# Virtuelle Umgebung
|
||||||
*.swp
|
venv/
|
||||||
bin/
|
env/
|
||||||
manage.py
|
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/
|
media/
|
||||||
old_stuff/
|
staticfiles/
|
||||||
static/
|
|
||||||
templates/blogindex.html-media
|
# IDE
|
||||||
tippy/static/
|
.vscode/
|
||||||
warnings.txt
|
.idea/
|
||||||
|
*.swp
|
||||||
|
*.swo
|
||||||
|
*~
|
||||||
|
|
||||||
|
# Umgebungsvariablen
|
||||||
|
.env
|
||||||
|
.env.local
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
- getSeason() only for staff members
|
||||||
|
- new view staff()
|
||||||
|
- updated bootstrap to version 3.4.1
|
||||||
|
- updated jquery to 3.6.3
|
||||||
|
- changed container layout in main view 'matchday'
|
||||||
|
|
||||||
+1
-11
@@ -16,7 +16,6 @@ import os
|
|||||||
# Build paths inside the project like this: BASE_DIR / 'subdir'.
|
# Build paths inside the project like this: BASE_DIR / 'subdir'.
|
||||||
BASE_DIR = Path(__file__).resolve().parent.parent
|
BASE_DIR = Path(__file__).resolve().parent.parent
|
||||||
|
|
||||||
|
|
||||||
# Quick-start development settings - unsuitable for production
|
# Quick-start development settings - unsuitable for production
|
||||||
# See https://docs.djangoproject.com/en/4.0/howto/deployment/checklist/
|
# See https://docs.djangoproject.com/en/4.0/howto/deployment/checklist/
|
||||||
|
|
||||||
@@ -28,9 +27,7 @@ DEBUG = True
|
|||||||
|
|
||||||
ALLOWED_HOSTS = []
|
ALLOWED_HOSTS = []
|
||||||
|
|
||||||
|
|
||||||
# Application definition
|
# Application definition
|
||||||
|
|
||||||
INSTALLED_APPS = [
|
INSTALLED_APPS = [
|
||||||
'django.contrib.admin',
|
'django.contrib.admin',
|
||||||
'django.contrib.auth',
|
'django.contrib.auth',
|
||||||
@@ -77,10 +74,9 @@ WSGI_APPLICATION = 'TipPy.wsgi.application'
|
|||||||
|
|
||||||
# Database
|
# Database
|
||||||
# https://docs.djangoproject.com/en/4.0/ref/settings/#databases
|
# https://docs.djangoproject.com/en/4.0/ref/settings/#databases
|
||||||
|
|
||||||
DATABASES = {
|
DATABASES = {
|
||||||
'default': {
|
'default': {
|
||||||
'ENGINE': 'django.db.backends.postgresql_psycopg2',
|
'ENGINE': 'django.db.backends.postgresql',
|
||||||
'NAME': 'tippy',
|
'NAME': 'tippy',
|
||||||
'USER': 'tippy',
|
'USER': 'tippy',
|
||||||
'PASSWORD': 'tippy',
|
'PASSWORD': 'tippy',
|
||||||
@@ -91,7 +87,6 @@ DATABASES = {
|
|||||||
|
|
||||||
# Password validation
|
# Password validation
|
||||||
# https://docs.djangoproject.com/en/4.0/ref/settings/#auth-password-validators
|
# https://docs.djangoproject.com/en/4.0/ref/settings/#auth-password-validators
|
||||||
|
|
||||||
AUTH_PASSWORD_VALIDATORS = [
|
AUTH_PASSWORD_VALIDATORS = [
|
||||||
{
|
{
|
||||||
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
|
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
|
||||||
@@ -109,13 +104,9 @@ AUTH_PASSWORD_VALIDATORS = [
|
|||||||
|
|
||||||
# Internationalization
|
# Internationalization
|
||||||
# https://docs.djangoproject.com/en/4.0/topics/i18n/
|
# https://docs.djangoproject.com/en/4.0/topics/i18n/
|
||||||
|
|
||||||
LANGUAGE_CODE = 'de-de'
|
LANGUAGE_CODE = 'de-de'
|
||||||
|
|
||||||
TIME_ZONE = 'Europe/Berlin'
|
TIME_ZONE = 'Europe/Berlin'
|
||||||
|
|
||||||
USE_I18N = True
|
USE_I18N = True
|
||||||
|
|
||||||
USE_TZ = True
|
USE_TZ = True
|
||||||
|
|
||||||
# Absolute filesystem path to the directory that will hold user-uploaded files.
|
# Absolute filesystem path to the directory that will hold user-uploaded files.
|
||||||
@@ -149,7 +140,6 @@ STATICFILES_DIRS = [
|
|||||||
|
|
||||||
# Default primary key field type
|
# Default primary key field type
|
||||||
# https://docs.djangoproject.com/en/4.0/ref/settings/#default-auto-field
|
# https://docs.djangoproject.com/en/4.0/ref/settings/#default-auto-field
|
||||||
|
|
||||||
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
|
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
|
||||||
|
|
||||||
# Redirect on logout
|
# Redirect on logout
|
||||||
|
|||||||
@@ -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
@@ -1,6 +1,8 @@
|
|||||||
Django~=4.0.6
|
asgiref==3.12.1
|
||||||
psycopg2~=2.9.3
|
Django==6.1.1
|
||||||
suds~=1.1.2
|
pillow==12.3.0
|
||||||
Pillow~=9.2.0
|
psycopg2==2.9.13
|
||||||
pytz~=2022.1
|
pytz==2026.3.post1
|
||||||
urllib3~=1.26.10
|
sqlparse==0.6.0
|
||||||
|
suds-community==1.2.0
|
||||||
|
urllib3==2.8.0
|
||||||
|
|||||||
+2
-2
@@ -36,7 +36,7 @@
|
|||||||
<body>
|
<body>
|
||||||
|
|
||||||
<!-- jQuery (wird für Bootstrap JavaScript-Plugins benötigt) -->
|
<!-- jQuery (wird für Bootstrap JavaScript-Plugins benötigt) -->
|
||||||
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
|
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.3/jquery.min.js"></script>
|
||||||
<!-- Binde alle kompilierten Plugins zusammen ein (wie hier unten) oder such dir einzelne Dateien nach Bedarf aus -->
|
<!-- Binde alle kompilierten Plugins zusammen ein (wie hier unten) oder such dir einzelne Dateien nach Bedarf aus -->
|
||||||
<script src="{% static 'js/bootstrap.min.js'%}"></script>
|
<script src="{% static 'js/bootstrap.min.js'%}"></script>
|
||||||
|
|
||||||
@@ -53,7 +53,7 @@
|
|||||||
<div class="col-md-7">
|
<div class="col-md-7">
|
||||||
<p>
|
<p>
|
||||||
<div class="pull-left">
|
<div class="pull-left">
|
||||||
© Martin Bley, 2014-2022 | Dank an
|
© Martin Bley, 2014-2023 | Dank an
|
||||||
<a href="http://openligadb.de">OpenLigaDB</a> für die Bereitstellung der
|
<a href="http://openligadb.de">OpenLigaDB</a> für die Bereitstellung der
|
||||||
Spielergebnisse.
|
Spielergebnisse.
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
+6
-11
@@ -12,8 +12,8 @@
|
|||||||
{% block body %}
|
{% block body %}
|
||||||
<div id="content-main">
|
<div id="content-main">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-7">
|
<div class="col-md-6">
|
||||||
<h2>Deine Tipps</h2>
|
<h3>Deine Tipps</h3>
|
||||||
<nav>
|
<nav>
|
||||||
<ul class="pager">
|
<ul class="pager">
|
||||||
<li data-toggle="tooltip" title="{% trans 'previous' %}" class="previous"><a
|
<li data-toggle="tooltip" title="{% trans 'previous' %}" class="previous"><a
|
||||||
@@ -67,12 +67,9 @@
|
|||||||
<span class="glyphicon glyphicon-saved" aria-hidden="true"></span> Tipps abgeben
|
<span class="glyphicon glyphicon-saved" aria-hidden="true"></span> Tipps abgeben
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row">
|
|
||||||
{% for mandant, list in mandant_dict.items %}
|
{% for mandant, list in mandant_dict.items %}
|
||||||
<div class="col-md-7">
|
<h3>Mitspieler {{ mandant }}</h3>
|
||||||
<h4>Mitspieler {{ mandant }}</h4>
|
|
||||||
<div class="table-responsive">
|
<div class="table-responsive">
|
||||||
<table class="table table-hover table-condensed">
|
<table class="table table-hover table-condensed">
|
||||||
<tr>
|
<tr>
|
||||||
@@ -105,12 +102,10 @@
|
|||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="col-md-6">
|
||||||
<div class="col-md-7">
|
<h3>Chat</h3>
|
||||||
<h2>Chat</h2>
|
|
||||||
{% block chat %}
|
{% block chat %}
|
||||||
{% include "chat.xhtml" with posts=posts %}
|
{% include "chat.xhtml" with posts=posts %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@@ -16,6 +16,9 @@
|
|||||||
<li><a href="/">Tipps</a></li>
|
<li><a href="/">Tipps</a></li>
|
||||||
<li><a href="/charts/{{ ls }}/{{ season }}{% if pos %}/{{ pos }}{% endif %}">Bestenliste</a></li>
|
<li><a href="/charts/{{ ls }}/{{ season }}{% if pos %}/{{ pos }}{% endif %}">Bestenliste</a></li>
|
||||||
<li><a href="/blog/1">Blog</a></li>
|
<li><a href="/blog/1">Blog</a></li>
|
||||||
|
{% if request.user.is_active and request.user.is_staff %}
|
||||||
|
<li><a href="/staff">Staff</a></li>
|
||||||
|
{% endif %}
|
||||||
</ul>
|
</ul>
|
||||||
<ul class="nav navbar-nav navbar-right">
|
<ul class="nav navbar-nav navbar-right">
|
||||||
{% if has_refresh %}
|
{% if has_refresh %}
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
{% load i18n static %}
|
||||||
|
{% load humanize %}
|
||||||
|
{% block head %}
|
||||||
|
<script type="text/javascript" src="{% static 'javascript/tippy.js' %}"></script>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block nav %}
|
||||||
|
{% include "nav.html" with ls=ls season=season %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block body %}
|
||||||
|
<div id="content-main">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-12">
|
||||||
|
<h3>Wettbewerbe</h3>
|
||||||
|
{% for comp in competitions %}
|
||||||
|
{{ comp }}<br/>
|
||||||
|
{% endfor %}
|
||||||
|
<h3></h3>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% if debug %}
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-12">
|
||||||
|
<h2 class="form-signin-heading">{% trans 'Debug' %}</h2>
|
||||||
|
{% for line in debug %}
|
||||||
|
<p>{{ line }}</p>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
+27
-7
@@ -18,8 +18,8 @@ You should have received a copy of the GNU General Public License
|
|||||||
along with TipPy. If not, see <http://www.gnu.org/licenses/>.
|
along with TipPy. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
from django.db import models
|
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
|
from django.db import models
|
||||||
|
|
||||||
class UserProfile(models.Model):
|
class UserProfile(models.Model):
|
||||||
user = models.OneToOneField(
|
user = models.OneToOneField(
|
||||||
@@ -31,13 +31,18 @@ class UserProfile(models.Model):
|
|||||||
def __unicode__(self):
|
def __unicode__(self):
|
||||||
return self.user.username
|
return self.user.username
|
||||||
|
|
||||||
|
|
||||||
class Competition(models.Model):
|
class Competition(models.Model):
|
||||||
leagueShortcut = models.CharField(max_length=32)
|
leagueShortcut = models.CharField(max_length=32)
|
||||||
leagueName = models.CharField(max_length=128)
|
leagueName = models.CharField(max_length=128)
|
||||||
season = models.CharField(max_length=4)
|
season = models.CharField(max_length=4)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
unique_together = ("leagueShortcut", "season")
|
||||||
|
|
||||||
def __unicode__(self):
|
def __unicode__(self):
|
||||||
return(self.leagueShortcut)
|
return (self.leagueShortcut)
|
||||||
|
|
||||||
|
|
||||||
class Team(models.Model):
|
class Team(models.Model):
|
||||||
teamID = models.IntegerField(unique=True, primary_key=True)
|
teamID = models.IntegerField(unique=True, primary_key=True)
|
||||||
@@ -55,11 +60,17 @@ class Team(models.Model):
|
|||||||
|
|
||||||
|
|
||||||
class Match(models.Model):
|
class Match(models.Model):
|
||||||
matchID = models.IntegerField(unique=True,primary_key=True)
|
matchID = models.IntegerField(unique=True, primary_key=True)
|
||||||
matchDateTime = models.DateTimeField()
|
matchDateTime = models.DateTimeField()
|
||||||
group = models.IntegerField()
|
group = models.IntegerField()
|
||||||
matchday = models.IntegerField()
|
matchday = models.IntegerField()
|
||||||
matchday_name = models.CharField(max_length=128)
|
matchday_name = models.CharField(max_length=128)
|
||||||
|
|
||||||
|
competition = models.ForeignKey(
|
||||||
|
Competition,
|
||||||
|
on_delete=models.CASCADE
|
||||||
|
)
|
||||||
|
|
||||||
idTeam1 = models.ForeignKey(
|
idTeam1 = models.ForeignKey(
|
||||||
Team, related_name='+',
|
Team, related_name='+',
|
||||||
on_delete=models.CASCADE
|
on_delete=models.CASCADE
|
||||||
@@ -68,15 +79,15 @@ class Match(models.Model):
|
|||||||
Team, related_name='+',
|
Team, related_name='+',
|
||||||
on_delete=models.CASCADE
|
on_delete=models.CASCADE
|
||||||
)
|
)
|
||||||
|
|
||||||
pointsTeam1 = models.SmallIntegerField()
|
pointsTeam1 = models.SmallIntegerField()
|
||||||
pointsTeam2 = models.SmallIntegerField()
|
pointsTeam2 = models.SmallIntegerField()
|
||||||
finished = models.BooleanField()
|
finished = models.BooleanField()
|
||||||
season = models.CharField(max_length=4)
|
|
||||||
leagueShortcut = models.CharField(max_length=12)
|
|
||||||
|
|
||||||
def __unicode__(self):
|
def __unicode__(self):
|
||||||
return (str(self.matchID))
|
return (str(self.matchID))
|
||||||
|
|
||||||
|
|
||||||
class Tipp(models.Model):
|
class Tipp(models.Model):
|
||||||
matchID = models.ForeignKey(
|
matchID = models.ForeignKey(
|
||||||
Match,
|
Match,
|
||||||
@@ -95,12 +106,15 @@ class Tipp(models.Model):
|
|||||||
class Meta:
|
class Meta:
|
||||||
unique_together = ("matchID", "tipperID")
|
unique_together = ("matchID", "tipperID")
|
||||||
|
|
||||||
|
|
||||||
class Mandant(models.Model):
|
class Mandant(models.Model):
|
||||||
name = models.CharField(max_length=32, unique=True)
|
name = models.CharField(max_length=32, unique=True)
|
||||||
description = models.CharField(max_length=255)
|
description = models.CharField(max_length=255)
|
||||||
|
|
||||||
def __unicode__(self):
|
def __unicode__(self):
|
||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
|
|
||||||
class Score(models.Model):
|
class Score(models.Model):
|
||||||
client = models.ForeignKey(
|
client = models.ForeignKey(
|
||||||
Mandant,
|
Mandant,
|
||||||
@@ -110,10 +124,12 @@ class Score(models.Model):
|
|||||||
exact = models.PositiveSmallIntegerField()
|
exact = models.PositiveSmallIntegerField()
|
||||||
diff = models.PositiveSmallIntegerField()
|
diff = models.PositiveSmallIntegerField()
|
||||||
tendency = models.PositiveSmallIntegerField()
|
tendency = models.PositiveSmallIntegerField()
|
||||||
|
|
||||||
def __unicode__(self):
|
def __unicode__(self):
|
||||||
return str(self.client)
|
return str(self.client)
|
||||||
|
|
||||||
class chart(models.Model):
|
|
||||||
|
class Chart(models.Model):
|
||||||
team = models.ForeignKey(
|
team = models.ForeignKey(
|
||||||
Team,
|
Team,
|
||||||
on_delete=models.CASCADE
|
on_delete=models.CASCADE
|
||||||
@@ -129,6 +145,7 @@ class chart(models.Model):
|
|||||||
class Meta:
|
class Meta:
|
||||||
unique_together = ("team", "competition")
|
unique_together = ("team", "competition")
|
||||||
|
|
||||||
|
|
||||||
class RelUserMandant(models.Model):
|
class RelUserMandant(models.Model):
|
||||||
user = models.ForeignKey(
|
user = models.ForeignKey(
|
||||||
User,
|
User,
|
||||||
@@ -141,9 +158,11 @@ class RelUserMandant(models.Model):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
unique_together = ("user", "mandant")
|
unique_together = ("user", "mandant")
|
||||||
|
|
||||||
def __unicode__(self):
|
def __unicode__(self):
|
||||||
return (str(self.user) + " -> " + str(self.mandant))
|
return (str(self.user) + " -> " + str(self.mandant))
|
||||||
|
|
||||||
|
|
||||||
class Post(models.Model):
|
class Post(models.Model):
|
||||||
content = models.TextField()
|
content = models.TextField()
|
||||||
published = models.BooleanField(default=True)
|
published = models.BooleanField(default=True)
|
||||||
@@ -159,6 +178,7 @@ class Post(models.Model):
|
|||||||
def __unicode__(self):
|
def __unicode__(self):
|
||||||
return u'%s' % self.created
|
return u'%s' % self.created
|
||||||
|
|
||||||
|
|
||||||
class RelPostMandant(models.Model):
|
class RelPostMandant(models.Model):
|
||||||
post = models.ForeignKey(
|
post = models.ForeignKey(
|
||||||
Post,
|
Post,
|
||||||
@@ -171,6 +191,6 @@ class RelPostMandant(models.Model):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
unique_together = ("post", "mandant")
|
unique_together = ("post", "mandant")
|
||||||
|
|
||||||
def __unicode__(self):
|
def __unicode__(self):
|
||||||
return (str(self.post) + " -> " + str(self.mandant))
|
return (str(self.post) + " -> " + str(self.mandant))
|
||||||
|
|
||||||
|
|||||||
+22
-33
@@ -18,13 +18,15 @@
|
|||||||
# You should have received a copy of the GNU General Public License
|
# You should have received a copy of the GNU General Public License
|
||||||
# along with TipPy. If not, see <http://www.gnu.org/licenses/>.
|
# along with TipPy. If not, see <http://www.gnu.org/licenses/>.
|
||||||
#
|
#
|
||||||
import sys
|
|
||||||
import locale
|
import locale
|
||||||
from suds.client import Client
|
|
||||||
import urllib.request
|
|
||||||
import urllib.error
|
import urllib.error
|
||||||
import urllib.parse
|
import urllib.parse
|
||||||
import psycopg2
|
import urllib.request
|
||||||
|
|
||||||
|
from zeep import Client
|
||||||
|
from zeep.transports import Transport
|
||||||
|
from requests import Session
|
||||||
|
|
||||||
|
|
||||||
class OpenLiga(object):
|
class OpenLiga(object):
|
||||||
version = "0.1"
|
version = "0.1"
|
||||||
@@ -40,57 +42,44 @@ class OpenLiga(object):
|
|||||||
|
|
||||||
self.debug = debug
|
self.debug = debug
|
||||||
|
|
||||||
# get a SUDS client object
|
# get a SOAP client object
|
||||||
if self.proxyurl is None:
|
wsdl_url = 'http://www.openligadb.de/Webservices/Sportsdata.asmx?WSDL'
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self.client = Client(
|
if proxyurl is None:
|
||||||
'http://www.openligadb.de/'
|
self.client = Client(wsdl=wsdl_url)
|
||||||
+ 'Webservices/Sportsdata.asmx?WSDL')
|
|
||||||
except (urllib.error.URLError):
|
|
||||||
self.error += "Connect to webservice failed."
|
|
||||||
else:
|
else:
|
||||||
try:
|
session = Session()
|
||||||
t = suds.transport.http.HttpTransport()
|
session.proxies = {'http': proxyurl, 'https': proxyurl}
|
||||||
proxy = urllib.request.ProxyHandler({'http':proxyurl})
|
transport = Transport(session=session)
|
||||||
opener = urllib.request.build_opener(proxy)
|
self.client = Client(wsdl=wsdl_url, transport=transport)
|
||||||
t.urlopener = opener
|
except (urllib.error.URLError, Exception) as e:
|
||||||
self.client = suds.client.Client(
|
self.error += f"Connect to webservice failed: {str(e)}\n"
|
||||||
'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"
|
|
||||||
|
|
||||||
def getSeason(self, season, league='bl1'):
|
def getSeason(self, season, league='bl1'):
|
||||||
""" Get the whole season.
|
""" Get the whole season.
|
||||||
|
|
||||||
Args: season and league shortcut (optional)
|
Args: season and league shortcut (optional)
|
||||||
"""
|
"""
|
||||||
return(self.client.service.GetMatchdataByLeagueSaison(
|
return self.client.service.GetMatchdataByLeagueSaison(
|
||||||
leagueShortcut=league,leagueSaison=season)
|
leagueShortcut=league, leagueSaison=season)
|
||||||
)
|
|
||||||
|
|
||||||
def getMatchday(self, season, matchdaynumber, league='bl1'):
|
def getMatchday(self, season, matchdaynumber, league='bl1'):
|
||||||
""" Get matchday.
|
""" Get matchday.
|
||||||
|
|
||||||
Args: season, matchdaynumber and league shortcut (optional)
|
Args: season, matchdaynumber and league shortcut (optional)
|
||||||
"""
|
"""
|
||||||
return(self.client.service.GetMatchdataByGroupLeagueSaison(
|
return self.client.service.GetMatchdataByGroupLeagueSaison(
|
||||||
groupOrderID=matchdaynumber,
|
groupOrderID=matchdaynumber,
|
||||||
leagueShortcut=league,
|
leagueShortcut=league,
|
||||||
leagueSaison=season)
|
leagueSaison=season)
|
||||||
)
|
|
||||||
|
|
||||||
def getTeams(self, season, league='bl1'):
|
def getTeams(self, season, league='bl1'):
|
||||||
return(self.client.service.GetTeamsByLeagueSaison(
|
return self.client.service.GetTeamsByLeagueSaison(
|
||||||
leagueShortcut=league,
|
leagueShortcut=league,
|
||||||
leagueSaison=season)
|
leagueSaison=season)
|
||||||
)
|
|
||||||
|
|
||||||
def getCurrentGroup(self, league):
|
def getCurrentGroup(self, league):
|
||||||
return(self.client.service.GetCurrentGroup(
|
return self.client.service.GetCurrentGroup(
|
||||||
leagueShortcut=league)
|
leagueShortcut=league)
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ urlpatterns = [
|
|||||||
re_path(r'^blog/newpost$', views.newBlogpost),
|
re_path(r'^blog/newpost$', views.newBlogpost),
|
||||||
re_path(r'^blog/(?P<page>\d)$', views.blogindex),
|
re_path(r'^blog/(?P<page>\d)$', views.blogindex),
|
||||||
re_path(r'^about$', views.about),
|
re_path(r'^about$', views.about),
|
||||||
|
re_path(r'^staff', views.staff),
|
||||||
re_path(r'^accounts/login/', auth_views.LoginView.as_view()),
|
re_path(r'^accounts/login/', auth_views.LoginView.as_view()),
|
||||||
re_path(r'^accounts/logout/', auth_views.LogoutView.as_view())
|
re_path(r'^accounts/logout/', auth_views.LogoutView.as_view())
|
||||||
]
|
]
|
||||||
|
|||||||
+503
-365
@@ -1,6 +1,6 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
"""
|
"""
|
||||||
Copyright (c) 2015-2016 by Martin Bley (martin@mb-oss.de)
|
Copyright (c) 2015-2016 by Martin Bley (dev@mb-oss.de)
|
||||||
|
|
||||||
This file is part of TipPy.
|
This file is part of TipPy.
|
||||||
|
|
||||||
@@ -18,466 +18,604 @@ You should have received a copy of the GNU General Public License
|
|||||||
along with TipPy. If not, see <http://www.gnu.org/licenses/>.
|
along with TipPy. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
import django.shortcuts
|
|
||||||
from django.contrib.auth.decorators import login_required
|
|
||||||
from django.core.exceptions import ObjectDoesNotExist
|
|
||||||
from django.core.paginator import Paginator, EmptyPage, \
|
|
||||||
PageNotAnInteger
|
|
||||||
from django.http import HttpResponseRedirect
|
|
||||||
from django.utils import timezone
|
|
||||||
from django.db.models import Sum
|
|
||||||
from django.conf import settings
|
|
||||||
from os.path import join as pjoin
|
|
||||||
from tipp.models import *
|
|
||||||
from tipp.forms import *
|
|
||||||
from tipp.openliga import *
|
|
||||||
from datetime import datetime
|
|
||||||
from PIL import Image as PImage
|
|
||||||
import pytz
|
|
||||||
import os
|
import os
|
||||||
|
from os.path import join as pjoin
|
||||||
|
|
||||||
timezoneLocal = pytz.timezone('Europe/Berlin')
|
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.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 *
|
||||||
|
|
||||||
|
TIMEZONE_LOCAL = pytz.timezone('Europe/Berlin')
|
||||||
|
THUMBNAIL_SIZE = (50, 50)
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
def about(request):
|
def about(request):
|
||||||
return django.shortcuts.render(request, 'about.html')
|
"""Render about page."""
|
||||||
|
return render(request, 'about.html')
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
def home(request):
|
def home(request):
|
||||||
|
"""Redirect to current matchday."""
|
||||||
ls = get_current_ls()
|
ls = get_current_ls()
|
||||||
season = get_current_season()
|
season = get_current_season()
|
||||||
md = get_current_md(ls)
|
md = get_current_md(ls)
|
||||||
|
|
||||||
return django.shortcuts.redirect("matchday",
|
return redirect(
|
||||||
|
'matchday',
|
||||||
ls=ls,
|
ls=ls,
|
||||||
season=season,
|
season=season,
|
||||||
matchday=str(md).zfill(2)
|
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
|
@login_required
|
||||||
|
@require_http_methods(["GET", "POST"])
|
||||||
def profile(request, pk):
|
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)
|
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':
|
if request.method == 'POST':
|
||||||
|
return _handle_profile_post(request, user, profile)
|
||||||
|
else:
|
||||||
|
return _handle_profile_get(request, user, profile)
|
||||||
|
|
||||||
|
|
||||||
|
def _handle_profile_post(request, user, profile):
|
||||||
|
"""Handle POST request for profile update."""
|
||||||
user_form = UserForm(data=request.POST, instance=user)
|
user_form = UserForm(data=request.POST, instance=user)
|
||||||
profile_form = UserProfileForm(
|
profile_form = UserProfileForm(data=request.POST, instance=profile)
|
||||||
data=request.POST, instance=profile
|
|
||||||
)
|
|
||||||
|
|
||||||
if user_form.is_valid() and profile_form.is_valid():
|
if user_form.is_valid() and profile_form.is_valid():
|
||||||
|
|
||||||
user = user_form.save()
|
user = user_form.save()
|
||||||
|
|
||||||
profile = profile_form.save(commit=False)
|
profile = profile_form.save(commit=False)
|
||||||
profile.user = user
|
profile.user = user
|
||||||
|
|
||||||
# Did the user provide a profile picture?
|
# Handle avatar upload
|
||||||
if 'avatar' in request.FILES:
|
if 'avatar' in request.FILES:
|
||||||
profile.avatar = request.FILES['avatar']
|
profile.avatar = request.FILES['avatar']
|
||||||
# Now we save the UserProfile model instance.
|
|
||||||
profile.save()
|
profile.save()
|
||||||
|
|
||||||
# create thumbnail
|
image_path = os.path.join(settings.MEDIA_ROOT, profile.avatar.name)
|
||||||
imfn = pjoin(settings.MEDIA_ROOT, profile.avatar.name)
|
try:
|
||||||
im = PImage.open(imfn)
|
_create_thumbnail(image_path)
|
||||||
im.thumbnail((50, 50), PImage.ANTIALIAS)
|
except Exception as e:
|
||||||
im.save(settings.MEDIA_ROOT + profile.avatar.name)
|
logger.error(f"Thumbnail creation failed: {str(e)}")
|
||||||
if 'avatar-clear' in request.POST:
|
|
||||||
if 'on' in request.POST['avatar-clear']:
|
# Handle avatar deletion
|
||||||
image = pjoin(settings.MEDIA_ROOT, img)
|
elif 'avatar-clear' in request.POST and request.POST['avatar-clear'] == 'on':
|
||||||
debug.append('Is the image ' + str(image)
|
if profile.avatar:
|
||||||
+ ' a regular file? '
|
old_image_path = os.path.join(settings.MEDIA_ROOT, profile.avatar.name)
|
||||||
+ str(os.path.isfile(image))
|
_delete_avatar(old_image_path)
|
||||||
)
|
profile.avatar = None
|
||||||
if os.path.isfile(image):
|
|
||||||
os.remove(image)
|
|
||||||
profile.save()
|
profile.save()
|
||||||
|
return redirect('profile', pk=user.id)
|
||||||
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:
|
else:
|
||||||
print(
|
logger.warning(f"Form validation failed for user {user.id}: "
|
||||||
user_form.errors,
|
f"User errors: {user_form.errors}, "
|
||||||
profile_form.errors
|
f"Profile errors: {profile_form.errors}")
|
||||||
)
|
|
||||||
|
|
||||||
return django.shortcuts.redirect('/accounts/profile/' + str(user.id))
|
return _render_profile_form(request, user_form, profile_form, profile)
|
||||||
|
|
||||||
# Not a HTTP POST, so we render our form using two ModelForm
|
|
||||||
# instances. These forms will be blank, ready for user input.
|
def _handle_profile_get(request, user, profile):
|
||||||
else:
|
"""Handle GET request for profile display."""
|
||||||
user_form = UserForm(
|
user_form = UserForm(instance=user)
|
||||||
initial={'last_name': user.last_name,
|
|
||||||
'first_name': user.first_name,
|
|
||||||
'email': user.email},
|
|
||||||
instance=user
|
|
||||||
)
|
|
||||||
profile_form = UserProfileForm(instance=profile)
|
profile_form = UserProfileForm(instance=profile)
|
||||||
|
|
||||||
return django.shortcuts.render(request, 'registration/profile.html', {
|
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,
|
'user_form': user_form,
|
||||||
'profile_form': profile_form,
|
'profile_form': profile_form,
|
||||||
'debug': debug,
|
'avatar': profile.avatar.name if profile.avatar else None,
|
||||||
'img': img}
|
}
|
||||||
)
|
return render(request, 'registration/profile.html', context)
|
||||||
|
|
||||||
|
@staff_member_required
|
||||||
|
def staff(request):
|
||||||
|
"""Display available competitions for current season."""
|
||||||
|
season = get_current_season()
|
||||||
|
competitions = Competition.objects.filter(
|
||||||
|
season=season
|
||||||
|
).order_by('leagueName')
|
||||||
|
|
||||||
|
return render(request, 'staff.html', {'competitions': competitions})
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
@staff_member_required
|
||||||
def getSeason(request, ls, season):
|
def getSeason(request, ls, season):
|
||||||
|
"""Fetch and import teams and matches for a season from OpenLiga."""
|
||||||
ol = OpenLiga()
|
ol = OpenLiga()
|
||||||
teams = ol.getTeams(str(season), ls)
|
teams_data = ol.getTeams(str(season), ls)
|
||||||
season = ol.getSeason(str(season), ls)
|
season_data = ol.getSeason(str(season), ls)
|
||||||
|
|
||||||
return_vals = []
|
# Bulk import teams
|
||||||
for team in teams[0]:
|
_import_teams(teams_data[0])
|
||||||
t = Team(
|
|
||||||
teamID=team['teamID'],
|
|
||||||
name=team['teamName'],
|
|
||||||
iconURL=team['teamIconURL']
|
|
||||||
)
|
|
||||||
|
|
||||||
"""
|
# Bulk import matches
|
||||||
check if object exits and do insert if not
|
_import_season_matches(season_data[0], ls)
|
||||||
"""
|
|
||||||
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]:
|
return redirect(
|
||||||
m = Match(matchID=match['matchID'],
|
'matchday',
|
||||||
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,
|
ls=ls,
|
||||||
season=get_current_season(),
|
season=get_current_season(),
|
||||||
matchday=str(get_current_md(ls)).zfill(2)
|
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']
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
if matches:
|
||||||
|
Match.objects.bulk_create(matches, ignore_conflicts=True)
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
def update(request, ls, season, cur_md):
|
def update(request, ls, season, cur_md):
|
||||||
"""
|
"""Update match results and calculate user scores for current matchday."""
|
||||||
To do:
|
|
||||||
- update table tipp_competition
|
|
||||||
"""
|
|
||||||
|
|
||||||
ol = OpenLiga()
|
ol = OpenLiga()
|
||||||
md_matches = ol.getMatchday(str(season), int(cur_md), ls)
|
md_matches = ol.getMatchday(str(season), int(cur_md), ls)
|
||||||
|
|
||||||
# Achtung: DateTimeField prüfen
|
# Update match results
|
||||||
for match in md_matches[0]:
|
_update_match_results(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
|
|
||||||
|
|
||||||
except:
|
# Calculate scores for all tips
|
||||||
pointsTeam1 = -1
|
_calculate_matchday_scores(request.user, ls, season, cur_md)
|
||||||
pointsTeam2 = -1
|
|
||||||
|
|
||||||
m = Match(matchID=match['matchID'],
|
return redirect('matchday', ls=ls, season=season, matchday=cur_md)
|
||||||
matchDateTime=match['matchDateTime'],
|
|
||||||
pointsTeam1=pointsTeam1,
|
|
||||||
pointsTeam2=pointsTeam2,
|
def _update_match_results(matches_data):
|
||||||
finished=bool(match['matchIsFinished']),
|
"""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']),
|
||||||
|
)
|
||||||
)
|
)
|
||||||
m.save(update_fields=['matchDateTime', 'pointsTeam1', 'pointsTeam2', 'finished'])
|
|
||||||
pointsTeam1 = -1
|
|
||||||
pointsTeam2 = -1
|
|
||||||
|
|
||||||
# evaluate scores
|
if matches_to_update:
|
||||||
""" get mandants current user is related """
|
Match.objects.bulk_update(
|
||||||
mandants = RelUserMandant.objects.filter(user=request.user). \
|
matches_to_update,
|
||||||
values_list('mandant', flat=True)
|
['matchDateTime', 'pointsTeam1', 'pointsTeam2', 'finished'],
|
||||||
|
batch_size=100
|
||||||
|
)
|
||||||
|
|
||||||
# scores = Score.objects.get(client=Mandant.objects.get(name='huse'))
|
|
||||||
scores = Score.objects.get(client=mandants[0])
|
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_matches = Match.objects.filter(
|
||||||
finished=True,
|
finished=True,
|
||||||
leagueShortcut=ls,
|
leagueShortcut=ls,
|
||||||
season=season,
|
season=season,
|
||||||
matchday=cur_md
|
matchday=cur_md
|
||||||
)
|
).prefetch_related('tipp_set')
|
||||||
|
|
||||||
|
tipps_to_update = []
|
||||||
|
|
||||||
for match in finished_matches:
|
for match in finished_matches:
|
||||||
tipps = Tipp.objects.filter(matchID=match.matchID)
|
for tipp in match.tipp_set.all():
|
||||||
for tipp in tipps:
|
score = _calculate_tipp_score(tipp, match, scores)
|
||||||
score = 0
|
tipp.score = score
|
||||||
|
tipps_to_update.append(tipp)
|
||||||
|
|
||||||
|
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
|
goals_total = match.pointsTeam1 + match.pointsTeam2
|
||||||
|
return scores.exact_high if goals_total >= 5 else scores.exact
|
||||||
|
|
||||||
# Tipp is acurate
|
# Winner and difference
|
||||||
if (tipp.pointsTeam1 == match.pointsTeam1
|
winner_real = _get_winner(match.pointsTeam1, match.pointsTeam2)
|
||||||
and tipp.pointsTeam2 == match.pointsTeam2):
|
winner_tipp = _get_winner(tipp.pointsTeam1, tipp.pointsTeam2)
|
||||||
if goals_total >= 5:
|
|
||||||
score = scores.exact_high
|
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:
|
else:
|
||||||
score = scores.exact
|
return scores.tendency
|
||||||
|
|
||||||
tipp.score = score
|
return 0
|
||||||
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
|
def _get_winner(points_team1, points_team2):
|
||||||
if tipp.pointsTeam1 > tipp.pointsTeam2:
|
"""Determine match winner.
|
||||||
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
|
|
||||||
|
|
||||||
tipp.score = score
|
|
||||||
tipp.save()
|
|
||||||
continue
|
|
||||||
|
|
||||||
return django.shortcuts.redirect("matchday",
|
|
||||||
ls=ls,
|
|
||||||
season=season,
|
|
||||||
matchday=cur_md
|
|
||||||
)
|
|
||||||
|
|
||||||
|
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
|
@login_required
|
||||||
|
@require_http_methods(["GET", "POST"])
|
||||||
def matchday(request, ls, season, matchday, template_name='md.html'):
|
def matchday(request, ls, season, matchday, template_name='md.html'):
|
||||||
debug = ''
|
"""Display matchday with user tips, match results, and mandant scores."""
|
||||||
debug += "Debugging: "
|
# Process form submissions first
|
||||||
has_refresh = True
|
_process_matchday_tips(request, ls, season, matchday)
|
||||||
|
|
||||||
""" get matches of the matchday """
|
user = request.user
|
||||||
md_matches = Match.objects.filter(leagueShortcut=ls, season=season,
|
matches = _build_matchday_matches(request, user, ls, season, matchday)
|
||||||
matchday=int(matchday)).order_by('matchDateTime')
|
mandant_dict = _build_mandant_scores(user, matches)
|
||||||
|
posts = _get_mandant_posts(user)
|
||||||
|
|
||||||
""" get the current user """
|
cur_group = matches[0]['md_name'] if matches else "N/A"
|
||||||
user = User.objects.get(username=request.user.username)
|
|
||||||
|
|
||||||
""" get mandants current user is related """
|
return render(request, template_name, {
|
||||||
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,
|
|
||||||
'cur_md': matchday,
|
'cur_md': matchday,
|
||||||
'md_name': cur_group,
|
'md_name': cur_group,
|
||||||
'matches': matches,
|
'matches': matches,
|
||||||
'ls': ls,
|
'ls': ls,
|
||||||
'season': season,
|
'season': season,
|
||||||
'username': user,
|
'username': user.username,
|
||||||
'mandant_dict': mandant_dict,
|
'mandant_dict': mandant_dict,
|
||||||
# 'tipp_mates': sorted(tipp_mates, key=lambda k: k['sum_score'],
|
|
||||||
# reverse=True),
|
|
||||||
'posts': posts,
|
'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
|
@login_required
|
||||||
def charts(request, ls, season, pos='default',
|
def charts(request, ls, season, pos='default',
|
||||||
template_name='charts.html'):
|
template_name='charts.html'):
|
||||||
|
|||||||
Reference in New Issue
Block a user