Compare commits
17 Commits
css3_redes
...
riichi_201
| Author | SHA1 | Date | |
|---|---|---|---|
| 0b2e040fc9 | |||
| fdbf819092 | |||
| 9f6fffa4f4 | |||
| b7fab97715 | |||
| c030a31e2b | |||
| ade2a568f7 | |||
| c5781246fe | |||
| bb5081a78b | |||
| 854fd38740 | |||
| 6de1ecb102 | |||
| bf12060c3b | |||
| 5ad628f33a | |||
| 36272c60d6 | |||
| c428f6ed1f | |||
| 9276e97c36 | |||
| fd244f10e8 | |||
| 0a45cf1fd8 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -62,6 +62,7 @@ docs/_build/
|
|||||||
target/
|
target/
|
||||||
|
|
||||||
#Django Development
|
#Django Development
|
||||||
|
backup/
|
||||||
/bower_components/
|
/bower_components/
|
||||||
/media/
|
/media/
|
||||||
/node_modules/
|
/node_modules/
|
||||||
|
|||||||
5
TODO
5
TODO
@@ -766,11 +766,6 @@ src/mahjong_ranking/models.py
|
|||||||
| | [NORMAL] PyLintBear (W0201):
|
| | [NORMAL] PyLintBear (W0201):
|
||||||
| | W0201 - Attribute 'kyu_points' defined outside __init__
|
| | W0201 - Attribute 'kyu_points' defined outside __init__
|
||||||
|
|
||||||
src/mahjong_ranking/models.py
|
|
||||||
| 330| class·KyuDanRanking(models.Model):
|
|
||||||
| | [NORMAL] PyLintBear (W5102):
|
|
||||||
| | W5102 - Found __unicode__ method on model (KyuDanRanking). Python3 uses __str__.
|
|
||||||
|
|
||||||
src/mahjong_ranking/models.py
|
src/mahjong_ranking/models.py
|
||||||
| 330| class·KyuDanRanking(models.Model):
|
| 330| class·KyuDanRanking(models.Model):
|
||||||
| | [INFO] PyLintBear (R0902):
|
| | [INFO] PyLintBear (R0902):
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
SSH_LOGIN="kasu@s21.wservices.ch"
|
SSH_LOGIN="kasu@s21.wservices.ch"
|
||||||
SYNC_ASSESTS="requirements"
|
SYNC_ASSESTS="requirements static"
|
||||||
SYNC_SOURCECODE="src"
|
SYNC_SOURCECODE="src"
|
||||||
EXCLUDE_FILES="*.pyc"
|
EXCLUDE_FILES="*.pyc"
|
||||||
|
|
||||||
@@ -19,5 +19,5 @@ rsync -r --copy-links --delete ${SYNC_SOURCECODE} ${SSH_LOGIN}:~/ --exclude 'src
|
|||||||
|
|
||||||
echo "Rebuild and reload django..."
|
echo "Rebuild and reload django..."
|
||||||
ssh ${SSH_LOGIN} "rm src/kasu/settings/development.*"
|
ssh ${SSH_LOGIN} "rm src/kasu/settings/development.*"
|
||||||
ssh ${SSH_LOGIN} "virtualenv/bin/python ~/src/manage.py collectstatic -l --noinput -v1"
|
ssh ${SSH_LOGIN} "~/virtualenv/bin/python ~/src/manage.py collectstatic -l --noinput -v1"
|
||||||
ssh ${SSH_LOGIN} "~/init/kasu restart"
|
ssh ${SSH_LOGIN} "~/init/kasu restart"
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
beautifulsoup4
|
beautifulsoup4
|
||||||
django
|
django < 2.0
|
||||||
django-appconf
|
django-appconf
|
||||||
django-ckeditor
|
django-ckeditor
|
||||||
django-contrib-comments
|
django-contrib-comments
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from django.db import models, migrations
|
|
||||||
import django.db.models.deletion
|
import django.db.models.deletion
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
from django.db import models, migrations
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
@@ -44,7 +44,8 @@ class Migration(migrations.Migration):
|
|||||||
('date_modified', models.DateTimeField(
|
('date_modified', models.DateTimeField(
|
||||||
auto_now=True, verbose_name='Bearbeitet')),
|
auto_now=True, verbose_name='Bearbeitet')),
|
||||||
('author', models.ForeignKey(
|
('author', models.ForeignKey(
|
||||||
verbose_name='Autor', to=settings.AUTH_USER_MODEL)),
|
verbose_name='Autor', to=settings.AUTH_USER_MODEL,
|
||||||
|
on_delete=models.CASCADE))
|
||||||
],
|
],
|
||||||
options={
|
options={
|
||||||
'ordering': ('-date_created',),
|
'ordering': ('-date_created',),
|
||||||
@@ -144,7 +145,8 @@ class Migration(migrations.Migration):
|
|||||||
model_name='article',
|
model_name='article',
|
||||||
name='category',
|
name='category',
|
||||||
field=models.ForeignKey(
|
field=models.ForeignKey(
|
||||||
verbose_name='Kategorie', to='content.Category'),
|
verbose_name='Kategorie', to='content.Category',
|
||||||
|
on_delete=models.CASCADE),
|
||||||
),
|
),
|
||||||
migrations.AlterUniqueTogether(
|
migrations.AlterUniqueTogether(
|
||||||
name='page',
|
name='page',
|
||||||
|
|||||||
91
src/content/migrations/0006_auto_20171115_0653.py
Normal file
91
src/content/migrations/0006_auto_20171115_0653.py
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.11.7 on 2017-11-15 05:53
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import ckeditor_uploader.fields
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('content', '0005_auto_20161012_2236'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='article',
|
||||||
|
name='content_de',
|
||||||
|
field=ckeditor_uploader.fields.RichTextUploadingField(verbose_name='Inhalt'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='article',
|
||||||
|
name='content_en',
|
||||||
|
field=ckeditor_uploader.fields.RichTextUploadingField(blank=True, verbose_name='Content'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='article',
|
||||||
|
name='headline_en',
|
||||||
|
field=models.CharField(blank=True, max_length=255, verbose_name='Headline'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='article',
|
||||||
|
name='image',
|
||||||
|
field=models.ImageField(blank=True, null=True, upload_to='news/', verbose_name='Bild'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='article',
|
||||||
|
name='slug',
|
||||||
|
field=models.SlugField(unique_for_month='date_created', verbose_name='Slug'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='category',
|
||||||
|
name='image',
|
||||||
|
field=models.ImageField(blank=True, null=True, upload_to='news/categories/', verbose_name='Bild'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='page',
|
||||||
|
name='content_de',
|
||||||
|
field=ckeditor_uploader.fields.RichTextUploadingField(blank=True, verbose_name='Inhalt'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='page',
|
||||||
|
name='content_en',
|
||||||
|
field=ckeditor_uploader.fields.RichTextUploadingField(blank=True, verbose_name='Content'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='page',
|
||||||
|
name='menu_name_de',
|
||||||
|
field=models.CharField(help_text='Ein kurzer Name für den Menüeintrag', max_length=255, verbose_name='Menü Name'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='page',
|
||||||
|
name='menu_name_en',
|
||||||
|
field=models.CharField(blank=True, help_text='Ein kurzer Name für den Menüeintrag', max_length=255, verbose_name='Menu Name'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='page',
|
||||||
|
name='pdf_de',
|
||||||
|
field=models.FileField(blank=True, null=True, upload_to='pdf/de/'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='page',
|
||||||
|
name='pdf_en',
|
||||||
|
field=models.FileField(blank=True, null=True, upload_to='pdf/en/'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='page',
|
||||||
|
name='template',
|
||||||
|
field=models.CharField(default='content/page.html', max_length=255, verbose_name='Vorlage'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='page',
|
||||||
|
name='title_de',
|
||||||
|
field=models.CharField(help_text="The page title as you'd like it to be seen by the public", max_length=255, verbose_name='Titel'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='page',
|
||||||
|
name='title_en',
|
||||||
|
field=models.CharField(blank=True, help_text="The page title as you'd like it to be seen by the public", max_length=255, verbose_name='Title'),
|
||||||
|
),
|
||||||
|
]
|
||||||
27
src/content/migrations/0007_auto_20171214_1215.py
Normal file
27
src/content/migrations/0007_auto_20171214_1215.py
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.11.8 on 2017-12-14 11:15
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('content', '0006_auto_20171115_0653'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='article',
|
||||||
|
name='author',
|
||||||
|
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL, verbose_name='Autor'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='article',
|
||||||
|
name='category',
|
||||||
|
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='content.Category', verbose_name='Kategorie'),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -3,9 +3,9 @@ from ckeditor_uploader.fields import RichTextUploadingField
|
|||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.core.cache import cache
|
from django.core.cache import cache
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
from django.core.urlresolvers import reverse
|
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.template.defaultfilters import slugify
|
from django.template.defaultfilters import slugify
|
||||||
|
from django.urls import reverse
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from django.utils.safestring import mark_safe
|
from django.utils.safestring import mark_safe
|
||||||
from django.utils.translation import get_language, ugettext as _
|
from django.utils.translation import get_language, ugettext as _
|
||||||
@@ -41,6 +41,14 @@ def get_upload_path(instance, filename):
|
|||||||
return "categories/%s.%s" % (instance.slug, extension)
|
return "categories/%s.%s" % (instance.slug, extension)
|
||||||
|
|
||||||
|
|
||||||
|
def get_localized(obj, attr):
|
||||||
|
""" Return the localilzed field, or the fallback if the localized is empty.
|
||||||
|
"""
|
||||||
|
fallback = attr + '_de'
|
||||||
|
localized = attr + '_' + get_language()[:2]
|
||||||
|
return getattr(obj, localized) or getattr(obj, fallback)
|
||||||
|
|
||||||
|
|
||||||
class ArticleManager(models.Manager):
|
class ArticleManager(models.Manager):
|
||||||
"""Adds some predifined querys and joins some tables for faster querys."""
|
"""Adds some predifined querys and joins some tables for faster querys."""
|
||||||
|
|
||||||
@@ -69,11 +77,14 @@ class Article(models.Model):
|
|||||||
headline_en = models.CharField('Headline', max_length=255, blank=True)
|
headline_en = models.CharField('Headline', max_length=255, blank=True)
|
||||||
content_de = RichTextUploadingField(_('Content'))
|
content_de = RichTextUploadingField(_('Content'))
|
||||||
content_en = RichTextUploadingField('Content', blank=True)
|
content_en = RichTextUploadingField('Content', blank=True)
|
||||||
category = models.ForeignKey('Category', verbose_name=_('Category'))
|
category = models.ForeignKey('Category',
|
||||||
|
on_delete=models.PROTECT,
|
||||||
|
verbose_name=_('Category'))
|
||||||
image = models.ImageField(_('Image'), upload_to='news/',
|
image = models.ImageField(_('Image'), upload_to='news/',
|
||||||
blank=True, null=True)
|
blank=True, null=True)
|
||||||
slug = models.SlugField(_('Slug'), unique_for_month='date_created')
|
slug = models.SlugField(_('Slug'), unique_for_month='date_created')
|
||||||
author = models.ForeignKey(settings.AUTH_USER_MODEL,
|
author = models.ForeignKey(settings.AUTH_USER_MODEL,
|
||||||
|
on_delete=models.PROTECT,
|
||||||
verbose_name=_('Author'))
|
verbose_name=_('Author'))
|
||||||
status = models.SmallIntegerField(_('Status'), choices=STATUS_CHOICES,
|
status = models.SmallIntegerField(_('Status'), choices=STATUS_CHOICES,
|
||||||
default=STATUS_PUBLISHED)
|
default=STATUS_PUBLISHED)
|
||||||
@@ -115,16 +126,12 @@ class Article(models.Model):
|
|||||||
@property
|
@property
|
||||||
def headline(self):
|
def headline(self):
|
||||||
"""Return the localized headline, fallback to german if necessary."""
|
"""Return the localized headline, fallback to german if necessary."""
|
||||||
return mark_safe(
|
return mark_safe(get_localized(self, 'headline'))
|
||||||
getattr(self, "headline_%s" % get_language(), self.headline_de)
|
|
||||||
)
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def content(self):
|
def content(self):
|
||||||
"""Return the localized content, fallback to german if necessary."""
|
"""Return the localized content, fallback to german if necessary."""
|
||||||
return mark_safe(
|
return mark_safe(get_localized(self, 'content'))
|
||||||
getattr(self, "content_%s" % get_language(), self.content_de)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class Category(models.Model):
|
class Category(models.Model):
|
||||||
@@ -146,13 +153,12 @@ class Category(models.Model):
|
|||||||
@property
|
@property
|
||||||
def name(self):
|
def name(self):
|
||||||
"""Return the localized name, fallback to german if necessary."""
|
"""Return the localized name, fallback to german if necessary."""
|
||||||
return getattr(self, "name_%s" % get_language(), self.name_de)
|
return get_localized(self, 'name')
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def description(self):
|
def description(self):
|
||||||
"""Return the localized description, fallback to german if necessary."""
|
"""Return the localized description, fallback to german if necessary."""
|
||||||
return getattr(self, "description_%s" % get_language(),
|
return get_localized(self, 'description')
|
||||||
self.description_de)
|
|
||||||
|
|
||||||
def get_absolute_url(self):
|
def get_absolute_url(self):
|
||||||
"""Return the URL of the article archive, filtered on this category."""
|
"""Return the URL of the article archive, filtered on this category."""
|
||||||
@@ -261,9 +267,7 @@ class Page(models.Model):
|
|||||||
@property
|
@property
|
||||||
def content(self):
|
def content(self):
|
||||||
"""Return the localized content, fallback to german if necessary."""
|
"""Return the localized content, fallback to german if necessary."""
|
||||||
return mark_safe(
|
return mark_safe(get_localized(self, 'content'))
|
||||||
getattr(self, "content_%s" % get_language(), self.content_de)
|
|
||||||
)
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def css_class(self):
|
def css_class(self):
|
||||||
@@ -275,23 +279,22 @@ class Page(models.Model):
|
|||||||
@property
|
@property
|
||||||
def description(self):
|
def description(self):
|
||||||
"""Return the localized description, fallback to german if necessary."""
|
"""Return the localized description, fallback to german if necessary."""
|
||||||
return getattr(self, "description_%s" % get_language(),
|
return get_localized(self, 'description')
|
||||||
self.description_de)
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def menu_name(self):
|
def menu_name(self):
|
||||||
"""Return the localized menu name, fallback to german if necessary."""
|
"""Return the localized menu name, fallback to german if necessary."""
|
||||||
return getattr(self, "menu_name_%s" % get_language(), self.menu_name_de)
|
return get_localized(self, 'menu_name')
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def pdf_file(self):
|
def pdf_file(self):
|
||||||
"""Return the localized PDF file, fallback to german if necessary."""
|
"""Return the localized PDF file, fallback to german if necessary."""
|
||||||
return getattr(self, "pdf_%s" % get_language(), self.pdf_de)
|
return get_localized(self, 'pdf_file')
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def title(self):
|
def title(self):
|
||||||
"""Return the localized title, fallback to german if necessary."""
|
"""Return the localized title, fallback to german if necessary."""
|
||||||
return getattr(self, "title_%s" % get_language(), self.title_de)
|
return get_localized(self, 'title')
|
||||||
|
|
||||||
def clean(self):
|
def clean(self):
|
||||||
"""set the URL path, the right content type, and scrub the HTML code."""
|
"""set the URL path, the right content type, and scrub the HTML code."""
|
||||||
|
|||||||
@@ -31,6 +31,10 @@ class EventManager(models.Manager):
|
|||||||
"""Returns all past events."""
|
"""Returns all past events."""
|
||||||
return self.filter(start__lt=now())
|
return self.filter(start__lt=now())
|
||||||
|
|
||||||
|
def latest(self, limit=None):
|
||||||
|
result = self.filter(start__lt=now()).order_by('-start', '-end')
|
||||||
|
return result[0:limit] if limit else result
|
||||||
|
|
||||||
def upcoming(self, limit=None):
|
def upcoming(self, limit=None):
|
||||||
"""Returns the next 'limit' upcoming events.
|
"""Returns the next 'limit' upcoming events.
|
||||||
|
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from django.db import models, migrations
|
|
||||||
import events.models
|
|
||||||
import django.db.models.deletion
|
import django.db.models.deletion
|
||||||
|
from django.db import models, migrations
|
||||||
|
|
||||||
|
import events.models
|
||||||
import utils
|
import utils
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -17,7 +17,8 @@ class Migration(migrations.Migration):
|
|||||||
name='Event',
|
name='Event',
|
||||||
fields=[
|
fields=[
|
||||||
('id', models.AutoField(verbose_name='ID',
|
('id', models.AutoField(verbose_name='ID',
|
||||||
serialize=False, auto_created=True, primary_key=True)),
|
serialize=False, auto_created=True,
|
||||||
|
primary_key=True)),
|
||||||
('name', models.CharField(max_length=255, verbose_name='Name')),
|
('name', models.CharField(max_length=255, verbose_name='Name')),
|
||||||
('description', models.TextField(
|
('description', models.TextField(
|
||||||
verbose_name='Beschreibung', blank=True)),
|
verbose_name='Beschreibung', blank=True)),
|
||||||
@@ -26,13 +27,20 @@ class Migration(migrations.Migration):
|
|||||||
null=True, verbose_name='Ende', blank=True)),
|
null=True, verbose_name='Ende', blank=True)),
|
||||||
('url', models.URLField(verbose_name='Homepage', blank=True)),
|
('url', models.URLField(verbose_name='Homepage', blank=True)),
|
||||||
('image', models.ImageField(storage=utils.OverwriteStorage(
|
('image', models.ImageField(storage=utils.OverwriteStorage(
|
||||||
), upload_to=events.models.get_upload_path, null=True, verbose_name='Bild', blank=True)),
|
), upload_to=events.models.get_upload_path, null=True,
|
||||||
|
verbose_name='Bild', blank=True)),
|
||||||
('is_tournament', models.BooleanField(default=False,
|
('is_tournament', models.BooleanField(default=False,
|
||||||
help_text='Diese Veranstaltung ist ein Turnier, es gelten andere Regeln f\xfcr das Kyu Ranking.', verbose_name='Turnier')),
|
help_text='Diese Veranstaltung ist ein Turnier, es gelten andere Regeln f\xfcr das Kyu Ranking.',
|
||||||
|
verbose_name='Turnier')),
|
||||||
('photo_count', models.PositiveIntegerField(
|
('photo_count', models.PositiveIntegerField(
|
||||||
default=0, editable=False)),
|
default=0, editable=False)),
|
||||||
('event_series', models.ForeignKey(on_delete=django.db.models.deletion.SET_NULL, editable=False, to='events.Event', blank=True,
|
('event_series',
|
||||||
help_text='Wenn dieser Termin zu einer Veranstaltungsreihe geh\xf6rt werden Ort, Beschreibung, Bild und Homepage von dem hier angegebenen Event \xfcbernommen.', null=True, verbose_name='Veranstaltungsreihen')),
|
models.ForeignKey(on_delete=django.db.models.deletion.SET_NULL,
|
||||||
|
editable=False, to='events.Event',
|
||||||
|
blank=True,
|
||||||
|
help_text='Wenn dieser Termin zu einer Veranstaltungsreihe geh\xf6rt werden Ort, Beschreibung, Bild und Homepage von dem hier angegebenen Event \xfcbernommen.',
|
||||||
|
null=True,
|
||||||
|
verbose_name='Veranstaltungsreihen')),
|
||||||
],
|
],
|
||||||
options={
|
options={
|
||||||
'ordering': ('-start', '-end'),
|
'ordering': ('-start', '-end'),
|
||||||
@@ -44,20 +52,310 @@ class Migration(migrations.Migration):
|
|||||||
name='Location',
|
name='Location',
|
||||||
fields=[
|
fields=[
|
||||||
('id', models.AutoField(verbose_name='ID',
|
('id', models.AutoField(verbose_name='ID',
|
||||||
serialize=False, auto_created=True, primary_key=True)),
|
serialize=False, auto_created=True,
|
||||||
|
primary_key=True)),
|
||||||
('name', models.CharField(max_length=200, verbose_name='Name')),
|
('name', models.CharField(max_length=200, verbose_name='Name')),
|
||||||
('description', models.TextField(
|
('description', models.TextField(
|
||||||
verbose_name='Beschreibung', blank=True)),
|
verbose_name='Beschreibung', blank=True)),
|
||||||
('image', models.ImageField(storage=utils.OverwriteStorage(
|
('image', models.ImageField(storage=utils.OverwriteStorage(
|
||||||
), upload_to=events.models.get_upload_path, null=True, verbose_name='Bild', blank=True)),
|
), upload_to=events.models.get_upload_path, null=True,
|
||||||
|
verbose_name='Bild', blank=True)),
|
||||||
('url', models.URLField(verbose_name='Homepage', blank=True)),
|
('url', models.URLField(verbose_name='Homepage', blank=True)),
|
||||||
('postal_code', models.CharField(
|
('postal_code', models.CharField(
|
||||||
max_length=6, verbose_name='Postleitzahl')),
|
max_length=6, verbose_name='Postleitzahl')),
|
||||||
('street_address', models.CharField(
|
('street_address', models.CharField(
|
||||||
max_length=127, verbose_name='Stra\xdfe')),
|
max_length=127, verbose_name='Stra\xdfe')),
|
||||||
('locality', models.CharField(max_length=127, verbose_name='Ort')),
|
('locality',
|
||||||
('country', models.CharField(max_length=2, verbose_name='Land', choices=[(b'GB', 'Vereinigtes K\xf6nigreich'), (b'AF', 'Afghanistan'), (b'AX', 'Aland Islands'), (b'AL', 'Albanien'), (b'DZ', 'Algerien'), (b'AS', 'Amerikanisch-Samoa'), (b'AD', 'Andorra'), (b'AO', 'Angola'), (b'AI', 'Anguilla'), (b'AQ', 'Antarktika'), (b'AG', 'Antigua und Barbuda'), (b'AR', 'Argentinien'), (b'AM', 'Armenien'), (b'AW', 'Aruba'), (b'AU', 'Australien'), (b'AT', '\xd6sterreich'), (b'AZ', 'Aserbaidschan'), (b'BS', 'Bahamas'), (b'BH', 'Bahrein'), (b'BD', 'Bangladesch'), (b'BB', 'Barbados'), (b'BY', 'Wei\xdfrussland'), (b'BE', 'Belgien'), (b'BZ', 'Belize'), (b'BJ', 'Benin'), (b'BM', 'Bermuda'), (b'BT', 'Bhutan'), (b'BO', 'Bolivien'), (b'BA', 'Bosnien und Herzegowina'), (b'BW', 'Botswana'), (b'BV', 'Bouvet Island'), (b'BR', 'Brasilien'), (b'IO', 'British Indian Ocean Territory'), (b'BN', 'Brunei Darussalam'), (b'BG', 'Bulgarien'), (b'BF', 'Burkina Faso'), (b'BI', 'Burundi'), (b'KH', 'Kambodscha'), (b'CM', 'Kamerun'), (b'CA', 'Kanada'), (b'CV', 'Cape Verde'), (b'KY', 'Cayman Islands'), (b'CF', 'Zentralafrikanische Republik'), (b'TD', 'Tschad'), (b'CL', 'Chile'), (b'CN', 'China'), (b'CX', 'Christmas Island'), (b'CC', 'Cocos (Keeling) Islands'), (b'CO', 'Kolumbien'), (b'KM', 'Komoren'), (b'CG', 'Kongo'), (b'CD', 'Kongo, Demokratische Republik'), (b'CK', 'Cook-Inseln'), (b'CR', 'Costa Rica'), (b'CI', "Cote d'Ivoire"), (b'HR', 'Kroatien'), (b'CU', 'Kuba'), (b'CY', 'Zypern'), (b'CZ', 'Tschechische Republik'), (b'DK', 'D\xe4nemark'), (b'DJ', 'Dschibuti'), (b'DM', 'Dominica'), (b'DO', 'Dominikanische Republik'), (b'EC', 'Ecuador'), (b'EG', '\xc4gypten'), (b'SV', 'El Salvador'), (b'GQ', '\xc4quatorial-Guinea'), (b'ER', 'Eritrea'), (b'EE', 'Estland'), (b'ET', '\xc4thiopien'), (b'FK', 'Falklandinseln (Malvinas)'), (b'FO', 'F\xe4r\xf6er-Inseln'), (b'FJ', 'Fidschi'), (b'FI', 'Finnland'), (b'FR', 'Frankreich'), (b'GF', 'Franz\xf6sisch-Guayana'), (b'PF', 'Franz\xf6sisch-Polynesien'), (b'TF', 'Franz\xf6sisch S\xfcdliche Territorien'), (b'GA', 'Gabun'), (b'GM', 'Gambia'), (b'GE', 'Georgia'), (b'DE', 'Deutschland'), (b'GH', 'Ghana'), (b'GI', 'Gibraltar'), (b'GR', 'Griechenland'), (b'GL', 'Gr\xf6nland'), (b'GD', 'Grenada'), (b'GP', 'Guadeloupe'), (b'GU', 'Guam'), (b'GT', 'Guatemala'), (b'GG', 'Guernsey'), (b'GN', 'Guinea'), (b'GW', 'Guinea-Bissau'), (b'GY', 'Guyana'), (b'HT', 'Haiti'), (b'HM', 'Heard und McDonald Inseln'), (b'VA', 'Heiliger Stuhl (Vatikanstadt)'), (b'HN', 'Honduras'), (b'HK', 'Hongkong'), (b'HU', 'Ungarn'), (b'IS', 'Island'), (b'IN', 'Indien'), (b'ID', 'Indonesien'), (b'IR', 'Iran, Islamische Republik'), (b'IQ', 'Irak'), (b'IE', 'Irland'), (b'IM', 'Isle of Man'), (b'IL', 'Israel'), (b'IT', 'Italien'), (b'JM', 'Jamaika'), (b'JP', 'Japan'), (b'JE', 'Jersey'), (b'JO', 'Jordan'), (b'KZ', 'Kasachstan'), (b'KE', 'Kenia'), (b'KI', 'Kiribati'), (b'KP', 'Korea, Demokratische Volksrepublik'), (b'KR', 'Korea, Republik'), (b'KW', 'Kuwait'), (b'KG', 'Kirgisistan'), (b'LA', 'Lao Demokratischen Volksrepublik'), (b'LV', 'Lettland'), (b'LB', 'Libanon'), (
|
models.CharField(max_length=127, verbose_name='Ort')),
|
||||||
b'LS', 'Lesotho'), (b'LR', 'Liberia'), (b'LY', 'Libyen'), (b'LI', 'Liechtenstein'), (b'LT', 'Litauen'), (b'LU', 'Luxemburg'), (b'MO', 'Macao'), (b'MK', 'Mazedonien, die ehemalige jugoslawische Republik'), (b'MG', 'Madagaskar'), (b'MW', 'Malawi'), (b'MY', 'Malaysia'), (b'MV', 'Malediven'), (b'ML', 'Mali'), (b'MT', 'Malta'), (b'MH', 'Marshall Islands'), (b'MQ', 'Martinique'), (b'MR', 'Mauretanien'), (b'MU', 'Mauritius'), (b'YT', 'Mayotte'), (b'MX', 'Mexiko'), (b'FM', 'Mikronesien, F\xf6derierte Staaten von'), (b'MD', 'Moldawien'), (b'MC', 'Monaco'), (b'MN', 'Mongolei'), (b'ME', 'Montenegro'), (b'MS', 'Montserrat'), (b'MA', 'Marokko'), (b'MZ', 'Mosambik'), (b'MM', 'Myanmar'), (b'NA', 'Namibia'), (b'NR', 'Nauru'), (b'NP', 'Nepal'), (b'NL', 'Niederlande'), (b'AN', 'Niederl\xe4ndische Antillen'), (b'NC', 'Neukaledonien'), (b'NZ', 'New Zealand'), (b'NI', 'Nicaragua'), (b'NE', 'Niger'), (b'NG', 'Nigeria'), (b'NU', 'Niue'), (b'NF', 'Norfolk Island'), (b'MP', 'Northern Mariana Islands'), (b'NO', 'Norwegen'), (b'OM', 'Oman'), (b'PK', 'Pakistan'), (b'PW', 'Palau'), (b'PS', 'Pal\xe4stinensische Autonomiegebiete'), (b'PA', 'Panama'), (b'PG', 'Papua-Neuguinea'), (b'PY', 'Paraguay'), (b'PE', 'Peru'), (b'PH', 'Philippinen'), (b'PN', 'Pitcairn'), (b'PL', 'Polen'), (b'PT', 'Portugal'), (b'PR', 'Puerto Rico'), (b'QA', 'Katar'), (b'RE', 'Wiedervereinigung'), (b'RO', 'Rum\xe4nien'), (b'RU', 'Russischen F\xf6deration'), (b'RW', 'Ruanda'), (b'BL', 'Saint Barthelemy'), (b'SH', 'Saint Helena'), (b'KN', 'Saint Kitts und Nevis'), (b'LC', 'Santa Lucia'), (b'MF', 'Santa Martin'), (b'PM', 'Saint Pierre und Miquelon'), (b'VC', 'Saint Vincent und die Grenadinen'), (b'WS', 'Samoa'), (b'SM', 'San Marino'), (b'ST', 'Sao Tome und Principe'), (b'SA', 'Saudi-Arabien'), (b'SN', 'Senegal'), (b'RS', 'Serbien'), (b'SC', 'Seychellen'), (b'SL', 'Sierra Leone'), (b'SG', 'Singapur'), (b'SK', 'Slowakei'), (b'SI', 'Slowenien'), (b'SB', 'Salomon-Inseln'), (b'SO', 'Somalia'), (b'ZA', 'S\xfcdafrika'), (b'GS', 'S\xfcdgeorgien und die S\xfcdlichen Sandwichinseln'), (b'ES', 'Spanien'), (b'LK', 'Sri Lanka'), (b'SD', 'Sudan'), (b'SR', 'Suriname'), (b'SJ', 'Svalbard und Jan Mayen'), (b'SZ', 'Swaziland'), (b'SE', 'Schweden'), (b'CH', 'Schweiz'), (b'SY', 'Arabische Republik Syrien'), (b'TW', 'Taiwan, Province of China'), (b'TJ', 'Tadschikistan'), (b'TZ', 'Tansania, Vereinigte Republik'), (b'TH', 'Thailand'), (b'TL', 'Timor-Leste'), (b'TG', 'Togo'), (b'TK', 'Tokelau'), (b'TO', 'Tonga'), (b'TT', 'Trinidad und Tobago'), (b'TN', 'Tunesien'), (b'TR', 'T\xfcrkei'), (b'TM', 'Turkmenistan'), (b'TC', 'Turks-und Caicosinseln'), (b'TV', 'Tuvalu'), (b'UG', 'Uganda'), (b'UA', 'Ukraine'), (b'AE', 'Vereinigte Arabische Emirate'), (b'US', 'Vereinigte Staaten'), (b'UM', 'United States Minor Outlying Islands'), (b'UY', 'Uruguay'), (b'UZ', 'Usbekistan'), (b'VU', 'Vanuatu'), (b'VE', 'Venezuela'), (b'VN', 'Vietnam'), (b'VG', 'Virgin Islands, British'), (b'VI', 'Virgin Islands, US'), (b'WF', 'Wallis und Futuna'), (b'EH', 'Westsahara'), (b'YE', 'Jemen'), (b'ZM', 'Sambia'), (b'ZW', 'Zimbabwe')])),
|
('country', models.CharField(max_length=2, verbose_name='Land',
|
||||||
|
choices=[(b'GB',
|
||||||
|
'Vereinigtes K\xf6nigreich'),
|
||||||
|
(b'AF', 'Afghanistan'),
|
||||||
|
(b'AX', 'Aland Islands'),
|
||||||
|
(b'AL', 'Albanien'),
|
||||||
|
(b'DZ', 'Algerien'), (
|
||||||
|
b'AS',
|
||||||
|
'Amerikanisch-Samoa'),
|
||||||
|
(b'AD', 'Andorra'),
|
||||||
|
(b'AO', 'Angola'),
|
||||||
|
(b'AI', 'Anguilla'),
|
||||||
|
(b'AQ', 'Antarktika'), (
|
||||||
|
b'AG',
|
||||||
|
'Antigua und Barbuda'),
|
||||||
|
(b'AR', 'Argentinien'),
|
||||||
|
(b'AM', 'Armenien'),
|
||||||
|
(b'AW', 'Aruba'),
|
||||||
|
(b'AU', 'Australien'),
|
||||||
|
(b'AT', '\xd6sterreich'),
|
||||||
|
(b'AZ', 'Aserbaidschan'),
|
||||||
|
(b'BS', 'Bahamas'),
|
||||||
|
(b'BH', 'Bahrein'),
|
||||||
|
(b'BD', 'Bangladesch'),
|
||||||
|
(b'BB', 'Barbados'), (
|
||||||
|
b'BY',
|
||||||
|
'Wei\xdfrussland'),
|
||||||
|
(b'BE', 'Belgien'),
|
||||||
|
(b'BZ', 'Belize'),
|
||||||
|
(b'BJ', 'Benin'),
|
||||||
|
(b'BM', 'Bermuda'),
|
||||||
|
(b'BT', 'Bhutan'),
|
||||||
|
(b'BO', 'Bolivien'), (
|
||||||
|
b'BA',
|
||||||
|
'Bosnien und Herzegowina'),
|
||||||
|
(b'BW', 'Botswana'),
|
||||||
|
(b'BV', 'Bouvet Island'),
|
||||||
|
(b'BR', 'Brasilien'), (
|
||||||
|
b'IO',
|
||||||
|
'British Indian Ocean Territory'),
|
||||||
|
(b'BN',
|
||||||
|
'Brunei Darussalam'),
|
||||||
|
(b'BG', 'Bulgarien'),
|
||||||
|
(b'BF', 'Burkina Faso'),
|
||||||
|
(b'BI', 'Burundi'),
|
||||||
|
(b'KH', 'Kambodscha'),
|
||||||
|
(b'CM', 'Kamerun'),
|
||||||
|
(b'CA', 'Kanada'),
|
||||||
|
(b'CV', 'Cape Verde'),
|
||||||
|
(b'KY', 'Cayman Islands'),
|
||||||
|
(b'CF',
|
||||||
|
'Zentralafrikanische Republik'),
|
||||||
|
(b'TD', 'Tschad'),
|
||||||
|
(b'CL', 'Chile'),
|
||||||
|
(b'CN', 'China'), (b'CX',
|
||||||
|
'Christmas Island'),
|
||||||
|
(b'CC',
|
||||||
|
'Cocos (Keeling) Islands'),
|
||||||
|
(b'CO', 'Kolumbien'),
|
||||||
|
(b'KM', 'Komoren'),
|
||||||
|
(b'CG', 'Kongo'), (b'CD',
|
||||||
|
'Kongo, Demokratische Republik'),
|
||||||
|
(b'CK', 'Cook-Inseln'),
|
||||||
|
(b'CR', 'Costa Rica'),
|
||||||
|
(b'CI', "Cote d'Ivoire"),
|
||||||
|
(b'HR', 'Kroatien'),
|
||||||
|
(b'CU', 'Kuba'),
|
||||||
|
(b'CY', 'Zypern'), (b'CZ',
|
||||||
|
'Tschechische Republik'),
|
||||||
|
(b'DK', 'D\xe4nemark'),
|
||||||
|
(b'DJ', 'Dschibuti'),
|
||||||
|
(b'DM', 'Dominica'), (
|
||||||
|
b'DO',
|
||||||
|
'Dominikanische Republik'),
|
||||||
|
(b'EC', 'Ecuador'),
|
||||||
|
(b'EG', '\xc4gypten'),
|
||||||
|
(b'SV', 'El Salvador'), (
|
||||||
|
b'GQ',
|
||||||
|
'\xc4quatorial-Guinea'),
|
||||||
|
(b'ER', 'Eritrea'),
|
||||||
|
(b'EE', 'Estland'),
|
||||||
|
(b'ET', '\xc4thiopien'), (
|
||||||
|
b'FK',
|
||||||
|
'Falklandinseln (Malvinas)'),
|
||||||
|
(b'FO',
|
||||||
|
'F\xe4r\xf6er-Inseln'),
|
||||||
|
(b'FJ', 'Fidschi'),
|
||||||
|
(b'FI', 'Finnland'),
|
||||||
|
(b'FR', 'Frankreich'), (
|
||||||
|
b'GF',
|
||||||
|
'Franz\xf6sisch-Guayana'),
|
||||||
|
(b'PF',
|
||||||
|
'Franz\xf6sisch-Polynesien'),
|
||||||
|
(b'TF',
|
||||||
|
'Franz\xf6sisch S\xfcdliche Territorien'),
|
||||||
|
(b'GA', 'Gabun'),
|
||||||
|
(b'GM', 'Gambia'),
|
||||||
|
(b'GE', 'Georgia'),
|
||||||
|
(b'DE', 'Deutschland'),
|
||||||
|
(b'GH', 'Ghana'),
|
||||||
|
(b'GI', 'Gibraltar'),
|
||||||
|
(b'GR', 'Griechenland'),
|
||||||
|
(b'GL', 'Gr\xf6nland'),
|
||||||
|
(b'GD', 'Grenada'),
|
||||||
|
(b'GP', 'Guadeloupe'),
|
||||||
|
(b'GU', 'Guam'),
|
||||||
|
(b'GT', 'Guatemala'),
|
||||||
|
(b'GG', 'Guernsey'),
|
||||||
|
(b'GN', 'Guinea'),
|
||||||
|
(b'GW', 'Guinea-Bissau'),
|
||||||
|
(b'GY', 'Guyana'),
|
||||||
|
(b'HT', 'Haiti'), (b'HM',
|
||||||
|
'Heard und McDonald Inseln'),
|
||||||
|
(b'VA',
|
||||||
|
'Heiliger Stuhl (Vatikanstadt)'),
|
||||||
|
(b'HN', 'Honduras'),
|
||||||
|
(b'HK', 'Hongkong'),
|
||||||
|
(b'HU', 'Ungarn'),
|
||||||
|
(b'IS', 'Island'),
|
||||||
|
(b'IN', 'Indien'),
|
||||||
|
(b'ID', 'Indonesien'), (
|
||||||
|
b'IR',
|
||||||
|
'Iran, Islamische Republik'),
|
||||||
|
(b'IQ', 'Irak'),
|
||||||
|
(b'IE', 'Irland'),
|
||||||
|
(b'IM', 'Isle of Man'),
|
||||||
|
(b'IL', 'Israel'),
|
||||||
|
(b'IT', 'Italien'),
|
||||||
|
(b'JM', 'Jamaika'),
|
||||||
|
(b'JP', 'Japan'),
|
||||||
|
(b'JE', 'Jersey'),
|
||||||
|
(b'JO', 'Jordan'),
|
||||||
|
(b'KZ', 'Kasachstan'),
|
||||||
|
(b'KE', 'Kenia'),
|
||||||
|
(b'KI', 'Kiribati'), (
|
||||||
|
b'KP',
|
||||||
|
'Korea, Demokratische Volksrepublik'),
|
||||||
|
(
|
||||||
|
b'KR',
|
||||||
|
'Korea, Republik'),
|
||||||
|
(b'KW', 'Kuwait'),
|
||||||
|
(b'KG', 'Kirgisistan'), (
|
||||||
|
b'LA',
|
||||||
|
'Lao Demokratischen Volksrepublik'),
|
||||||
|
(b'LV', 'Lettland'),
|
||||||
|
(b'LB', 'Libanon'), (
|
||||||
|
b'LS', 'Lesotho'),
|
||||||
|
(b'LR', 'Liberia'),
|
||||||
|
(b'LY', 'Libyen'),
|
||||||
|
(b'LI', 'Liechtenstein'),
|
||||||
|
(b'LT', 'Litauen'),
|
||||||
|
(b'LU', 'Luxemburg'),
|
||||||
|
(b'MO', 'Macao'), (b'MK',
|
||||||
|
'Mazedonien, die ehemalige jugoslawische Republik'),
|
||||||
|
(b'MG', 'Madagaskar'),
|
||||||
|
(b'MW', 'Malawi'),
|
||||||
|
(b'MY', 'Malaysia'),
|
||||||
|
(b'MV', 'Malediven'),
|
||||||
|
(b'ML', 'Mali'),
|
||||||
|
(b'MT', 'Malta'), (b'MH',
|
||||||
|
'Marshall Islands'),
|
||||||
|
(b'MQ', 'Martinique'),
|
||||||
|
(b'MR', 'Mauretanien'),
|
||||||
|
(b'MU', 'Mauritius'),
|
||||||
|
(b'YT', 'Mayotte'),
|
||||||
|
(b'MX', 'Mexiko'), (b'FM',
|
||||||
|
'Mikronesien, F\xf6derierte Staaten von'),
|
||||||
|
(b'MD', 'Moldawien'),
|
||||||
|
(b'MC', 'Monaco'),
|
||||||
|
(b'MN', 'Mongolei'),
|
||||||
|
(b'ME', 'Montenegro'),
|
||||||
|
(b'MS', 'Montserrat'),
|
||||||
|
(b'MA', 'Marokko'),
|
||||||
|
(b'MZ', 'Mosambik'),
|
||||||
|
(b'MM', 'Myanmar'),
|
||||||
|
(b'NA', 'Namibia'),
|
||||||
|
(b'NR', 'Nauru'),
|
||||||
|
(b'NP', 'Nepal'),
|
||||||
|
(b'NL', 'Niederlande'), (
|
||||||
|
b'AN',
|
||||||
|
'Niederl\xe4ndische Antillen'),
|
||||||
|
(b'NC', 'Neukaledonien'),
|
||||||
|
(b'NZ', 'New Zealand'),
|
||||||
|
(b'NI', 'Nicaragua'),
|
||||||
|
(b'NE', 'Niger'),
|
||||||
|
(b'NG', 'Nigeria'),
|
||||||
|
(b'NU', 'Niue'),
|
||||||
|
(b'NF', 'Norfolk Island'),
|
||||||
|
(b'MP',
|
||||||
|
'Northern Mariana Islands'),
|
||||||
|
(b'NO', 'Norwegen'),
|
||||||
|
(b'OM', 'Oman'),
|
||||||
|
(b'PK', 'Pakistan'),
|
||||||
|
(b'PW', 'Palau'), (b'PS',
|
||||||
|
'Pal\xe4stinensische Autonomiegebiete'),
|
||||||
|
(b'PA', 'Panama'), (
|
||||||
|
b'PG',
|
||||||
|
'Papua-Neuguinea'),
|
||||||
|
(b'PY', 'Paraguay'),
|
||||||
|
(b'PE', 'Peru'),
|
||||||
|
(b'PH', 'Philippinen'),
|
||||||
|
(b'PN', 'Pitcairn'),
|
||||||
|
(b'PL', 'Polen'),
|
||||||
|
(b'PT', 'Portugal'),
|
||||||
|
(b'PR', 'Puerto Rico'),
|
||||||
|
(b'QA', 'Katar'), (b'RE',
|
||||||
|
'Wiedervereinigung'),
|
||||||
|
(b'RO', 'Rum\xe4nien'), (
|
||||||
|
b'RU',
|
||||||
|
'Russischen F\xf6deration'),
|
||||||
|
(b'RW', 'Ruanda'), (b'BL',
|
||||||
|
'Saint Barthelemy'),
|
||||||
|
(b'SH', 'Saint Helena'), (
|
||||||
|
b'KN',
|
||||||
|
'Saint Kitts und Nevis'),
|
||||||
|
(b'LC', 'Santa Lucia'),
|
||||||
|
(b'MF', 'Santa Martin'), (
|
||||||
|
b'PM',
|
||||||
|
'Saint Pierre und Miquelon'),
|
||||||
|
(b'VC',
|
||||||
|
'Saint Vincent und die Grenadinen'),
|
||||||
|
(b'WS', 'Samoa'),
|
||||||
|
(b'SM', 'San Marino'), (
|
||||||
|
b'ST',
|
||||||
|
'Sao Tome und Principe'),
|
||||||
|
(b'SA', 'Saudi-Arabien'),
|
||||||
|
(b'SN', 'Senegal'),
|
||||||
|
(b'RS', 'Serbien'),
|
||||||
|
(b'SC', 'Seychellen'),
|
||||||
|
(b'SL', 'Sierra Leone'),
|
||||||
|
(b'SG', 'Singapur'),
|
||||||
|
(b'SK', 'Slowakei'),
|
||||||
|
(b'SI', 'Slowenien'),
|
||||||
|
(b'SB', 'Salomon-Inseln'),
|
||||||
|
(b'SO', 'Somalia'),
|
||||||
|
(b'ZA', 'S\xfcdafrika'), (
|
||||||
|
b'GS',
|
||||||
|
'S\xfcdgeorgien und die S\xfcdlichen Sandwichinseln'),
|
||||||
|
(b'ES', 'Spanien'),
|
||||||
|
(b'LK', 'Sri Lanka'),
|
||||||
|
(b'SD', 'Sudan'),
|
||||||
|
(b'SR', 'Suriname'), (
|
||||||
|
b'SJ',
|
||||||
|
'Svalbard und Jan Mayen'),
|
||||||
|
(b'SZ', 'Swaziland'),
|
||||||
|
(b'SE', 'Schweden'),
|
||||||
|
(b'CH', 'Schweiz'), (
|
||||||
|
b'SY',
|
||||||
|
'Arabische Republik Syrien'),
|
||||||
|
(b'TW',
|
||||||
|
'Taiwan, Province of China'),
|
||||||
|
(b'TJ', 'Tadschikistan'),
|
||||||
|
(b'TZ',
|
||||||
|
'Tansania, Vereinigte Republik'),
|
||||||
|
(b'TH', 'Thailand'),
|
||||||
|
(b'TL', 'Timor-Leste'),
|
||||||
|
(b'TG', 'Togo'),
|
||||||
|
(b'TK', 'Tokelau'),
|
||||||
|
(b'TO', 'Tonga'), (b'TT',
|
||||||
|
'Trinidad und Tobago'),
|
||||||
|
(b'TN', 'Tunesien'),
|
||||||
|
(b'TR', 'T\xfcrkei'),
|
||||||
|
(b'TM', 'Turkmenistan'), (
|
||||||
|
b'TC',
|
||||||
|
'Turks-und Caicosinseln'),
|
||||||
|
(b'TV', 'Tuvalu'),
|
||||||
|
(b'UG', 'Uganda'),
|
||||||
|
(b'UA', 'Ukraine'), (
|
||||||
|
b'AE',
|
||||||
|
'Vereinigte Arabische Emirate'),
|
||||||
|
(b'US',
|
||||||
|
'Vereinigte Staaten'), (
|
||||||
|
b'UM',
|
||||||
|
'United States Minor Outlying Islands'),
|
||||||
|
(b'UY', 'Uruguay'),
|
||||||
|
(b'UZ', 'Usbekistan'),
|
||||||
|
(b'VU', 'Vanuatu'),
|
||||||
|
(b'VE', 'Venezuela'),
|
||||||
|
(b'VN', 'Vietnam'), (
|
||||||
|
b'VG',
|
||||||
|
'Virgin Islands, British'),
|
||||||
|
(b'VI',
|
||||||
|
'Virgin Islands, US'), (
|
||||||
|
b'WF',
|
||||||
|
'Wallis und Futuna'),
|
||||||
|
(b'EH', 'Westsahara'),
|
||||||
|
(b'YE', 'Jemen'),
|
||||||
|
(b'ZM', 'Sambia'),
|
||||||
|
(b'ZW', 'Zimbabwe')])),
|
||||||
],
|
],
|
||||||
options={
|
options={
|
||||||
'verbose_name': 'Veranstaltungsort',
|
'verbose_name': 'Veranstaltungsort',
|
||||||
@@ -67,6 +365,8 @@ class Migration(migrations.Migration):
|
|||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='event',
|
model_name='event',
|
||||||
name='location',
|
name='location',
|
||||||
field=models.ForeignKey(to='events.Location'),
|
field=models.ForeignKey(
|
||||||
|
to='events.Location',
|
||||||
|
on_delete=models.CASCADE),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -1,17 +1,17 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from django.db import models, migrations
|
|
||||||
import ckeditor.fields
|
import ckeditor.fields
|
||||||
import events.models
|
|
||||||
import easy_thumbnails.fields
|
|
||||||
import django.db.models.deletion
|
import django.db.models.deletion
|
||||||
import utils
|
import easy_thumbnails.fields
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
from django.db import models, migrations
|
||||||
|
|
||||||
|
import events.models
|
||||||
|
import utils
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
('events', '0003_auto_20150823_2232'),
|
('events', '0003_auto_20150823_2232'),
|
||||||
@@ -22,18 +22,24 @@ class Migration(migrations.Migration):
|
|||||||
name='Photo',
|
name='Photo',
|
||||||
fields=[
|
fields=[
|
||||||
('id', models.AutoField(verbose_name='ID',
|
('id', models.AutoField(verbose_name='ID',
|
||||||
serialize=False, auto_created=True, primary_key=True)),
|
serialize=False, auto_created=True,
|
||||||
|
primary_key=True)),
|
||||||
('name', models.CharField(max_length=100,
|
('name', models.CharField(max_length=100,
|
||||||
verbose_name='Name', blank=True)),
|
verbose_name='Name', blank=True)),
|
||||||
('image', easy_thumbnails.fields.ThumbnailerImageField(
|
('image', easy_thumbnails.fields.ThumbnailerImageField(
|
||||||
upload_to=events.models.get_upload_path, storage=utils.OverwriteStorage(), verbose_name='Bild')),
|
upload_to=events.models.get_upload_path,
|
||||||
|
storage=utils.OverwriteStorage(), verbose_name='Bild')),
|
||||||
('description', models.TextField(max_length=300,
|
('description', models.TextField(max_length=300,
|
||||||
verbose_name='Beschreibung', blank=True)),
|
verbose_name='Beschreibung',
|
||||||
|
blank=True)),
|
||||||
('on_startpage', models.BooleanField(default=False,
|
('on_startpage', models.BooleanField(default=False,
|
||||||
help_text='Display this Photo on the Startpage Teaser', verbose_name='Startpage')),
|
help_text='Display this Photo on the Startpage Teaser',
|
||||||
('created_date', models.DateTimeField(verbose_name='Published on')),
|
verbose_name='Startpage')),
|
||||||
|
('created_date',
|
||||||
|
models.DateTimeField(verbose_name='Published on')),
|
||||||
('views', models.PositiveIntegerField(default=0,
|
('views', models.PositiveIntegerField(default=0,
|
||||||
verbose_name='Number of views', editable=False)),
|
verbose_name='Number of views',
|
||||||
|
editable=False)),
|
||||||
],
|
],
|
||||||
options={
|
options={
|
||||||
'ordering': ['created_date'],
|
'ordering': ['created_date'],
|
||||||
@@ -46,7 +52,8 @@ class Migration(migrations.Migration):
|
|||||||
migrations.AlterModelOptions(
|
migrations.AlterModelOptions(
|
||||||
name='event',
|
name='event',
|
||||||
options={'ordering': (
|
options={'ordering': (
|
||||||
'start', 'end'), 'verbose_name': 'Termin', 'verbose_name_plural': 'Termine'},
|
'start', 'end'), 'verbose_name': 'Termin',
|
||||||
|
'verbose_name_plural': 'Termine'},
|
||||||
),
|
),
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name='event',
|
model_name='event',
|
||||||
@@ -57,14 +64,19 @@ class Migration(migrations.Migration):
|
|||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name='event',
|
model_name='event',
|
||||||
name='event_series',
|
name='event_series',
|
||||||
field=models.ForeignKey(on_delete=django.db.models.deletion.SET_NULL, blank=True, to='events.Event',
|
field=models.ForeignKey(
|
||||||
help_text='Wenn dieser Termin zu einer Veranstaltungsreihe geh\xf6rt werden Ort, Beschreibung, Bild und Homepage von dem hier angegebenen Event \xfcbernommen.', null=True, verbose_name='Veranstaltungsreihen'),
|
on_delete=django.db.models.deletion.SET_NULL, blank=True,
|
||||||
|
to='events.Event',
|
||||||
|
help_text='Wenn dieser Termin zu einer Veranstaltungsreihe geh\xf6rt werden Ort, Beschreibung, Bild und Homepage von dem hier angegebenen Event \xfcbernommen.',
|
||||||
|
null=True, verbose_name='Veranstaltungsreihen'),
|
||||||
),
|
),
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name='event',
|
model_name='event',
|
||||||
name='image',
|
name='image',
|
||||||
field=easy_thumbnails.fields.ThumbnailerImageField(storage=utils.OverwriteStorage(
|
field=easy_thumbnails.fields.ThumbnailerImageField(
|
||||||
), upload_to=events.models.get_upload_path, null=True, verbose_name='Bild', blank=True),
|
storage=utils.OverwriteStorage(
|
||||||
|
), upload_to=events.models.get_upload_path, null=True,
|
||||||
|
verbose_name='Bild', blank=True),
|
||||||
),
|
),
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name='location',
|
model_name='location',
|
||||||
@@ -75,17 +87,21 @@ class Migration(migrations.Migration):
|
|||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name='location',
|
model_name='location',
|
||||||
name='image',
|
name='image',
|
||||||
field=easy_thumbnails.fields.ThumbnailerImageField(storage=utils.OverwriteStorage(
|
field=easy_thumbnails.fields.ThumbnailerImageField(
|
||||||
), upload_to=events.models.get_upload_path, null=True, verbose_name='Bild', blank=True),
|
storage=utils.OverwriteStorage(
|
||||||
|
), upload_to=events.models.get_upload_path, null=True,
|
||||||
|
verbose_name='Bild', blank=True),
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='photo',
|
model_name='photo',
|
||||||
name='event',
|
name='event',
|
||||||
field=models.ForeignKey(to='events.Event'),
|
field=models.ForeignKey(
|
||||||
|
to='events.Event', on_delete=models.CASCADE),
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='photo',
|
model_name='photo',
|
||||||
name='photographer',
|
name='photographer',
|
||||||
field=models.ForeignKey(to=settings.AUTH_USER_MODEL),
|
field=models.ForeignKey(
|
||||||
|
to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|||||||
20
src/events/migrations/0008_auto_20171115_0653.py
Normal file
20
src/events/migrations/0008_auto_20171115_0653.py
Normal file
File diff suppressed because one or more lines are too long
32
src/events/migrations/0009_auto_20171214_1215.py
Normal file
32
src/events/migrations/0009_auto_20171214_1215.py
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.11.8 on 2017-12-14 11:15
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('events', '0008_auto_20171115_0653'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='event',
|
||||||
|
name='location',
|
||||||
|
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='events.Location'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='photo',
|
||||||
|
name='event',
|
||||||
|
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='events.Event'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='photo',
|
||||||
|
name='photographer',
|
||||||
|
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -1,4 +1,6 @@
|
|||||||
"""Mixins for Events."""
|
"""Mixins for Events."""
|
||||||
|
from django.http import Http404
|
||||||
|
|
||||||
from . import models
|
from . import models
|
||||||
|
|
||||||
|
|
||||||
@@ -9,7 +11,6 @@ class EventArchiveMixin(object):
|
|||||||
date_field = 'start'
|
date_field = 'start'
|
||||||
make_object_list = True
|
make_object_list = True
|
||||||
model = models.Event
|
model = models.Event
|
||||||
ordering = ('start', 'end')
|
|
||||||
paginate_by = 15
|
paginate_by = 15
|
||||||
template_name = 'events/event_archive.html'
|
template_name = 'events/event_archive.html'
|
||||||
|
|
||||||
@@ -40,3 +41,20 @@ class EventDetailMixin(object):
|
|||||||
elif hasattr(self, 'object') and hasattr(self.object, 'event'):
|
elif hasattr(self, 'object') and hasattr(self.object, 'event'):
|
||||||
context['event'] = self.object.event
|
context['event'] = self.object.event
|
||||||
return context
|
return context
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
"""set event attribute from the URL kwarg event and
|
||||||
|
load all related objects from the set model.
|
||||||
|
|
||||||
|
:return: a django QuerySets
|
||||||
|
"""
|
||||||
|
if self.model == models.Event:
|
||||||
|
self.event = models.Event.objects.get(pk=self.kwargs['pk'])
|
||||||
|
queryset = self.model.objects.all()
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
self.event = models.Event.objects.get(pk=self.kwargs['event'])
|
||||||
|
queryset = self.model.objects.filter(event=self.event)
|
||||||
|
except models.Event.DoesNotExist:
|
||||||
|
raise Http404(_('Event does not exist'))
|
||||||
|
return queryset.prefetch_related()
|
||||||
|
|||||||
@@ -4,10 +4,10 @@ import os
|
|||||||
from ckeditor.fields import RichTextField
|
from ckeditor.fields import RichTextField
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
from django.core.urlresolvers import reverse
|
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
from django.template.defaultfilters import slugify
|
from django.template.defaultfilters import slugify
|
||||||
|
from django.urls import reverse
|
||||||
from django.utils.timezone import now
|
from django.utils.timezone import now
|
||||||
from django.utils.translation import ugettext as _
|
from django.utils.translation import ugettext as _
|
||||||
from easy_thumbnails.fields import ThumbnailerImageField
|
from easy_thumbnails.fields import ThumbnailerImageField
|
||||||
@@ -51,7 +51,7 @@ class Event(models.Model):
|
|||||||
"""An Event that could be a tournament, a game session, or an convention."""
|
"""An Event that could be a tournament, a game session, or an convention."""
|
||||||
name = models.CharField(_('Name'), max_length=255)
|
name = models.CharField(_('Name'), max_length=255)
|
||||||
description = RichTextField(_("Description"), blank=True)
|
description = RichTextField(_("Description"), blank=True)
|
||||||
location = models.ForeignKey('Location')
|
location = models.ForeignKey('Location', on_delete=models.PROTECT)
|
||||||
start = models.DateTimeField(_('Start'))
|
start = models.DateTimeField(_('Start'))
|
||||||
end = models.DateTimeField(_('End'), blank=True, null=True)
|
end = models.DateTimeField(_('End'), blank=True, null=True)
|
||||||
url = models.URLField(_('Homepage'), blank=True)
|
url = models.URLField(_('Homepage'), blank=True)
|
||||||
@@ -220,13 +220,14 @@ class Photo(models.Model):
|
|||||||
upload_to=get_upload_path,
|
upload_to=get_upload_path,
|
||||||
storage=OverwriteStorage()
|
storage=OverwriteStorage()
|
||||||
)
|
)
|
||||||
event = models.ForeignKey('events.Event')
|
event = models.ForeignKey('events.Event', on_delete=models.PROTECT, )
|
||||||
description = models.TextField(
|
description = models.TextField(
|
||||||
_("Description"),
|
_("Description"),
|
||||||
max_length=300,
|
max_length=300,
|
||||||
blank=True
|
blank=True
|
||||||
)
|
)
|
||||||
photographer = models.ForeignKey(settings.AUTH_USER_MODEL)
|
photographer = models.ForeignKey(settings.AUTH_USER_MODEL,
|
||||||
|
on_delete=models.PROTECT)
|
||||||
on_startpage = models.BooleanField(
|
on_startpage = models.BooleanField(
|
||||||
_("Startpage"),
|
_("Startpage"),
|
||||||
default=False,
|
default=False,
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ from datetime import timedelta
|
|||||||
|
|
||||||
from django.contrib.auth import get_user_model
|
from django.contrib.auth import get_user_model
|
||||||
from django.contrib.auth.mixins import PermissionRequiredMixin
|
from django.contrib.auth.mixins import PermissionRequiredMixin
|
||||||
from django.core.urlresolvers import reverse
|
from django.urls import reverse
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
from django.http import HttpResponse, Http404
|
from django.http import HttpResponse, Http404
|
||||||
from django.shortcuts import redirect
|
from django.shortcuts import redirect
|
||||||
@@ -30,7 +30,6 @@ class DeleteEventPhoto(PermissionRequiredMixin, mixins.EventDetailMixin,
|
|||||||
class EventArchiveIndex(mixins.EventArchiveMixin, generic.ArchiveIndexView):
|
class EventArchiveIndex(mixins.EventArchiveMixin, generic.ArchiveIndexView):
|
||||||
"""Index of the event archive, displays the upcoming events first."""
|
"""Index of the event archive, displays the upcoming events first."""
|
||||||
allow_empty = True
|
allow_empty = True
|
||||||
ordering = ('-start', '-end')
|
|
||||||
|
|
||||||
|
|
||||||
class EventArchiveMonth(mixins.EventArchiveMixin, generic.MonthArchiveView):
|
class EventArchiveMonth(mixins.EventArchiveMixin, generic.MonthArchiveView):
|
||||||
@@ -73,7 +72,7 @@ class EventForm(PermissionRequiredMixin, mixins.EventDetailMixin,
|
|||||||
if self.kwargs.get('pk') else models.Event()
|
if self.kwargs.get('pk') else models.Event()
|
||||||
|
|
||||||
|
|
||||||
class EventGallery(mixins.EventDetailMixin, generic.ListView):
|
class EventGallery(generic.ListView):
|
||||||
"""Display a overview of all event photo albums."""
|
"""Display a overview of all event photo albums."""
|
||||||
template_name = 'events/photo_gallery.html'
|
template_name = 'events/photo_gallery.html'
|
||||||
queryset = models.Event.objects.filter(
|
queryset = models.Event.objects.filter(
|
||||||
|
|||||||
@@ -80,6 +80,7 @@ MIDDLEWARE_CLASSES = [
|
|||||||
'django.middleware.locale.LocaleMiddleware',
|
'django.middleware.locale.LocaleMiddleware',
|
||||||
'django.contrib.messages.middleware.MessageMiddleware',
|
'django.contrib.messages.middleware.MessageMiddleware',
|
||||||
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
||||||
|
'utils.middleware.SetRemoteAddrFromForwardedFor',
|
||||||
'mahjong_ranking.middleware.DenormalizationUpdateMiddleware',
|
'mahjong_ranking.middleware.DenormalizationUpdateMiddleware',
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -229,7 +230,7 @@ LOGGING = {
|
|||||||
'loggers': {
|
'loggers': {
|
||||||
'django': {
|
'django': {
|
||||||
'handlers': ['console'],
|
'handlers': ['console'],
|
||||||
'level': 'INFO',
|
'level': 'DEBUG',
|
||||||
'propagate': True,
|
'propagate': True,
|
||||||
},
|
},
|
||||||
'django.request': {
|
'django.request': {
|
||||||
@@ -246,6 +247,22 @@ LOGGING = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
################################
|
||||||
|
# Settings for mahjong_ranking #
|
||||||
|
################################
|
||||||
|
|
||||||
|
MIN_HANCHANS_FOR_LADDER = 5
|
||||||
|
RANKING_EXPORT_PATH = path.join(PROJECT_PATH, 'backup', 'mahjong_ranking')
|
||||||
|
|
||||||
|
# Old Tournament System
|
||||||
|
TOURNAMENT_POINT_SYSTEM = True
|
||||||
|
TOURNAMENT_WIN_BONUSPOINTS = 4
|
||||||
|
TOURNAMENT_FLAWLESS_VICTORY_BONUSPOINTS = 8
|
||||||
|
|
||||||
|
# Old Dan System
|
||||||
|
DAN_3_WINS_IN_A_ROW = True
|
||||||
|
DAN_ALLOW_DROP_DOWN = True
|
||||||
|
|
||||||
DAN_RANKS = (
|
DAN_RANKS = (
|
||||||
(80, 9),
|
(80, 9),
|
||||||
(70, 8),
|
(70, 8),
|
||||||
@@ -255,7 +272,7 @@ DAN_RANKS = (
|
|||||||
(30, 4),
|
(30, 4),
|
||||||
(20, 3),
|
(20, 3),
|
||||||
(10, 2),
|
(10, 2),
|
||||||
(0, 1),
|
(-1, 1),
|
||||||
)
|
)
|
||||||
|
|
||||||
KYU_RANKS = (
|
KYU_RANKS = (
|
||||||
@@ -268,12 +285,9 @@ KYU_RANKS = (
|
|||||||
(15, 7),
|
(15, 7),
|
||||||
(10, 8),
|
(10, 8),
|
||||||
(5, 9),
|
(5, 9),
|
||||||
(0, 10),
|
(-1, 10),
|
||||||
)
|
)
|
||||||
|
|
||||||
DAN_ALLOW_DROP_DOWN = True
|
|
||||||
MIN_HANCHANS_FOR_LADDER = 5
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from .local_settings import * # Ignore PyLintBear (W0401, W0614)
|
from .local_settings import * # Ignore PyLintBear (W0401, W0614)
|
||||||
except ImportError:
|
except ImportError:
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
4
src/kasu/templates/django/forms/widgets/date.html
Normal file
4
src/kasu/templates/django/forms/widgets/date.html
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
{% with type="date" %}
|
||||||
|
{% include "django/forms/widgets/html5input.html" %}
|
||||||
|
{% endwith %}
|
||||||
|
|
||||||
4
src/kasu/templates/django/forms/widgets/datetime.html
Normal file
4
src/kasu/templates/django/forms/widgets/datetime.html
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
{% with type="datetime-local" %}
|
||||||
|
{% include "django/forms/widgets/html5input.html" %}
|
||||||
|
{% endwith %}
|
||||||
|
|
||||||
1
src/kasu/templates/django/forms/widgets/html5input.html
Normal file
1
src/kasu/templates/django/forms/widgets/html5input.html
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<input type="{{ type|default:'text' }}" name="{{ widget.name }}"{% if widget.value != None %} value="{{ widget.value|stringformat:'s' }}"{% endif %}{% include "django/forms/widgets/attrs.html" %} />
|
||||||
4
src/kasu/templates/django/forms/widgets/time.html
Normal file
4
src/kasu/templates/django/forms/widgets/time.html
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
{% with type="time" %}
|
||||||
|
{% include "django/forms/widgets/html5input.html" %}
|
||||||
|
{% endwith %}
|
||||||
|
|
||||||
@@ -32,7 +32,7 @@ urlpatterns = [ # Ignore PyLintBear (C0103)
|
|||||||
url(r'^add_page/(?P<path>[\+\.\-\d\w\/]+)/$',
|
url(r'^add_page/(?P<path>[\+\.\-\d\w\/]+)/$',
|
||||||
views.PageAddForm.as_view(), name='add-page'),
|
views.PageAddForm.as_view(), name='add-page'),
|
||||||
url(r'^admin/doc/', include('django.contrib.admindocs.urls')),
|
url(r'^admin/doc/', include('django.contrib.admindocs.urls')),
|
||||||
url(r'^admin/', include(admin.site.urls)),
|
url(r'^admin/', admin.site.urls),
|
||||||
url(r'^ckeditor/', include('ckeditor_uploader.urls')),
|
url(r'^ckeditor/', include('ckeditor_uploader.urls')),
|
||||||
url(r'^comments/', include('django_comments.urls')),
|
url(r'^comments/', include('django_comments.urls')),
|
||||||
url(r'^edit_page/(?P<path>[\+\.\-\d\w\/]+)/$',
|
url(r'^edit_page/(?P<path>[\+\.\-\d\w\/]+)/$',
|
||||||
|
|||||||
147
src/kasu/xlsx.py
Normal file
147
src/kasu/xlsx.py
Normal file
@@ -0,0 +1,147 @@
|
|||||||
|
"""
|
||||||
|
Helper to generate XLSX Spreadsheets in an uniform way.
|
||||||
|
"""
|
||||||
|
from datetime import date
|
||||||
|
|
||||||
|
import openpyxl
|
||||||
|
from openpyxl.styles import NamedStyle, Font, Border, Side
|
||||||
|
|
||||||
|
DEFAULT_FONT = Font(name='Philosopher', size=10, bold=False, color='000000')
|
||||||
|
THIN_BORDER = Border(
|
||||||
|
bottom=Side(style='thin', color="d3d7cf"),
|
||||||
|
top=Side(style='thin', color="d3d7cf"))
|
||||||
|
XLSX_STYLES = dict()
|
||||||
|
|
||||||
|
XLSX_STYLES['Content'] = NamedStyle(
|
||||||
|
name='Content',
|
||||||
|
font=DEFAULT_FONT,
|
||||||
|
border=THIN_BORDER
|
||||||
|
)
|
||||||
|
|
||||||
|
XLSX_STYLES['Headline'] = NamedStyle(
|
||||||
|
name="Headline",
|
||||||
|
font=openpyxl.styles.Font(name='Philosopher', size=11,
|
||||||
|
bold=True, color='ffffff'),
|
||||||
|
fill=openpyxl.styles.PatternFill(fill_type='solid',
|
||||||
|
start_color='a40000',
|
||||||
|
end_color='a40000')
|
||||||
|
)
|
||||||
|
|
||||||
|
XLSX_STYLES['Date'] = NamedStyle(
|
||||||
|
name='Date',
|
||||||
|
font=DEFAULT_FONT,
|
||||||
|
border=THIN_BORDER,
|
||||||
|
number_format='dd.mm.yyyy'
|
||||||
|
)
|
||||||
|
|
||||||
|
XLSX_STYLES['Date Time'] = NamedStyle(
|
||||||
|
name='Date Time',
|
||||||
|
font=DEFAULT_FONT,
|
||||||
|
border=THIN_BORDER,
|
||||||
|
number_format='dd.mm.yyyy hh:MM'
|
||||||
|
)
|
||||||
|
|
||||||
|
XLSX_STYLES['Float'] = NamedStyle(
|
||||||
|
name='Float',
|
||||||
|
font=DEFAULT_FONT,
|
||||||
|
border=THIN_BORDER,
|
||||||
|
number_format='#,##0.00'
|
||||||
|
)
|
||||||
|
|
||||||
|
XLSX_STYLES['Integer'] = NamedStyle(
|
||||||
|
name='Integer',
|
||||||
|
font=DEFAULT_FONT,
|
||||||
|
border=THIN_BORDER,
|
||||||
|
number_format='#,##0'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def getattr_recursive(obj, attr_string):
|
||||||
|
"""A recusive version of gettattr. the attr_string is splitted on the ".".
|
||||||
|
|
||||||
|
:param obj: a python object.
|
||||||
|
:param attr_string: the desired attribute of the object.
|
||||||
|
:return: a getattr_recursice(obj, 'attr1.attr2') will return the value of attr2 of attr1 from obj
|
||||||
|
"""
|
||||||
|
attr_list = attr_string.split('.')
|
||||||
|
return_value = None
|
||||||
|
for attr in attr_list:
|
||||||
|
return_value = getattr(obj, attr)
|
||||||
|
obj = return_value
|
||||||
|
return return_value
|
||||||
|
|
||||||
|
|
||||||
|
class Workbook(object):
|
||||||
|
workbook = None
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
"""Generate an XLSX Workbook in memory
|
||||||
|
|
||||||
|
:rtype: object
|
||||||
|
"""
|
||||||
|
self.workbook = openpyxl.Workbook()
|
||||||
|
[self.workbook.add_named_style(style) for style in XLSX_STYLES.values()]
|
||||||
|
[self.workbook.remove(sheet) for sheet in self.workbook.worksheets]
|
||||||
|
|
||||||
|
def generate_sheet(self, title, columns_settings, object_list,
|
||||||
|
orientation='landscape'):
|
||||||
|
"""
|
||||||
|
|
||||||
|
:param title: Title of the Sheet
|
||||||
|
:param columns_settings: a list of dicts for the settings of each column
|
||||||
|
:param object_list: List of objects that should be added to the sheet
|
||||||
|
:param orientation: Paper Orientation 'landscape' or 'portrait'
|
||||||
|
"""
|
||||||
|
row = 1
|
||||||
|
ws = self.workbook.create_sheet()
|
||||||
|
ws.title = title
|
||||||
|
ws.syncHorizontal = True
|
||||||
|
ws.filterMode = True
|
||||||
|
|
||||||
|
# setup print orientation
|
||||||
|
ws.page_setup.fitToHeight = 0
|
||||||
|
ws.page_setup.fitToWidth = 1
|
||||||
|
if orientation == 'landscape':
|
||||||
|
ws.page_setup.orientation = ws.ORIENTATION_LANDSCAPE
|
||||||
|
else:
|
||||||
|
ws.page_setup.orientation = ws.ORIENTATION_PORTRAIT
|
||||||
|
ws.page_setup.paperSize = ws.PAPERSIZE_A4
|
||||||
|
ws.print_options.horizontalCentered = True
|
||||||
|
|
||||||
|
# setup page header
|
||||||
|
ws.oddHeader.left.text = title
|
||||||
|
ws.oddHeader.left.size = 14
|
||||||
|
ws.oddHeader.left.font = "Amerika Sans"
|
||||||
|
ws.oddHeader.left.color = "000000"
|
||||||
|
|
||||||
|
ws.oddHeader.right.text = str(date.today())
|
||||||
|
ws.oddHeader.right.size = 14
|
||||||
|
ws.oddHeader.right.font = "Amerika Sans"
|
||||||
|
ws.oddHeader.right.color = "000000"
|
||||||
|
|
||||||
|
# write table header
|
||||||
|
for column, data in enumerate(columns_settings, 1):
|
||||||
|
cell = ws.cell(column=column, row=row, value=data['title'])
|
||||||
|
cell.style = 'Headline'
|
||||||
|
row += 1
|
||||||
|
|
||||||
|
# write the table content
|
||||||
|
for line in object_list:
|
||||||
|
for column, settings in enumerate(columns_settings, 1):
|
||||||
|
cell = ws.cell(column=column, row=row,
|
||||||
|
value=getattr_recursive(line, settings['attr']))
|
||||||
|
cell.style = settings['style']
|
||||||
|
row += 1
|
||||||
|
|
||||||
|
# write table footer
|
||||||
|
for column, settings in enumerate(columns_settings, 1):
|
||||||
|
cell = ws.cell(column=column, row=row, value=settings.get('footer'))
|
||||||
|
cell.style = settings['style']
|
||||||
|
row += 1
|
||||||
|
|
||||||
|
# set column widths
|
||||||
|
for settings in columns_settings:
|
||||||
|
ws.column_dimensions[settings['col']].width = settings['width']
|
||||||
|
|
||||||
|
def save(self, *args, **kwargs):
|
||||||
|
return self.workbook.save(*args, **kwargs)
|
||||||
@@ -21,8 +21,6 @@ def recalculate(modeladmin, request, queryset): # Ignore PyLintBear (W0613)
|
|||||||
for ladder_ranking in queryset:
|
for ladder_ranking in queryset:
|
||||||
set_dirty(user=ladder_ranking.user_id,
|
set_dirty(user=ladder_ranking.user_id,
|
||||||
season=ladder_ranking.season)
|
season=ladder_ranking.season)
|
||||||
|
|
||||||
|
|
||||||
recalculate.short_description = _("Recalculate")
|
recalculate.short_description = _("Recalculate")
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,158 +0,0 @@
|
|||||||
"""Export Mahjong Rankings as excel files."""
|
|
||||||
|
|
||||||
from datetime import date
|
|
||||||
from operator import itemgetter
|
|
||||||
|
|
||||||
import openpyxl
|
|
||||||
from django.core.management.base import BaseCommand
|
|
||||||
from openpyxl.styles import Border
|
|
||||||
|
|
||||||
from mahjong_ranking.models import SeasonRanking, KyuDanRanking
|
|
||||||
|
|
||||||
THIN_BORDER = openpyxl.styles.Side(style='thin', color="d3d7cf")
|
|
||||||
|
|
||||||
HEADING_STYLE = openpyxl.styles.NamedStyle(name="heading")
|
|
||||||
HEADING_STYLE.font = openpyxl.styles.Font(name='Philosopher', size=11,
|
|
||||||
bold=True, color='ffffff')
|
|
||||||
HEADING_STYLE.fill = openpyxl.styles.PatternFill(fill_type='solid',
|
|
||||||
start_color='a40000',
|
|
||||||
end_color='a40000')
|
|
||||||
|
|
||||||
DEFAULT_STYLE = openpyxl.styles.NamedStyle(name='content')
|
|
||||||
DEFAULT_STYLE.font = openpyxl.styles.Font(name='Philosopher', size=10,
|
|
||||||
bold=False, color='000000')
|
|
||||||
DEFAULT_STYLE.border = openpyxl.styles.Border(bottom=THIN_BORDER,
|
|
||||||
top=THIN_BORDER)
|
|
||||||
|
|
||||||
INT_STYLE = openpyxl.styles.NamedStyle(name='int')
|
|
||||||
INT_STYLE.font = DEFAULT_STYLE.font
|
|
||||||
INT_STYLE.border = DEFAULT_STYLE.border
|
|
||||||
INT_STYLE.number_format = '#,##0'
|
|
||||||
|
|
||||||
FLOAT_STYLE = openpyxl.styles.NamedStyle(name='float')
|
|
||||||
FLOAT_STYLE.font = DEFAULT_STYLE.font
|
|
||||||
FLOAT_STYLE.border = DEFAULT_STYLE.border
|
|
||||||
FLOAT_STYLE.number_format = '#,##0.00'
|
|
||||||
|
|
||||||
|
|
||||||
def geneate_excel():
|
|
||||||
"""Generate an excel .xlsx spreadsheet from json data of the kyu/dan
|
|
||||||
rankings.
|
|
||||||
|
|
||||||
:param json_data: The ladder ranking as JSON export."""
|
|
||||||
workbook = openpyxl.Workbook()
|
|
||||||
workbook.add_named_style(HEADING_STYLE)
|
|
||||||
workbook.add_named_style(DEFAULT_STYLE)
|
|
||||||
workbook.add_named_style(INT_STYLE)
|
|
||||||
workbook.add_named_style(FLOAT_STYLE)
|
|
||||||
for sheet in workbook.worksheets:
|
|
||||||
print(sheet)
|
|
||||||
workbook.remove(sheet)
|
|
||||||
return workbook
|
|
||||||
|
|
||||||
|
|
||||||
def generate_sheet(workbook, title, columns_settings, json_data):
|
|
||||||
row = 1
|
|
||||||
ws = workbook.create_sheet()
|
|
||||||
ws.title = title
|
|
||||||
|
|
||||||
# setup print orientation
|
|
||||||
ws.page_setup.orientation = ws.ORIENTATION_PORTRAIT
|
|
||||||
ws.page_setup.paperSize = ws.PAPERSIZE_A4
|
|
||||||
ws.page_setup.fitToWidth = True
|
|
||||||
ws.print_options.horizontalCentered = True
|
|
||||||
|
|
||||||
# setup page header
|
|
||||||
ws.oddHeader.left.text = title
|
|
||||||
ws.oddHeader.left.size = 14
|
|
||||||
ws.oddHeader.left.font = "Amerika Sans"
|
|
||||||
ws.oddHeader.left.color = "000000"
|
|
||||||
|
|
||||||
ws.oddHeader.right.text = str(date.today())
|
|
||||||
ws.oddHeader.right.size = 14
|
|
||||||
ws.oddHeader.right.font = "Amerika Sans"
|
|
||||||
ws.oddHeader.right.color = "000000"
|
|
||||||
|
|
||||||
# write table header
|
|
||||||
for column, data in enumerate(columns_settings, 1):
|
|
||||||
cell = ws.cell(column=column, row=row, value=data['title'])
|
|
||||||
cell.style = 'heading'
|
|
||||||
|
|
||||||
# write the table content
|
|
||||||
for line in json_data:
|
|
||||||
row += 1
|
|
||||||
for column, settings in enumerate(columns_settings, 1):
|
|
||||||
cell = ws.cell(column=column, row=row, value=line[settings['attr']])
|
|
||||||
cell.style = settings['style']
|
|
||||||
|
|
||||||
# set column widths
|
|
||||||
for settings in columns_settings:
|
|
||||||
ws.column_dimensions[settings['col']].width = settings['width']
|
|
||||||
|
|
||||||
|
|
||||||
def export_season_rankings(workbook):
|
|
||||||
json_data = sorted(SeasonRanking.objects.json_data(),
|
|
||||||
key=itemgetter('placement'))
|
|
||||||
title = "Mahjong Ladder - {}".format(date.today().year)
|
|
||||||
columns_settings = (
|
|
||||||
{'col': 'A', 'title': 'Rang', 'attr': 'placement', 'style': 'int',
|
|
||||||
'width': 8},
|
|
||||||
{'col': 'B', 'title': 'Spitzname', 'attr': 'username',
|
|
||||||
'style': 'content',
|
|
||||||
'width': 25},
|
|
||||||
{'col': 'C', 'title': '⌀ Platz', 'attr': 'avg_placement',
|
|
||||||
'style': 'int', 'width': 8},
|
|
||||||
{'col': 'D', 'title': '⌀ Punkte', 'attr': 'avg_score',
|
|
||||||
'style': 'float', 'width': 12},
|
|
||||||
{'col': 'E', 'title': 'Hanchans', 'attr': 'hanchan_count',
|
|
||||||
'style': 'int', 'width': 10},
|
|
||||||
{'col': 'F', 'title': 'Gut', 'attr': 'good_hanchans',
|
|
||||||
'style': 'int', 'width': 5},
|
|
||||||
{'col': 'G', 'title': 'Gewonnen', 'attr': 'won_hanchans',
|
|
||||||
'style': 'int', 'width': 10},
|
|
||||||
)
|
|
||||||
|
|
||||||
generate_sheet(
|
|
||||||
workbook=workbook,
|
|
||||||
title=title,
|
|
||||||
columns_settings=columns_settings,
|
|
||||||
json_data=json_data)
|
|
||||||
|
|
||||||
|
|
||||||
def export_kyu_dan_rankings(workbook):
|
|
||||||
json_data = KyuDanRanking.objects.json_data()
|
|
||||||
title = "Kyū & Dan Rankings"
|
|
||||||
columns_settings = (
|
|
||||||
{'col': 'A', 'title': 'Spitzname', 'attr': 'username',
|
|
||||||
'style': 'content', 'width': 14},
|
|
||||||
{'col': 'B', 'title': 'Voller Name', 'attr': 'full_name',
|
|
||||||
'style': 'content', 'width': 20},
|
|
||||||
{'col': 'C', 'title': 'Rang', 'attr': 'rank',
|
|
||||||
'style': 'content', 'width': 8},
|
|
||||||
{'col': 'D', 'title': 'Punkte', 'attr': 'points',
|
|
||||||
'style': 'int', 'width': 8},
|
|
||||||
{'col': 'E', 'title': 'Hanchans', 'attr': 'hanchan_count',
|
|
||||||
'style': 'int', 'width': 10},
|
|
||||||
{'col': 'F', 'title': 'Gut', 'attr': 'good_hanchans',
|
|
||||||
'style': 'int', 'width': 5},
|
|
||||||
{'col': 'G', 'title': 'Gewonnen', 'attr': 'won_hanchans',
|
|
||||||
'style': 'int', 'width': 10},
|
|
||||||
)
|
|
||||||
generate_sheet(
|
|
||||||
workbook=workbook,
|
|
||||||
title=title,
|
|
||||||
columns_settings=columns_settings,
|
|
||||||
json_data=json_data)
|
|
||||||
|
|
||||||
|
|
||||||
class Command(BaseCommand):
|
|
||||||
"""Exports the SeasonRankings"""
|
|
||||||
|
|
||||||
def handle(self, *args, **options):
|
|
||||||
"""Exports the current ladder ranking in a spreadsheet.
|
|
||||||
This is useful as a backup in form of a hardcopy."""
|
|
||||||
workbook = geneate_excel()
|
|
||||||
export_season_rankings(workbook)
|
|
||||||
export_kyu_dan_rankings(workbook)
|
|
||||||
workbook.save('sample.x')
|
|
||||||
workbook.save('mahjong_rankings_{}.xlsx'.format(str(date.today())))
|
|
||||||
126
src/mahjong_ranking/management/commands/exportranking.py
Normal file
126
src/mahjong_ranking/management/commands/exportranking.py
Normal file
@@ -0,0 +1,126 @@
|
|||||||
|
"""Export Mahjong Rankings as excel files."""
|
||||||
|
|
||||||
|
import os
|
||||||
|
from datetime import date, time, datetime
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
from django.core.mail import EmailMessage
|
||||||
|
from django.core.management.base import BaseCommand
|
||||||
|
from django.utils import timezone
|
||||||
|
from django.utils.dateparse import parse_date
|
||||||
|
|
||||||
|
from kasu import xlsx
|
||||||
|
from mahjong_ranking.models import SeasonRanking, KyuDanRanking
|
||||||
|
|
||||||
|
MAIL_BODY = """
|
||||||
|
Hallo! Ich bin's dein Server.
|
||||||
|
|
||||||
|
Ich habe gerade die Mahjong Rankings als Excel exportiert und dachte mir das
|
||||||
|
ich sie dir am besten gleich schicke.
|
||||||
|
|
||||||
|
Bitte versuche nicht auf diese E-Mail zu antworten.
|
||||||
|
Ich bin nur ein dummes Programm.
|
||||||
|
|
||||||
|
mit lieben Grüßen
|
||||||
|
|
||||||
|
Der Kasu Server
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def export_season_rankings(workbook, until):
|
||||||
|
SeasonRanking.objects.update(until=until)
|
||||||
|
season = until.year if until else date.today().year
|
||||||
|
object_list = SeasonRanking.objects.season_rankings()
|
||||||
|
title = "Mahjong Ladder - {}".format(season)
|
||||||
|
columns_settings = (
|
||||||
|
{'col': 'A', 'title': 'Rang', 'attr': 'placement', 'style': 'Integer',
|
||||||
|
'width': 8},
|
||||||
|
{'col': 'B', 'title': 'Spitzname', 'attr': 'user.username',
|
||||||
|
'style': 'Content',
|
||||||
|
'width': 25},
|
||||||
|
{'col': 'C', 'title': '⌀ Platz', 'attr': 'avg_placement',
|
||||||
|
'style': 'Float', 'width': 8},
|
||||||
|
{'col': 'D', 'title': '⌀ Punkte', 'attr': 'avg_score',
|
||||||
|
'style': 'Float', 'width': 12},
|
||||||
|
{'col': 'E', 'title': 'Hanchans', 'attr': 'hanchan_count',
|
||||||
|
'style': 'Integer', 'width': 10},
|
||||||
|
{'col': 'F', 'title': 'Gut', 'attr': 'good_hanchans',
|
||||||
|
'style': 'Integer', 'width': 5},
|
||||||
|
{'col': 'G', 'title': 'Gewonnen', 'attr': 'won_hanchans',
|
||||||
|
'style': 'Integer', 'width': 10},
|
||||||
|
)
|
||||||
|
workbook.generate_sheet(
|
||||||
|
title=title,
|
||||||
|
columns_settings=columns_settings,
|
||||||
|
object_list=object_list)
|
||||||
|
|
||||||
|
|
||||||
|
def export_kyu_dan_rankings(workbook, until):
|
||||||
|
KyuDanRanking.objects.update(until=until, force_recalc=True)
|
||||||
|
object_list = KyuDanRanking.objects.all()
|
||||||
|
title = "Kyū & Dan Rankings"
|
||||||
|
columns_settings = (
|
||||||
|
{'col': 'A', 'title': 'Spitzname', 'attr': 'user.username',
|
||||||
|
'style': 'Content', 'width': 14},
|
||||||
|
{'col': 'B', 'title': 'Voller Name', 'attr': 'user.full_name',
|
||||||
|
'style': 'Content', 'width': 20},
|
||||||
|
{'col': 'C', 'title': 'Rang', 'attr': 'rank',
|
||||||
|
'style': 'Content', 'width': 8},
|
||||||
|
{'col': 'D', 'title': 'Punkte', 'attr': 'points',
|
||||||
|
'style': 'Integer', 'width': 8},
|
||||||
|
{'col': 'E', 'title': 'Hanchans', 'attr': 'hanchan_count',
|
||||||
|
'style': 'Integer', 'width': 10},
|
||||||
|
{'col': 'F', 'title': 'Gut', 'attr': 'good_hanchans',
|
||||||
|
'style': 'Integer', 'width': 5},
|
||||||
|
{'col': 'G', 'title': 'Gewonnen', 'attr': 'won_hanchans',
|
||||||
|
'style': 'Integer', 'width': 10},
|
||||||
|
{'col': 'H', 'title': 'letzte Hanchan', 'attr': 'last_hanchan_date',
|
||||||
|
'style': 'Date', 'width': 16},
|
||||||
|
)
|
||||||
|
workbook.generate_sheet(
|
||||||
|
title=title,
|
||||||
|
columns_settings=columns_settings,
|
||||||
|
object_list=object_list)
|
||||||
|
|
||||||
|
|
||||||
|
class Command(BaseCommand):
|
||||||
|
"""Exports the SeasonRankings"""
|
||||||
|
filename = str()
|
||||||
|
until = datetime
|
||||||
|
|
||||||
|
def add_arguments(self, parser):
|
||||||
|
parser.add_argument(
|
||||||
|
'--until', nargs='?', type=parse_date,
|
||||||
|
default=date.today(), metavar='YYYY-MM-DD',
|
||||||
|
help='Calculate and export rankings until the given date.')
|
||||||
|
parser.add_argument(
|
||||||
|
'--mail', nargs='*', type=str, metavar='user@example.com',
|
||||||
|
help='Send the spreadsheet via eMail to the given recipient.')
|
||||||
|
|
||||||
|
def handle(self, *args, **options):
|
||||||
|
"""Exports the current ladder ranking in a spreadsheet.
|
||||||
|
This is useful as a backup in form of a hardcopy."""
|
||||||
|
self.until = timezone.make_aware(datetime.combine(
|
||||||
|
options['until'], time(23, 59, 59)
|
||||||
|
))
|
||||||
|
|
||||||
|
self.filename = os.path.join(
|
||||||
|
settings.RANKING_EXPORT_PATH,
|
||||||
|
'mahjong_rankings_{:%Y-%m-%d}.xlsx'.format(self.until)
|
||||||
|
)
|
||||||
|
workbook = xlsx.Workbook()
|
||||||
|
export_season_rankings(workbook, until=self.until)
|
||||||
|
export_kyu_dan_rankings(workbook, until=self.until)
|
||||||
|
os.makedirs(settings.RANKING_EXPORT_PATH, exist_ok=True)
|
||||||
|
workbook.save(self.filename)
|
||||||
|
if options['mail']:
|
||||||
|
self.send_mail(options['mail'])
|
||||||
|
|
||||||
|
def send_mail(self, recipients):
|
||||||
|
mail = EmailMessage(
|
||||||
|
subject='Mahjong Rankings vom {:%d.%m.%Y}'.format(self.until),
|
||||||
|
body=MAIL_BODY,
|
||||||
|
from_email=settings.DEFAULT_FROM_EMAIL,
|
||||||
|
to=recipients)
|
||||||
|
mail.attach_file(self.filename)
|
||||||
|
mail.send()
|
||||||
38
src/mahjong_ranking/management/commands/resetdanranking.py
Normal file
38
src/mahjong_ranking/management/commands/resetdanranking.py
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
"""
|
||||||
|
Rest all dan points to 0 at a given date.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from django.core.management.base import BaseCommand
|
||||||
|
from datetime import date, datetime, time
|
||||||
|
from mahjong_ranking import models
|
||||||
|
from django.utils.dateparse import parse_date
|
||||||
|
from django.utils import timezone
|
||||||
|
|
||||||
|
class Command(BaseCommand):
|
||||||
|
""" Recalculate all Kyu/Dan Rankings """
|
||||||
|
|
||||||
|
help = "reset every dan player to 1st dan with 0 points."
|
||||||
|
|
||||||
|
def add_arguments(self, parser):
|
||||||
|
parser.add_argument('reset_date', type=parse_date)
|
||||||
|
|
||||||
|
def handle(self, *args, **options):
|
||||||
|
legacy_attrs = [ key for key in models.KyuDanRanking.__dict__.keys()
|
||||||
|
if key.startswith('legacy') ]
|
||||||
|
legacy_attrs.remove('legacy_date')
|
||||||
|
reset_date = timezone.make_aware(datetime.combine(
|
||||||
|
options.get('reset_date'), time(23, 59, 59)))
|
||||||
|
models.KyuDanRanking.objects.update(until=reset_date, force_recalc=True)
|
||||||
|
for ranking in models.KyuDanRanking.objects.filter(dan__gt=0):
|
||||||
|
print(ranking)
|
||||||
|
ranking.dan = 1
|
||||||
|
ranking.dan_points = 0
|
||||||
|
ranking.kyu = None
|
||||||
|
ranking.kyu_points = 0
|
||||||
|
ranking.wins_in_a_row = 0
|
||||||
|
ranking.legacy_date = reset_date.date()
|
||||||
|
for legacy_attr in legacy_attrs:
|
||||||
|
attr = legacy_attr.split("_", maxsplit=1)[1]
|
||||||
|
print(ranking, legacy_attr, attr, getattr(ranking, attr))
|
||||||
|
setattr(ranking, legacy_attr, getattr(ranking, attr))
|
||||||
|
ranking.save()
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
"""
|
|
||||||
Recalculate Mahjong Rankings...
|
|
||||||
"""
|
|
||||||
|
|
||||||
from django.core.management.base import BaseCommand
|
|
||||||
|
|
||||||
from mahjong_ranking import LOGGER
|
|
||||||
from mahjong_ranking import models
|
|
||||||
|
|
||||||
|
|
||||||
class Command(BaseCommand):
|
|
||||||
""" Recalculate all Kyu/Dan Rankings """
|
|
||||||
|
|
||||||
help = "Recalculate all Kyu/Dan Rankings"
|
|
||||||
|
|
||||||
def handle(self, *args, **options):
|
|
||||||
old_attr = {'dan': None, 'dan_points': None,
|
|
||||||
'kyu': None, 'kyu_points': None, 'won_hanchans': None,
|
|
||||||
'good_hanchans': None, 'hanchan_count': None}
|
|
||||||
for ranking in models.KyuDanRanking.objects.all():
|
|
||||||
old_attr = {attr: getattr(ranking, attr) for attr in old_attr.keys()}
|
|
||||||
ranking.recalculate()
|
|
||||||
for attr, old_value in old_attr.items():
|
|
||||||
if getattr(ranking, attr) != old_value:
|
|
||||||
LOGGER.warning(
|
|
||||||
"%(user)s recalc shows differences in %(attr)s! old: %(old)d, new: %(new)d",
|
|
||||||
{'user': ranking.user, 'attr': attr,
|
|
||||||
'old': old_value, 'new': getattr(ranking, attr)}
|
|
||||||
)
|
|
||||||
42
src/mahjong_ranking/management/commands/updateranking.py
Normal file
42
src/mahjong_ranking/management/commands/updateranking.py
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
"""
|
||||||
|
Recalculate Mahjong Rankings...
|
||||||
|
"""
|
||||||
|
|
||||||
|
from datetime import date, datetime, time
|
||||||
|
|
||||||
|
from django.core.management.base import BaseCommand
|
||||||
|
from django.utils import timezone
|
||||||
|
from django.utils.dateparse import parse_date
|
||||||
|
|
||||||
|
from mahjong_ranking import models
|
||||||
|
|
||||||
|
|
||||||
|
class Command(BaseCommand):
|
||||||
|
""" Recalculate all Kyu/Dan Rankings """
|
||||||
|
|
||||||
|
help = "Recalculate the Kyu/Dan Rankings"
|
||||||
|
|
||||||
|
def add_arguments(self, parser):
|
||||||
|
parser.add_argument('-s', '--since', nargs='?', type=parse_date,
|
||||||
|
metavar='YYYY-MM-DD',
|
||||||
|
help='Use all Hanchans since the given date.')
|
||||||
|
parser.add_argument('-u', '--until', nargs='?', type=parse_date,
|
||||||
|
metavar='YYYY-MM-DD',
|
||||||
|
help='Only use Hanchans until the given date.')
|
||||||
|
parser.add_argument('-f', '--force', action='store_true',
|
||||||
|
help="Force the recalculation of all Hanchans.")
|
||||||
|
|
||||||
|
def handle(self, *args, **options):
|
||||||
|
since = options.get('since', None)
|
||||||
|
until = options.get('until', None)
|
||||||
|
force_recalc = options.get('force')
|
||||||
|
if isinstance(since, date):
|
||||||
|
since = datetime.combine(since, time(0, 0, 0))
|
||||||
|
since = timezone.make_aware(since)
|
||||||
|
if isinstance(until, date):
|
||||||
|
until = datetime.combine(until, time(23, 59, 59))
|
||||||
|
until = timezone.make_aware(until)
|
||||||
|
models.KyuDanRanking.objects.update(since=since, until=until,
|
||||||
|
force_recalc=force_recalc)
|
||||||
@@ -1,7 +1,8 @@
|
|||||||
"""ObjectManagers for the Django Models used in the Mahjong-Ranking."""
|
"""ObjectManagers for the Django Models used in the Mahjong-Ranking."""
|
||||||
from datetime import date
|
from datetime import date
|
||||||
|
from . import LOGGER
|
||||||
from django.db import models
|
from django.db import models
|
||||||
|
from django.conf import settings
|
||||||
|
|
||||||
|
|
||||||
class HanchanManager(models.Manager):
|
class HanchanManager(models.Manager):
|
||||||
@@ -12,23 +13,31 @@ class HanchanManager(models.Manager):
|
|||||||
"""
|
"""
|
||||||
use_for_related_fields = True
|
use_for_related_fields = True
|
||||||
|
|
||||||
def confirmed_hanchans(self, user=None, **filter_args):
|
def confirmed(self, user=None, since=None, until=None, **filter_args):
|
||||||
""" Return all valid and confirmed Hanchans.
|
""" Return all valid and confirmed Hanchans.
|
||||||
|
|
||||||
:param user: Only return Hanchans where this user participated.
|
:param user: Only return Hanchans where this user participated.
|
||||||
|
:param since: only return Hanchans played since the given datetime
|
||||||
|
:param until: only return Hanchans played until the given datetime
|
||||||
:param filter_args: To add specific arguments to the Django filter.
|
:param filter_args: To add specific arguments to the Django filter.
|
||||||
:return: QuerySet Object
|
:return: QuerySet Object
|
||||||
"""
|
"""
|
||||||
if user:
|
if user:
|
||||||
return self.user_hanchans(user, confirmed=True, **filter_args)
|
return self.user_hanchans(user, confirmed=True, until=until,
|
||||||
else:
|
**filter_args)
|
||||||
return self.filter(confirmed=True, **filter_args)
|
hanchans = self.filter(confirmed=True, **filter_args)
|
||||||
|
if since:
|
||||||
|
hanchans = hanchans.filter(start__gt=since)
|
||||||
|
if until:
|
||||||
|
hanchans = hanchans.filter(start__lte=until)
|
||||||
|
return hanchans
|
||||||
|
|
||||||
def dan_hanchans(self, user, **filter_args):
|
def dan_hanchans(self, user, since = None, **filter_args):
|
||||||
""" Return all Hanchans where a specific user has participated and had
|
""" Return all Hanchans where a specific user has participated and had
|
||||||
gain dan points and make his gamestats availabale.
|
gain dan points and make his gamestats availabale.
|
||||||
|
|
||||||
:param user: Only return Hanchans where this user participated.
|
:param user: Only return Hanchans where this user participated.
|
||||||
|
:param since: only return Hanchans played since the given datetime
|
||||||
:param filter_args: To add specific arguments to the Django filter.
|
:param filter_args: To add specific arguments to the Django filter.
|
||||||
:return: QuerySet Object
|
:return: QuerySet Object
|
||||||
"""
|
"""
|
||||||
@@ -38,15 +47,18 @@ class HanchanManager(models.Manager):
|
|||||||
models.Q(player3=user, player3_dan_points__isnull=False) |
|
models.Q(player3=user, player3_dan_points__isnull=False) |
|
||||||
models.Q(player4=user, player4_dan_points__isnull=False)
|
models.Q(player4=user, player4_dan_points__isnull=False)
|
||||||
).filter(confirmed=True, **filter_args)
|
).filter(confirmed=True, **filter_args)
|
||||||
|
if since:
|
||||||
|
queryset = queryset.filter(start__gt=since)
|
||||||
queryset = queryset.select_related().order_by('-start')
|
queryset = queryset.select_related().order_by('-start')
|
||||||
[ hanchan.get_playerdata(user) for hanchan in queryset ]
|
[hanchan.get_playerdata(user) for hanchan in queryset]
|
||||||
return queryset
|
return queryset
|
||||||
|
|
||||||
def kyu_hanchans(self, user, **filter_args):
|
def kyu_hanchans(self, user, since = None, **filter_args):
|
||||||
""" Return all Hanchans where a specific user has participated and had
|
""" Return all Hanchans where a specific user has participated and had
|
||||||
gain kyū points and make his gamestats availabale.
|
gain kyū points and make his gamestats availabale.
|
||||||
|
|
||||||
:param user: Only return Hanchans where this user participated.
|
:param user: Only return Hanchans where this user participated.
|
||||||
|
:param since: only return Hanchans played since the given datetime
|
||||||
:param filter_args: To add specific arguments to the Django filter.
|
:param filter_args: To add specific arguments to the Django filter.
|
||||||
:return: QuerySet Object
|
:return: QuerySet Object
|
||||||
"""
|
"""
|
||||||
@@ -56,25 +68,30 @@ class HanchanManager(models.Manager):
|
|||||||
models.Q(player3=user, player3_kyu_points__isnull=False) |
|
models.Q(player3=user, player3_kyu_points__isnull=False) |
|
||||||
models.Q(player4=user, player4_kyu_points__isnull=False)
|
models.Q(player4=user, player4_kyu_points__isnull=False)
|
||||||
).filter(confirmed=True, **filter_args)
|
).filter(confirmed=True, **filter_args)
|
||||||
|
if since:
|
||||||
|
queryset = queryset.filter(start__gt=since)
|
||||||
queryset = queryset.select_related().order_by('-start')
|
queryset = queryset.select_related().order_by('-start')
|
||||||
[ hanchan.get_playerdata(user) for hanchan in queryset ]
|
[hanchan.get_playerdata(user) for hanchan in queryset]
|
||||||
return queryset
|
return queryset
|
||||||
|
|
||||||
def season_hanchans(self, user=None, season=None):
|
def season_hanchans(self, user=None, season=None, until=None):
|
||||||
"""Return all Hanchans that belong to a given or the current season.
|
"""Return all Hanchans that belong to a given or the current season.
|
||||||
|
|
||||||
:param user: Only return Hanchans where this user participated.
|
:param user: Only return Hanchans where this user participated.
|
||||||
:param season: the year of the wanted season, current year if None.
|
:param season: the year of the wanted season, current year if None.
|
||||||
:return: QuerySet Object
|
:return: QuerySet Object
|
||||||
"""
|
"""
|
||||||
season = season or date.today().year
|
try:
|
||||||
return self.confirmed_hanchans(user=user, season=season)
|
season = season or until.year
|
||||||
|
except AttributeError:
|
||||||
|
season = date.today().year
|
||||||
|
return self.confirmed(user=user, season=season, until=until)
|
||||||
|
|
||||||
def user_hanchans(self, user, since=None, **filter_args):
|
def user_hanchans(self, user, since=None, until=None, **filter_args):
|
||||||
"""Return all Hanchans where a specific user has participated.
|
"""Return all Hanchans where a specific user has participated.
|
||||||
|
|
||||||
:param user: Return Hanchans where this user participated.
|
:param user: Return Hanchans where this user participated.
|
||||||
:param since: optional a date value since when you want to hanchans
|
:param since: only return Hanchans played since the given datetime
|
||||||
:param filter_args: To add specific arguments to the Django filter.
|
:param filter_args: To add specific arguments to the Django filter.
|
||||||
:return: a QuerySet Object
|
:return: a QuerySet Object
|
||||||
"""
|
"""
|
||||||
@@ -82,15 +99,16 @@ class HanchanManager(models.Manager):
|
|||||||
models.Q(player1=user) | models.Q(player2=user) |
|
models.Q(player1=user) | models.Q(player2=user) |
|
||||||
models.Q(player3=user) | models.Q(player4=user)
|
models.Q(player3=user) | models.Q(player4=user)
|
||||||
)
|
)
|
||||||
|
queryset = queryset.filter(**filter_args)
|
||||||
if since:
|
if since:
|
||||||
queryset = queryset.filter(start__gte=since, **filter_args)
|
queryset = queryset.filter(start__gte=since)
|
||||||
else:
|
if until:
|
||||||
queryset = queryset.filter(**filter_args)
|
queryset = queryset.filter(start__lte=until)
|
||||||
queryset = queryset.select_related().order_by('-start')
|
queryset = queryset.select_related().order_by('-start')
|
||||||
[ hanchan.get_playerdata(user) for hanchan in queryset ]
|
[hanchan.get_playerdata(user) for hanchan in queryset]
|
||||||
return queryset
|
return queryset
|
||||||
|
|
||||||
def unconfirmed_hanchans(self, user=None, **filter_args):
|
def unconfirmed(self, user=None, **filter_args):
|
||||||
""" Return all Hanchans that have been set to unconfirmed.
|
""" Return all Hanchans that have been set to unconfirmed.
|
||||||
|
|
||||||
:param user: Only return Hanchans where this user participated.
|
:param user: Only return Hanchans where this user participated.
|
||||||
@@ -158,8 +176,27 @@ class SeasonRankingManager(models.Manager):
|
|||||||
})
|
})
|
||||||
return json_data
|
return json_data
|
||||||
|
|
||||||
class KyuDanRankingManager(models.Manager):
|
def update(self, season=None, until=None, force_recalc=False):
|
||||||
|
try:
|
||||||
|
season = season or until.year
|
||||||
|
except AttributeError:
|
||||||
|
season = date.today().year
|
||||||
|
if until or force_recalc:
|
||||||
|
for ranking in self.filter(season=season):
|
||||||
|
ranking.recalculate(until=until)
|
||||||
|
for placement, ranking in enumerate(self.season_rankings(season), start=1):
|
||||||
|
ranking.placement = placement
|
||||||
|
ranking.save(force_update=True, update_fields=['placement'])
|
||||||
|
|
||||||
|
def season_rankings(self, season=None):
|
||||||
|
season = season or date.today().year
|
||||||
|
rankings = self.filter(
|
||||||
|
season=season,
|
||||||
|
hanchan_count__gt=settings.MIN_HANCHANS_FOR_LADDER)
|
||||||
|
return rankings.order_by('avg_placement', '-avg_score')
|
||||||
|
|
||||||
|
|
||||||
|
class KyuDanRankingManager(models.Manager):
|
||||||
def json_data(self):
|
def json_data(self):
|
||||||
""" Get all Rankings for a given Season and return them as a list of
|
""" Get all Rankings for a given Season and return them as a list of
|
||||||
dict objects, suitable for JSON exports and other processings.
|
dict objects, suitable for JSON exports and other processings.
|
||||||
@@ -172,9 +209,12 @@ class KyuDanRankingManager(models.Manager):
|
|||||||
values = values.values('user_id', 'user__username',
|
values = values.values('user_id', 'user__username',
|
||||||
'user__first_name', 'user__last_name',
|
'user__first_name', 'user__last_name',
|
||||||
'dan', 'dan_points', 'kyu', 'kyu_points',
|
'dan', 'dan_points', 'kyu', 'kyu_points',
|
||||||
'hanchan_count', 'won_hanchans', 'good_hanchans')
|
'hanchan_count', 'won_hanchans', 'good_hanchans',
|
||||||
|
'last_hanchan_date')
|
||||||
for user in values:
|
for user in values:
|
||||||
if user['dan']:
|
if user['hanchan_count'] == 0:
|
||||||
|
continue
|
||||||
|
elif user['dan']:
|
||||||
rank = '{}. Dan'.format(user['dan'])
|
rank = '{}. Dan'.format(user['dan'])
|
||||||
points = user['dan_points']
|
points = user['dan_points']
|
||||||
else:
|
else:
|
||||||
@@ -183,11 +223,31 @@ class KyuDanRankingManager(models.Manager):
|
|||||||
json_data.append({
|
json_data.append({
|
||||||
'user_id': user['user_id'],
|
'user_id': user['user_id'],
|
||||||
'username': user['user__username'],
|
'username': user['user__username'],
|
||||||
'full_name': " ".join([user['user__last_name'], user['user__first_name']]),
|
'full_name': " ".join(
|
||||||
|
[user['user__last_name'], user['user__first_name']]),
|
||||||
'rank': rank,
|
'rank': rank,
|
||||||
'points': points,
|
'points': points,
|
||||||
'hanchan_count': user['hanchan_count'],
|
'hanchan_count': user['hanchan_count'],
|
||||||
'good_hanchans': user['good_hanchans'],
|
'good_hanchans': user['good_hanchans'],
|
||||||
'won_hanchans': user['won_hanchans']
|
'won_hanchans': user['won_hanchans'],
|
||||||
|
'last_hanchan_date': user['last_hanchan_date']
|
||||||
|
|
||||||
})
|
})
|
||||||
return json_data
|
return json_data
|
||||||
|
|
||||||
|
def update(self, since=None, until=None, force_recalc=False):
|
||||||
|
old_attr = {'dan': None, 'dan_points': None,
|
||||||
|
'kyu': None, 'kyu_points': None, 'won_hanchans': None,
|
||||||
|
'good_hanchans': None, 'hanchan_count': None}
|
||||||
|
for ranking in self.all():
|
||||||
|
old_attr = {attr: getattr(ranking, attr) for attr in
|
||||||
|
old_attr.keys()}
|
||||||
|
ranking.calculate(since=since, until=until,
|
||||||
|
force_recalc=force_recalc)
|
||||||
|
for attr, old_value in old_attr.items():
|
||||||
|
if getattr(ranking, attr) != old_value:
|
||||||
|
LOGGER.warning(
|
||||||
|
"%(user)s recalc shows differences in %(attr)s! old: %(old)d, new: %(new)d",
|
||||||
|
{'user': ranking.user, 'attr': attr,
|
||||||
|
'old': old_value or 0, 'new': getattr(ranking, attr) or 0}
|
||||||
|
)
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
"""Middleware to defer slow denormalization at the end of a request."""
|
"""Middleware to defer slow denormalization at the end of a request."""
|
||||||
from django.core.cache import cache
|
from django.core.cache import cache
|
||||||
|
|
||||||
from mahjong_ranking import models
|
from mahjong_ranking import models
|
||||||
from . import LOGGER, MIN_HANCHANS_FOR_LADDER
|
from . import LOGGER
|
||||||
|
|
||||||
|
|
||||||
class DenormalizationUpdateMiddleware(object): # Ignore PyLintBear (R0903)
|
class DenormalizationUpdateMiddleware(object): # Ignore PyLintBear (R0903)
|
||||||
@@ -35,7 +34,7 @@ class DenormalizationUpdateMiddleware(object): # Ignore PyLintBear (R0903)
|
|||||||
user_id, hanchan_start = kyu_dan_ranking_queue.pop()
|
user_id, hanchan_start = kyu_dan_ranking_queue.pop()
|
||||||
kyu_dan_ranking = models.KyuDanRanking.objects.get_or_create(
|
kyu_dan_ranking = models.KyuDanRanking.objects.get_or_create(
|
||||||
user_id=user_id)[0]
|
user_id=user_id)[0]
|
||||||
kyu_dan_ranking.recalculate(hanchan_start)
|
kyu_dan_ranking.calculate(since=hanchan_start)
|
||||||
cache.set('kyu_dan_ranking_queue', set(), 360)
|
cache.set('kyu_dan_ranking_queue', set(), 360)
|
||||||
|
|
||||||
ladder_ranking_queue = cache.get('ladder_ranking_queue', set())
|
ladder_ranking_queue = cache.get('ladder_ranking_queue', set())
|
||||||
@@ -58,12 +57,5 @@ class DenormalizationUpdateMiddleware(object): # Ignore PyLintBear (R0903)
|
|||||||
|
|
||||||
for season in season_queue:
|
for season in season_queue:
|
||||||
LOGGER.info(u'Recalculate placements for Season %d', season)
|
LOGGER.info(u'Recalculate placements for Season %d', season)
|
||||||
season_rankings = models.SeasonRanking.objects.filter(
|
models.SeasonRanking.objects.update(season=season)
|
||||||
season=season, hanchan_count__gt=MIN_HANCHANS_FOR_LADDER
|
|
||||||
).order_by('avg_placement', '-avg_score')
|
|
||||||
placement = 1
|
|
||||||
for ranking in season_rankings:
|
|
||||||
ranking.placement = placement
|
|
||||||
ranking.save(force_update=True)
|
|
||||||
placement += 1
|
|
||||||
return response
|
return response
|
||||||
|
|||||||
@@ -1,12 +1,11 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from django.db import models, migrations
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
from django.db import models, migrations
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
('events', '0005_auto_20150907_2021'),
|
('events', '0005_auto_20150907_2021'),
|
||||||
@@ -17,15 +16,19 @@ class Migration(migrations.Migration):
|
|||||||
name='EventRanking',
|
name='EventRanking',
|
||||||
fields=[
|
fields=[
|
||||||
('id', models.AutoField(verbose_name='ID',
|
('id', models.AutoField(verbose_name='ID',
|
||||||
serialize=False, auto_created=True, primary_key=True)),
|
serialize=False, auto_created=True,
|
||||||
('placement', models.PositiveIntegerField(null=True, blank=True)),
|
primary_key=True)),
|
||||||
|
('placement',
|
||||||
|
models.PositiveIntegerField(null=True, blank=True)),
|
||||||
('avg_placement', models.FloatField(default=4)),
|
('avg_placement', models.FloatField(default=4)),
|
||||||
('avg_score', models.FloatField(default=0)),
|
('avg_score', models.FloatField(default=0)),
|
||||||
('hanchan_count', models.PositiveIntegerField(default=0)),
|
('hanchan_count', models.PositiveIntegerField(default=0)),
|
||||||
('good_hanchans', models.PositiveIntegerField(default=0)),
|
('good_hanchans', models.PositiveIntegerField(default=0)),
|
||||||
('won_hanchans', models.PositiveIntegerField(default=0)),
|
('won_hanchans', models.PositiveIntegerField(default=0)),
|
||||||
('event', models.ForeignKey(to='events.Event')),
|
('event', models.ForeignKey(to='events.Event',
|
||||||
('user', models.ForeignKey(to=settings.AUTH_USER_MODEL)),
|
on_delete=models.CASCADE)),
|
||||||
|
('user', models.ForeignKey(to=settings.AUTH_USER_MODEL,
|
||||||
|
on_delete=models.CASCADE)),
|
||||||
],
|
],
|
||||||
options={
|
options={
|
||||||
'ordering': ('placement', 'avg_placement', '-avg_score'),
|
'ordering': ('placement', 'avg_placement', '-avg_score'),
|
||||||
@@ -35,10 +38,13 @@ class Migration(migrations.Migration):
|
|||||||
name='Hanchan',
|
name='Hanchan',
|
||||||
fields=[
|
fields=[
|
||||||
('id', models.AutoField(verbose_name='ID',
|
('id', models.AutoField(verbose_name='ID',
|
||||||
serialize=False, auto_created=True, primary_key=True)),
|
serialize=False, auto_created=True,
|
||||||
|
primary_key=True)),
|
||||||
('start', models.DateTimeField(
|
('start', models.DateTimeField(
|
||||||
help_text='Wichtig damit die richtigen Hanchans in die Wertung kommen.', verbose_name='Beginn')),
|
help_text='Wichtig damit die richtigen Hanchans in die Wertung kommen.',
|
||||||
('player1_input_score', models.IntegerField(verbose_name='Punkte')),
|
verbose_name='Beginn')),
|
||||||
|
('player1_input_score',
|
||||||
|
models.IntegerField(verbose_name='Punkte')),
|
||||||
('player1_game_score', models.PositiveIntegerField(
|
('player1_game_score', models.PositiveIntegerField(
|
||||||
default=0, verbose_name='Punkte', editable=False)),
|
default=0, verbose_name='Punkte', editable=False)),
|
||||||
('player1_placement', models.PositiveSmallIntegerField(
|
('player1_placement', models.PositiveSmallIntegerField(
|
||||||
@@ -50,8 +56,11 @@ class Migration(migrations.Migration):
|
|||||||
('player1_bonus_points', models.SmallIntegerField(
|
('player1_bonus_points', models.SmallIntegerField(
|
||||||
null=True, editable=False, blank=True)),
|
null=True, editable=False, blank=True)),
|
||||||
('player1_comment', models.CharField(verbose_name='Anmerkung',
|
('player1_comment', models.CharField(verbose_name='Anmerkung',
|
||||||
max_length=255, editable=False, blank=True)),
|
max_length=255,
|
||||||
('player2_input_score', models.IntegerField(verbose_name='Punkte')),
|
editable=False,
|
||||||
|
blank=True)),
|
||||||
|
('player2_input_score',
|
||||||
|
models.IntegerField(verbose_name='Punkte')),
|
||||||
('player2_game_score', models.PositiveIntegerField(
|
('player2_game_score', models.PositiveIntegerField(
|
||||||
default=0, verbose_name='Punkte', editable=False)),
|
default=0, verbose_name='Punkte', editable=False)),
|
||||||
('player2_placement', models.PositiveSmallIntegerField(
|
('player2_placement', models.PositiveSmallIntegerField(
|
||||||
@@ -63,8 +72,11 @@ class Migration(migrations.Migration):
|
|||||||
('player2_bonus_points', models.SmallIntegerField(
|
('player2_bonus_points', models.SmallIntegerField(
|
||||||
null=True, editable=False, blank=True)),
|
null=True, editable=False, blank=True)),
|
||||||
('player2_comment', models.CharField(verbose_name='Anmerkung',
|
('player2_comment', models.CharField(verbose_name='Anmerkung',
|
||||||
max_length=255, editable=False, blank=True)),
|
max_length=255,
|
||||||
('player3_input_score', models.IntegerField(verbose_name='Punkte')),
|
editable=False,
|
||||||
|
blank=True)),
|
||||||
|
('player3_input_score',
|
||||||
|
models.IntegerField(verbose_name='Punkte')),
|
||||||
('player3_game_score', models.PositiveIntegerField(
|
('player3_game_score', models.PositiveIntegerField(
|
||||||
default=0, verbose_name='Punkte', editable=False)),
|
default=0, verbose_name='Punkte', editable=False)),
|
||||||
('player3_placement', models.PositiveSmallIntegerField(
|
('player3_placement', models.PositiveSmallIntegerField(
|
||||||
@@ -76,8 +88,11 @@ class Migration(migrations.Migration):
|
|||||||
('player3_bonus_points', models.SmallIntegerField(
|
('player3_bonus_points', models.SmallIntegerField(
|
||||||
null=True, editable=False, blank=True)),
|
null=True, editable=False, blank=True)),
|
||||||
('player3_comment', models.CharField(verbose_name='Anmerkung',
|
('player3_comment', models.CharField(verbose_name='Anmerkung',
|
||||||
max_length=255, editable=False, blank=True)),
|
max_length=255,
|
||||||
('player4_input_score', models.IntegerField(verbose_name='Punkte')),
|
editable=False,
|
||||||
|
blank=True)),
|
||||||
|
('player4_input_score',
|
||||||
|
models.IntegerField(verbose_name='Punkte')),
|
||||||
('player4_game_score', models.PositiveIntegerField(
|
('player4_game_score', models.PositiveIntegerField(
|
||||||
default=0, verbose_name='Punkte', editable=False)),
|
default=0, verbose_name='Punkte', editable=False)),
|
||||||
('player4_placement', models.PositiveSmallIntegerField(
|
('player4_placement', models.PositiveSmallIntegerField(
|
||||||
@@ -89,22 +104,37 @@ class Migration(migrations.Migration):
|
|||||||
('player4_bonus_points', models.SmallIntegerField(
|
('player4_bonus_points', models.SmallIntegerField(
|
||||||
null=True, editable=False, blank=True)),
|
null=True, editable=False, blank=True)),
|
||||||
('player4_comment', models.CharField(verbose_name='Anmerkung',
|
('player4_comment', models.CharField(verbose_name='Anmerkung',
|
||||||
max_length=255, editable=False, blank=True)),
|
max_length=255,
|
||||||
('comment', models.TextField(verbose_name='Anmerkung', blank=True)),
|
editable=False,
|
||||||
|
blank=True)),
|
||||||
|
('comment',
|
||||||
|
models.TextField(verbose_name='Anmerkung', blank=True)),
|
||||||
('confirmed', models.BooleanField(
|
('confirmed', models.BooleanField(
|
||||||
default=True, help_text='Nur g\xfcltige und best\xe4tigte Hanchans kommen in die Wertung.', verbose_name='Wurde best\xe4tigt')),
|
default=True,
|
||||||
('player_names', models.CharField(max_length=255, editable=False)),
|
help_text='Nur g\xfcltige und best\xe4tigte Hanchans kommen in die Wertung.',
|
||||||
|
verbose_name='Wurde best\xe4tigt')),
|
||||||
|
('player_names',
|
||||||
|
models.CharField(max_length=255, editable=False)),
|
||||||
('season', models.PositiveSmallIntegerField(
|
('season', models.PositiveSmallIntegerField(
|
||||||
verbose_name='Saison', editable=False, db_index=True)),
|
verbose_name='Saison', editable=False, db_index=True)),
|
||||||
('event', models.ForeignKey(to='events.Event')),
|
('event', models.ForeignKey(to='events.Event',
|
||||||
|
on_delete=models.CASCADE)),
|
||||||
('player1', models.ForeignKey(related_name='user_hanchan+',
|
('player1', models.ForeignKey(related_name='user_hanchan+',
|
||||||
verbose_name='Spieler 1', to=settings.AUTH_USER_MODEL)),
|
verbose_name='Spieler 1',
|
||||||
|
to=settings.AUTH_USER_MODEL,
|
||||||
|
on_delete=models.CASCADE)),
|
||||||
('player2', models.ForeignKey(related_name='user_hanchan+',
|
('player2', models.ForeignKey(related_name='user_hanchan+',
|
||||||
verbose_name='Spieler 2', to=settings.AUTH_USER_MODEL)),
|
verbose_name='Spieler 2',
|
||||||
|
to=settings.AUTH_USER_MODEL,
|
||||||
|
on_delete=models.CASCADE)),
|
||||||
('player3', models.ForeignKey(related_name='user_hanchan+',
|
('player3', models.ForeignKey(related_name='user_hanchan+',
|
||||||
verbose_name='Spieler 3', to=settings.AUTH_USER_MODEL)),
|
verbose_name='Spieler 3',
|
||||||
|
to=settings.AUTH_USER_MODEL,
|
||||||
|
on_delete=models.CASCADE)),
|
||||||
('player4', models.ForeignKey(related_name='user_hanchan+',
|
('player4', models.ForeignKey(related_name='user_hanchan+',
|
||||||
verbose_name='Spieler 4', to=settings.AUTH_USER_MODEL)),
|
verbose_name='Spieler 4',
|
||||||
|
to=settings.AUTH_USER_MODEL,
|
||||||
|
on_delete=models.CASCADE)),
|
||||||
],
|
],
|
||||||
options={
|
options={
|
||||||
'ordering': ('-start',),
|
'ordering': ('-start',),
|
||||||
@@ -116,8 +146,11 @@ class Migration(migrations.Migration):
|
|||||||
name='KyuDanRanking',
|
name='KyuDanRanking',
|
||||||
fields=[
|
fields=[
|
||||||
('id', models.AutoField(verbose_name='ID',
|
('id', models.AutoField(verbose_name='ID',
|
||||||
serialize=False, auto_created=True, primary_key=True)),
|
serialize=False, auto_created=True,
|
||||||
('dan', models.PositiveSmallIntegerField(null=True, blank=True)),
|
primary_key=True)),
|
||||||
|
(
|
||||||
|
'dan',
|
||||||
|
models.PositiveSmallIntegerField(null=True, blank=True)),
|
||||||
('dan_points', models.PositiveIntegerField(default=0)),
|
('dan_points', models.PositiveIntegerField(default=0)),
|
||||||
('kyu', models.PositiveSmallIntegerField(
|
('kyu', models.PositiveSmallIntegerField(
|
||||||
default=10, null=True, blank=True)),
|
default=10, null=True, blank=True)),
|
||||||
@@ -128,7 +161,8 @@ class Migration(migrations.Migration):
|
|||||||
('legacy_date', models.DateField(null=True, blank=True)),
|
('legacy_date', models.DateField(null=True, blank=True)),
|
||||||
('legacy_dan_points', models.PositiveIntegerField(default=0)),
|
('legacy_dan_points', models.PositiveIntegerField(default=0)),
|
||||||
('legacy_kyu_points', models.PositiveIntegerField(default=0)),
|
('legacy_kyu_points', models.PositiveIntegerField(default=0)),
|
||||||
('user', models.OneToOneField(to=settings.AUTH_USER_MODEL)),
|
('user', models.OneToOneField(to=settings.AUTH_USER_MODEL,
|
||||||
|
on_delete=models.CASCADE)),
|
||||||
],
|
],
|
||||||
options={
|
options={
|
||||||
'ordering': ('-dan', '-dan_points', '-kyu_points'),
|
'ordering': ('-dan', '-dan_points', '-kyu_points'),
|
||||||
@@ -140,15 +174,19 @@ class Migration(migrations.Migration):
|
|||||||
name='SeasonRanking',
|
name='SeasonRanking',
|
||||||
fields=[
|
fields=[
|
||||||
('id', models.AutoField(verbose_name='ID',
|
('id', models.AutoField(verbose_name='ID',
|
||||||
serialize=False, auto_created=True, primary_key=True)),
|
serialize=False, auto_created=True,
|
||||||
('season', models.PositiveSmallIntegerField(verbose_name='Saison')),
|
primary_key=True)),
|
||||||
('placement', models.PositiveIntegerField(null=True, blank=True)),
|
('season',
|
||||||
|
models.PositiveSmallIntegerField(verbose_name='Saison')),
|
||||||
|
('placement',
|
||||||
|
models.PositiveIntegerField(null=True, blank=True)),
|
||||||
('avg_placement', models.FloatField(null=True, blank=True)),
|
('avg_placement', models.FloatField(null=True, blank=True)),
|
||||||
('avg_score', models.FloatField(null=True, blank=True)),
|
('avg_score', models.FloatField(null=True, blank=True)),
|
||||||
('hanchan_count', models.PositiveIntegerField(default=0)),
|
('hanchan_count', models.PositiveIntegerField(default=0)),
|
||||||
('good_hanchans', models.PositiveIntegerField(default=0)),
|
('good_hanchans', models.PositiveIntegerField(default=0)),
|
||||||
('won_hanchans', models.PositiveIntegerField(default=0)),
|
('won_hanchans', models.PositiveIntegerField(default=0)),
|
||||||
('user', models.ForeignKey(to=settings.AUTH_USER_MODEL)),
|
('user', models.ForeignKey(to=settings.AUTH_USER_MODEL,
|
||||||
|
on_delete=models.CASCADE)),
|
||||||
],
|
],
|
||||||
options={
|
options={
|
||||||
'ordering': ('placement', 'avg_placement', '-avg_score'),
|
'ordering': ('placement', 'avg_placement', '-avg_score'),
|
||||||
|
|||||||
29
src/mahjong_ranking/migrations/0005_auto_20171115_0653.py
Normal file
29
src/mahjong_ranking/migrations/0005_auto_20171115_0653.py
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.11.7 on 2017-11-15 05:53
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('mahjong_ranking', '0004_auto_20170218_1947'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name='kyudanranking',
|
||||||
|
options={'ordering': ('-dan_points', 'dan', '-kyu_points'), 'verbose_name': 'Kyū/Dan Wertung', 'verbose_name_plural': 'Kyū/Dan Wertungen'},
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='kyudanranking',
|
||||||
|
name='last_hanchan_date',
|
||||||
|
field=models.DateTimeField(blank=True, null=True),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='kyudanranking',
|
||||||
|
name='wins_in_a_row',
|
||||||
|
field=models.PositiveIntegerField(default=0),
|
||||||
|
),
|
||||||
|
]
|
||||||
87
src/mahjong_ranking/migrations/0006_auto_20171214_1318.py
Normal file
87
src/mahjong_ranking/migrations/0006_auto_20171214_1318.py
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.11.8 on 2017-12-14 12:18
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('mahjong_ranking', '0005_auto_20171115_0653'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='kyudanranking',
|
||||||
|
name='legacy_dan',
|
||||||
|
field=models.PositiveSmallIntegerField(blank=True, null=True),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='kyudanranking',
|
||||||
|
name='legacy_good_hanchans',
|
||||||
|
field=models.PositiveIntegerField(blank=True, null=True),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='kyudanranking',
|
||||||
|
name='legacy_kyu',
|
||||||
|
field=models.PositiveSmallIntegerField(blank=True, null=True),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='kyudanranking',
|
||||||
|
name='legacy_won_hanchans',
|
||||||
|
field=models.PositiveIntegerField(blank=True, null=True),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='kyudanranking',
|
||||||
|
name='max_dan_points',
|
||||||
|
field=models.PositiveIntegerField(default=0),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='eventranking',
|
||||||
|
name='user',
|
||||||
|
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='hanchan',
|
||||||
|
name='player1',
|
||||||
|
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='user_hanchan+', to=settings.AUTH_USER_MODEL, verbose_name='Spieler 1'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='hanchan',
|
||||||
|
name='player2',
|
||||||
|
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='user_hanchan+', to=settings.AUTH_USER_MODEL, verbose_name='Spieler 2'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='hanchan',
|
||||||
|
name='player3',
|
||||||
|
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='user_hanchan+', to=settings.AUTH_USER_MODEL, verbose_name='Spieler 3'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='hanchan',
|
||||||
|
name='player4',
|
||||||
|
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='user_hanchan+', to=settings.AUTH_USER_MODEL, verbose_name='Spieler 4'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='kyudanranking',
|
||||||
|
name='legacy_dan_points',
|
||||||
|
field=models.PositiveIntegerField(blank=True, null=True),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='kyudanranking',
|
||||||
|
name='legacy_hanchan_count',
|
||||||
|
field=models.PositiveIntegerField(blank=True, null=True),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='kyudanranking',
|
||||||
|
name='legacy_kyu_points',
|
||||||
|
field=models.PositiveIntegerField(blank=True, null=True),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='seasonranking',
|
||||||
|
name='user',
|
||||||
|
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL),
|
||||||
|
),
|
||||||
|
]
|
||||||
18
src/mahjong_ranking/mixins.py
Normal file
18
src/mahjong_ranking/mixins.py
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
from datetime import date
|
||||||
|
from .models import Hanchan, SeasonRanking
|
||||||
|
from events.models import Event
|
||||||
|
|
||||||
|
|
||||||
|
class MahjongMixin(object):
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
|
context = super(MahjongMixin, self).get_context_data(**kwargs)
|
||||||
|
try:
|
||||||
|
context['season'] = self.season
|
||||||
|
context['season_start'] = date(year=self.season, month=1, day=1)
|
||||||
|
context['season_end'] = date(year=self.season, month=12, day=31)
|
||||||
|
context['season_list'] = SeasonRanking.objects.season_list
|
||||||
|
except AttributeError:
|
||||||
|
pass
|
||||||
|
context['latest_hanchan_list'] = Hanchan.objects.confirmed()[:3]
|
||||||
|
context['latest_event_list'] = Event.objects.latest(limit=3)
|
||||||
|
return context
|
||||||
@@ -5,10 +5,12 @@
|
|||||||
|
|
||||||
from __future__ import division
|
from __future__ import division
|
||||||
|
|
||||||
|
from datetime import datetime, time
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
from django.core.urlresolvers import reverse
|
|
||||||
from django.db import models
|
from django.db import models
|
||||||
|
from django.urls import reverse
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from django.utils.translation import ugettext as _
|
from django.utils.translation import ugettext as _
|
||||||
|
|
||||||
@@ -27,8 +29,8 @@ class EventRanking(models.Model):
|
|||||||
Sie beschränken sich aber auf einen Event und werden nur dann angestossen,
|
Sie beschränken sich aber auf einen Event und werden nur dann angestossen,
|
||||||
wenn der Event als Turnier markiert wurde.
|
wenn der Event als Turnier markiert wurde.
|
||||||
"""
|
"""
|
||||||
user = models.ForeignKey(settings.AUTH_USER_MODEL)
|
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.PROTECT)
|
||||||
event = models.ForeignKey(Event)
|
event = models.ForeignKey(Event, on_delete=models.CASCADE)
|
||||||
placement = models.PositiveIntegerField(blank=True, null=True)
|
placement = models.PositiveIntegerField(blank=True, null=True)
|
||||||
avg_placement = models.FloatField(default=4)
|
avg_placement = models.FloatField(default=4)
|
||||||
avg_score = models.FloatField(default=0)
|
avg_score = models.FloatField(default=0)
|
||||||
@@ -56,7 +58,7 @@ class EventRanking(models.Model):
|
|||||||
)
|
)
|
||||||
sum_placement = 0.0
|
sum_placement = 0.0
|
||||||
sum_score = 0.0
|
sum_score = 0.0
|
||||||
event_hanchans = Hanchan.objects.confirmed_hanchans(
|
event_hanchans = Hanchan.objects.confirmed(
|
||||||
user=self.user_id,
|
user=self.user_id,
|
||||||
event=self.event_id
|
event=self.event_id
|
||||||
)
|
)
|
||||||
@@ -84,7 +86,7 @@ class Hanchan(models.Model):
|
|||||||
Es werden aber noch andere Tests durchgeführt, ob sie gültig ist.
|
Es werden aber noch andere Tests durchgeführt, ob sie gültig ist.
|
||||||
Außerdem gehört jede Hanchan zu einer Veranstaltung.
|
Außerdem gehört jede Hanchan zu einer Veranstaltung.
|
||||||
"""
|
"""
|
||||||
event = models.ForeignKey(Event)
|
event = models.ForeignKey(Event, on_delete=models.CASCADE)
|
||||||
start = models.DateTimeField(
|
start = models.DateTimeField(
|
||||||
_('Start'),
|
_('Start'),
|
||||||
help_text=_('This is crucial to get the right Hanchans that scores')
|
help_text=_('This is crucial to get the right Hanchans that scores')
|
||||||
@@ -92,7 +94,7 @@ class Hanchan(models.Model):
|
|||||||
|
|
||||||
player1 = models.ForeignKey(
|
player1 = models.ForeignKey(
|
||||||
settings.AUTH_USER_MODEL,
|
settings.AUTH_USER_MODEL,
|
||||||
on_delete=models.CASCADE,
|
on_delete=models.PROTECT,
|
||||||
related_name='user_hanchan+',
|
related_name='user_hanchan+',
|
||||||
verbose_name=_('Player 1'))
|
verbose_name=_('Player 1'))
|
||||||
player1_input_score = models.IntegerField(_('Score'))
|
player1_input_score = models.IntegerField(_('Score'))
|
||||||
@@ -111,7 +113,7 @@ class Hanchan(models.Model):
|
|||||||
|
|
||||||
player2 = models.ForeignKey(
|
player2 = models.ForeignKey(
|
||||||
settings.AUTH_USER_MODEL,
|
settings.AUTH_USER_MODEL,
|
||||||
on_delete=models.CASCADE,
|
on_delete=models.PROTECT,
|
||||||
related_name='user_hanchan+',
|
related_name='user_hanchan+',
|
||||||
verbose_name=_('Player 2'))
|
verbose_name=_('Player 2'))
|
||||||
player2_input_score = models.IntegerField(_('Score'))
|
player2_input_score = models.IntegerField(_('Score'))
|
||||||
@@ -130,7 +132,7 @@ class Hanchan(models.Model):
|
|||||||
|
|
||||||
player3 = models.ForeignKey(
|
player3 = models.ForeignKey(
|
||||||
settings.AUTH_USER_MODEL,
|
settings.AUTH_USER_MODEL,
|
||||||
on_delete=models.CASCADE,
|
on_delete=models.PROTECT,
|
||||||
related_name='user_hanchan+',
|
related_name='user_hanchan+',
|
||||||
verbose_name=_('Player 3'))
|
verbose_name=_('Player 3'))
|
||||||
player3_input_score = models.IntegerField(_('Score'))
|
player3_input_score = models.IntegerField(_('Score'))
|
||||||
@@ -149,7 +151,7 @@ class Hanchan(models.Model):
|
|||||||
|
|
||||||
player4 = models.ForeignKey(
|
player4 = models.ForeignKey(
|
||||||
settings.AUTH_USER_MODEL,
|
settings.AUTH_USER_MODEL,
|
||||||
on_delete=models.CASCADE,
|
on_delete=models.PROTECT,
|
||||||
related_name='user_hanchan+',
|
related_name='user_hanchan+',
|
||||||
verbose_name=_('Player 4'))
|
verbose_name=_('Player 4'))
|
||||||
player4_input_score = models.IntegerField(_('Score'))
|
player4_input_score = models.IntegerField(_('Score'))
|
||||||
@@ -333,32 +335,49 @@ class KyuDanRanking(models.Model):
|
|||||||
Im Gegensatz zum Ladder Ranking ist das nicht Saison gebunden.
|
Im Gegensatz zum Ladder Ranking ist das nicht Saison gebunden.
|
||||||
Deswegen läuft es getrennt.
|
Deswegen läuft es getrennt.
|
||||||
"""
|
"""
|
||||||
user = models.OneToOneField(settings.AUTH_USER_MODEL)
|
user = models.OneToOneField(settings.AUTH_USER_MODEL,
|
||||||
|
on_delete=models.CASCADE)
|
||||||
dan = models.PositiveSmallIntegerField(blank=True, null=True)
|
dan = models.PositiveSmallIntegerField(blank=True, null=True)
|
||||||
dan_points = models.PositiveIntegerField(default=0)
|
dan_points = models.PositiveIntegerField(default=0)
|
||||||
|
max_dan_points = models.PositiveIntegerField(default=0)
|
||||||
kyu = models.PositiveSmallIntegerField(default=10, blank=True, null=True)
|
kyu = models.PositiveSmallIntegerField(default=10, blank=True, null=True)
|
||||||
kyu_points = models.PositiveIntegerField(default=0)
|
kyu_points = models.PositiveIntegerField(default=0)
|
||||||
won_hanchans = models.PositiveIntegerField(default=0)
|
won_hanchans = models.PositiveIntegerField(default=0)
|
||||||
good_hanchans = models.PositiveIntegerField(default=0)
|
good_hanchans = models.PositiveIntegerField(default=0)
|
||||||
hanchan_count = models.PositiveIntegerField(default=0)
|
hanchan_count = models.PositiveIntegerField(default=0)
|
||||||
legacy_date = models.DateField(blank=True, null=True)
|
legacy_date = models.DateField(blank=True, null=True)
|
||||||
legacy_hanchan_count = models.PositiveIntegerField(default=0)
|
legacy_dan = models.PositiveSmallIntegerField(blank=True, null=True)
|
||||||
legacy_dan_points = models.PositiveIntegerField(default=0)
|
legacy_dan_points = models.PositiveIntegerField(blank=True, null=True)
|
||||||
legacy_kyu_points = models.PositiveIntegerField(default=0)
|
legacy_kyu = models.PositiveSmallIntegerField(blank=True, null=True)
|
||||||
wins_in_a_row = 0
|
legacy_kyu_points = models.PositiveIntegerField(blank=True, null=True)
|
||||||
|
legacy_hanchan_count = models.PositiveIntegerField(blank=True, null=True)
|
||||||
|
legacy_good_hanchans = models.PositiveIntegerField(blank=True, null=True)
|
||||||
|
legacy_won_hanchans = models.PositiveIntegerField(blank=True, null=True)
|
||||||
|
wins_in_a_row = models.PositiveIntegerField(default=0)
|
||||||
|
last_hanchan_date = models.DateTimeField(blank=True, null=True)
|
||||||
objects = managers.KyuDanRankingManager()
|
objects = managers.KyuDanRankingManager()
|
||||||
|
|
||||||
|
|
||||||
class Meta(object):
|
class Meta(object):
|
||||||
ordering = ('-dan_points', 'dan', '-kyu_points')
|
ordering = ('-dan_points', 'dan', '-kyu_points')
|
||||||
verbose_name = _(u'Kyū/Dan Ranking')
|
verbose_name = _(u'Kyū/Dan Ranking')
|
||||||
verbose_name_plural = _(u'Kyū/Dan Rankings')
|
verbose_name_plural = _(u'Kyū/Dan Rankings')
|
||||||
|
|
||||||
def __unicode__(self):
|
@property
|
||||||
if self.dan_points is not None:
|
def rank(self):
|
||||||
|
if self.dan is not None:
|
||||||
|
return "{0:d}. Dan".format(self.dan)
|
||||||
|
else:
|
||||||
|
return "{0:d}. Kyū".format(self.kyu or 10)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def points(self):
|
||||||
|
return self.dan_points if self.dan is not None else self.kyu_points
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
if self.dan is not None:
|
||||||
return u"%s - %d. Dan" % (self.user.username, self.dan or 1)
|
return u"%s - %d. Dan" % (self.user.username, self.dan or 1)
|
||||||
else:
|
else:
|
||||||
return u"%s - %d. Kyu" % (self.user.username, self.kyu or 10)
|
return u"%s - %d. Kyū" % (self.user.username, self.kyu or 10)
|
||||||
|
|
||||||
def append_3_in_a_row_bonuspoints(self, hanchan):
|
def append_3_in_a_row_bonuspoints(self, hanchan):
|
||||||
u"""
|
u"""
|
||||||
@@ -366,12 +385,14 @@ class KyuDanRanking(models.Model):
|
|||||||
das er einen Dan Rang aufsteigt. Dies wird als Kommentar abgespeichert,
|
das er einen Dan Rang aufsteigt. Dies wird als Kommentar abgespeichert,
|
||||||
um es besser nachvollziehen zu können.
|
um es besser nachvollziehen zu können.
|
||||||
"""
|
"""
|
||||||
if self.dan and hanchan.placement == 1:
|
if not self.dan or not settings.DAN_3_WINS_IN_A_ROW:
|
||||||
|
return
|
||||||
|
if hanchan.placement == 1:
|
||||||
self.wins_in_a_row += 1
|
self.wins_in_a_row += 1
|
||||||
else:
|
else:
|
||||||
self.wins_in_a_row = 0
|
self.wins_in_a_row = 0
|
||||||
|
return
|
||||||
if self.dan and self.wins_in_a_row >= 3 and self.dan < 9:
|
if self.wins_in_a_row >= 3 and self.dan < 9:
|
||||||
LOGGER.info(
|
LOGGER.info(
|
||||||
'adding bonuspoints for 3 wins in a row for %s', self.user)
|
'adding bonuspoints for 3 wins in a row for %s', self.user)
|
||||||
new_dan_rank = self.dan + 1
|
new_dan_rank = self.dan + 1
|
||||||
@@ -392,8 +413,8 @@ class KyuDanRanking(models.Model):
|
|||||||
bonus_points, new_dan_rank)
|
bonus_points, new_dan_rank)
|
||||||
self.dan_points += bonus_points
|
self.dan_points += bonus_points
|
||||||
self.wins_in_a_row = 0
|
self.wins_in_a_row = 0
|
||||||
|
self.update_rank()
|
||||||
|
|
||||||
# TODO: Komplett Überabreiten!
|
|
||||||
def append_tournament_bonuspoints(self, hanchan):
|
def append_tournament_bonuspoints(self, hanchan):
|
||||||
"""
|
"""
|
||||||
Prüft ob es die letzte Hanchan in einem Turnier war. Wenn ja werden
|
Prüft ob es die letzte Hanchan in einem Turnier war. Wenn ja werden
|
||||||
@@ -406,29 +427,29 @@ class KyuDanRanking(models.Model):
|
|||||||
user=self.user, event=hanchan.event
|
user=self.user, event=hanchan.event
|
||||||
).order_by('-start')
|
).order_by('-start')
|
||||||
last_hanchan_this_event = hanchans_this_event[0]
|
last_hanchan_this_event = hanchans_this_event[0]
|
||||||
if hanchan != last_hanchan_this_event:
|
# Das braucht nur am Ende eines Turnieres gemacht werden.
|
||||||
# Das braucht nur am Ende eines Turnieres gemacht werden.
|
if hanchan != last_hanchan_this_event: return False
|
||||||
return False
|
event_ranking = EventRanking.objects.get(
|
||||||
else:
|
user=self.user,
|
||||||
event_ranking = EventRanking.objects.get(
|
event=hanchan.event
|
||||||
user=self.user,
|
)
|
||||||
event=hanchan.event
|
if event_ranking.placement == 1:
|
||||||
)
|
bonus_points += settings.TOURNAMENT_WIN_BONUSPOINTS
|
||||||
if event_ranking.placement == 1:
|
hanchan.player_comment += u'+{0:d} Punkte Turnier gewonnen. '.format(
|
||||||
bonus_points += 4
|
settings.TOURNAMENT_WIN_BONUSPOINTS)
|
||||||
hanchan.player_comment += u'+4 Punkte Turnier gewonnen. '
|
if event_ranking.avg_placement == 1:
|
||||||
if event_ranking.avg_placement == 1:
|
bonus_points += settings.TOURNAMENT_FLAWLESS_VICTORY_BONUSPOINTS
|
||||||
bonus_points += 8
|
hanchan.player_comment += u'+{0:d} Pkt: alle Spiele des Turnieres gewonnen. '.format(
|
||||||
hanchan.player_comment += u'+8 Pkt: alle Spiele des Turnieres gewonnen. '
|
settings.TOURNAMENT_FLAWLESS_VICTORY_BONUSPOINTS)
|
||||||
|
|
||||||
if bonus_points and self.dan:
|
if bonus_points and self.dan:
|
||||||
hanchan.dan_points += bonus_points
|
hanchan.dan_points += bonus_points
|
||||||
self.dan_points += bonus_points
|
self.dan_points += bonus_points
|
||||||
elif bonus_points:
|
elif bonus_points:
|
||||||
hanchan.kyu_points += bonus_points
|
hanchan.kyu_points += bonus_points
|
||||||
self.kyu_points += bonus_points
|
self.kyu_points += bonus_points
|
||||||
hanchan.bonus_points += bonus_points
|
hanchan.bonus_points += bonus_points
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def get_absolute_url(self):
|
def get_absolute_url(self):
|
||||||
if self.dan or self.dan_points > 0:
|
if self.dan or self.dan_points > 0:
|
||||||
@@ -436,62 +457,67 @@ class KyuDanRanking(models.Model):
|
|||||||
else:
|
else:
|
||||||
return reverse('player-kyu-score', args=[self.user.username])
|
return reverse('player-kyu-score', args=[self.user.username])
|
||||||
|
|
||||||
def recalculate(self, hanchan_start=None):
|
def calculate(self, since=None, until=None, force_recalc=False):
|
||||||
"""
|
"""
|
||||||
Fetches all valid Hanchans from this Player and recalculates his
|
Fetches all valid Hanchans from this Player and recalculates his
|
||||||
Kyu/Dan Ranking.
|
Kyu/Dan Ranking.
|
||||||
"""
|
"""
|
||||||
self.dan = None
|
valid_hanchans = Hanchan.objects.confirmed(user=self.user)
|
||||||
self.dan_points = self.legacy_dan_points or 0
|
valid_hanchans = valid_hanchans.order_by('start')
|
||||||
self.kyu = None
|
if since and self.last_hanchan_date and since < self.last_hanchan_date:
|
||||||
self.kyu_points = self.legacy_kyu_points or 0
|
force_recalc = True
|
||||||
self.hanchan_count = self.legacy_hanchan_count or 0
|
if until and self.last_hanchan_date and until < self.last_hanchan_date:
|
||||||
self.good_hanchans = 0
|
force_recalc = True
|
||||||
self.won_hanchans = 0
|
if force_recalc:
|
||||||
self.update_rank()
|
# Setze alles auf die legacy Werte und berechne alles von neuem.
|
||||||
|
self.dan = self.legacy_dan
|
||||||
|
self.dan_points = self.legacy_dan_points or 0
|
||||||
|
self.max_dan_points = self.dan_points
|
||||||
|
self.kyu = self.legacy_kyu
|
||||||
|
self.kyu_points = self.legacy_kyu_points or 0
|
||||||
|
self.hanchan_count = self.legacy_hanchan_count or 0
|
||||||
|
self.good_hanchans = self.legacy_good_hanchans or 0
|
||||||
|
self.won_hanchans = self.legacy_won_hanchans or 0
|
||||||
|
self.last_hanchan_date = None
|
||||||
|
self.update_rank()
|
||||||
|
since = timezone.make_aware(datetime.combine(
|
||||||
|
self.legacy_date,
|
||||||
|
time(23, 59, 59))) if self.legacy_date else None
|
||||||
|
elif self.last_hanchan_date:
|
||||||
|
since = self.last_hanchan_date
|
||||||
|
elif self.legacy_date:
|
||||||
|
since = timezone.make_aware(
|
||||||
|
datetime.combine(self.legacy_date, time(0, 0, 0))
|
||||||
|
)
|
||||||
LOGGER.info(
|
LOGGER.info(
|
||||||
"recalculating Kyu/Dan points for %s since %s...",
|
"recalculating Kyu/Dan points for %(user)s since %(since)s...",
|
||||||
self.user, str(hanchan_start)
|
{'user': self.user, 'since': str(since)}
|
||||||
)
|
)
|
||||||
valid_hanchans = Hanchan.objects.confirmed_hanchans(
|
if since:
|
||||||
user=self.user).order_by('start')
|
valid_hanchans = valid_hanchans.filter(start__gt=since)
|
||||||
if self.legacy_date:
|
if until:
|
||||||
valid_hanchans = valid_hanchans.filter(start__gt=self.legacy_date)
|
valid_hanchans = valid_hanchans.filter(start__lte=until)
|
||||||
|
|
||||||
""" TODO: Hanchan Punkte nur neu berechnen wenn sie nach hachan_start
|
|
||||||
lagen. Es müssen aber alle durch die Schleife rennen, damit die Punkte
|
|
||||||
richtig gezählt werden."""
|
|
||||||
self.hanchan_count += valid_hanchans.count()
|
|
||||||
for hanchan in valid_hanchans:
|
for hanchan in valid_hanchans:
|
||||||
|
self.hanchan_count += 1
|
||||||
hanchan.get_playerdata(self.user)
|
hanchan.get_playerdata(self.user)
|
||||||
if hanchan_start and hanchan_start < hanchan.start:
|
if since and hanchan.start < since:
|
||||||
|
LOGGER.debug(hanchan, "<", since, "no recalc")
|
||||||
self.dan_points += hanchan.dan_points or 0
|
self.dan_points += hanchan.dan_points or 0
|
||||||
self.kyu_points += hanchan.kyu_points or 0
|
self.kyu_points += hanchan.kyu_points or 0
|
||||||
self.update_rank()
|
self.update_rank()
|
||||||
else:
|
else:
|
||||||
hanchan.bonus_points = 0
|
hanchan.bonus_points = 0
|
||||||
hanchan.player_comment = u""
|
hanchan.player_comment = ""
|
||||||
self.update_hanchan_points(hanchan)
|
self.update_hanchan_points(hanchan)
|
||||||
if hanchan.event.mahjong_tournament:
|
if hanchan.event.mahjong_tournament:
|
||||||
self.append_tournament_bonuspoints(hanchan)
|
self.append_tournament_bonuspoints(hanchan)
|
||||||
self.update_rank()
|
self.update_rank()
|
||||||
self.append_3_in_a_row_bonuspoints(hanchan)
|
self.append_3_in_a_row_bonuspoints(hanchan)
|
||||||
self.update_rank()
|
|
||||||
hanchan.update_playerdata(self.user)
|
hanchan.update_playerdata(self.user)
|
||||||
hanchan.save(recalculate=False)
|
hanchan.save(recalculate=False)
|
||||||
self.won_hanchans += 1 if hanchan.placement == 1 else 0
|
self.won_hanchans += 1 if hanchan.placement == 1 else 0
|
||||||
self.good_hanchans += 1 if hanchan.placement == 2 else 0
|
self.good_hanchans += 1 if hanchan.placement == 2 else 0
|
||||||
LOGGER.debug(
|
self.last_hanchan_date = hanchan.start
|
||||||
'id: %(id)d, start: %(start)s, placement: %(placement)d, '
|
|
||||||
'score: %(score)d, kyu points: %(kyu_points)d, dan points: '
|
|
||||||
'%(dan_points)d, bonus points: %(bonus_points)d',
|
|
||||||
{'id': hanchan.pk, 'start': hanchan.start,
|
|
||||||
'placement': hanchan.placement, 'score': hanchan.game_score,
|
|
||||||
'kyu_points': hanchan.kyu_points or 0,
|
|
||||||
'dan_points': hanchan.dan_points or 0,
|
|
||||||
'bonus_points': hanchan.bonus_points or 0}
|
|
||||||
)
|
|
||||||
self.save(force_update=True)
|
self.save(force_update=True)
|
||||||
|
|
||||||
def update_hanchan_points(self, hanchan):
|
def update_hanchan_points(self, hanchan):
|
||||||
@@ -502,7 +528,7 @@ class KyuDanRanking(models.Model):
|
|||||||
"""
|
"""
|
||||||
hanchan.kyu_points = None
|
hanchan.kyu_points = None
|
||||||
hanchan.dan_points = None
|
hanchan.dan_points = None
|
||||||
if hanchan.event.mahjong_tournament:
|
if hanchan.event.mahjong_tournament and settings.TOURNAMENT_POINT_SYSTEM:
|
||||||
"""Für Turniere gelten andere Regeln zur Punktevergabe:
|
"""Für Turniere gelten andere Regeln zur Punktevergabe:
|
||||||
1. Platz 4 Punkte
|
1. Platz 4 Punkte
|
||||||
2. Platz 3 Punkte
|
2. Platz 3 Punkte
|
||||||
@@ -526,6 +552,7 @@ class KyuDanRanking(models.Model):
|
|||||||
hanchan.dan_points = -1
|
hanchan.dan_points = -1
|
||||||
elif hanchan.placement == 4:
|
elif hanchan.placement == 4:
|
||||||
hanchan.dan_points = -2
|
hanchan.dan_points = -2
|
||||||
|
# otherwise player must be in the kyu ranking
|
||||||
elif hanchan.game_score >= 60000:
|
elif hanchan.game_score >= 60000:
|
||||||
hanchan.kyu_points = 3
|
hanchan.kyu_points = 3
|
||||||
elif hanchan.game_score >= 30000:
|
elif hanchan.game_score >= 30000:
|
||||||
@@ -539,45 +566,44 @@ class KyuDanRanking(models.Model):
|
|||||||
if self.dan:
|
if self.dan:
|
||||||
# Only substract so much points that player has 0 Points:
|
# Only substract so much points that player has 0 Points:
|
||||||
if self.dan_points + hanchan.dan_points < 0:
|
if self.dan_points + hanchan.dan_points < 0:
|
||||||
|
hanchan.player_comment = 'Spieler unterschreitet 0 Punkte.' \
|
||||||
|
'(Original {} Punkte)'.format(hanchan.dan_points)
|
||||||
hanchan.dan_points -= (self.dan_points + hanchan.dan_points)
|
hanchan.dan_points -= (self.dan_points + hanchan.dan_points)
|
||||||
self.dan_points += hanchan.dan_points
|
self.dan_points += hanchan.dan_points
|
||||||
else:
|
else:
|
||||||
# Only substract so much points that player has 0 Points:
|
# Only substract so much points that player has 0 Points:
|
||||||
if self.kyu_points + hanchan.kyu_points < 0:
|
if self.kyu_points + hanchan.kyu_points < 0:
|
||||||
|
hanchan.player_comment = 'Spieler unterschreitet 0 Punkte.' \
|
||||||
|
'(Original {} Punkte)'.format(hanchan.kyu_points)
|
||||||
hanchan.kyu_points -= (self.kyu_points + hanchan.kyu_points)
|
hanchan.kyu_points -= (self.kyu_points + hanchan.kyu_points)
|
||||||
self.kyu_points += hanchan.kyu_points
|
self.kyu_points += hanchan.kyu_points
|
||||||
|
|
||||||
# TODO: Merkwürdige Methode die zwar funktioniert aber nicht sehr
|
|
||||||
# aussagekräfig ist. Überarbeiten?
|
|
||||||
def update_rank(self):
|
def update_rank(self):
|
||||||
if self.dan and self.dan_points < 0:
|
# Update Dan ranking:
|
||||||
self.dan_points = 0
|
if self.dan or self.dan_points > 0:
|
||||||
self.dan = 1
|
if settings.DAN_ALLOW_DROP_DOWN:
|
||||||
elif self.dan or self.dan_points > 0:
|
self.dan = max((dan for min_points, dan in settings.DAN_RANKS
|
||||||
old_dan = self.dan
|
if self.dan_points > min_points))
|
||||||
for min_points, dan_rank in settings.DAN_RANKS:
|
else:
|
||||||
if self.dan_points > min_points:
|
self.max_dan_points = max(self.max_dan_points, self.dan_points)
|
||||||
self.dan = dan_rank
|
self.dan = max((dan for min_points, dan in settings.DAN_RANKS
|
||||||
break
|
if self.max_dan_points > min_points))
|
||||||
if old_dan is None or self.dan > old_dan:
|
|
||||||
self.wins_in_a_row = 0
|
# jump from Kyu to Dan
|
||||||
elif self.kyu_points < 1:
|
|
||||||
self.kyu_points = 0
|
|
||||||
self.kyu = 10
|
|
||||||
elif self.kyu_points > 50:
|
elif self.kyu_points > 50:
|
||||||
self.dan = 1
|
self.dan = 1
|
||||||
self.kyu = None
|
|
||||||
self.dan_points = 0
|
self.dan_points = 0
|
||||||
|
self.kyu = None
|
||||||
self.kyu_points = 0
|
self.kyu_points = 0
|
||||||
|
self.wins_in_a_row = 0
|
||||||
|
# update Kyu ranking_
|
||||||
else:
|
else:
|
||||||
for min_points, kyu_rank in settings.KYU_RANKS:
|
self.kyu = min((kyu for min_points, kyu in settings.KYU_RANKS
|
||||||
if self.kyu_points > min_points:
|
if self.kyu_points > min_points))
|
||||||
self.kyu = kyu_rank
|
|
||||||
break
|
|
||||||
|
|
||||||
|
|
||||||
class SeasonRanking(models.Model):
|
class SeasonRanking(models.Model):
|
||||||
user = models.ForeignKey(settings.AUTH_USER_MODEL)
|
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.PROTECT)
|
||||||
season = models.PositiveSmallIntegerField(_('Season'))
|
season = models.PositiveSmallIntegerField(_('Season'))
|
||||||
placement = models.PositiveIntegerField(blank=True, null=True)
|
placement = models.PositiveIntegerField(blank=True, null=True)
|
||||||
avg_placement = models.FloatField(blank=True, null=True)
|
avg_placement = models.FloatField(blank=True, null=True)
|
||||||
@@ -593,9 +619,9 @@ class SeasonRanking(models.Model):
|
|||||||
def get_absolute_url(self):
|
def get_absolute_url(self):
|
||||||
return reverse('player-ladder-score', args=[self.user.username])
|
return reverse('player-ladder-score', args=[self.user.username])
|
||||||
|
|
||||||
def recalculate(self):
|
def recalculate(self, until=None):
|
||||||
season_hanchans = Hanchan.objects.season_hanchans(
|
season_hanchans = Hanchan.objects.season_hanchans(
|
||||||
user=self.user, season=self.season)
|
user=self.user, season=self.season, until=until)
|
||||||
sum_placement = 0
|
sum_placement = 0
|
||||||
sum_score = 0
|
sum_score = 0
|
||||||
self.placement = None
|
self.placement = None
|
||||||
|
|||||||
@@ -50,4 +50,12 @@
|
|||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
|
{% if kyu_dan_ranking.legacy_date %}
|
||||||
|
<p><strong>Frühere Dan Punkte vom {{ kyu_dan_ranking.legacy_date|date }}:</strong> {{kyu_dan_ranking.legacy_dan_points }}</p>
|
||||||
|
{% endif %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block buttonbar %}
|
||||||
|
<a href="?download=xlsx" class="button"><span class="fa fa-table"></span> Download</a>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
@@ -40,3 +40,7 @@
|
|||||||
{% endfor %}
|
{% endfor %}
|
||||||
</table>
|
</table>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block buttonbar %}
|
||||||
|
<a href="?download=xlsx" class="button"><span class="fa fa-table"></span> Download</a>
|
||||||
|
{% endblock %}
|
||||||
|
|||||||
@@ -48,3 +48,7 @@
|
|||||||
{% endfor %}
|
{% endfor %}
|
||||||
</table>
|
</table>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block buttonbar %}
|
||||||
|
<a href="?download=xlsx" class="button"><span class="fa fa-table"></span> Download</a>
|
||||||
|
{% endblock %}
|
||||||
|
|||||||
@@ -72,5 +72,8 @@
|
|||||||
</form>
|
</form>
|
||||||
</td></tr></tfoot>
|
</td></tr></tfoot>
|
||||||
</table>
|
</table>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block buttonbar %}
|
||||||
|
<a href="?download=xlsx" class="button"><span class="fa fa-table"></span> Download</a>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
@@ -34,7 +34,7 @@ class KyuDanTest(TestCase):
|
|||||||
|
|
||||||
for ranking in KyuDanRanking.objects.all():
|
for ranking in KyuDanRanking.objects.all():
|
||||||
original = {a: getattr(ranking, a) for a in self.equal_attrs}
|
original = {a: getattr(ranking, a) for a in self.equal_attrs}
|
||||||
ranking.recalculate()
|
ranking.calculate()
|
||||||
for attr in self.equal_attrs:
|
for attr in self.equal_attrs:
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
original[attr],
|
original[attr],
|
||||||
@@ -54,7 +54,7 @@ class KyuDanTest(TestCase):
|
|||||||
|
|
||||||
for ranking in KyuDanRanking.objects.all():
|
for ranking in KyuDanRanking.objects.all():
|
||||||
original = {a: getattr(ranking, a) for a in self.equal_attrs}
|
original = {a: getattr(ranking, a) for a in self.equal_attrs}
|
||||||
confirmed_hanchans = Hanchan.objects.confirmed_hanchans(
|
confirmed_hanchans = Hanchan.objects.confirmed(
|
||||||
user=ranking.user,
|
user=ranking.user,
|
||||||
since=ranking.legacy_date
|
since=ranking.legacy_date
|
||||||
)
|
)
|
||||||
@@ -62,7 +62,7 @@ class KyuDanTest(TestCase):
|
|||||||
continue
|
continue
|
||||||
rnd = random.randrange(confirmed_hanchans.count())
|
rnd = random.randrange(confirmed_hanchans.count())
|
||||||
since = confirmed_hanchans[rnd].start
|
since = confirmed_hanchans[rnd].start
|
||||||
ranking.recalculate(hanchan_start=since)
|
ranking.calculate(since=since)
|
||||||
for attr in self.equal_attrs:
|
for attr in self.equal_attrs:
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
original[attr],
|
original[attr],
|
||||||
@@ -86,7 +86,7 @@ class KyuDanTest(TestCase):
|
|||||||
'dan_points': ranking.legacy_dan_points or 0,
|
'dan_points': ranking.legacy_dan_points or 0,
|
||||||
'kyu_points': ranking.legacy_kyu_points or 0
|
'kyu_points': ranking.legacy_kyu_points or 0
|
||||||
}
|
}
|
||||||
confirmed_hanchans = Hanchan.objects.confirmed_hanchans(
|
confirmed_hanchans = Hanchan.objects.confirmed(
|
||||||
user=ranking.user,
|
user=ranking.user,
|
||||||
since=ranking.legacy_date
|
since=ranking.legacy_date
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -7,15 +7,16 @@ from django.contrib import auth
|
|||||||
from django.contrib.auth.mixins import LoginRequiredMixin, \
|
from django.contrib.auth.mixins import LoginRequiredMixin, \
|
||||||
PermissionRequiredMixin
|
PermissionRequiredMixin
|
||||||
from django.contrib.messages.views import SuccessMessageMixin
|
from django.contrib.messages.views import SuccessMessageMixin
|
||||||
from django.core.urlresolvers import reverse
|
from django.urls import reverse
|
||||||
from django.utils.translation import ugettext as _
|
from django.utils.translation import ugettext as _
|
||||||
from django.views import generic
|
from django.views import generic
|
||||||
|
|
||||||
from events.models import Event
|
|
||||||
from events.mixins import EventDetailMixin
|
from events.mixins import EventDetailMixin
|
||||||
|
from kasu import xlsx
|
||||||
from . import forms, models
|
from . import forms, models
|
||||||
|
from .mixins import MahjongMixin
|
||||||
|
|
||||||
kyu_dan_order = {
|
KYU_DAN_ORDER = { # map sort URL args to Django ORM order_by args
|
||||||
'+full_name': ('user__last_name', 'user__first_name'),
|
'+full_name': ('user__last_name', 'user__first_name'),
|
||||||
'-full_name': ('-user__last_name', '-user__first_name'),
|
'-full_name': ('-user__last_name', '-user__first_name'),
|
||||||
'+hanchan_count': ('hanchan_count',),
|
'+hanchan_count': ('hanchan_count',),
|
||||||
@@ -31,16 +32,17 @@ kyu_dan_order = {
|
|||||||
|
|
||||||
class DeleteHanchan(EventDetailMixin, PermissionRequiredMixin,
|
class DeleteHanchan(EventDetailMixin, PermissionRequiredMixin,
|
||||||
generic.DeleteView):
|
generic.DeleteView):
|
||||||
"""
|
"""Deletes a Hanchan if confimration has been answerd with 'yes'."""
|
||||||
Fragt zuerst nach, ob die Hanchan wirklich gelöscht werden soll.
|
|
||||||
Wir die Frage mit "Ja" beantwortet, wird die die Hanchan gelöscht.
|
|
||||||
"""
|
|
||||||
form_class = forms.HanchanForm
|
form_class = forms.HanchanForm
|
||||||
model = models.Hanchan
|
model = models.Hanchan
|
||||||
permission_required = 'mahjong_ranking.delete_hanchan'
|
permission_required = 'mahjong_ranking.delete_hanchan'
|
||||||
pk_url_kwarg = 'hanchan'
|
pk_url_kwarg = 'hanchan'
|
||||||
|
|
||||||
def get_success_url(self):
|
def get_success_url(self):
|
||||||
|
"""
|
||||||
|
Return to the HachanList of the event form the deleted hanchan.
|
||||||
|
:return: URL of the EventHanchanList for the event
|
||||||
|
"""
|
||||||
return reverse('event-hanchan-list',
|
return reverse('event-hanchan-list',
|
||||||
kwargs={'event': self.object.event.pk})
|
kwargs={'event': self.object.event.pk})
|
||||||
|
|
||||||
@@ -48,32 +50,30 @@ class DeleteHanchan(EventDetailMixin, PermissionRequiredMixin,
|
|||||||
class HanchanForm(SuccessMessageMixin, EventDetailMixin,
|
class HanchanForm(SuccessMessageMixin, EventDetailMixin,
|
||||||
PermissionRequiredMixin, generic.UpdateView):
|
PermissionRequiredMixin, generic.UpdateView):
|
||||||
"""
|
"""
|
||||||
Ein Formular um neue Hanchans anzulegen, bzw. eine bestehende zu
|
A Form to add a new or edit an existing Hanchan.
|
||||||
bearbeitsen
|
|
||||||
"""
|
"""
|
||||||
form_class = forms.HanchanForm
|
form_class = forms.HanchanForm
|
||||||
model = models.Hanchan
|
model = models.Hanchan
|
||||||
permission_required = 'mahjong_ranking.add_hanchan'
|
permission_required = 'mahjong_ranking.add_hanchan'
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
|
||||||
context = generic.UpdateView.get_context_data(self, **kwargs)
|
|
||||||
context['event'] = self.event
|
|
||||||
return context
|
|
||||||
|
|
||||||
def get_form_class(self):
|
def get_form_class(self):
|
||||||
"""
|
"""
|
||||||
Users with edit Persmission will see the AdminForm to confirm
|
Users with hanchan edit persmission can also un-/confirm hanchans.
|
||||||
unconfirmed Hanchans.
|
:return: forms.HanchanForm, or forms.HanchanAdminForm
|
||||||
"""
|
"""
|
||||||
if self.request.user.has_perm('mahjong_ranking.change_hanchan'):
|
return forms.HanchanAdminForm if self.request.user.has_perm(
|
||||||
return forms.HanchanAdminForm
|
'mahjong_ranking.change_hanchan') else forms.HanchanForm
|
||||||
else:
|
|
||||||
return forms.HanchanForm
|
|
||||||
|
|
||||||
def get_object(self, queryset=None):
|
def get_object(self, queryset=None):
|
||||||
|
"""
|
||||||
|
load the hanchan form the db, or create a new one with the event set.
|
||||||
|
Also sets the event attribute.
|
||||||
|
:param queryset:
|
||||||
|
:return: models.Hanchan object
|
||||||
|
"""
|
||||||
if self.kwargs.get('hanchan') and self.request.user.has_perm(
|
if self.kwargs.get('hanchan') and self.request.user.has_perm(
|
||||||
'mahjong_ranking.change_hanchan'):
|
'mahjong_ranking.change_hanchan'):
|
||||||
hanchan = models.Hanchan.objects.get(id=self.kwargs['hanchan'])
|
hanchan = self.model.objects.get(id=self.kwargs['hanchan'])
|
||||||
self.event = hanchan.event
|
self.event = hanchan.event
|
||||||
elif self.kwargs.get('event'):
|
elif self.kwargs.get('event'):
|
||||||
self.event = models.Event.objects.get(id=self.kwargs['event'])
|
self.event = models.Event.objects.get(id=self.kwargs['event'])
|
||||||
@@ -94,6 +94,11 @@ class HanchanForm(SuccessMessageMixin, EventDetailMixin,
|
|||||||
return reverse('add-hanchan-form', kwargs={'event': self.event.pk})
|
return reverse('add-hanchan-form', kwargs={'event': self.event.pk})
|
||||||
|
|
||||||
def get_success_message(self, cleaned_data):
|
def get_success_message(self, cleaned_data):
|
||||||
|
"""
|
||||||
|
Get the right sucsess message for the django notification subsystem.
|
||||||
|
:param cleaned_data:
|
||||||
|
:return: Sucsess message
|
||||||
|
"""
|
||||||
if self.kwargs.get('hanchan'):
|
if self.kwargs.get('hanchan'):
|
||||||
return _('%s has been updated successfully.') % self.object
|
return _('%s has been updated successfully.') % self.object
|
||||||
else:
|
else:
|
||||||
@@ -103,72 +108,32 @@ class HanchanForm(SuccessMessageMixin, EventDetailMixin,
|
|||||||
|
|
||||||
|
|
||||||
class EventHanchanList(EventDetailMixin, generic.ListView):
|
class EventHanchanList(EventDetailMixin, generic.ListView):
|
||||||
"""
|
"List all hanchans played on a given event."
|
||||||
Auflistung aller Hanchan die während der Veranstaltung gespielt wurden.
|
|
||||||
"""
|
|
||||||
model = models.Hanchan
|
model = models.Hanchan
|
||||||
template_name = 'mahjong_ranking/eventhanchan_list.html'
|
template_name = 'mahjong_ranking/eventhanchan_list.html'
|
||||||
|
|
||||||
def get_queryset(self):
|
|
||||||
try:
|
|
||||||
self.event = models.Event.objects.get(pk=self.kwargs['event'])
|
|
||||||
queryset = models.Hanchan.objects.filter(event=self.event)
|
|
||||||
queryset = queryset.order_by('start')
|
|
||||||
return queryset
|
|
||||||
except models.Event.DoesNotExist:
|
|
||||||
raise django.http.Http404(_('Event does not exist'))
|
|
||||||
|
|
||||||
|
|
||||||
class EventRankingList(EventDetailMixin, generic.ListView):
|
class EventRankingList(EventDetailMixin, generic.ListView):
|
||||||
"""
|
"""Display the event ranking for the given event."""
|
||||||
Anzeige des Eventrankings, daß erstellt wurde falls der Termin als internes
|
|
||||||
Turnier markiert wurde.
|
|
||||||
"""
|
|
||||||
model = models.EventRanking
|
model = models.EventRanking
|
||||||
|
|
||||||
def get_queryset(self):
|
|
||||||
try:
|
|
||||||
self.event = models.Event.objects.get(pk=self.kwargs['event'])
|
|
||||||
queryset = models.EventRanking.objects.filter(event=self.event)
|
|
||||||
return queryset.prefetch_related()
|
|
||||||
except models.Event.DoesNotExist:
|
|
||||||
raise django.http.Http404(_('Event does not exist'))
|
|
||||||
|
|
||||||
|
|
||||||
class MahjongMixin(object):
|
|
||||||
def get_context_data(self, **kwargs):
|
|
||||||
context = super(MahjongMixin, self).get_context_data(**kwargs)
|
|
||||||
try:
|
|
||||||
context['season'] = self.season
|
|
||||||
context['season_start'] = date(year=self.season, month=1, day=1)
|
|
||||||
context['season_end'] = date(year=self.season, month=12, day=31)
|
|
||||||
context['season_list'] = models.SeasonRanking.objects.season_list
|
|
||||||
except AttributeError:
|
|
||||||
pass
|
|
||||||
context[
|
|
||||||
'latest_hanchan_list'] = \
|
|
||||||
models.Hanchan.objects.confirmed_hanchans()[
|
|
||||||
:3]
|
|
||||||
context['latest_event_list'] = Event.objects.upcoming(limit=3)
|
|
||||||
return context
|
|
||||||
|
|
||||||
|
|
||||||
class KyuDanRankingList(MahjongMixin, generic.ListView):
|
class KyuDanRankingList(MahjongMixin, generic.ListView):
|
||||||
"""
|
"""List all Players with an Kyu or Dan score. """
|
||||||
Anzeige aller Spiele mit ihrem Kyu bzw Dan Grad.
|
|
||||||
"""
|
|
||||||
default_order = '-score'
|
default_order = '-score'
|
||||||
order_by = ''
|
order_by = ''
|
||||||
paginate_by = 25
|
paginate_by = 25
|
||||||
|
|
||||||
def dispatch(self, request, *args, **kwargs):
|
def dispatch(self, request, *args, **kwargs):
|
||||||
self.order_by = kyu_dan_order[
|
"""Set the order_by settings, revert to default_order if necessary."""
|
||||||
|
self.order_by = KYU_DAN_ORDER[
|
||||||
kwargs.get('order_by', self.default_order)
|
kwargs.get('order_by', self.default_order)
|
||||||
]
|
]
|
||||||
return generic.ListView.dispatch(self, request, *args, **kwargs)
|
return super(KyuDanRankingList, self).dispatch(request, *args, **kwargs)
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
queryset = models.KyuDanRanking.objects.all().order_by(*self.order_by)
|
queryset = models.KyuDanRanking.objects.filter(
|
||||||
|
hanchan_count__gt=0).order_by(*self.order_by)
|
||||||
return queryset.select_related()
|
return queryset.select_related()
|
||||||
|
|
||||||
|
|
||||||
@@ -192,7 +157,6 @@ class PlayerScore(LoginRequiredMixin, generic.ListView):
|
|||||||
|
|
||||||
def get(self, request, *args, **kwargs):
|
def get(self, request, *args, **kwargs):
|
||||||
user_model = auth.get_user_model()
|
user_model = auth.get_user_model()
|
||||||
username = kwargs.get('username')
|
|
||||||
try:
|
try:
|
||||||
self.user = user_model.objects.get(
|
self.user = user_model.objects.get(
|
||||||
username=self.kwargs.get('username'))
|
username=self.kwargs.get('username'))
|
||||||
@@ -200,6 +164,9 @@ class PlayerScore(LoginRequiredMixin, generic.ListView):
|
|||||||
raise django.http.Http404(
|
raise django.http.Http404(
|
||||||
_("No user found matching the name {}").format(
|
_("No user found matching the name {}").format(
|
||||||
self.kwargs.get('username')))
|
self.kwargs.get('username')))
|
||||||
|
print(request.GET)
|
||||||
|
if request.GET.get('download') == 'xlsx':
|
||||||
|
return self.get_xlsx(request, *args, **kwargs)
|
||||||
return super(PlayerScore, self).get(request, *args, **kwargs)
|
return super(PlayerScore, self).get(request, *args, **kwargs)
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
@@ -218,26 +185,124 @@ class PlayerScore(LoginRequiredMixin, generic.ListView):
|
|||||||
context['ladder_ranking'] = models.SeasonRanking(user=self.user)
|
context['ladder_ranking'] = models.SeasonRanking(user=self.user)
|
||||||
return context
|
return context
|
||||||
|
|
||||||
|
def get_xlsx(self, request, *args, **kwargs):
|
||||||
|
self.object_list = self.get_queryset()
|
||||||
|
response = django.http.HttpResponse(
|
||||||
|
content_type='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet')
|
||||||
|
response['Content-Disposition'] = 'attachment; ' \
|
||||||
|
'filename="{xlsx_filename}"'.format(
|
||||||
|
xlsx_filename=self.xlsx_filename)
|
||||||
|
xlxs_workbook = xlsx.Workbook()
|
||||||
|
xlxs_workbook.generate_sheet(
|
||||||
|
title=self.xlsx_filename.split('.')[0],
|
||||||
|
columns_settings=self.xlsx_columns,
|
||||||
|
object_list=self.object_list
|
||||||
|
)
|
||||||
|
xlxs_workbook.save(response)
|
||||||
|
return response
|
||||||
|
|
||||||
|
|
||||||
class PlayerDanScore(PlayerScore):
|
class PlayerDanScore(PlayerScore):
|
||||||
template_name = 'mahjong_ranking/player_dan_score.html'
|
template_name = 'mahjong_ranking/player_dan_score.html'
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
return models.Hanchan.objects.dan_hanchans(user=self.user)
|
self.kyu_dan_ranking = models.KyuDanRanking.objects.get(user=self.user)
|
||||||
|
return models.Hanchan.objects.dan_hanchans(
|
||||||
|
user=self.user,
|
||||||
|
since=self.kyu_dan_ranking.legacy_date)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def xlsx_columns(self):
|
||||||
|
return (
|
||||||
|
{'col': 'A', 'title': 'Beginn', 'attr': 'start',
|
||||||
|
'style': 'Date Time',
|
||||||
|
'width': 14, 'footer': self.kyu_dan_ranking.legacy_date},
|
||||||
|
{'col': 'B', 'title': 'Termin', 'attr': 'event.name',
|
||||||
|
'style': 'Content', 'width': 16},
|
||||||
|
{'col': 'C', 'title': 'Platzierung', 'attr': 'placement',
|
||||||
|
'style': 'Integer', 'width': 11},
|
||||||
|
{'col': 'D', 'title': 'Spieler 1', 'attr': 'player1.username',
|
||||||
|
'style': 'Content', 'width': 16},
|
||||||
|
{'col': 'E', 'title': 'Punkte', 'attr': 'player1_game_score',
|
||||||
|
'style': 'Integer', 'width': 8},
|
||||||
|
{'col': 'F', 'title': 'Spieler 2', 'attr': 'player2.username',
|
||||||
|
'style': 'Content', 'width': 16},
|
||||||
|
{'col': 'G', 'title': 'Punkte', 'attr': 'player2_game_score',
|
||||||
|
'style': 'Integer', 'width': 8},
|
||||||
|
{'col': 'H', 'title': 'Spieler 3', 'attr': 'player3.username',
|
||||||
|
'style': 'Content', 'width': 16},
|
||||||
|
{'col': 'I', 'title': 'Punkte', 'attr': 'player3_game_score',
|
||||||
|
'style': 'Integer', 'width': 8},
|
||||||
|
{'col': 'J', 'title': 'Spieler 4', 'attr': 'player4.username',
|
||||||
|
'style': 'Content', 'width': 16},
|
||||||
|
{'col': 'K', 'title': 'Punkte', 'attr': 'player4_game_score',
|
||||||
|
'style': 'Integer', 'width': 8},
|
||||||
|
{'col': 'L', 'title': 'Dan Punkte', 'attr': 'dan_points',
|
||||||
|
'style': 'Integer', 'width': 12,
|
||||||
|
'footer': self.kyu_dan_ranking.legacy_dan_points},
|
||||||
|
{'col': 'M', 'title': 'Anmerkung', 'attr': 'player_comment',
|
||||||
|
'style': 'Content', 'width': 20, 'footer': 'Legacy Dan Punkte'},
|
||||||
|
)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def xlsx_filename(self):
|
||||||
|
return "{username}_dan_score.xlsx".format(username=self.user.username)
|
||||||
|
|
||||||
|
|
||||||
class PlayerInvalidScore(PlayerScore):
|
class PlayerInvalidScore(PlayerScore):
|
||||||
template_name = 'mahjong_ranking/player_invalid_score.html'
|
template_name = 'mahjong_ranking/player_invalid_score.html'
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
return models.Hanchan.objects.unconfirmed_hanchans(user=self.user)
|
self.xlsx_filename = "{username}_invalid_score.xlsx".format(
|
||||||
|
username=self.user.username)
|
||||||
|
return models.Hanchan.objects.unconfirmed(user=self.user)
|
||||||
|
|
||||||
|
|
||||||
class PlayerKyuScore(PlayerScore):
|
class PlayerKyuScore(PlayerScore):
|
||||||
template_name = 'mahjong_ranking/player_kyu_score.html'
|
template_name = 'mahjong_ranking/player_kyu_score.html'
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
return models.Hanchan.objects.kyu_hanchans(self.user)
|
self.kyu_dan_ranking = models.KyuDanRanking.objects.get(user=self.user)
|
||||||
|
return models.Hanchan.objects.kyu_hanchans(
|
||||||
|
user=self.user,
|
||||||
|
since=self.kyu_dan_ranking.legacy_date)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def xlsx_columns(self):
|
||||||
|
return (
|
||||||
|
{'col': 'A', 'title': 'Beginn', 'attr': 'start',
|
||||||
|
'style': 'Date Time',
|
||||||
|
'width': 14, 'footer': self.kyu_dan_ranking.legacy_date},
|
||||||
|
{'col': 'B', 'title': 'Termin', 'attr': 'event.name',
|
||||||
|
'style': 'Content', 'width': 16},
|
||||||
|
{'col': 'C', 'title': 'Platzierung', 'attr': 'placement',
|
||||||
|
'style': 'Integer', 'width': 11},
|
||||||
|
{'col': 'D', 'title': 'Spieler 1', 'attr': 'player1.username',
|
||||||
|
'style': 'Content', 'width': 16},
|
||||||
|
{'col': 'E', 'title': 'Punkte', 'attr': 'player1_game_score',
|
||||||
|
'style': 'Integer', 'width': 8},
|
||||||
|
{'col': 'F', 'title': 'Spieler 2', 'attr': 'player2.username',
|
||||||
|
'style': 'Content', 'width': 16},
|
||||||
|
{'col': 'G', 'title': 'Punkte', 'attr': 'player2_game_score',
|
||||||
|
'style': 'Integer', 'width': 8},
|
||||||
|
{'col': 'H', 'title': 'Spieler 3', 'attr': 'player3.username',
|
||||||
|
'style': 'Content', 'width': 16},
|
||||||
|
{'col': 'I', 'title': 'Punkte', 'attr': 'player3_game_score',
|
||||||
|
'style': 'Integer', 'width': 8},
|
||||||
|
{'col': 'J', 'title': 'Spieler 4', 'attr': 'player4.username',
|
||||||
|
'style': 'Content', 'width': 16},
|
||||||
|
{'col': 'K', 'title': 'Punkte', 'attr': 'player4_game_score',
|
||||||
|
'style': 'Integer', 'width': 8},
|
||||||
|
{'col': 'L', 'title': 'Kyū Punkte', 'attr': 'kyu_points',
|
||||||
|
'style': 'Integer', 'width': 12,
|
||||||
|
'footer': self.kyu_dan_ranking.legacy_kyu_points},
|
||||||
|
{'col': 'M', 'title': 'Anmerkung', 'attr': 'comment',
|
||||||
|
'style': 'Content', 'width': 24, 'footer': 'Legacy Kyū Punkte'},
|
||||||
|
)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def xlsx_filename(self):
|
||||||
|
return "{username}_kyu_score.xlsx".format(username=self.user.username)
|
||||||
|
|
||||||
|
|
||||||
class PlayerLadderScore(PlayerScore):
|
class PlayerLadderScore(PlayerScore):
|
||||||
@@ -259,3 +324,39 @@ class PlayerLadderScore(PlayerScore):
|
|||||||
season=self.season
|
season=self.season
|
||||||
)
|
)
|
||||||
return hanchan_list
|
return hanchan_list
|
||||||
|
|
||||||
|
@property
|
||||||
|
def xlsx_columns(self):
|
||||||
|
return (
|
||||||
|
{'col': 'A', 'title': 'Beginn', 'attr': 'start',
|
||||||
|
'style': 'Date Time', 'width': 14},
|
||||||
|
{'col': 'B', 'title': 'Termin', 'attr': 'event.name',
|
||||||
|
'style': 'Content', 'width': 16},
|
||||||
|
{'col': 'C', 'title': 'Platzierung', 'attr': 'placement',
|
||||||
|
'style': 'Integer', 'width': 11},
|
||||||
|
{'col': 'D', 'title': 'Spieler 1', 'attr': 'player1.username',
|
||||||
|
'style': 'Content', 'width': 16},
|
||||||
|
{'col': 'E', 'title': 'Punkte', 'attr': 'player1_game_score',
|
||||||
|
'style': 'Integer', 'width': 8},
|
||||||
|
{'col': 'F', 'title': 'Spieler 2', 'attr': 'player2.username',
|
||||||
|
'style': 'Content', 'width': 16},
|
||||||
|
{'col': 'G', 'title': 'Punkte', 'attr': 'player2_game_score',
|
||||||
|
'style': 'Integer', 'width': 8},
|
||||||
|
{'col': 'H', 'title': 'Spieler 3', 'attr': 'player3.username',
|
||||||
|
'style': 'Content', 'width': 16},
|
||||||
|
{'col': 'I', 'title': 'Punkte', 'attr': 'player3_game_score',
|
||||||
|
'style': 'Integer', 'width': 8},
|
||||||
|
{'col': 'J', 'title': 'Spieler 4', 'attr': 'player4.username',
|
||||||
|
'style': 'Content', 'width': 16},
|
||||||
|
{'col': 'K', 'title': 'Punkte', 'attr': 'player4_game_score',
|
||||||
|
'style': 'Integer', 'width': 8},
|
||||||
|
{'col': 'L', 'title': 'Punkte', 'attr': 'game_score',
|
||||||
|
'style': 'Integer', 'width': 8},
|
||||||
|
)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def xlsx_filename(self):
|
||||||
|
return "{username}_ladder_score_{season}.xlsx".format(
|
||||||
|
username=self.user.username,
|
||||||
|
season=self.season
|
||||||
|
)
|
||||||
|
|||||||
@@ -37,19 +37,19 @@ class Migration(migrations.Migration):
|
|||||||
('season', models.PositiveSmallIntegerField(
|
('season', models.PositiveSmallIntegerField(
|
||||||
verbose_name='Saison', editable=False, db_index=True)),
|
verbose_name='Saison', editable=False, db_index=True)),
|
||||||
('event', models.ForeignKey(
|
('event', models.ForeignKey(
|
||||||
related_name='maistargame_set', to='events.Event')),
|
related_name='maistargame_set', to='events.Event', on_delete=models.CASCADE)),
|
||||||
('player1', models.ForeignKey(related_name='+',
|
('player1', models.ForeignKey(related_name='+',
|
||||||
verbose_name='Spieler 1', to=settings.AUTH_USER_MODEL)),
|
verbose_name='Spieler 1', to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE)),
|
||||||
('player2', models.ForeignKey(related_name='+',
|
('player2', models.ForeignKey(related_name='+',
|
||||||
verbose_name='Spieler 2', to=settings.AUTH_USER_MODEL)),
|
verbose_name='Spieler 2', to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE)),
|
||||||
('player3', models.ForeignKey(related_name='+',
|
('player3', models.ForeignKey(related_name='+',
|
||||||
verbose_name='Spieler 3', to=settings.AUTH_USER_MODEL)),
|
verbose_name='Spieler 3', to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE)),
|
||||||
('player4', models.ForeignKey(related_name='+',
|
('player4', models.ForeignKey(related_name='+',
|
||||||
verbose_name='Spieler 4', to=settings.AUTH_USER_MODEL)),
|
verbose_name='Spieler 4', to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE)),
|
||||||
('player5', models.ForeignKey(related_name='+',
|
('player5', models.ForeignKey(related_name='+',
|
||||||
verbose_name='Spieler 5', to=settings.AUTH_USER_MODEL)),
|
verbose_name='Spieler 5', to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE)),
|
||||||
('player6', models.ForeignKey(related_name='+',
|
('player6', models.ForeignKey(related_name='+',
|
||||||
verbose_name='Spieler 6', to=settings.AUTH_USER_MODEL)),
|
verbose_name='Spieler 6', to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE)),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
@@ -65,7 +65,7 @@ class Migration(migrations.Migration):
|
|||||||
('games_count', models.PositiveSmallIntegerField(default=0)),
|
('games_count', models.PositiveSmallIntegerField(default=0)),
|
||||||
('games_good', models.PositiveSmallIntegerField(default=0)),
|
('games_good', models.PositiveSmallIntegerField(default=0)),
|
||||||
('games_won', models.PositiveSmallIntegerField(default=0)),
|
('games_won', models.PositiveSmallIntegerField(default=0)),
|
||||||
('user', models.ForeignKey(to=settings.AUTH_USER_MODEL)),
|
('user', models.ForeignKey(to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE)),
|
||||||
],
|
],
|
||||||
options={
|
options={
|
||||||
'ordering': ('-season', 'placement', 'avg_placement', '-avg_score'),
|
'ordering': ('-season', 'placement', 'avg_placement', '-avg_score'),
|
||||||
|
|||||||
19
src/maistar_ranking/migrations/0006_auto_20171115_0653.py
Normal file
19
src/maistar_ranking/migrations/0006_auto_20171115_0653.py
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.11.7 on 2017-11-15 05:53
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('maistar_ranking', '0005_auto_20170218_1947'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name='game',
|
||||||
|
options={'ordering': ('-event__start', '-id')},
|
||||||
|
),
|
||||||
|
]
|
||||||
52
src/maistar_ranking/migrations/0007_auto_20171214_1215.py
Normal file
52
src/maistar_ranking/migrations/0007_auto_20171214_1215.py
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.11.8 on 2017-12-14 11:15
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('maistar_ranking', '0006_auto_20171115_0653'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='game',
|
||||||
|
name='player1',
|
||||||
|
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='+', to=settings.AUTH_USER_MODEL, verbose_name='Spieler 1'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='game',
|
||||||
|
name='player2',
|
||||||
|
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='+', to=settings.AUTH_USER_MODEL, verbose_name='Spieler 2'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='game',
|
||||||
|
name='player3',
|
||||||
|
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='+', to=settings.AUTH_USER_MODEL, verbose_name='Spieler 3'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='game',
|
||||||
|
name='player4',
|
||||||
|
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='+', to=settings.AUTH_USER_MODEL, verbose_name='Spieler 4'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='game',
|
||||||
|
name='player5',
|
||||||
|
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='+', to=settings.AUTH_USER_MODEL, verbose_name='Spieler 5'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='game',
|
||||||
|
name='player6',
|
||||||
|
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='+', to=settings.AUTH_USER_MODEL, verbose_name='Spieler 6'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='ranking',
|
||||||
|
name='user',
|
||||||
|
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -2,11 +2,11 @@
|
|||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from django.core.urlresolvers import reverse
|
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.db.models.signals import post_delete, post_save
|
from django.db.models.signals import post_delete, post_save
|
||||||
from django.utils.translation import ugettext as _
|
|
||||||
from django.dispatch import receiver
|
from django.dispatch import receiver
|
||||||
|
from django.urls import reverse
|
||||||
|
from django.utils.translation import ugettext as _
|
||||||
|
|
||||||
from events.models import Event
|
from events.models import Event
|
||||||
from . import settings, managers
|
from . import settings, managers
|
||||||
@@ -16,40 +16,47 @@ class Game(models.Model):
|
|||||||
"""to record a complete game with 6 different players."""
|
"""to record a complete game with 6 different players."""
|
||||||
|
|
||||||
_player_list = list()
|
_player_list = list()
|
||||||
event = models.ForeignKey(Event, related_name='maistargame_set')
|
event = models.ForeignKey(Event, on_delete=models.CASCADE,
|
||||||
|
related_name='maistargame_set')
|
||||||
comment = models.TextField(_('Comment'), blank=True)
|
comment = models.TextField(_('Comment'), blank=True)
|
||||||
player1 = models.ForeignKey(
|
player1 = models.ForeignKey(
|
||||||
settings.AUTH_USER_MODEL, verbose_name=_("Player 1"), related_name='+'
|
settings.AUTH_USER_MODEL, on_delete=models.PROTECT,
|
||||||
|
verbose_name=_("Player 1"), related_name='+'
|
||||||
)
|
)
|
||||||
player1_score = models.SmallIntegerField(_("Score"))
|
player1_score = models.SmallIntegerField(_("Score"))
|
||||||
player1_placement = models.PositiveSmallIntegerField(editable=False)
|
player1_placement = models.PositiveSmallIntegerField(editable=False)
|
||||||
|
|
||||||
player2 = models.ForeignKey(
|
player2 = models.ForeignKey(
|
||||||
settings.AUTH_USER_MODEL, verbose_name=_("Player 2"), related_name='+'
|
settings.AUTH_USER_MODEL, on_delete=models.PROTECT,
|
||||||
|
verbose_name=_("Player 2"), related_name='+'
|
||||||
)
|
)
|
||||||
player2_score = models.SmallIntegerField(_("Score"))
|
player2_score = models.SmallIntegerField(_("Score"))
|
||||||
player2_placement = models.PositiveSmallIntegerField(editable=False)
|
player2_placement = models.PositiveSmallIntegerField(editable=False)
|
||||||
|
|
||||||
player3 = models.ForeignKey(
|
player3 = models.ForeignKey(
|
||||||
settings.AUTH_USER_MODEL, verbose_name=_("Player 3"), related_name='+'
|
settings.AUTH_USER_MODEL, on_delete=models.PROTECT,
|
||||||
|
verbose_name=_("Player 3"), related_name='+'
|
||||||
)
|
)
|
||||||
player3_score = models.SmallIntegerField(_("Score"))
|
player3_score = models.SmallIntegerField(_("Score"))
|
||||||
player3_placement = models.PositiveSmallIntegerField(editable=False)
|
player3_placement = models.PositiveSmallIntegerField(editable=False)
|
||||||
|
|
||||||
player4 = models.ForeignKey(
|
player4 = models.ForeignKey(
|
||||||
settings.AUTH_USER_MODEL, verbose_name=_("Player 4"), related_name='+'
|
settings.AUTH_USER_MODEL, on_delete=models.PROTECT,
|
||||||
|
verbose_name=_("Player 4"), related_name='+'
|
||||||
)
|
)
|
||||||
player4_score = models.SmallIntegerField(_("Score"))
|
player4_score = models.SmallIntegerField(_("Score"))
|
||||||
player4_placement = models.PositiveSmallIntegerField(editable=False)
|
player4_placement = models.PositiveSmallIntegerField(editable=False)
|
||||||
|
|
||||||
player5 = models.ForeignKey(
|
player5 = models.ForeignKey(
|
||||||
settings.AUTH_USER_MODEL, verbose_name=_("Player 5"), related_name='+'
|
settings.AUTH_USER_MODEL, on_delete=models.PROTECT,
|
||||||
|
verbose_name=_("Player 5"), related_name='+'
|
||||||
)
|
)
|
||||||
player5_score = models.SmallIntegerField(_("Score"))
|
player5_score = models.SmallIntegerField(_("Score"))
|
||||||
player5_placement = models.PositiveSmallIntegerField(editable=False)
|
player5_placement = models.PositiveSmallIntegerField(editable=False)
|
||||||
|
|
||||||
player6 = models.ForeignKey(
|
player6 = models.ForeignKey(
|
||||||
settings.AUTH_USER_MODEL, verbose_name=_("Player 6"), related_name='+'
|
settings.AUTH_USER_MODEL, on_delete=models.PROTECT,
|
||||||
|
verbose_name=_("Player 6"), related_name='+'
|
||||||
)
|
)
|
||||||
player6_score = models.SmallIntegerField(_("Score"))
|
player6_score = models.SmallIntegerField(_("Score"))
|
||||||
player6_placement = models.PositiveSmallIntegerField(editable=False)
|
player6_placement = models.PositiveSmallIntegerField(editable=False)
|
||||||
@@ -69,7 +76,6 @@ class Game(models.Model):
|
|||||||
"""Display rankings by placement, best players first."""
|
"""Display rankings by placement, best players first."""
|
||||||
ordering = ('-event__start', '-id')
|
ordering = ('-event__start', '-id')
|
||||||
|
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return _("Mai-Star Game with {0} from {1:%Y-%m-%d}").format(
|
return _("Mai-Star Game with {0} from {1:%Y-%m-%d}").format(
|
||||||
self.player_names, self.event.start
|
self.player_names, self.event.start
|
||||||
@@ -143,7 +149,7 @@ class Game(models.Model):
|
|||||||
|
|
||||||
class Ranking(models.Model):
|
class Ranking(models.Model):
|
||||||
"""the player scores in the ladder for one season. """
|
"""the player scores in the ladder for one season. """
|
||||||
user = models.ForeignKey(settings.AUTH_USER_MODEL)
|
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.PROTECT)
|
||||||
season = models.PositiveSmallIntegerField(_("Season"))
|
season = models.PositiveSmallIntegerField(_("Season"))
|
||||||
placement = models.PositiveIntegerField(blank=True, null=True)
|
placement = models.PositiveIntegerField(blank=True, null=True)
|
||||||
avg_placement = models.PositiveSmallIntegerField(blank=True, null=True)
|
avg_placement = models.PositiveSmallIntegerField(blank=True, null=True)
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
from datetime import date
|
from datetime import date
|
||||||
|
|
||||||
from django.contrib import auth
|
from django.contrib import auth
|
||||||
from django.core.urlresolvers import reverse
|
from django.urls import reverse
|
||||||
from django.shortcuts import get_object_or_404
|
from django.shortcuts import get_object_or_404
|
||||||
from django.views import generic
|
from django.views import generic
|
||||||
|
|
||||||
|
|||||||
@@ -1,17 +1,17 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from django.db import models, migrations
|
|
||||||
import django.core.validators
|
|
||||||
import django.contrib.auth.models
|
import django.contrib.auth.models
|
||||||
from django.conf import settings
|
import django.core.validators
|
||||||
import django.utils.timezone
|
import django.utils.timezone
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import models, migrations
|
||||||
|
|
||||||
import membership.models
|
import membership.models
|
||||||
import utils
|
import utils
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
('auth', '0006_require_contenttypes_0002'),
|
('auth', '0006_require_contenttypes_0002'),
|
||||||
]
|
]
|
||||||
@@ -21,56 +21,94 @@ class Migration(migrations.Migration):
|
|||||||
name='Membership',
|
name='Membership',
|
||||||
fields=[
|
fields=[
|
||||||
('id', models.AutoField(verbose_name='ID',
|
('id', models.AutoField(verbose_name='ID',
|
||||||
serialize=False, auto_created=True, primary_key=True)),
|
serialize=False, auto_created=True,
|
||||||
|
primary_key=True)),
|
||||||
('password', models.CharField(
|
('password', models.CharField(
|
||||||
max_length=128, verbose_name='password')),
|
max_length=128, verbose_name='password')),
|
||||||
('last_login', models.DateTimeField(
|
('last_login', models.DateTimeField(
|
||||||
null=True, verbose_name='last login', blank=True)),
|
null=True, verbose_name='last login', blank=True)),
|
||||||
('is_superuser', models.BooleanField(default=False,
|
('is_superuser', models.BooleanField(default=False,
|
||||||
help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')),
|
help_text='Designates that this user has all permissions without explicitly assigning them.',
|
||||||
('username', models.CharField(error_messages={'unique': 'A user with that username already exists.'}, max_length=30, validators=[django.core.validators.RegexValidator(
|
verbose_name='superuser status')),
|
||||||
'^[\\w.@+-]+$', 'Enter a valid username. This value may contain only letters, numbers and @/./+/-/_ characters.', 'invalid')], help_text='Required. 30 characters or fewer. Letters, digits and @/./+/-/_ only.', unique=True, verbose_name='username')),
|
('username', models.CharField(error_messages={
|
||||||
|
'unique': 'A user with that username already exists.'},
|
||||||
|
max_length=30, validators=[
|
||||||
|
django.core.validators.RegexValidator(
|
||||||
|
'^[\\w.@+-]+$',
|
||||||
|
'Enter a valid username. This value may contain only letters, numbers and @/./+/-/_ characters.',
|
||||||
|
'invalid')],
|
||||||
|
help_text='Required. 30 characters or fewer. Letters, digits and @/./+/-/_ only.',
|
||||||
|
unique=True,
|
||||||
|
verbose_name='username')),
|
||||||
('first_name', models.CharField(max_length=30,
|
('first_name', models.CharField(max_length=30,
|
||||||
verbose_name='first name', blank=True)),
|
verbose_name='first name',
|
||||||
|
blank=True)),
|
||||||
('last_name', models.CharField(max_length=30,
|
('last_name', models.CharField(max_length=30,
|
||||||
verbose_name='last name', blank=True)),
|
verbose_name='last name',
|
||||||
|
blank=True)),
|
||||||
('email', models.EmailField(max_length=254,
|
('email', models.EmailField(max_length=254,
|
||||||
verbose_name='email address', blank=True)),
|
verbose_name='email address',
|
||||||
|
blank=True)),
|
||||||
('is_staff', models.BooleanField(default=False,
|
('is_staff', models.BooleanField(default=False,
|
||||||
help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')),
|
help_text='Designates whether the user can log into this admin site.',
|
||||||
|
verbose_name='staff status')),
|
||||||
('is_active', models.BooleanField(
|
('is_active', models.BooleanField(
|
||||||
default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')),
|
default=True,
|
||||||
|
help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.',
|
||||||
|
verbose_name='active')),
|
||||||
('date_joined', models.DateTimeField(
|
('date_joined', models.DateTimeField(
|
||||||
default=django.utils.timezone.now, verbose_name='date joined')),
|
default=django.utils.timezone.now,
|
||||||
('gender', models.CharField(max_length=1, verbose_name='Geschlecht', choices=[
|
verbose_name='date joined')),
|
||||||
(b'm', 'M\xe4nnlich'), (b'f', 'Weiblich')])),
|
('gender',
|
||||||
|
models.CharField(max_length=1, verbose_name='Geschlecht',
|
||||||
|
choices=[
|
||||||
|
(b'm', 'M\xe4nnlich'),
|
||||||
|
(b'f', 'Weiblich')])),
|
||||||
('website', models.URLField(blank=True)),
|
('website', models.URLField(blank=True)),
|
||||||
('avatar', models.ImageField(storage=utils.OverwriteStorage(
|
('avatar', models.ImageField(storage=utils.OverwriteStorage(
|
||||||
), null=True, upload_to=membership.models.get_upload_path, blank=True)),
|
), null=True, upload_to=membership.models.get_upload_path,
|
||||||
|
blank=True)),
|
||||||
('membership', models.BooleanField(default=False,
|
('membership', models.BooleanField(default=False,
|
||||||
help_text='Ja, ich bin mit den Statuen einverstanden und m\xf6chte Mitglied werden.', verbose_name='Mitgliedschaft')),
|
help_text='Ja, ich bin mit den Statuen einverstanden und m\xf6chte Mitglied werden.',
|
||||||
|
verbose_name='Mitgliedschaft')),
|
||||||
('birthday', models.DateField(null=True,
|
('birthday', models.DateField(null=True,
|
||||||
verbose_name='Geburtstag', blank=True)),
|
verbose_name='Geburtstag',
|
||||||
|
blank=True)),
|
||||||
('telephone', models.CharField(max_length=30,
|
('telephone', models.CharField(max_length=30,
|
||||||
null=True, verbose_name='Telefon', blank=True)),
|
null=True,
|
||||||
|
verbose_name='Telefon',
|
||||||
|
blank=True)),
|
||||||
('street_name', models.CharField(max_length=75,
|
('street_name', models.CharField(max_length=75,
|
||||||
null=True, verbose_name='Adresse', blank=True)),
|
null=True,
|
||||||
|
verbose_name='Adresse',
|
||||||
|
blank=True)),
|
||||||
('post_code', models.PositiveSmallIntegerField(
|
('post_code', models.PositiveSmallIntegerField(
|
||||||
null=True, verbose_name='Postleitzahl', blank=True)),
|
null=True, verbose_name='Postleitzahl', blank=True)),
|
||||||
('city', models.CharField(max_length=75,
|
('city', models.CharField(max_length=75,
|
||||||
null=True, verbose_name='Ort', blank=True)),
|
null=True, verbose_name='Ort',
|
||||||
|
blank=True)),
|
||||||
('deposit', models.PositiveSmallIntegerField(
|
('deposit', models.PositiveSmallIntegerField(
|
||||||
default=0, editable=False)),
|
default=0, editable=False)),
|
||||||
('registration_date', models.DateField(auto_now_add=True)),
|
('registration_date', models.DateField(auto_now_add=True)),
|
||||||
('paid_until', models.DateField(null=True,
|
('paid_until', models.DateField(null=True,
|
||||||
verbose_name='Bezahlt bis', blank=True)),
|
verbose_name='Bezahlt bis',
|
||||||
|
blank=True)),
|
||||||
('confirmed', models.BooleanField(default=False,
|
('confirmed', models.BooleanField(default=False,
|
||||||
help_text='Diese Person hat ihre Mitgliedschaft bezahlt', verbose_name='Best\xe4tigt')),
|
help_text='Diese Person hat ihre Mitgliedschaft bezahlt',
|
||||||
|
verbose_name='Best\xe4tigt')),
|
||||||
('comment', models.TextField(blank=True)),
|
('comment', models.TextField(blank=True)),
|
||||||
('groups', models.ManyToManyField(related_query_name='user', related_name='user_set', to='auth.Group', blank=True,
|
('groups', models.ManyToManyField(related_query_name='user',
|
||||||
help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', verbose_name='groups')),
|
related_name='user_set',
|
||||||
('user_permissions', models.ManyToManyField(related_query_name='user', related_name='user_set', to='auth.Permission',
|
to='auth.Group', blank=True,
|
||||||
blank=True, help_text='Specific permissions for this user.', verbose_name='user permissions')),
|
help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.',
|
||||||
|
verbose_name='groups')),
|
||||||
|
('user_permissions',
|
||||||
|
models.ManyToManyField(related_query_name='user',
|
||||||
|
related_name='user_set',
|
||||||
|
to='auth.Permission',
|
||||||
|
blank=True,
|
||||||
|
help_text='Specific permissions for this user.',
|
||||||
|
verbose_name='user permissions')),
|
||||||
],
|
],
|
||||||
options={
|
options={
|
||||||
'ordering': ('last_name', 'first_name'),
|
'ordering': ('last_name', 'first_name'),
|
||||||
@@ -86,11 +124,13 @@ class Migration(migrations.Migration):
|
|||||||
name='ActivationRequest',
|
name='ActivationRequest',
|
||||||
fields=[
|
fields=[
|
||||||
('id', models.AutoField(verbose_name='ID',
|
('id', models.AutoField(verbose_name='ID',
|
||||||
serialize=False, auto_created=True, primary_key=True)),
|
serialize=False, auto_created=True,
|
||||||
|
primary_key=True)),
|
||||||
('activation_key', models.CharField(
|
('activation_key', models.CharField(
|
||||||
max_length=40, verbose_name='Aktivierungsschl\xfcssel')),
|
max_length=40, verbose_name='Aktivierungsschl\xfcssel')),
|
||||||
('user', models.OneToOneField(
|
('user', models.OneToOneField(
|
||||||
verbose_name='Benutzer', to=settings.AUTH_USER_MODEL)),
|
verbose_name='Benutzer', to=settings.AUTH_USER_MODEL,
|
||||||
|
on_delete=models.CASCADE)),
|
||||||
],
|
],
|
||||||
options={
|
options={
|
||||||
'verbose_name': 'Ausstehende Aktivierung',
|
'verbose_name': 'Ausstehende Aktivierung',
|
||||||
|
|||||||
26
src/membership/migrations/0007_auto_20171115_0653.py
Normal file
26
src/membership/migrations/0007_auto_20171115_0653.py
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.11.7 on 2017-11-15 05:53
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import django.contrib.auth.validators
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('membership', '0006_auto_20160916_1759'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='membership',
|
||||||
|
name='gender',
|
||||||
|
field=models.CharField(blank=True, choices=[('m', 'Male'), ('f', 'Female')], max_length=1, null=True, verbose_name='Geschlecht'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='membership',
|
||||||
|
name='username',
|
||||||
|
field=models.CharField(error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=150, unique=True, validators=[django.contrib.auth.validators.UnicodeUsernameValidator()], verbose_name='username'),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -7,7 +7,7 @@ from os import path
|
|||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib.auth.models import AbstractUser
|
from django.contrib.auth.models import AbstractUser
|
||||||
from django.core.urlresolvers import reverse
|
from django.urls import reverse
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from django.utils.translation import ugettext as _
|
from django.utils.translation import ugettext as _
|
||||||
@@ -80,6 +80,7 @@ class ActivationRequest(models.Model):
|
|||||||
"""
|
"""
|
||||||
user = models.OneToOneField(
|
user = models.OneToOneField(
|
||||||
settings.AUTH_USER_MODEL,
|
settings.AUTH_USER_MODEL,
|
||||||
|
on_delete=models.CASCADE,
|
||||||
verbose_name=_('user')
|
verbose_name=_('user')
|
||||||
)
|
)
|
||||||
activation_key = models.CharField(_('activation key'), max_length=40)
|
activation_key = models.CharField(_('activation key'), max_length=40)
|
||||||
@@ -215,6 +216,10 @@ class Membership(AbstractUser):
|
|||||||
verbose_name = _('Membership')
|
verbose_name = _('Membership')
|
||||||
verbose_name_plural = _('Memberships')
|
verbose_name_plural = _('Memberships')
|
||||||
|
|
||||||
|
@property
|
||||||
|
def full_name(self):
|
||||||
|
return " ".join([self.last_name, self.first_name])
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.username
|
return self.username
|
||||||
|
|
||||||
|
|||||||
@@ -35,7 +35,10 @@
|
|||||||
<h3>Mahjong</h3>
|
<h3>Mahjong</h3>
|
||||||
<ul>
|
<ul>
|
||||||
{% if kyu_dan_ranking.dan %}
|
{% if kyu_dan_ranking.dan %}
|
||||||
<li><strong>{{kyu_dan_ranking.dan}}. Dan: </strong> {{ kyu_dan_ranking.dan_points }} {% trans 'Points' %}</li>
|
<li>
|
||||||
|
<strong>{{kyu_dan_ranking.dan}}. Dan: </strong> {{ kyu_dan_ranking.dan_points }} {% trans 'Points' %}
|
||||||
|
({% trans 'Maximum' %}: {{ kyu_dan_ranking.max_dan_points }})
|
||||||
|
</li>
|
||||||
{% elif kyu_dan_ranking.kyu%}
|
{% elif kyu_dan_ranking.kyu%}
|
||||||
<li><strong>{{kyu_dan_ranking.kyu}}. Kyu: </strong> {{ kyu_dan_ranking.kyu_points }} {% trans 'Points' %}</li>
|
<li><strong>{{kyu_dan_ranking.kyu}}. Kyu: </strong> {{ kyu_dan_ranking.kyu_points }} {% trans 'Points' %}</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ from django import http
|
|||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib import auth, messages
|
from django.contrib import auth, messages
|
||||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||||
from django.core.urlresolvers import reverse
|
from django.urls import reverse
|
||||||
from django.http import Http404
|
from django.http import Http404
|
||||||
from django.utils.decorators import method_decorator
|
from django.utils.decorators import method_decorator
|
||||||
from django.utils.translation import ugettext as _
|
from django.utils.translation import ugettext as _
|
||||||
|
|||||||
@@ -31,3 +31,15 @@ class CompressHtmlMiddleware(object):
|
|||||||
response.content = strip_spaces_between_tags(
|
response.content = strip_spaces_between_tags(
|
||||||
response.content).strip()
|
response.content).strip()
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
class SetRemoteAddrFromForwardedFor(object):
|
||||||
|
def process_request(self, request):
|
||||||
|
try:
|
||||||
|
real_ip = request.META['HTTP_X_FORWARDED_FOR']
|
||||||
|
real_ip = real_ip.split(",")[0]
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
# HTTP_X_FORWARDED_FOR can be a comma-separated list of IPs.
|
||||||
|
# Take just the first one.
|
||||||
|
request.META['REMOTE_ADDR'] = real_ip
|
||||||
|
|||||||
Reference in New Issue
Block a user