Merge branch 'master' into css3_redesign

# Conflicts:
#	requirements/base.txt
#	src/content/locale/de/LC_MESSAGES/django.mo
#	src/content/locale/de/LC_MESSAGES/django.po
#	src/events/locale/de/LC_MESSAGES/django.mo
#	src/events/locale/de/LC_MESSAGES/django.po
#	src/kasu/locale/de/LC_MESSAGES/django.po
#	src/mahjong_ranking/locale/de/LC_MESSAGES/django.mo
#	src/mahjong_ranking/locale/de/LC_MESSAGES/django.po
#	src/maistar_ranking/locale/de/LC_MESSAGES/django.po
#	src/membership/locale/de/LC_MESSAGES/django.mo
#	src/membership/locale/de/LC_MESSAGES/django.po
#	src/utils/locale/de/LC_MESSAGES/django.po
This commit is contained in:
2018-04-30 08:29:14 +02:00
60 changed files with 5966 additions and 1455 deletions

1
.gitignore vendored
View File

@@ -62,6 +62,7 @@ docs/_build/
target/
#Django Development
backup/
/bower_components/
/media/
/node_modules/

View File

@@ -21,9 +21,10 @@ module.exports = function(grunt) {
report: 'min'
},
kasu: {
src: 'static/css/kasu.css',
dest: 'static/css/kasu.css'
}
files: {
'src/kasu/static/css/kasu.min.css': ['src/kasu/static/css/kasu.css'],
},
},
},
watch: {
styles: {

5
TODO
View File

@@ -766,11 +766,6 @@ src/mahjong_ranking/models.py
| | [NORMAL] PyLintBear (W0201):
| | 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
| 330| class·KyuDanRanking(models.Model):
| | [INFO] PyLintBear (R0902):

View File

@@ -1,7 +1,7 @@
#!/bin/bash
SSH_LOGIN="kasu@s21.wservices.ch"
SYNC_ASSESTS="requirements"
SYNC_ASSESTS="requirements static"
SYNC_SOURCECODE="src"
EXCLUDE_FILES="*.pyc"
@@ -19,5 +19,5 @@ rsync -r --copy-links --delete ${SYNC_SOURCECODE} ${SSH_LOGIN}:~/ --exclude 'src
echo "Rebuild and reload django..."
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"

View File

@@ -8,7 +8,7 @@
"grunt": ">=0.4.5",
"grunt-contrib-less": ">=1.0.1",
"grunt-contrib-watch": ">=0.6.1",
"grunt-more-css": ">=0.1.0"
"grunt-more-css": "^0.1.1"
},
"dependencies": {
"ckeditor-dev": "git://github.com/ckeditor/ckeditor-dev.git"

View File

@@ -8,8 +8,8 @@ msgid ""
msgstr ""
"Project-Id-Version: kasu.content\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2018-04-27 09:49+0200\n"
"PO-Revision-Date: 2018-04-27 10:05+0105\n"
"POT-Creation-Date: 2018-01-11 22:50+0100\n"
"PO-Revision-Date: 2018-01-12 15:25+0105\n"
"Last-Translator: b'Christian Berg <kasu@xendynastie.at>'\n"
"Language-Team: Deutsch <>\n"
"Language: de\n"
@@ -20,250 +20,258 @@ msgstr ""
"X-Generator: Poedit 1.8.9\n"
"X-Translated-Using: django-rosetta 0.7.14\n"
#: feeds.py:18
#: src/content/feeds.py:18
msgid "Current news from Kasu"
msgstr "Aktuelle Nachrichten von Kasu"
#: feeds.py:51
#: src/content/feeds.py:51
msgid "Latest comments on kasu.at"
msgstr "Neueste Kommentare auf Kasu.at "
#: feeds.py:52
#: src/content/feeds.py:52
msgid "Kasu - latest comments"
msgstr "Kasu - neue Kommentare"
#: forms.py:57 models.py:315
#: src/content/forms.py:57 src/content/models.py:318
msgid "Please upload a PDF-File to this PDF-Page."
msgstr "Bitte eine PDF Datei für diese PDF Seite hochladen."
#: models.py:68
#: src/content/models.py:76
msgid "Headline"
msgstr "Schlagzeile"
#: models.py:70
#: src/content/models.py:78
msgid "Content"
msgstr "Inhalt"
#: models.py:72 models.py:143 templates/content/article_detail.html:25
#: src/content/models.py:82 src/content/models.py:150
#: src/content/templates/content/article_detail.html:25
msgid "Category"
msgstr "Kategorie"
#: models.py:73 models.py:136
#: src/content/models.py:83 src/content/models.py:143
msgid "Image"
msgstr "Bild"
#: models.py:75 models.py:138
#: src/content/models.py:85 src/content/models.py:145
msgid "Slug"
msgstr "Slug"
#: models.py:77 templates/content/article_detail.html:23
#: src/content/models.py:88
#: src/content/templates/content/article_detail.html:23
msgid "Author"
msgstr "Autor"
#: models.py:78
#: src/content/models.py:89
msgid "Status"
msgstr "Status"
#: models.py:80
#: src/content/models.py:91
msgid "Created"
msgstr "Erstellt"
#: models.py:81
#: src/content/models.py:92
msgid "Modified"
msgstr "Bearbeitet"
#: models.py:86
#: src/content/models.py:97
msgid "Article"
msgstr "Artikel"
#: models.py:87
#: src/content/models.py:98
msgid "Articles"
msgstr "Artikel"
#: models.py:132 models.py:133
#: src/content/models.py:139 src/content/models.py:140
msgid "Name"
msgstr "Name"
#: models.py:134 models.py:135
#: src/content/models.py:141 src/content/models.py:142
msgid "Description"
msgstr "Beschreibung"
#: models.py:144
#: src/content/models.py:151
msgid "Categories"
msgstr "Kategorien"
#: models.py:176 models.py:182
#: src/content/models.py:182 src/content/models.py:188
msgid "The short name for the menu-entry of this page"
msgstr "Ein kurzer Name für den Menüeintrag"
#: models.py:187 models.py:192
#: src/content/models.py:193 src/content/models.py:198
msgid "The page title as you'd like it to be seen by the public"
msgstr "Der Seitentitel der öffentlich gemacht wird."
msgstr "Der Seitentitel der öffentlich angezeigt werden soll"
#: models.py:194
#: src/content/models.py:200
msgid "slug"
msgstr "Slug"
#: models.py:197
#: src/content/models.py:203
msgid ""
"The name of the page as it will appear in URLs e.g "
"http://domain.com/blog/[my-slug]/"
msgstr ""
"Der Seitenname wie er in der URL erscheint. z.B: "
"http://domain.com/blog/[slug]/"
"Wie die Seite in der URL aufscheint also http://domain.com/blog/[slug]"
#: models.py:206
#: src/content/models.py:212
msgid "Path"
msgstr "Pfad"
#: models.py:218
#: src/content/models.py:224
msgid "Position"
msgstr "Position"
#: models.py:223
#: src/content/models.py:229
msgid "status"
msgstr "Status"
#: models.py:226 models.py:228
#: src/content/models.py:232 src/content/models.py:234
#| msgid "Description"
msgid "search description"
msgstr "Suchbeschreibung"
msgstr "Beschreibung für Suchfunktion"
#: models.py:231
#: src/content/models.py:237
#| msgid "Content"
msgid "content type"
msgstr "Inhaltstyp"
#: models.py:236
#: src/content/models.py:242
msgid "enable comments"
msgstr "Kommentare möglich"
#: models.py:241
#: src/content/models.py:247
msgid "Template"
msgstr "Vorlage"
#: models.py:249
#: src/content/models.py:255
#| msgid "created on"
msgid "first created at"
msgstr "erstellt am"
#: models.py:254
#: src/content/models.py:260
msgid "latest updated at"
msgstr "letzte Änderung"
msgstr "letzte Aktualisierung am"
#: models.py:328
#: src/content/models.py:331
msgid "Page"
msgstr "Seite"
#: models.py:329
#: src/content/models.py:332
msgid "Pages"
msgstr "Seiten"
#: templates/content/article_archive.html:5
#: templates/content/article_archive.html:20
#: src/content/templates/content/article_archive.html:5
#: src/content/templates/content/article_archive.html:20
msgid "Article Archive"
msgstr "Nachrichtenarchiv"
#: templates/content/article_archive.html:35
#: templates/content/article_archive_month.html:5
#: templates/content/article_archive_year.html:7
#: src/content/templates/content/article_archive.html:35
#: src/content/templates/content/article_archive_month.html:5
#: src/content/templates/content/article_archive_year.html:7
msgid "Archive"
msgstr "Archiv"
#: templates/content/article_archive.html:56
#: src/content/templates/content/article_archive.html:56
msgid "All Categories"
msgstr "Alle Kategorien"
#: templates/content/article_archive.html:71
#: src/content/templates/content/article_archive.html:71
msgid "created on"
msgstr "erstellt am"
#: templates/content/article_archive.html:73
#: src/content/templates/content/article_archive.html:73
msgid "by"
msgstr "von"
#: templates/content/article_archive.html:74
#: templates/content/article_archive.html:75
#: src/content/templates/content/article_archive.html:74
#: src/content/templates/content/article_archive.html:75
msgid "comments"
msgstr "Kommentare"
#: templates/content/article_archive.html:81
#: src/content/templates/content/article_archive.html:81
msgid "Read More"
msgstr "Mehr lesen"
#: templates/content/article_archive.html:86
#: src/content/templates/content/article_archive.html:86
msgid "We're sorry. Your search yielded no results."
msgstr "Es tut uns leid. Deine Suche ergab keine Treffer."
#: templates/content/article_archive.html:104
#: src/content/templates/content/article_archive.html:104
msgid "Add Article"
msgstr "neuer Artikel "
#: templates/content/article_archive_month.html:7
#: src/content/templates/content/article_archive_month.html:7
msgid "back"
msgstr "Zurück"
#: templates/content/article_detail.html:24
#: src/content/templates/content/article_detail.html:24
msgid "Created on"
msgstr "Erstellt am"
#: templates/content/article_detail.html:36
#: src/content/templates/content/article_detail.html:36
msgid "share on"
msgstr "Teile auf"
#: templates/content/article_detail.html:51 views.py:156
#: src/content/templates/content/article_detail.html:51
#: src/content/views.py:156
msgid "Edit Article"
msgstr "Artikel bearbeiten"
#: templates/content/article_form.html:32 templates/content/page_form.html:42
#: templates/content/page_form.html:49
#: src/content/templates/content/article_form.html:32
#: src/content/templates/content/page_form.html:42
#: src/content/templates/content/page_form.html:49
msgid "German"
msgstr "Deutsch"
#: templates/content/article_form.html:33 templates/content/page_form.html:43
#: templates/content/page_form.html:57
#: src/content/templates/content/article_form.html:33
#: src/content/templates/content/page_form.html:43
#: src/content/templates/content/page_form.html:57
msgid "English"
msgstr "Englisch"
#: templates/content/article_form.html:59 templates/content/page_form.html:66
#: src/content/templates/content/article_form.html:59
#: src/content/templates/content/page_form.html:66
msgid "reset"
msgstr "Zurücksetzen"
#: templates/content/article_form.html:60 templates/content/page_form.html:67
#: src/content/templates/content/article_form.html:60
#: src/content/templates/content/page_form.html:67
msgid "save"
msgstr "Speichern"
#: templates/content/page_form.html:5 templates/content/page_form.html:35
#: src/content/templates/content/page_form.html:5
#: src/content/templates/content/page_form.html:35
msgid "Edit Page"
msgstr "Seite bearbeiten"
#: templates/content/page_form.html:5 templates/content/page_form.html:19
#: templates/content/page_form.html:35
#: src/content/templates/content/page_form.html:5
#: src/content/templates/content/page_form.html:19
#: src/content/templates/content/page_form.html:35
msgid "Add Page"
msgstr "Seite hinzufügen"
#: templates/content/page_form.html:18
#: src/content/templates/content/page_form.html:18
msgid "Edit"
msgstr "Bearbeiten"
#: templates/content/page_form.html:37
#: src/content/templates/content/page_form.html:37
msgid "HTML Specific"
msgstr "HTML spezifisch"
#: views.py:53
#: src/content/views.py:53
msgid "This Category does not exist."
msgstr "Diese Kategorie existiert nicht."
#: views.py:157
#: src/content/views.py:157
msgid "Create Article"
msgstr "Artikel erstellen"
#: views.py:233
#: src/content/views.py:233
#, python-format
msgid "No Page found matching the Path %s"
msgstr "Keine Seite unter dem Pfad %s gefunden"
#: views.py:262
#: src/content/views.py:262
#, python-format
msgid "No PDF Document found matching the Path %s"
msgstr "Kein PDF Dokument unter dem Pfad %s gefunden."

View File

@@ -1,9 +1,9 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
import django.db.models.deletion
from django.conf import settings
from django.db import models, migrations
class Migration(migrations.Migration):
@@ -44,7 +44,8 @@ class Migration(migrations.Migration):
('date_modified', models.DateTimeField(
auto_now=True, verbose_name='Bearbeitet')),
('author', models.ForeignKey(
verbose_name='Autor', to=settings.AUTH_USER_MODEL)),
verbose_name='Autor', to=settings.AUTH_USER_MODEL,
on_delete=models.CASCADE))
],
options={
'ordering': ('-date_created',),
@@ -144,7 +145,8 @@ class Migration(migrations.Migration):
model_name='article',
name='category',
field=models.ForeignKey(
verbose_name='Kategorie', to='content.Category'),
verbose_name='Kategorie', to='content.Category',
on_delete=models.CASCADE),
),
migrations.AlterUniqueTogether(
name='page',

View 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'),
),
]

View File

@@ -3,9 +3,9 @@ from ckeditor_uploader.fields import RichTextUploadingField
from django.conf import settings
from django.core.cache import cache
from django.core.exceptions import ValidationError
from django.core.urlresolvers import reverse
from django.db import models
from django.template.defaultfilters import slugify
from django.urls import reverse
from django.utils import timezone
from django.utils.safestring import mark_safe
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)
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):
"""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)
content_de = RichTextUploadingField(_('Content'))
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/',
blank=True, null=True)
slug = models.SlugField(_('Slug'), unique_for_month='date_created')
author = models.ForeignKey(settings.AUTH_USER_MODEL,
on_delete=models.PROTECT,
verbose_name=_('Author'))
status = models.SmallIntegerField(_('Status'), choices=STATUS_CHOICES,
default=STATUS_PUBLISHED)
@@ -115,16 +126,12 @@ class Article(models.Model):
@property
def headline(self):
"""Return the localized headline, fallback to german if necessary."""
return mark_safe(
getattr(self, "headline_%s" % get_language(), self.headline_de)
)
return mark_safe(get_localized(self, 'headline'))
@property
def content(self):
"""Return the localized content, fallback to german if necessary."""
return mark_safe(
getattr(self, "content_%s" % get_language(), self.content_de)
)
return mark_safe(get_localized(self, 'content'))
class Category(models.Model):
@@ -146,13 +153,12 @@ class Category(models.Model):
@property
def name(self):
"""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
def description(self):
"""Return the localized description, fallback to german if necessary."""
return getattr(self, "description_%s" % get_language(),
self.description_de)
return get_localized(self, 'description')
def get_absolute_url(self):
"""Return the URL of the article archive, filtered on this category."""
@@ -261,9 +267,7 @@ class Page(models.Model):
@property
def content(self):
"""Return the localized content, fallback to german if necessary."""
return mark_safe(
getattr(self, "content_%s" % get_language(), self.content_de)
)
return mark_safe(get_localized(self, 'content'))
@property
def css_class(self):
@@ -275,23 +279,22 @@ class Page(models.Model):
@property
def description(self):
"""Return the localized description, fallback to german if necessary."""
return getattr(self, "description_%s" % get_language(),
self.description_de)
return get_localized(self, 'description')
@property
def menu_name(self):
"""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
def pdf_file(self):
"""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
def title(self):
"""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):
"""set the URL path, the right content type, and scrub the HTML code."""

View File

@@ -7,8 +7,8 @@ msgid ""
msgstr ""
"Project-Id-Version: kasu.events\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2018-04-27 09:49+0200\n"
"PO-Revision-Date: 2018-04-27 10:29+0105\n"
"POT-Creation-Date: 2018-01-11 22:50+0100\n"
"PO-Revision-Date: 2018-01-12 15:25+0105\n"
"Last-Translator: b'Christian Berg <kasu@xendynastie.at>'\n"
"Language-Team: Kasu <verein@kasu.at>\n"
"Language: de\n"
@@ -19,59 +19,58 @@ msgstr ""
"X-Generator: Poedit 1.8.9\n"
"X-Translated-Using: django-rosetta 0.7.14\n"
#: admin.py:14 models.py:82
#: src/events/admin.py:14 src/events/models.py:82
msgid "Event Series"
msgstr "Veranstaltungsreihen"
#: forms.py:17
#: src/events/forms.py:17
msgid "Images"
msgstr "Bilder"
#: forms.py:46
#: src/events/forms.py:46
msgid "start"
msgstr "Beginn"
#: forms.py:49
#: src/events/forms.py:49
msgid "end"
msgstr "Ende"
#: mixins.py:57 views.py:149
msgid "Event does not exist"
msgstr "Veranstaltung gibt es nicht"
#: models.py:52 models.py:176 models.py:217
#: src/events/models.py:52 src/events/models.py:176 src/events/models.py:217
msgid "Name"
msgstr "Name"
#: models.py:53 models.py:177 models.py:225
#: src/events/models.py:53 src/events/models.py:177 src/events/models.py:225
msgid "Description"
msgstr "Beschreibung"
#: models.py:55 templates/events/event_detail.html:29
#: templates/events/event_detail.html:87 templates/events/event_list.html:28
#: templates/events/photo_upload.html:13
#: src/events/models.py:55 src/events/templates/events/event_detail.html:29
#: src/events/templates/events/event_detail.html:87
#: src/events/templates/events/event_list.html:28
#: src/events/templates/events/photo_upload.html:13
msgid "Start"
msgstr "Beginn"
#: models.py:56 templates/events/event_detail.html:30
#: templates/events/event_detail.html:89
#: src/events/models.py:56 src/events/templates/events/event_detail.html:30
#: src/events/templates/events/event_detail.html:89
msgid "End"
msgstr "Ende"
#: models.py:57 models.py:185 templates/events/event_detail.html:34
#: templates/events/event_detail.html:80 templates/events/event_detail.html:92
#: src/events/models.py:57 src/events/models.py:185
#: src/events/templates/events/event_detail.html:34
#: src/events/templates/events/event_detail.html:80
#: src/events/templates/events/event_detail.html:92
msgid "Homepage"
msgstr "Homepage"
#: models.py:59 models.py:179 models.py:219
#: src/events/models.py:59 src/events/models.py:179 src/events/models.py:219
msgid "Image"
msgstr "Bild"
#: models.py:66
#: src/events/models.py:66
msgid "Mahjong Tournament"
msgstr "Mahjong Turnier"
#: models.py:68
#: src/events/models.py:68
msgid ""
"This event is a tournament, different rules apply for the kyu "
"ranking."
@@ -79,11 +78,11 @@ msgstr ""
"Diese Veranstaltung ist ein Turnier, es gelten andere Regeln für das Kyu "
"Ranking."
#: models.py:72
#: src/events/models.py:72
msgid "Mahjong Season"
msgstr "Mahjong Saison"
#: models.py:83
#: src/events/models.py:83
msgid ""
"Wenn dieser Event zu einer Veranstaltungsreihe gehört werden Ort, "
"Beschreibung, Bild und Homepage von dem hier angegebenen Event "
@@ -92,231 +91,248 @@ msgstr ""
"Wenn dieser Termin zu einer Veranstaltungsreihe gehört werden Ort, "
"Beschreibung, Bild und Homepage von dem hier angegebenen Event übernommen."
#: models.py:92 models.py:195 models.py:247
#: src/events/models.py:92 src/events/models.py:195 src/events/models.py:248
msgid "first created at"
msgstr "Erstellt am"
msgstr "erstellt am"
#: models.py:97 models.py:200 models.py:252
#: src/events/models.py:97 src/events/models.py:200 src/events/models.py:253
msgid "latest updated at"
msgstr "Geändert am"
msgstr "letzte Aktualisierung am"
#: models.py:103
#: src/events/models.py:103
msgid "Event"
msgstr "Termin"
#: models.py:104
#: src/events/models.py:104
msgid "Events"
msgstr "Termine"
#: models.py:117
#: src/events/models.py:117
msgid "A event can't end before it had started"
msgstr "Eine Veranstaltung kann nicht enden bevor sie begonnen hat"
#: models.py:186
#: src/events/models.py:186
msgid "Postal Code"
msgstr "Postleitzahl"
#: models.py:187
#: src/events/models.py:187
msgid "Street Address"
msgstr "Straße"
#: models.py:188
#: src/events/models.py:188
msgid "Locality"
msgstr "Ort"
#: models.py:189
#: src/events/models.py:189
msgid "Country"
msgstr "Land"
#: models.py:204
#: src/events/models.py:204
msgid "Venue"
msgstr "Veranstaltungsort"
#: models.py:205
#: src/events/models.py:205
msgid "Venues"
msgstr "Veranstaltungsorte"
#: models.py:231
#: src/events/models.py:232
msgid "Startpage"
msgstr "Startseite"
#: models.py:234
#: src/events/models.py:235
msgid "Display this Photo on the Startpage Teaser"
msgstr "Foto als Teaser auf der Startseite verwenden."
#: models.py:236
#: src/events/models.py:237
msgid "Published on"
msgstr "Veröffentlicht am"
#: models.py:238
#: src/events/models.py:239
msgid "Number of views"
msgstr "Wie oft gesehen"
#: models.py:262 templates/events/event_archive.html:38
#: templates/events/event_list.html:18
#: src/events/models.py:263 src/events/templates/events/event_archive.html:38
#: src/events/templates/events/event_list.html:18
msgid "Event Image"
msgstr "Veranstaltungsbild"
#: models.py:263
#: src/events/models.py:264
msgid "Event Images"
msgstr "Veranstaltungsbilder"
#: templates/events/event_archive.html:5 templates/events/event_archive.html:9
#: src/events/templates/events/event_archive.html:5
#: src/events/templates/events/event_archive.html:9
msgid "Event Archive"
msgstr "Veranstaltungsarchiv"
#: templates/events/event_archive.html:42
#: templates/events/event_detail.html:85 templates/events/event_list.html:22
#: templates/events/photo_detail.html:53
#: src/events/templates/events/event_archive.html:42
#: src/events/templates/events/event_detail.html:85
#: src/events/templates/events/event_list.html:22
#: src/events/templates/events/photo_detail.html:53
msgid "Date"
msgstr "Datum"
#: templates/events/event_archive.html:47
#: src/events/templates/events/event_archive.html:47
msgid "Time"
msgstr "Zeit"
#: templates/events/event_archive.html:49
#: templates/events/photo_upload.html:16
#: src/events/templates/events/event_archive.html:49
#: src/events/templates/events/photo_upload.html:16
msgid "from"
msgstr "von"
#: templates/events/event_archive.html:49
#: templates/events/photo_upload.html:16
#: src/events/templates/events/event_archive.html:49
#: src/events/templates/events/photo_upload.html:16
msgid "to"
msgstr "bis"
#: templates/events/event_archive.html:57
#: templates/events/event_detail.html:31 templates/events/event_detail.html:72
#: templates/events/event_list.html:32 templates/events/photo_upload.html:23
#: src/events/templates/events/event_archive.html:57
#: src/events/templates/events/event_detail.html:31
#: src/events/templates/events/event_detail.html:72
#: src/events/templates/events/event_list.html:32
#: src/events/templates/events/photo_upload.html:23
msgid "Location"
msgstr "Ort"
#: templates/events/event_archive.html:58 templates/events/event_list.html:35
#: templates/events/photo_upload.html:25 templates/events/photo_upload.html:26
#: src/events/templates/events/event_archive.html:58
#: src/events/templates/events/event_list.html:35
#: src/events/templates/events/photo_upload.html:25
#: src/events/templates/events/photo_upload.html:26
msgid "Comments"
msgstr "Kommentare"
#: templates/events/event_archive.html:59
#: templates/events/event_detail.html:36 templates/events/event_detail.html:48
#: templates/events/photo_list.html:4 templates/events/photo_upload.html:28
#: templates/events/photo_upload.html:29
#: src/events/templates/events/event_archive.html:59
#: src/events/templates/events/event_detail.html:36
#: src/events/templates/events/event_detail.html:48
#: src/events/templates/events/photo_list.html:4
#: src/events/templates/events/photo_upload.html:28
#: src/events/templates/events/photo_upload.html:29
msgid "Photos"
msgstr "Fotos"
#: templates/events/event_archive.html:60
#: templates/events/event_archive.html:61
#: templates/events/event_detail.html:35 templates/events/event_detail.html:51
#: src/events/templates/events/event_archive.html:60
#: src/events/templates/events/event_archive.html:61
#: src/events/templates/events/event_detail.html:35
#: src/events/templates/events/event_detail.html:51
msgid "Hanchans"
msgstr "Hanchans"
#: templates/events/event_detail.html:37
#: src/events/templates/events/event_detail.html:37
msgid "tourney"
msgstr "Turnier"
#: templates/events/event_detail.html:37
#: src/events/templates/events/event_detail.html:37
msgid "other rules apply here"
msgstr "hier gelten andere Regeln"
#: templates/events/event_detail.html:45
#: src/events/templates/events/event_detail.html:45
msgid "Info"
msgstr "Info"
#: templates/events/event_detail.html:54
#: src/events/templates/events/event_detail.html:54
msgid "Mai-Star Games"
msgstr "Mai-Star Spiele"
#: templates/events/event_detail.html:57
#: src/events/templates/events/event_detail.html:57
msgid "Event Ranking"
msgstr "Veranstaltungs Wertung"
#: templates/events/event_detail.html:100
#: src/events/templates/events/event_detail.html:100
msgid "Share on Facebook"
msgstr "Auf Facebook teilen"
#: templates/events/event_detail.html:104
#: src/events/templates/events/event_detail.html:104
msgid "Share on Google+"
msgstr "Auf Google+ teilen"
#: templates/events/event_detail.html:109
#: src/events/templates/events/event_detail.html:109
msgid "Share on Twitter"
msgstr "Auf Twitter teilen"
#: templates/events/event_detail.html:113
#: src/events/templates/events/event_detail.html:113
msgid "Show on Google Maps"
msgstr "Auf Google Maps zeigen"
#: templates/events/event_detail.html:127 templates/events/event_form.html:9
#: views.py:62
#: src/events/templates/events/event_detail.html:127
#: src/events/templates/events/event_form.html:9 src/events/views.py:62
msgid "Edit Event"
msgstr "Termin bearbeiten"
#: templates/events/event_detail.html:131
#: src/events/templates/events/event_detail.html:131
msgid "Add Dates"
msgstr "Termine hinzufügen"
#: templates/events/event_form.html:9 templates/events/page.html:9 views.py:64
#: src/events/templates/events/event_form.html:9
#: src/events/templates/events/page.html:9 src/events/views.py:64
msgid "Add Event"
msgstr "Neuer Termin"
#: templates/events/event_form.html:18 templates/events/photo_list.html:35
#: src/events/templates/events/event_form.html:18
#: src/events/templates/events/photo_list.html:35
msgid "reset"
msgstr "Zurücksetzen"
#: templates/events/event_form.html:19
#: templates/events/eventseries_form.html:25
#: src/events/templates/events/event_form.html:19
#: src/events/templates/events/eventseries_form.html:25
msgid "save"
msgstr "Speichern"
#: templates/events/event_list.html:4 templates/events/event_list.html:5
#: src/events/templates/events/event_list.html:4
#: src/events/templates/events/event_list.html:5
msgid "Upcoming Events"
msgstr "Bevorstehende Veranstaltungen"
#: templates/events/eventseries_form.html:24
#: src/events/templates/events/eventseries_form.html:24
msgid "back"
msgstr "Zurück"
#: templates/events/photo_confirm_delete.html:17
#: src/events/templates/events/photo_confirm_delete.html:17
msgid "Cancel"
msgstr "Abbrechen"
#: templates/events/photo_confirm_delete.html:21
#: templates/events/photo_list.html:21
#: src/events/templates/events/photo_confirm_delete.html:21
#: src/events/templates/events/photo_list.html:21
msgid "Delete"
msgstr "Löschen"
#: templates/events/photo_detail.html:44
#: src/events/templates/events/photo_detail.html:44
msgid "previous"
msgstr "Zurück"
#: templates/events/photo_detail.html:52
#: src/events/templates/events/photo_detail.html:52
msgid "Photographer"
msgstr "Fotograf"
#: templates/events/photo_detail.html:58
#: src/events/templates/events/photo_detail.html:58
msgid "share on"
msgstr "Teile auf"
#: templates/events/photo_detail.html:81
#: src/events/templates/events/photo_detail.html:81
msgid "download"
msgstr "Herunterladen"
#: templates/events/photo_detail.html:82
#: src/events/templates/events/photo_detail.html:82
msgid "Rotate counter clockwise"
msgstr "mit dem Uhrzeiger drehen"
#: templates/events/photo_detail.html:83
#: src/events/templates/events/photo_detail.html:83
msgid "Rotate clockwise"
msgstr "gegen den Uhrzeiger drehen"
#: templates/events/photo_detail.html:84
#: src/events/templates/events/photo_detail.html:84
msgid "Save"
msgstr "Speichern"
#: templates/events/photo_list.html:36 templates/events/photo_upload.html:35
#: templates/events/photo_upload.html:49
#: src/events/templates/events/photo_list.html:36
#: src/events/templates/events/photo_upload.html:35
#: src/events/templates/events/photo_upload.html:49
msgid "Upload"
msgstr "Hochladen"
#: src/events/views.py:149
msgid "Event does not exist"
msgstr "Veranstaltung gibt es nicht"
#~ msgid " Edit"
#~ msgstr "Bearbeiten"

View File

@@ -1,14 +1,14 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
import events.models
import django.db.models.deletion
from django.db import models, migrations
import events.models
import utils
class Migration(migrations.Migration):
dependencies = [
]
@@ -17,7 +17,8 @@ class Migration(migrations.Migration):
name='Event',
fields=[
('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')),
('description', models.TextField(
verbose_name='Beschreibung', blank=True)),
@@ -26,13 +27,20 @@ class Migration(migrations.Migration):
null=True, verbose_name='Ende', blank=True)),
('url', models.URLField(verbose_name='Homepage', blank=True)),
('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,
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(
default=0, editable=False)),
('event_series', 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')),
('event_series',
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={
'ordering': ('-start', '-end'),
@@ -44,20 +52,310 @@ class Migration(migrations.Migration):
name='Location',
fields=[
('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')),
('description', models.TextField(
verbose_name='Beschreibung', blank=True)),
('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)),
('postal_code', models.CharField(
max_length=6, verbose_name='Postleitzahl')),
('street_address', models.CharField(
max_length=127, verbose_name='Stra\xdfe')),
('locality', models.CharField(max_length=127, verbose_name='Ort')),
('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')])),
('locality',
models.CharField(max_length=127, verbose_name='Ort')),
('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={
'verbose_name': 'Veranstaltungsort',
@@ -67,6 +365,8 @@ class Migration(migrations.Migration):
migrations.AddField(
model_name='event',
name='location',
field=models.ForeignKey(to='events.Location'),
field=models.ForeignKey(
to='events.Location',
on_delete=models.CASCADE),
),
]

View File

@@ -1,17 +1,17 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
import ckeditor.fields
import events.models
import easy_thumbnails.fields
import django.db.models.deletion
import utils
import easy_thumbnails.fields
from django.conf import settings
from django.db import models, migrations
import events.models
import utils
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('events', '0003_auto_20150823_2232'),
@@ -22,18 +22,24 @@ class Migration(migrations.Migration):
name='Photo',
fields=[
('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,
verbose_name='Name', blank=True)),
('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,
verbose_name='Beschreibung', blank=True)),
verbose_name='Beschreibung',
blank=True)),
('on_startpage', models.BooleanField(default=False,
help_text='Display this Photo on the Startpage Teaser', verbose_name='Startpage')),
('created_date', models.DateTimeField(verbose_name='Published on')),
help_text='Display this Photo on the Startpage Teaser',
verbose_name='Startpage')),
('created_date',
models.DateTimeField(verbose_name='Published on')),
('views', models.PositiveIntegerField(default=0,
verbose_name='Number of views', editable=False)),
verbose_name='Number of views',
editable=False)),
],
options={
'ordering': ['created_date'],
@@ -46,7 +52,8 @@ class Migration(migrations.Migration):
migrations.AlterModelOptions(
name='event',
options={'ordering': (
'start', 'end'), 'verbose_name': 'Termin', 'verbose_name_plural': 'Termine'},
'start', 'end'), 'verbose_name': 'Termin',
'verbose_name_plural': 'Termine'},
),
migrations.AlterField(
model_name='event',
@@ -57,14 +64,19 @@ class Migration(migrations.Migration):
migrations.AlterField(
model_name='event',
name='event_series',
field=models.ForeignKey(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'),
field=models.ForeignKey(
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(
model_name='event',
name='image',
field=easy_thumbnails.fields.ThumbnailerImageField(storage=utils.OverwriteStorage(
), upload_to=events.models.get_upload_path, null=True, verbose_name='Bild', blank=True),
field=easy_thumbnails.fields.ThumbnailerImageField(
storage=utils.OverwriteStorage(
), upload_to=events.models.get_upload_path, null=True,
verbose_name='Bild', blank=True),
),
migrations.AlterField(
model_name='location',
@@ -75,17 +87,21 @@ class Migration(migrations.Migration):
migrations.AlterField(
model_name='location',
name='image',
field=easy_thumbnails.fields.ThumbnailerImageField(storage=utils.OverwriteStorage(
), upload_to=events.models.get_upload_path, null=True, verbose_name='Bild', blank=True),
field=easy_thumbnails.fields.ThumbnailerImageField(
storage=utils.OverwriteStorage(
), upload_to=events.models.get_upload_path, null=True,
verbose_name='Bild', blank=True),
),
migrations.AddField(
model_name='photo',
name='event',
field=models.ForeignKey(to='events.Event'),
field=models.ForeignKey(
to='events.Event', on_delete=models.CASCADE),
),
migrations.AddField(
model_name='photo',
name='photographer',
field=models.ForeignKey(to=settings.AUTH_USER_MODEL),
field=models.ForeignKey(
to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE),
),
]

View 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),
),
]

View File

@@ -1,6 +1,6 @@
"""Mixins for Events."""
from django.http import Http404
from django.shortcuts import get_object_or_404
from . import models
@@ -34,12 +34,14 @@ class EventDetailMixin(object):
:return: TemplateContext object"""
context = super(EventDetailMixin, self).get_context_data(**kwargs)
if hasattr(self, 'event') and self.event:
if hasattr(self, 'event'):
context['event'] = self.event
elif hasattr(self, 'object') and isinstance(self.object, models.Event):
context['event'] = self.object
elif hasattr(self, 'object') and hasattr(self.object, 'event'):
context['event'] = self.object.event
else:
print("No Event in Context!")
return context
def get_queryset(self):
@@ -49,10 +51,9 @@ class EventDetailMixin(object):
:return: a django QuerySets
"""
if self.model == models.Event:
return models.Event.objects.all()
try:
self.event = models.Event.objects.get(pk=self.kwargs['event'])
self.event = get_object_or_404(models.Event, pk=self.kwargs['pk'])
queryset = self.model.objects.all()
else:
self.event = get_object_or_404(models.Event, 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()

View File

@@ -4,10 +4,10 @@ import os
from ckeditor.fields import RichTextField
from django.conf import settings
from django.core.exceptions import ValidationError
from django.core.urlresolvers import reverse
from django.db import models
from django.db.models import Q
from django.template.defaultfilters import slugify
from django.urls import reverse
from django.utils.timezone import now
from django.utils.translation import ugettext as _
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."""
name = models.CharField(_('Name'), max_length=255)
description = RichTextField(_("Description"), blank=True)
location = models.ForeignKey('Location')
location = models.ForeignKey('Location', on_delete=models.PROTECT)
start = models.DateTimeField(_('Start'))
end = models.DateTimeField(_('End'), blank=True, null=True)
url = models.URLField(_('Homepage'), blank=True)
@@ -220,13 +220,14 @@ class Photo(models.Model):
upload_to=get_upload_path,
storage=OverwriteStorage()
)
event = models.ForeignKey('events.Event')
event = models.ForeignKey('events.Event', on_delete=models.PROTECT, )
description = models.TextField(
_("Description"),
max_length=300,
blank=True
)
photographer = models.ForeignKey(settings.AUTH_USER_MODEL)
photographer = models.ForeignKey(settings.AUTH_USER_MODEL,
on_delete=models.PROTECT)
on_startpage = models.BooleanField(
_("Startpage"),
default=False,

View File

@@ -41,5 +41,4 @@
{% if forloop.counter|divisibleby:2 %}<br class="clear">{% endif %}
{% endfor %}
{% endfor %}
{% if page_obj.has_other_pages %}{% include 'paginator.html' %}{% endif %}
{% endblock %}

View File

@@ -3,7 +3,7 @@ from datetime import timedelta
from django.contrib.auth import get_user_model
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.http import HttpResponse, Http404
from django.shortcuts import redirect

View File

@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: kasu.utils\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2018-04-27 09:49+0200\n"
"POT-Creation-Date: 2018-01-11 22:50+0100\n"
"PO-Revision-Date: 2016-09-28 00:24+0200\n"
"Last-Translator: Christian Berg <xeniac.at@gmail.com>\n"
"Language-Team: Kasu <verein@kasu.at>\n"
@@ -19,176 +19,175 @@ msgstr ""
"X-Generator: Poedit 1.8.9\n"
"X-Translated-Using: django-rosetta 0.7.6\n"
#: settings.py:157
#: src/kasu/settings.py:158
msgid "German"
msgstr "Deutsch"
#: settings.py:157
#: src/kasu/settings.py:158
msgid "English"
msgstr "Englisch"
#: templates/404.html:8
#: src/kasu/templates/404.html:8
msgid "The page your requested does not exist on this server."
msgstr "Die angeforderte Seite existiert auf diesem Server nicht."
#: templates/base.html:22
#: src/kasu/templates/base.html:22
msgid "Current News"
msgstr "Aktuelle Neuigkeiten"
#: templates/base.html:24 templates/index.html:40
#: src/kasu/templates/base.html:24 src/kasu/templates/index.html:40
msgid "Recent Comments"
msgstr "Kürzliche Kommentare"
#: templates/base.html:45
#: src/kasu/templates/base.html:45
msgid "Menu"
msgstr "Menü"
#: templates/base.html:69 templates/redbox.html:3
#: src/kasu/templates/base.html:69
msgid "Current Event"
msgstr "Aktuelle Veranstaltung"
#: templates/base.html:72 templates/redbox.html:6
#: src/kasu/templates/base.html:72
msgid "Since"
msgstr "seit"
#: templates/base.html:73 templates/base.html:85 templates/redbox.html:8
#: templates/redbox.html:22
#: src/kasu/templates/base.html:73 src/kasu/templates/base.html:85
msgid "Start"
msgstr "Beginn"
#: templates/base.html:76 templates/base.html:88 templates/redbox.html:11
#: templates/redbox.html:25
#: src/kasu/templates/base.html:76 src/kasu/templates/base.html:88
msgid "Location"
msgstr "Ort"
#: templates/base.html:79 templates/base.html:90 templates/redbox.html:14
#: templates/redbox.html:28
#: src/kasu/templates/base.html:79 src/kasu/templates/base.html:90
msgid "More Details"
msgstr "Mehr Details"
#: templates/base.html:81 templates/redbox.html:17
#: src/kasu/templates/base.html:81
msgid "Next Event"
msgstr "Nächste Veranstaltung"
#: templates/base.html:84 templates/redbox.html:20
#: src/kasu/templates/base.html:84
msgid "in"
msgstr "in"
#: templates/base.html:93 templates/redbox.html:30
#: src/kasu/templates/base.html:93
msgid "Upcoming events"
msgstr "Bevorstehende Veranstaltungen"
#: templates/base.html:143
#: src/kasu/templates/base.html:143
msgid "Add Subpage"
msgstr "Unterseite Hinzufügen"
#: templates/base.html:148
#: src/kasu/templates/base.html:148
msgid "Edit Page"
msgstr "Seite bearbeiten"
#: templates/base.html:156
#: src/kasu/templates/base.html:156
msgid "Imprint"
msgstr "Impressum"
#: templates/base.html:157
#: src/kasu/templates/base.html:157
msgid "contact"
msgstr "Kontakt"
#: templates/base.html:162
#: src/kasu/templates/base.html:162
msgid "Language"
msgstr "Sprache"
#: templates/base.html:171
#: src/kasu/templates/base.html:171
msgid "Go"
msgstr "Los"
#: templates/base.html:176
#: src/kasu/templates/base.html:176
msgid "Logged in as"
msgstr "Angemeldet als"
#: templates/base.html:178
#: src/kasu/templates/base.html:178
msgid "Admin"
msgstr "Admin"
#: templates/base.html:179
#: src/kasu/templates/base.html:179
msgid "Logout"
msgstr "Abmelden"
#: templates/base.html:181
#: src/kasu/templates/base.html:181
msgid "no user logged in"
msgstr "Niemand angemeldet"
#: templates/base.html:182 templates/comments/form.html:43
#: src/kasu/templates/base.html:182 src/kasu/templates/comments/form.html:43
msgid "register"
msgstr "Registrieren"
#: templates/base.html:183 templates/comments/form.html:44
#: src/kasu/templates/base.html:183 src/kasu/templates/comments/form.html:44
msgid "login"
msgstr "anmelden"
#: templates/base.html:185
#: src/kasu/templates/base.html:185
msgid "Login with Facebook"
msgstr "über Facebook anmelden"
#: templates/base.html:187
#: src/kasu/templates/base.html:187
msgid "Login with Twitter"
msgstr "über Twitter anmelden"
#: templates/base.html:189
#: src/kasu/templates/base.html:189
msgid "Login with Google"
msgstr "über Google anmelden"
#: templates/comments/form.html:5
#: src/kasu/templates/comments/form.html:5
msgid "New Comment"
msgstr "Neuer Kommentar"
#: templates/comments/form.html:20
#: src/kasu/templates/comments/form.html:20
msgid "now"
msgstr "Jetzt"
#: templates/comments/form.html:25
#: src/kasu/templates/comments/form.html:25
msgid "Preview"
msgstr "Vorschau"
#: templates/comments/form.html:26
#: src/kasu/templates/comments/form.html:26
msgid "Post"
msgstr "Schreiben"
#: templates/comments/form.html:34
#: src/kasu/templates/comments/form.html:34
msgid "not logged in"
msgstr "Nicht angemeldet"
#: templates/comments/form.html:38
#: src/kasu/templates/comments/form.html:38
msgid "Register now, or Login to leave a comment here."
msgstr "Jetzt registrieren, oder anmelden um einen Kommentar zu schreiben."
#: templates/comments/list.html:2 templates/index.html:25
#: src/kasu/templates/comments/list.html:2 src/kasu/templates/index.html:25
msgid "Comments"
msgstr "Kommentare"
#: templates/comments/posted.html:4 templates/comments/posted.html:7
#: src/kasu/templates/comments/posted.html:4
#: src/kasu/templates/comments/posted.html:7
msgid "Thank you for your comment"
msgstr "Danke für deinen Kommentar."
#: templates/comments/preview.html:4 templates/comments/preview.html:6
#: src/kasu/templates/comments/preview.html:4
#: src/kasu/templates/comments/preview.html:6
msgid "Preview your comment"
msgstr "Vorschau deines Kommentars"
#: templates/comments/preview.html:10
#: src/kasu/templates/comments/preview.html:10
msgid "Please correct the error below"
msgid_plural "Please correct the errors below"
msgstr[0] "Bitte den Fehler weiter unten beheben"
msgstr[1] "Bitte die Fehler weiter unten beheben"
#: templates/index.html:4
#: src/kasu/templates/index.html:4
msgid "traditional Asian game culture"
msgstr "traditionelle asiatische Spielkultur"
#: templates/index.html:33
#: src/kasu/templates/index.html:33
msgid "Read More"
msgstr "Mehr lesen"
#: templates/index.html:47
#: src/kasu/templates/index.html:47
#, python-format
msgid ""
"\n"
@@ -205,23 +204,23 @@ msgstr ""
" <time datetime=\"%(submit_date)s\">%(since)s</time>\n"
" "
#: templates/index.html:59
#: src/kasu/templates/index.html:59
msgid "Kasu in the social network"
msgstr "Kasu im sozialem Netzwerk"
#: templates/index.html:62 templates/index.html:65
#: src/kasu/templates/index.html:62 src/kasu/templates/index.html:65
msgid "Visit us on"
msgstr "Besuche uns auf"
#: templates/index.html:74
#: src/kasu/templates/index.html:74
msgid "Add Article"
msgstr "Artikel hinzufügen"
#: templates/paginator.html:8
#: src/kasu/templates/paginator.html:8
msgid "Previous"
msgstr "Vorherige"
#: templates/paginator.html:20
#: src/kasu/templates/paginator.html:20
msgid "Next"
msgstr "Nächste"

View File

@@ -80,6 +80,7 @@ MIDDLEWARE_CLASSES = [
'django.middleware.locale.LocaleMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
'utils.middleware.SetRemoteAddrFromForwardedFor',
'mahjong_ranking.middleware.DenormalizationUpdateMiddleware',
]
@@ -229,7 +230,7 @@ LOGGING = {
'loggers': {
'django': {
'handlers': ['console'],
'level': 'INFO',
'level': 'DEBUG',
'propagate': True,
},
'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 = (
(80, 9),
(70, 8),
@@ -255,7 +272,7 @@ DAN_RANKS = (
(30, 4),
(20, 3),
(10, 2),
(0, 1),
(-1, 1),
)
KYU_RANKS = (
@@ -268,13 +285,9 @@ KYU_RANKS = (
(15, 7),
(10, 8),
(5, 9),
(0, 10),
(-1, 10),
)
DAN_ALLOW_DROP_DOWN = True
MIN_HANCHANS_FOR_LADDER = 5
RANKING_EXPORT_PATH = path.join(PROJECT_PATH, 'backup', 'mahjong_ranking')
try:
from .local_settings import * # Ignore PyLintBear (W0401, W0614)
except ImportError:

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,231 @@
/**
* jQuery Formset 1.3-pre
* @author Stanislaus Madueke (stan DOT madueke AT gmail DOT com)
* @requires jQuery 1.2.6 or later
*
* Copyright (c) 2009, Stanislaus Madueke
* All rights reserved.
*
* Licensed under the New BSD License
* See: http://www.opensource.org/licenses/bsd-license.php
*/
;(function($) {
$.fn.formset = function(opts)
{
var options = $.extend({}, $.fn.formset.defaults, opts),
flatExtraClasses = options.extraClasses.join(' '),
totalForms = $('#id_' + options.prefix + '-TOTAL_FORMS'),
maxForms = $('#id_' + options.prefix + '-MAX_NUM_FORMS'),
minForms = $('#id_' + options.prefix + '-MIN_NUM_FORMS'),
childElementSelector = 'input,select,textarea,label,div',
$$ = $(this),
applyExtraClasses = function(row, ndx) {
if (options.extraClasses) {
row.removeClass(flatExtraClasses);
row.addClass(options.extraClasses[ndx % options.extraClasses.length]);
}
},
updateElementIndex = function(elem, prefix, ndx) {
var idRegex = new RegExp(prefix + '-(\\d+|__prefix__)-'),
replacement = prefix + '-' + ndx + '-';
if (elem.attr("for")) elem.attr("for", elem.attr("for").replace(idRegex, replacement));
if (elem.attr('id')) elem.attr('id', elem.attr('id').replace(idRegex, replacement));
if (elem.attr('name')) elem.attr('name', elem.attr('name').replace(idRegex, replacement));
},
hasChildElements = function(row) {
return row.find(childElementSelector).length > 0;
},
showAddButton = function() {
return maxForms.length == 0 || // For Django versions pre 1.2
(maxForms.val() == '' || (maxForms.val() - totalForms.val() > 0));
},
/**
* Indicates whether delete link(s) can be displayed - when total forms > min forms
*/
showDeleteLinks = function() {
return minForms.length == 0 || // For Django versions pre 1.7
(minForms.val() == '' || (totalForms.val() - minForms.val() > 0));
},
insertDeleteLink = function(row) {
var delCssSelector = $.trim(options.deleteCssClass).replace(/\s+/g, '.'),
addCssSelector = $.trim(options.addCssClass).replace(/\s+/g, '.');
if (row.is('TR')) {
// If the forms are laid out in table rows, insert
// the remove button into the last table cell:
row.children(':last').append('<a class="' + options.deleteCssClass +'" href="javascript:void(0)">' + options.deleteText + '</a>');
} else if (row.is('UL') || row.is('OL')) {
// If they're laid out as an ordered/unordered list,
// insert an <li> after the last list item:
row.append('<li><a class="' + options.deleteCssClass + '" href="javascript:void(0)">' + options.deleteText +'</a></li>');
} else {
// Otherwise, just insert the remove button as the
// last child element of the form's container:
row.append('<a class="' + options.deleteCssClass + '" href="javascript:void(0)">' + options.deleteText +'</a>');
}
// Check if we're under the minimum number of forms - not to display delete link at rendering
if (!showDeleteLinks()){
row.find('a.' + delCssSelector).hide();
}
row.find('a.' + delCssSelector).click(function() {
var row = $(this).parents('.' + options.formCssClass),
del = row.find('input:hidden[id $= "-DELETE"]'),
buttonRow = row.siblings("a." + addCssSelector + ', .' + options.formCssClass + '-add'),
forms;
if (del.length) {
// We're dealing with an inline formset.
// Rather than remove this form from the DOM, we'll mark it as deleted
// and hide it, then let Django handle the deleting:
del.val('on');
row.hide();
forms = $('.' + options.formCssClass).not(':hidden');
} else {
row.remove();
// Update the TOTAL_FORMS count:
forms = $('.' + options.formCssClass).not('.formset-custom-template');
totalForms.val(forms.length);
}
for (var i=0, formCount=forms.length; i<formCount; i++) {
// Apply `extraClasses` to form rows so they're nicely alternating:
applyExtraClasses(forms.eq(i), i);
if (!del.length) {
// Also update names and IDs for all child controls (if this isn't
// a delete-able inline formset) so they remain in sequence:
forms.eq(i).find(childElementSelector).each(function() {
updateElementIndex($(this), options.prefix, i);
});
}
}
// Check if we've reached the minimum number of forms - hide all delete link(s)
if (!showDeleteLinks()){
$('a.' + delCssSelector).each(function(){$(this).hide();});
}
// Check if we need to show the add button:
if (buttonRow.is(':hidden') && showAddButton()) buttonRow.show();
// If a post-delete callback was provided, call it with the deleted form:
if (options.removed) options.removed(row);
return false;
});
};
$$.each(function(i) {
var row = $(this),
del = row.find('input:checkbox[id $= "-DELETE"]');
if (del.length) {
// If you specify "can_delete = True" when creating an inline formset,
// Django adds a checkbox to each form in the formset.
// Replace the default checkbox with a hidden field:
if (del.is(':checked')) {
// If an inline formset containing deleted forms fails validation, make sure
// we keep the forms hidden (thanks for the bug report and suggested fix Mike)
del.before('<input type="hidden" name="' + del.attr('name') +'" id="' + del.attr('id') +'" value="on" />');
row.hide();
} else {
del.before('<input type="hidden" name="' + del.attr('name') +'" id="' + del.attr('id') +'" />');
}
// Hide any labels associated with the DELETE checkbox:
$('label[for="' + del.attr('id') + '"]').hide();
del.remove();
}
if (hasChildElements(row)) {
row.addClass(options.formCssClass);
if (row.is(':visible')) {
insertDeleteLink(row);
applyExtraClasses(row, i);
}
}
});
if ($$.length) {
var hideAddButton = !showAddButton(),
addButton, template;
if (options.formTemplate) {
// If a form template was specified, we'll clone it to generate new form instances:
template = (options.formTemplate instanceof $) ? options.formTemplate : $(options.formTemplate);
template.removeAttr('id').addClass(options.formCssClass + ' formset-custom-template');
template.find(childElementSelector).each(function() {
updateElementIndex($(this), options.prefix, '__prefix__');
});
insertDeleteLink(template);
} else {
// Otherwise, use the last form in the formset; this works much better if you've got
// extra (>= 1) forms (thnaks to justhamade for pointing this out):
template = $('.' + options.formCssClass + ':last').clone(true).removeAttr('id');
template.find('input:hidden[id $= "-DELETE"]').remove();
// Clear all cloned fields, except those the user wants to keep (thanks to brunogola for the suggestion):
template.find(childElementSelector).not(options.keepFieldValues).each(function() {
var elem = $(this);
// If this is a checkbox or radiobutton, uncheck it.
// This fixes Issue 1, reported by Wilson.Andrew.J:
if (elem.is('input:checkbox') || elem.is('input:radio')) {
elem.attr('checked', false);
} else {
elem.val('');
}
});
}
// FIXME: Perhaps using $.data would be a better idea?
options.formTemplate = template;
if ($$.is('TR')) {
// If forms are laid out as table rows, insert the
// "add" button in a new table row:
var numCols = $$.eq(0).children().length, // This is a bit of an assumption :|
buttonRow = $('<tr><td colspan="' + numCols + '"><a class="' + options.addCssClass + '" href="javascript:void(0)">' + options.addText + '</a></tr>')
.addClass(options.formCssClass + '-add');
$$.parent().append(buttonRow);
if (hideAddButton) buttonRow.hide();
addButton = buttonRow.find('a');
} else {
// Otherwise, insert it immediately after the last form:
$$.filter(':last').after('<a class="' + options.addCssClass + '" href="javascript:void(0)">' + options.addText + '</a>');
addButton = $$.filter(':last').next();
if (hideAddButton) addButton.hide();
}
addButton.click(function() {
var formCount = parseInt(totalForms.val()),
row = options.formTemplate.clone(true).removeClass('formset-custom-template'),
buttonRow = $($(this).parents('tr.' + options.formCssClass + '-add').get(0) || this),
delCssSelector = $.trim(options.deleteCssClass).replace(/\s+/g, '.');
applyExtraClasses(row, formCount);
row.insertBefore(buttonRow).show();
row.find(childElementSelector).each(function() {
updateElementIndex($(this), options.prefix, formCount);
});
totalForms.val(formCount + 1);
// Check if we're above the minimum allowed number of forms -> show all delete link(s)
if (showDeleteLinks()){
$('a.' + delCssSelector).each(function(){$(this).show();});
}
// Check if we've exceeded the maximum allowed number of forms:
if (!showAddButton()) buttonRow.hide();
// If a post-add callback was supplied, call it with the added form:
if (options.added) options.added(row);
return false;
});
}
return $$;
};
/* Setup plugin defaults */
$.fn.formset.defaults = {
prefix: 'form', // The form prefix for your django formset
formTemplate: null, // The jQuery selection cloned to generate new form instances
addText: 'add another', // Text for the add link
deleteText: 'remove', // Text for the delete link
addCssClass: 'add-row', // CSS class applied to the add link
deleteCssClass: 'delete-row', // CSS class applied to the delete link
formCssClass: 'dynamic-form', // CSS class applied to each form in a formset
extraClasses: [], // Additional CSS classes, which will be applied to each form in turn
keepFieldValues: '', // jQuery selector for fields whose values should be kept when the form is cloned
added: null, // Function called each time a new form is added
removed: null // Function called each time a form is deleted
};
})(jQuery);

View File

@@ -258,6 +258,7 @@ ul.tabs {
padding: 0;
}
.error, ul.errorlist li {color: #a40000;}
input.error {border-color:#a40000; background-color: rgba(164, 0, 0, 0.25);}
.game h2 {margin: 0.5em 0;}
.grid_1,.grid_2,.grid_3,.grid_4,.grid_5,.grid_6,.grid_7,.grid_8,.grid_9,.grid_10,.grid_11,.grid_12
{

View File

@@ -0,0 +1,4 @@
{% with type="date" %}
{% include "django/forms/widgets/html5input.html" %}
{% endwith %}

View File

@@ -0,0 +1,4 @@
{% with type="datetime-local" %}
{% include "django/forms/widgets/html5input.html" %}
{% endwith %}

View 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" %} />

View File

@@ -0,0 +1,4 @@
{% with type="time" %}
{% include "django/forms/widgets/html5input.html" %}
{% endwith %}

View File

@@ -32,7 +32,7 @@ urlpatterns = [ # Ignore PyLintBear (C0103)
url(r'^add_page/(?P<path>[\+\.\-\d\w\/]+)/$',
views.PageAddForm.as_view(), name='add-page'),
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'^comments/', include('django_comments.urls')),
url(r'^edit_page/(?P<path>[\+\.\-\d\w\/]+)/$',

147
src/kasu/xlsx.py Normal file
View 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)

View File

@@ -21,8 +21,6 @@ def recalculate(modeladmin, request, queryset): # Ignore PyLintBear (W0613)
for ladder_ranking in queryset:
set_dirty(user=ladder_ranking.user_id,
season=ladder_ranking.season)
recalculate.short_description = _("Recalculate")

View File

@@ -5,10 +5,11 @@ Created on 04.10.2011
@author: christian
"""
from django.contrib.auth import get_user_model
from django import forms
from django.contrib.auth import get_user_model
from django.utils.translation import ugettext as _
from events.models import Event
from . import models
USER_MODEL = get_user_model()
@@ -47,6 +48,17 @@ class HanchanForm(forms.ModelForm):
self.fields[player_input_score].widget.attrs['type'] = 'number'
self.fields[player].queryset = player_queryset
def is_valid(self):
ret = forms.Form.is_valid(self)
for field, errors in self.errors.items():
message = ", ".join(set(errors))
print(type(field), type(errors))
self.fields[field].widget.attrs.update({
'class': self.fields[field].widget.attrs.get('class', '') + ' error',
'title': message
})
return ret
class HanchanAdminForm(HanchanForm):
""" Extends the HanchanForm for users with admin privileges.
@@ -58,3 +70,9 @@ class HanchanAdminForm(HanchanForm):
""" Extend the formfields to add the confirmed checkbox. """
model = models.Hanchan
fields = HanchanForm.Meta.fields + ('confirmed',)
HanchanFormset = forms.inlineformset_factory(Event, models.Hanchan,
form=HanchanForm,
extra=1,
can_delete=True)

View File

@@ -7,8 +7,8 @@ msgid ""
msgstr ""
"Project-Id-Version: kasu.mahjong_ranking\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2018-04-27 09:49+0200\n"
"PO-Revision-Date: 2018-04-27 09:54+0105\n"
"POT-Creation-Date: 2018-01-11 22:50+0100\n"
"PO-Revision-Date: 2018-01-12 15:23+0105\n"
"Last-Translator: b'Christian Berg <kasu@xendynastie.at>'\n"
"Language-Team: Kasu <verein@kasu.at>\n"
"Language: de\n"
@@ -19,369 +19,382 @@ msgstr ""
"X-Generator: Poedit 1.8.9\n"
"X-Translated-Using: django-rosetta 0.7.14\n"
#: admin.py:26
#: src/mahjong_ranking/admin.py:24
msgid "Recalculate"
msgstr "Neuberechnen"
#: admin.py:36
#: src/mahjong_ranking/admin.py:34
msgid "Confirm"
msgstr "Bestätigen"
#: admin.py:46
#: src/mahjong_ranking/admin.py:44
msgid "Set unconfirmed"
msgstr "Als unbestätigt markieren"
#: forms.py:21
#: src/mahjong_ranking/forms.py:22
msgid "start"
msgstr "Beginn"
#: models.py:91 templates/mahjong_ranking/player_dan_score.html:14
#: templates/mahjong_ranking/player_invalid_score.html:13
#: templates/mahjong_ranking/player_kyu_score.html:15
#: templates/mahjong_ranking/player_ladder_score.html:15
#: templates/mahjong_ranking/seasonranking_list.html:10
#: src/mahjong_ranking/models.py:91
#: src/mahjong_ranking/templates/mahjong_ranking/player_dan_score.html:14
#: src/mahjong_ranking/templates/mahjong_ranking/player_invalid_score.html:13
#: src/mahjong_ranking/templates/mahjong_ranking/player_kyu_score.html:15
#: src/mahjong_ranking/templates/mahjong_ranking/player_ladder_score.html:15
#: src/mahjong_ranking/templates/mahjong_ranking/seasonranking_list.html:10
msgid "Start"
msgstr "Beginn"
#: models.py:92
#: src/mahjong_ranking/models.py:92
msgid "This is crucial to get the right Hanchans that scores"
msgstr "Wichtig damit die richtigen Hanchans in die Wertung kommen."
#: models.py:99
#: src/mahjong_ranking/models.py:99
msgid "Player 1"
msgstr "Spieler 1"
#: models.py:100 models.py:102 models.py:119 models.py:121 models.py:138
#: models.py:140 models.py:157 models.py:159
#: templates/mahjong_ranking/eventhanchan_list.html:19
#: templates/mahjong_ranking/eventranking_list.html:21
#: templates/mahjong_ranking/hanchan_confirm_delete.html:16
#: templates/mahjong_ranking/hanchan_form.html:19
#: templates/mahjong_ranking/kyudanranking_list.html:35
#: templates/mahjong_ranking/seasonranking_list.html:32
#: src/mahjong_ranking/models.py:100 src/mahjong_ranking/models.py:102
#: src/mahjong_ranking/models.py:119 src/mahjong_ranking/models.py:121
#: src/mahjong_ranking/models.py:138 src/mahjong_ranking/models.py:140
#: src/mahjong_ranking/models.py:157 src/mahjong_ranking/models.py:159
#: src/mahjong_ranking/templates/mahjong_ranking/eventhanchan_list.html:19
#: src/mahjong_ranking/templates/mahjong_ranking/eventranking_list.html:21
#: src/mahjong_ranking/templates/mahjong_ranking/hanchan_confirm_delete.html:16
#: src/mahjong_ranking/templates/mahjong_ranking/hanchan_form.html:19
#: src/mahjong_ranking/templates/mahjong_ranking/kyudanranking_list.html:35
#: src/mahjong_ranking/templates/mahjong_ranking/seasonranking_list.html:32
msgid "Score"
msgstr "Punkte"
#: models.py:112 models.py:131 models.py:150 models.py:169 models.py:171
#: templates/mahjong_ranking/hanchan_form.html:20
#: templates/mahjong_ranking/player_dan_score.html:18
#: templates/mahjong_ranking/player_invalid_score.html:17
#: src/mahjong_ranking/models.py:112 src/mahjong_ranking/models.py:131
#: src/mahjong_ranking/models.py:150 src/mahjong_ranking/models.py:169
#: src/mahjong_ranking/models.py:171
#: src/mahjong_ranking/templates/mahjong_ranking/hanchan_form.html:20
#: src/mahjong_ranking/templates/mahjong_ranking/player_dan_score.html:18
#: src/mahjong_ranking/templates/mahjong_ranking/player_invalid_score.html:17
msgid "Comment"
msgstr "Kommentar"
#: models.py:118
#: src/mahjong_ranking/models.py:118
msgid "Player 2"
msgstr "Spieler 2"
#: models.py:137
#: src/mahjong_ranking/models.py:137
msgid "Player 3"
msgstr "Spieler 3"
#: models.py:156
#: src/mahjong_ranking/models.py:156
msgid "Player 4"
msgstr "Spieler 4"
#: models.py:173
#: src/mahjong_ranking/models.py:173
msgid "Has been Confirmed"
msgstr "Wurde bestätigt"
#: models.py:174
#: src/mahjong_ranking/models.py:174
msgid "Only valid and confirmed Hanchans will be counted in the rating."
msgstr "Nur gültige und bestätigte Hanchans kommen in die Wertung."
#: models.py:179 models.py:603 templates/mahjong_ranking/ladder_redbox.html:29
#: templates/mahjong_ranking/player_ladder_score.html:63
#: src/mahjong_ranking/models.py:179 src/mahjong_ranking/models.py:607
#: src/mahjong_ranking/templates/mahjong_ranking/ladder_redbox.html:29
#: src/mahjong_ranking/templates/mahjong_ranking/player_ladder_score.html:63
msgid "Season"
msgstr "Saison"
#: models.py:184
#: src/mahjong_ranking/models.py:184
msgid "Hanchan"
msgstr "Hanchan"
#: models.py:185 templates/mahjong_ranking/eventranking_list.html:17
#: src/mahjong_ranking/models.py:185
#: src/mahjong_ranking/templates/mahjong_ranking/eventranking_list.html:17
msgid "Hanchans"
msgstr "Hanchans"
#: models.py:188
#: src/mahjong_ranking/models.py:188
msgid "Hanchan from {0:%Y-%m-%d} at {0:%H:%M} with {1}"
msgstr "Hanchan vom {0:%Y-%m-%d} um {0:%H:%M} mit {1}"
#: models.py:215
#: src/mahjong_ranking/models.py:215
#, python-format
msgid "%s can't attend the same game multiple times"
msgstr "%s kann an einem Spiel nicht mehrfach teilnehmen."
#: models.py:223
#: src/mahjong_ranking/models.py:223
msgid "Games in the future may not be added, Dr. Brown"
msgstr "Spiele aus der Zukunft dürfen noch nicht erfasst werden. Dr. Brown."
#: models.py:225
#: src/mahjong_ranking/models.py:225
msgid "Only games during the event are allowed"
msgstr "Nur Spiele während der Veranstaltung zählen."
#: models.py:228
#: src/mahjong_ranking/models.py:228
msgid "Gamescore is lower then 100.000 Pt."
msgstr "Spielstand ist weniger als 100.000 Punkte"
#: models.py:230
#: src/mahjong_ranking/models.py:230
msgid "Gamescore is over 100.000 Pt."
msgstr "Spielstand ist über 100.000 Punkte."
#: models.py:356
#: src/mahjong_ranking/models.py:362
msgid "Kyū/Dan Ranking"
msgstr "Kyū/Dan Wertung"
#: models.py:357
#: src/mahjong_ranking/models.py:363
msgid "Kyū/Dan Rankings"
msgstr "Kyū/Dan Wertungen"
#: templates/mahjong_ranking/eventhanchan_list.html:7
msgid "Played Hanchans"
msgstr "Gespielte Hanchans"
#: src/mahjong_ranking/templates/mahjong_ranking/eventhanchan_form.html:11
#: src/mahjong_ranking/templates/mahjong_ranking/eventhanchan_list.html:55
#| msgid "Edit Hanchan"
msgid "Edit Hanchans"
msgstr "Hanchans bearbeiten"
#: templates/mahjong_ranking/eventhanchan_list.html:18
#: templates/mahjong_ranking/hanchan_confirm_delete.html:15
msgid "Place"
msgstr "Platz"
#: templates/mahjong_ranking/eventhanchan_list.html:21
#: templates/mahjong_ranking/hanchan_confirm_delete.html:18
#: templates/mahjong_ranking/player_dan_score.html:17
msgid "Dan Points"
msgstr "Dan Punkte"
#: templates/mahjong_ranking/eventhanchan_list.html:23
#: templates/mahjong_ranking/hanchan_confirm_delete.html:20
#: templates/mahjong_ranking/player_invalid_score.html:16
#: templates/mahjong_ranking/player_kyu_score.html:18
msgid "Kyu Points"
msgstr "Kyu Punkte"
#: templates/mahjong_ranking/eventhanchan_list.html:37
#: templates/mahjong_ranking/hanchan_confirm_delete.html:4
#: templates/mahjong_ranking/hanchan_confirm_delete.html:33
#: templates/mahjong_ranking/player_dan_score.html:44
#: templates/mahjong_ranking/player_invalid_score.html:33
#: templates/mahjong_ranking/player_kyu_score.html:41
#: templates/mahjong_ranking/player_ladder_score.html:52
msgid "Delete Hanchan"
msgstr "Hanchan löschen"
#: templates/mahjong_ranking/eventhanchan_list.html:43
#: templates/mahjong_ranking/hanchan_form.html:4
#: templates/mahjong_ranking/hanchan_form.html:14
#: templates/mahjong_ranking/player_dan_score.html:47
#: templates/mahjong_ranking/player_invalid_score.html:36
#: templates/mahjong_ranking/player_kyu_score.html:44
#: templates/mahjong_ranking/player_ladder_score.html:55
msgid "Edit Hanchan"
msgstr "Hanchan bearbeiten"
#: templates/mahjong_ranking/eventhanchan_list.html:48
msgid "No Hanchan has been added to this event yet."
msgstr "Für diese Veranstaltung wurde noch keine Hanchan eingetragen."
#: templates/mahjong_ranking/eventhanchan_list.html:54
#: templates/mahjong_ranking/eventranking_list.html:51
msgid "Edit Event"
msgstr "Veranstaltung bearbeiten"
#: templates/mahjong_ranking/eventhanchan_list.html:55
#: templates/mahjong_ranking/eventranking_list.html:52
#: templates/mahjong_ranking/hanchan_form.html:4
#: templates/mahjong_ranking/hanchan_form.html:14
#: src/mahjong_ranking/templates/mahjong_ranking/eventhanchan_form.html:82
#: src/mahjong_ranking/templates/mahjong_ranking/eventhanchan_list.html:56
#: src/mahjong_ranking/templates/mahjong_ranking/eventranking_list.html:52
#: src/mahjong_ranking/templates/mahjong_ranking/hanchan_form.html:4
#: src/mahjong_ranking/templates/mahjong_ranking/hanchan_form.html:14
msgid "Add Hanchan"
msgstr "Hanchan hinzufügen"
#: templates/mahjong_ranking/eventranking_list.html:4
#: templates/mahjong_ranking/eventranking_list.html:5
msgid "Tournament Ranking"
msgstr "Turnierwertung"
#: src/mahjong_ranking/templates/mahjong_ranking/eventhanchan_form.html:84
#: src/mahjong_ranking/templates/mahjong_ranking/eventhanchan_list.html:37
#: src/mahjong_ranking/templates/mahjong_ranking/hanchan_confirm_delete.html:4
#: src/mahjong_ranking/templates/mahjong_ranking/hanchan_confirm_delete.html:33
#: src/mahjong_ranking/templates/mahjong_ranking/player_dan_score.html:44
#: src/mahjong_ranking/templates/mahjong_ranking/player_invalid_score.html:33
#: src/mahjong_ranking/templates/mahjong_ranking/player_kyu_score.html:41
#: src/mahjong_ranking/templates/mahjong_ranking/player_ladder_score.html:52
msgid "Delete Hanchan"
msgstr "Hanchan löschen"
#: templates/mahjong_ranking/eventranking_list.html:12
#: templates/mahjong_ranking/kyudanranking_list.html:30
#: templates/mahjong_ranking/seasonranking_list.html:23
msgid "Rank"
msgstr "Rang"
#: templates/mahjong_ranking/eventranking_list.html:13
#: templates/mahjong_ranking/kyudanranking_list.html:17
#: templates/mahjong_ranking/seasonranking_list.html:24
msgid "Avatar"
msgstr "Avatar"
#: templates/mahjong_ranking/eventranking_list.html:14
#: templates/mahjong_ranking/kyudanranking_list.html:20
#: templates/mahjong_ranking/seasonranking_list.html:25
msgid "Nickname"
msgstr "Spitzname"
#: templates/mahjong_ranking/eventranking_list.html:15
#: templates/mahjong_ranking/seasonranking_list.html:26
msgid "Name"
msgstr "Name"
#: templates/mahjong_ranking/eventranking_list.html:16
#: templates/mahjong_ranking/seasonranking_list.html:27
msgid "Average"
msgstr "Durchschnitt"
#: templates/mahjong_ranking/eventranking_list.html:20
#: templates/mahjong_ranking/player_dan_score.html:15
#: templates/mahjong_ranking/player_invalid_score.html:15
#: templates/mahjong_ranking/player_kyu_score.html:16
#: templates/mahjong_ranking/player_ladder_score.html:16
#: templates/mahjong_ranking/seasonranking_list.html:31
msgid "Placement"
msgstr "Platzierung"
#: templates/mahjong_ranking/eventranking_list.html:22
#: templates/mahjong_ranking/seasonranking_list.html:33
msgid "count"
msgstr "Anzahl"
#: templates/mahjong_ranking/eventranking_list.html:23
#: templates/mahjong_ranking/seasonranking_list.html:34
msgid "good"
msgstr "gut"
#: templates/mahjong_ranking/eventranking_list.html:24
#: templates/mahjong_ranking/seasonranking_list.html:35
msgid "won"
msgstr "gewonnen"
#: templates/mahjong_ranking/hanchan_confirm_delete.html:39
msgid "Cancel"
msgstr "Abbruch"
#: templates/mahjong_ranking/hanchan_confirm_delete.html:40
msgid "Delete"
msgstr "Löschen"
#: templates/mahjong_ranking/hanchan_form.html:18
msgid "Player"
msgstr "Spieler"
#: templates/mahjong_ranking/hanchan_form.html:58
msgid "Total"
msgstr "Total"
#: templates/mahjong_ranking/hanchan_form.html:71
#: src/mahjong_ranking/templates/mahjong_ranking/eventhanchan_form.html:94
#: src/mahjong_ranking/templates/mahjong_ranking/hanchan_form.html:71
msgid "back"
msgstr "Zurück"
#: templates/mahjong_ranking/hanchan_form.html:72
#: src/mahjong_ranking/templates/mahjong_ranking/eventhanchan_form.html:95
#: src/mahjong_ranking/templates/mahjong_ranking/hanchan_form.html:72
msgid "save"
msgstr "Speichern"
#: templates/mahjong_ranking/kyudanranking_list.html:4
#| msgid "Player List"
msgid "Players list"
msgstr "Spielerliste"
#: src/mahjong_ranking/templates/mahjong_ranking/eventhanchan_list.html:7
msgid "Played Hanchans"
msgstr "Gespielte Hanchans"
#: templates/mahjong_ranking/kyudanranking_list.html:9
#: src/mahjong_ranking/templates/mahjong_ranking/eventhanchan_list.html:18
#: src/mahjong_ranking/templates/mahjong_ranking/hanchan_confirm_delete.html:15
msgid "Place"
msgstr "Platz"
#: src/mahjong_ranking/templates/mahjong_ranking/eventhanchan_list.html:21
#: src/mahjong_ranking/templates/mahjong_ranking/hanchan_confirm_delete.html:18
#: src/mahjong_ranking/templates/mahjong_ranking/player_dan_score.html:17
msgid "Dan Points"
msgstr "Dan Punkte"
#: src/mahjong_ranking/templates/mahjong_ranking/eventhanchan_list.html:23
#: src/mahjong_ranking/templates/mahjong_ranking/hanchan_confirm_delete.html:20
#: src/mahjong_ranking/templates/mahjong_ranking/player_invalid_score.html:16
#: src/mahjong_ranking/templates/mahjong_ranking/player_kyu_score.html:18
msgid "Kyu Points"
msgstr "Kyu Punkte"
#: src/mahjong_ranking/templates/mahjong_ranking/eventhanchan_list.html:43
#: src/mahjong_ranking/templates/mahjong_ranking/hanchan_form.html:4
#: src/mahjong_ranking/templates/mahjong_ranking/hanchan_form.html:14
#: src/mahjong_ranking/templates/mahjong_ranking/player_dan_score.html:47
#: src/mahjong_ranking/templates/mahjong_ranking/player_invalid_score.html:36
#: src/mahjong_ranking/templates/mahjong_ranking/player_kyu_score.html:44
#: src/mahjong_ranking/templates/mahjong_ranking/player_ladder_score.html:55
msgid "Edit Hanchan"
msgstr "Hanchan bearbeiten"
#: src/mahjong_ranking/templates/mahjong_ranking/eventhanchan_list.html:48
msgid "No Hanchan has been added to this event yet."
msgstr "Für diese Veranstaltung wurde noch keine Hanchan eingetragen."
#: src/mahjong_ranking/templates/mahjong_ranking/eventhanchan_list.html:54
#: src/mahjong_ranking/templates/mahjong_ranking/eventranking_list.html:51
msgid "Edit Event"
msgstr "Veranstaltung bearbeiten"
#: src/mahjong_ranking/templates/mahjong_ranking/eventranking_list.html:4
#: src/mahjong_ranking/templates/mahjong_ranking/eventranking_list.html:5
msgid "Tournament Ranking"
msgstr "Turnierwertung"
#: src/mahjong_ranking/templates/mahjong_ranking/eventranking_list.html:12
#: src/mahjong_ranking/templates/mahjong_ranking/kyudanranking_list.html:30
#: src/mahjong_ranking/templates/mahjong_ranking/seasonranking_list.html:23
msgid "Rank"
msgstr "Rang"
#: src/mahjong_ranking/templates/mahjong_ranking/eventranking_list.html:13
#: src/mahjong_ranking/templates/mahjong_ranking/kyudanranking_list.html:17
#: src/mahjong_ranking/templates/mahjong_ranking/seasonranking_list.html:24
msgid "Avatar"
msgstr "Avatar"
#: src/mahjong_ranking/templates/mahjong_ranking/eventranking_list.html:14
#: src/mahjong_ranking/templates/mahjong_ranking/kyudanranking_list.html:20
#: src/mahjong_ranking/templates/mahjong_ranking/seasonranking_list.html:25
msgid "Nickname"
msgstr "Spitzname"
#: src/mahjong_ranking/templates/mahjong_ranking/eventranking_list.html:15
#: src/mahjong_ranking/templates/mahjong_ranking/seasonranking_list.html:26
msgid "Name"
msgstr "Name"
#: src/mahjong_ranking/templates/mahjong_ranking/eventranking_list.html:16
#: src/mahjong_ranking/templates/mahjong_ranking/seasonranking_list.html:27
msgid "Average"
msgstr "Durchschnitt"
#: src/mahjong_ranking/templates/mahjong_ranking/eventranking_list.html:20
#: src/mahjong_ranking/templates/mahjong_ranking/player_dan_score.html:15
#: src/mahjong_ranking/templates/mahjong_ranking/player_invalid_score.html:15
#: src/mahjong_ranking/templates/mahjong_ranking/player_kyu_score.html:16
#: src/mahjong_ranking/templates/mahjong_ranking/player_ladder_score.html:16
#: src/mahjong_ranking/templates/mahjong_ranking/seasonranking_list.html:31
msgid "Placement"
msgstr "Platzierung"
#: src/mahjong_ranking/templates/mahjong_ranking/eventranking_list.html:22
#: src/mahjong_ranking/templates/mahjong_ranking/seasonranking_list.html:33
msgid "count"
msgstr "Anzahl"
#: src/mahjong_ranking/templates/mahjong_ranking/eventranking_list.html:23
#: src/mahjong_ranking/templates/mahjong_ranking/seasonranking_list.html:34
msgid "good"
msgstr "gut"
#: src/mahjong_ranking/templates/mahjong_ranking/eventranking_list.html:24
#: src/mahjong_ranking/templates/mahjong_ranking/seasonranking_list.html:35
msgid "won"
msgstr "gewonnen"
#: src/mahjong_ranking/templates/mahjong_ranking/hanchan_confirm_delete.html:39
msgid "Cancel"
msgstr "Abbruch"
#: src/mahjong_ranking/templates/mahjong_ranking/hanchan_confirm_delete.html:40
msgid "Delete"
msgstr "Löschen"
#: src/mahjong_ranking/templates/mahjong_ranking/hanchan_form.html:18
msgid "Player"
msgstr "Spieler"
#: src/mahjong_ranking/templates/mahjong_ranking/hanchan_form.html:58
msgid "Total"
msgstr "Total"
#: src/mahjong_ranking/templates/mahjong_ranking/kyudanranking_list.html:4
#: src/mahjong_ranking/templates/mahjong_ranking/kyudanranking_list.html:9
msgid "Player List"
msgstr "Spieler Liste"
#: templates/mahjong_ranking/kyudanranking_list.html:25
#: src/mahjong_ranking/templates/mahjong_ranking/kyudanranking_list.html:25
msgid "Full Name"
msgstr "Voller Name"
#: templates/mahjong_ranking/kyudanranking_list.html:40
#: src/mahjong_ranking/templates/mahjong_ranking/kyudanranking_list.html:40
msgid "Games Total"
msgstr "Spiele total"
#: templates/mahjong_ranking/ladder_redbox.html:3
#: src/mahjong_ranking/templates/mahjong_ranking/ladder_redbox.html:3
msgid "Latest Hanchans"
msgstr "Letzten Hanchans"
#: templates/mahjong_ranking/ladder_redbox.html:15
#: src/mahjong_ranking/templates/mahjong_ranking/ladder_redbox.html:15
msgid "Latest Events"
msgstr "Letzte Veranstaltungen"
#: templates/mahjong_ranking/ladder_redbox.html:27
#: src/mahjong_ranking/templates/mahjong_ranking/ladder_redbox.html:27
msgid "Ladder Archive"
msgstr "Ladder Archiv"
#: templates/mahjong_ranking/player_dan_score.html:4
#: templates/mahjong_ranking/player_dan_score.html:5
#: src/mahjong_ranking/templates/mahjong_ranking/player_dan_score.html:4
#: src/mahjong_ranking/templates/mahjong_ranking/player_dan_score.html:5
msgid "Dan Score for"
msgstr "Dan Wertung für"
#: templates/mahjong_ranking/player_dan_score.html:8
#: src/mahjong_ranking/templates/mahjong_ranking/player_dan_score.html:8
msgid "Hanchans that apply to the Dan Score"
msgstr "Hanchans welche zur Dan Wertung zählen"
#: templates/mahjong_ranking/player_dan_score.html:12
#: templates/mahjong_ranking/player_kyu_score.html:13
#: templates/mahjong_ranking/player_ladder_score.html:13
#: src/mahjong_ranking/templates/mahjong_ranking/player_dan_score.html:12
#: src/mahjong_ranking/templates/mahjong_ranking/player_kyu_score.html:13
#: src/mahjong_ranking/templates/mahjong_ranking/player_ladder_score.html:13
msgid "Date"
msgstr "Datum"
#: templates/mahjong_ranking/player_dan_score.html:13
#: templates/mahjong_ranking/player_invalid_score.html:12
#: templates/mahjong_ranking/player_kyu_score.html:14
#: templates/mahjong_ranking/player_ladder_score.html:14
#: src/mahjong_ranking/templates/mahjong_ranking/player_dan_score.html:13
#: src/mahjong_ranking/templates/mahjong_ranking/player_invalid_score.html:12
#: src/mahjong_ranking/templates/mahjong_ranking/player_kyu_score.html:14
#: src/mahjong_ranking/templates/mahjong_ranking/player_ladder_score.html:14
msgid "Event"
msgstr "Veranstaltung"
#: templates/mahjong_ranking/player_dan_score.html:16
#: templates/mahjong_ranking/player_invalid_score.html:14
#: templates/mahjong_ranking/player_kyu_score.html:17
#: templates/mahjong_ranking/player_ladder_score.html:17
#: src/mahjong_ranking/templates/mahjong_ranking/player_dan_score.html:16
#: src/mahjong_ranking/templates/mahjong_ranking/player_invalid_score.html:14
#: src/mahjong_ranking/templates/mahjong_ranking/player_kyu_score.html:17
#: src/mahjong_ranking/templates/mahjong_ranking/player_ladder_score.html:17
msgid "Players"
msgstr "Spieler"
#: templates/mahjong_ranking/player_invalid_score.html:4
#: templates/mahjong_ranking/player_invalid_score.html:6
#: src/mahjong_ranking/templates/mahjong_ranking/player_invalid_score.html:4
#: src/mahjong_ranking/templates/mahjong_ranking/player_invalid_score.html:6
msgid "Unconfirmed Hanchans from"
msgstr "Nicht bestätigte Hanchans von"
#: templates/mahjong_ranking/player_invalid_score.html:9
#: src/mahjong_ranking/templates/mahjong_ranking/player_invalid_score.html:9
msgid "Invalid hanchans with"
msgstr "Ungültige Hanchans mit"
#: templates/mahjong_ranking/player_kyu_score.html:4
#: templates/mahjong_ranking/player_kyu_score.html:6
#: src/mahjong_ranking/templates/mahjong_ranking/player_kyu_score.html:4
#: src/mahjong_ranking/templates/mahjong_ranking/player_kyu_score.html:6
msgid "Kyu Score for"
msgstr "Kyu Wertung für"
#: templates/mahjong_ranking/player_kyu_score.html:9
#: src/mahjong_ranking/templates/mahjong_ranking/player_kyu_score.html:9
msgid "Hanchans that apply to the Kyu Score"
msgstr "Hanchans welche zur Kyu Wertung zählen"
#: templates/mahjong_ranking/player_ladder_score.html:4
#: templates/mahjong_ranking/player_ladder_score.html:5
#: src/mahjong_ranking/templates/mahjong_ranking/player_ladder_score.html:4
#: src/mahjong_ranking/templates/mahjong_ranking/player_ladder_score.html:5
msgid "Ladder Score for"
msgstr "Ladder Wertung für"
#: templates/mahjong_ranking/player_ladder_score.html:8
#: src/mahjong_ranking/templates/mahjong_ranking/player_ladder_score.html:8
msgid "Hanchans that apply to the Ladder Score"
msgstr "Hanchans welche in der Ladder zählen"
#: templates/mahjong_ranking/player_ladder_score.html:71
#: src/mahjong_ranking/templates/mahjong_ranking/player_ladder_score.html:71
msgid "Go"
msgstr "Los"
#: templates/mahjong_ranking/seasonranking_list.html:11
#: src/mahjong_ranking/templates/mahjong_ranking/seasonranking_list.html:11
msgid "End"
msgstr "Ende"
#: templates/mahjong_ranking/seasonranking_list.html:12
#: src/mahjong_ranking/templates/mahjong_ranking/seasonranking_list.html:12
msgid "Participants"
msgstr "Teilnehmer"
#: views.py:102
#: src/mahjong_ranking/views.py:104
#, python-format
msgid "%s has been updated successfully."
msgstr "%s wurde erfolgreich aktualisiert."
#: views.py:105
#: src/mahjong_ranking/views.py:107
#, python-format
msgid "%s has been added successfully. You can now add a new one."
msgstr "%s wurde erfolgreich hinzugefügt. Du kannst eine neue eintragen."
#: views.py:169
#: src/mahjong_ranking/views.py:207
msgid "No user found matching the name {}"
msgstr "Kein Benutzer mit dem Namen %s gefunden"

View File

@@ -1,214 +0,0 @@
"""Export Mahjong Rankings as excel files."""
import os
from datetime import date, time, datetime
import openpyxl
from django.conf import settings
from django.core.management.base import BaseCommand
from django.utils import timezone
from django.utils.dateparse import parse_date
from django.core.mail import EmailMessage
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'
DATE_STYLE = openpyxl.styles.NamedStyle(name='date')
DATE_STYLE.font = DEFAULT_STYLE.font
DATE_STYLE.border = DEFAULT_STYLE.border
DATE_STYLE.number_format = 'dd.mm.yyyy'
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 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)
workbook.add_named_style(DATE_STYLE)
for sheet in workbook.worksheets:
workbook.remove(sheet)
return workbook
def generate_sheet(workbook, title, columns_settings, json_data):
row = 1
ws = workbook.create_sheet()
ws.title = title
ws.syncHorizontal = True
ws.filterMode = True
# 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, until):
SeasonRanking.objects.update(until=until)
json_data = SeasonRanking.objects.json_data()
title = "Mahjong Ladder - {}".format(until.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': 'float', '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, until):
KyuDanRanking.objects.update(until=until)
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': 8},
{'col': 'H', 'title': 'letzte Hanchan', 'attr': 'last_hanchan_date',
'style': 'date', 'width': 16},
)
generate_sheet(
workbook=workbook,
title=title,
columns_settings=columns_settings,
json_data=json_data)
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 = geneate_excel()
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()

View 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()

View File

@@ -17,16 +17,22 @@ class Command(BaseCommand):
parser.add_argument('reset_date', type=parse_date)
def handle(self, *args, **options):
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)
dan_rankigns = models.KyuDanRanking.objects.filter(dan__isnull=False)
for ranking in dan_rankigns:
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()
ranking.legacy_hanchan_count = ranking.hanchan_count
ranking.legacy_dan_points = ranking.dan_points
ranking.legacy_kyu_points = ranking.kyu_points
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()

View File

@@ -4,30 +4,39 @@
Recalculate Mahjong Rankings...
"""
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.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 all Kyu/Dan Rankings"
help = "Recalculate the Kyu/Dan Rankings"
def add_arguments(self, parser):
parser.add_argument('--since', nargs='?', type=parse_date)
parser.add_argument('--until', nargs='?', type=parse_date)
parser.add_argument('--forcerecalc', action='store_true')
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('forecerecalc', False)
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)
models.KyuDanRanking.objects.update(since=since, until=until,
force_recalc=force_recalc)

View File

@@ -23,8 +23,8 @@ class HanchanManager(models.Manager):
:return: QuerySet Object
"""
if user:
return self.user_hanchans(
user, confirmed=True, since=since, until=until, **filter_args)
return self.user_hanchans(user, confirmed=True, until=until,
**filter_args)
hanchans = self.filter(confirmed=True, **filter_args)
if since:
hanchans = hanchans.filter(start__gt=since)
@@ -101,7 +101,7 @@ class HanchanManager(models.Manager):
)
queryset = queryset.filter(**filter_args)
if since:
queryset = queryset.filter(start__gt=since)
queryset = queryset.filter(start__gte=since)
if until:
queryset = queryset.filter(start__lte=until)
queryset = queryset.select_related().order_by('-start')

View File

@@ -1,12 +1,11 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
from django.conf import settings
from django.db import models, migrations
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('events', '0005_auto_20150907_2021'),
@@ -17,15 +16,19 @@ class Migration(migrations.Migration):
name='EventRanking',
fields=[
('id', models.AutoField(verbose_name='ID',
serialize=False, auto_created=True, primary_key=True)),
('placement', models.PositiveIntegerField(null=True, blank=True)),
serialize=False, auto_created=True,
primary_key=True)),
('placement',
models.PositiveIntegerField(null=True, blank=True)),
('avg_placement', models.FloatField(default=4)),
('avg_score', models.FloatField(default=0)),
('hanchan_count', models.PositiveIntegerField(default=0)),
('good_hanchans', models.PositiveIntegerField(default=0)),
('won_hanchans', models.PositiveIntegerField(default=0)),
('event', models.ForeignKey(to='events.Event')),
('user', models.ForeignKey(to=settings.AUTH_USER_MODEL)),
('event', models.ForeignKey(to='events.Event',
on_delete=models.CASCADE)),
('user', models.ForeignKey(to=settings.AUTH_USER_MODEL,
on_delete=models.CASCADE)),
],
options={
'ordering': ('placement', 'avg_placement', '-avg_score'),
@@ -35,10 +38,13 @@ class Migration(migrations.Migration):
name='Hanchan',
fields=[
('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(
help_text='Wichtig damit die richtigen Hanchans in die Wertung kommen.', verbose_name='Beginn')),
('player1_input_score', models.IntegerField(verbose_name='Punkte')),
help_text='Wichtig damit die richtigen Hanchans in die Wertung kommen.',
verbose_name='Beginn')),
('player1_input_score',
models.IntegerField(verbose_name='Punkte')),
('player1_game_score', models.PositiveIntegerField(
default=0, verbose_name='Punkte', editable=False)),
('player1_placement', models.PositiveSmallIntegerField(
@@ -50,8 +56,11 @@ class Migration(migrations.Migration):
('player1_bonus_points', models.SmallIntegerField(
null=True, editable=False, blank=True)),
('player1_comment', models.CharField(verbose_name='Anmerkung',
max_length=255, editable=False, blank=True)),
('player2_input_score', models.IntegerField(verbose_name='Punkte')),
max_length=255,
editable=False,
blank=True)),
('player2_input_score',
models.IntegerField(verbose_name='Punkte')),
('player2_game_score', models.PositiveIntegerField(
default=0, verbose_name='Punkte', editable=False)),
('player2_placement', models.PositiveSmallIntegerField(
@@ -63,8 +72,11 @@ class Migration(migrations.Migration):
('player2_bonus_points', models.SmallIntegerField(
null=True, editable=False, blank=True)),
('player2_comment', models.CharField(verbose_name='Anmerkung',
max_length=255, editable=False, blank=True)),
('player3_input_score', models.IntegerField(verbose_name='Punkte')),
max_length=255,
editable=False,
blank=True)),
('player3_input_score',
models.IntegerField(verbose_name='Punkte')),
('player3_game_score', models.PositiveIntegerField(
default=0, verbose_name='Punkte', editable=False)),
('player3_placement', models.PositiveSmallIntegerField(
@@ -76,8 +88,11 @@ class Migration(migrations.Migration):
('player3_bonus_points', models.SmallIntegerField(
null=True, editable=False, blank=True)),
('player3_comment', models.CharField(verbose_name='Anmerkung',
max_length=255, editable=False, blank=True)),
('player4_input_score', models.IntegerField(verbose_name='Punkte')),
max_length=255,
editable=False,
blank=True)),
('player4_input_score',
models.IntegerField(verbose_name='Punkte')),
('player4_game_score', models.PositiveIntegerField(
default=0, verbose_name='Punkte', editable=False)),
('player4_placement', models.PositiveSmallIntegerField(
@@ -89,22 +104,37 @@ class Migration(migrations.Migration):
('player4_bonus_points', models.SmallIntegerField(
null=True, editable=False, blank=True)),
('player4_comment', models.CharField(verbose_name='Anmerkung',
max_length=255, editable=False, blank=True)),
('comment', models.TextField(verbose_name='Anmerkung', blank=True)),
max_length=255,
editable=False,
blank=True)),
('comment',
models.TextField(verbose_name='Anmerkung', blank=True)),
('confirmed', models.BooleanField(
default=True, 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)),
default=True,
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(
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+',
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+',
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+',
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+',
verbose_name='Spieler 4', to=settings.AUTH_USER_MODEL)),
verbose_name='Spieler 4',
to=settings.AUTH_USER_MODEL,
on_delete=models.CASCADE)),
],
options={
'ordering': ('-start',),
@@ -116,8 +146,11 @@ class Migration(migrations.Migration):
name='KyuDanRanking',
fields=[
('id', models.AutoField(verbose_name='ID',
serialize=False, auto_created=True, primary_key=True)),
('dan', models.PositiveSmallIntegerField(null=True, blank=True)),
serialize=False, auto_created=True,
primary_key=True)),
(
'dan',
models.PositiveSmallIntegerField(null=True, blank=True)),
('dan_points', models.PositiveIntegerField(default=0)),
('kyu', models.PositiveSmallIntegerField(
default=10, null=True, blank=True)),
@@ -128,7 +161,8 @@ class Migration(migrations.Migration):
('legacy_date', models.DateField(null=True, blank=True)),
('legacy_dan_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={
'ordering': ('-dan', '-dan_points', '-kyu_points'),
@@ -140,15 +174,19 @@ class Migration(migrations.Migration):
name='SeasonRanking',
fields=[
('id', models.AutoField(verbose_name='ID',
serialize=False, auto_created=True, primary_key=True)),
('season', models.PositiveSmallIntegerField(verbose_name='Saison')),
('placement', models.PositiveIntegerField(null=True, blank=True)),
serialize=False, auto_created=True,
primary_key=True)),
('season',
models.PositiveSmallIntegerField(verbose_name='Saison')),
('placement',
models.PositiveIntegerField(null=True, blank=True)),
('avg_placement', models.FloatField(null=True, blank=True)),
('avg_score', models.FloatField(null=True, blank=True)),
('hanchan_count', models.PositiveIntegerField(default=0)),
('good_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={
'ordering': ('placement', 'avg_placement', '-avg_score'),

View 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),
),
]

View File

@@ -9,8 +9,8 @@ from datetime import datetime, time
from django.conf import settings
from django.core.exceptions import ValidationError
from django.core.urlresolvers import reverse
from django.db import models
from django.urls import reverse
from django.utils import timezone
from django.utils.translation import ugettext as _
@@ -29,8 +29,8 @@ class EventRanking(models.Model):
Sie beschränken sich aber auf einen Event und werden nur dann angestossen,
wenn der Event als Turnier markiert wurde.
"""
user = models.ForeignKey(settings.AUTH_USER_MODEL)
event = models.ForeignKey(Event)
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.PROTECT)
event = models.ForeignKey(Event, on_delete=models.CASCADE)
placement = models.PositiveIntegerField(blank=True, null=True)
avg_placement = models.FloatField(default=4)
avg_score = models.FloatField(default=0)
@@ -86,7 +86,7 @@ class Hanchan(models.Model):
Es werden aber noch andere Tests durchgeführt, ob sie gültig ist.
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'),
help_text=_('This is crucial to get the right Hanchans that scores')
@@ -94,7 +94,7 @@ class Hanchan(models.Model):
player1 = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
on_delete=models.PROTECT,
related_name='user_hanchan+',
verbose_name=_('Player 1'))
player1_input_score = models.IntegerField(_('Score'))
@@ -113,7 +113,7 @@ class Hanchan(models.Model):
player2 = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
on_delete=models.PROTECT,
related_name='user_hanchan+',
verbose_name=_('Player 2'))
player2_input_score = models.IntegerField(_('Score'))
@@ -132,7 +132,7 @@ class Hanchan(models.Model):
player3 = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
on_delete=models.PROTECT,
related_name='user_hanchan+',
verbose_name=_('Player 3'))
player3_input_score = models.IntegerField(_('Score'))
@@ -151,7 +151,7 @@ class Hanchan(models.Model):
player4 = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
on_delete=models.PROTECT,
related_name='user_hanchan+',
verbose_name=_('Player 4'))
player4_input_score = models.IntegerField(_('Score'))
@@ -335,18 +335,24 @@ class KyuDanRanking(models.Model):
Im Gegensatz zum Ladder Ranking ist das nicht Saison gebunden.
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_points = models.PositiveIntegerField(default=0)
max_dan_points = models.PositiveIntegerField(default=0)
kyu = models.PositiveSmallIntegerField(default=10, blank=True, null=True)
kyu_points = models.PositiveIntegerField(default=0)
won_hanchans = models.PositiveIntegerField(default=0)
good_hanchans = models.PositiveIntegerField(default=0)
hanchan_count = models.PositiveIntegerField(default=0)
legacy_date = models.DateField(blank=True, null=True)
legacy_hanchan_count = models.PositiveIntegerField(default=0)
legacy_dan_points = models.PositiveIntegerField(default=0)
legacy_kyu_points = models.PositiveIntegerField(default=0)
legacy_dan = models.PositiveSmallIntegerField(blank=True, null=True)
legacy_dan_points = models.PositiveIntegerField(blank=True, null=True)
legacy_kyu = models.PositiveSmallIntegerField(blank=True, null=True)
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()
@@ -356,11 +362,22 @@ class KyuDanRanking(models.Model):
verbose_name = _(u'Kyū/Dan Ranking')
verbose_name_plural = _(u'Kyū/Dan Rankings')
def __unicode__(self):
if self.dan_points is not None:
@property
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)
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):
u"""
@@ -368,12 +385,14 @@ class KyuDanRanking(models.Model):
das er einen Dan Rang aufsteigt. Dies wird als Kommentar abgespeichert,
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
else:
self.wins_in_a_row = 0
if self.dan and self.wins_in_a_row >= 3 and self.dan < 9:
return
if self.wins_in_a_row >= 3 and self.dan < 9:
LOGGER.info(
'adding bonuspoints for 3 wins in a row for %s', self.user)
new_dan_rank = self.dan + 1
@@ -394,8 +413,8 @@ class KyuDanRanking(models.Model):
bonus_points, new_dan_rank)
self.dan_points += bonus_points
self.wins_in_a_row = 0
self.update_rank()
# TODO: Komplett Überabreiten!
def append_tournament_bonuspoints(self, hanchan):
"""
Prüft ob es die letzte Hanchan in einem Turnier war. Wenn ja werden
@@ -408,20 +427,20 @@ class KyuDanRanking(models.Model):
user=self.user, event=hanchan.event
).order_by('-start')
last_hanchan_this_event = hanchans_this_event[0]
if hanchan != last_hanchan_this_event:
# Das braucht nur am Ende eines Turnieres gemacht werden.
return False
else:
if hanchan != last_hanchan_this_event: return False
event_ranking = EventRanking.objects.get(
user=self.user,
event=hanchan.event
)
if event_ranking.placement == 1:
bonus_points += 4
hanchan.player_comment += u'+4 Punkte Turnier gewonnen. '
bonus_points += settings.TOURNAMENT_WIN_BONUSPOINTS
hanchan.player_comment += u'+{0:d} Punkte Turnier gewonnen. '.format(
settings.TOURNAMENT_WIN_BONUSPOINTS)
if event_ranking.avg_placement == 1:
bonus_points += 8
hanchan.player_comment += u'+8 Pkt: alle Spiele des Turnieres gewonnen. '
bonus_points += settings.TOURNAMENT_FLAWLESS_VICTORY_BONUSPOINTS
hanchan.player_comment += u'+{0:d} Pkt: alle Spiele des Turnieres gewonnen. '.format(
settings.TOURNAMENT_FLAWLESS_VICTORY_BONUSPOINTS)
if bonus_points and self.dan:
hanchan.dan_points += bonus_points
@@ -451,20 +470,19 @@ class KyuDanRanking(models.Model):
force_recalc = True
if force_recalc:
# Setze alles auf die legacy Werte und berechne alles von neuem.
self.dan = None
self.dan = self.legacy_dan
self.dan_points = self.legacy_dan_points or 0
self.kyu = None
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 = 0
self.won_hanchans = 0
self.update_rank()
self.good_hanchans = self.legacy_good_hanchans or 0
self.won_hanchans = self.legacy_won_hanchans or 0
self.last_hanchan_date = None
if self.legacy_date:
since = timezone.make_aware(
datetime.combine(self.legacy_date, time(0, 0, 0)))
else:
since = 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:
@@ -479,42 +497,29 @@ class KyuDanRanking(models.Model):
valid_hanchans = valid_hanchans.filter(start__gt=since)
if until:
valid_hanchans = valid_hanchans.filter(start__lte=until)
self.hanchan_count += valid_hanchans.count()
for hanchan in valid_hanchans:
self.hanchan_count += 1
hanchan.get_playerdata(self.user)
if since and hanchan.start < since:
print(hanchan, "<", since, "no recalc")
LOGGER.debug(hanchan, "<", since, "no recalc")
self.dan_points += hanchan.dan_points or 0
self.kyu_points += hanchan.kyu_points or 0
self.update_rank()
else:
hanchan.bonus_points = 0
hanchan.player_comment = u""
hanchan.player_comment = ""
self.update_hanchan_points(hanchan)
if hanchan.event.mahjong_tournament:
self.append_tournament_bonuspoints(hanchan)
self.update_rank()
self.append_3_in_a_row_bonuspoints(hanchan)
self.update_rank()
hanchan.update_playerdata(self.user)
hanchan.save(recalculate=False)
self.won_hanchans += 1 if hanchan.placement == 1 else 0
self.good_hanchans += 1 if hanchan.placement == 2 else 0
self.last_hanchan_date = hanchan.start
LOGGER.debug(
'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)
def update_hanchan_points(self, hanchan):
"""
Berechne die Kyu bzw. Dan Punkte für eine Hanchan neu.
@@ -523,7 +528,7 @@ class KyuDanRanking(models.Model):
"""
hanchan.kyu_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:
1. Platz 4 Punkte
2. Platz 3 Punkte
@@ -547,6 +552,7 @@ class KyuDanRanking(models.Model):
hanchan.dan_points = -1
elif hanchan.placement == 4:
hanchan.dan_points = -2
# otherwise player must be in the kyu ranking
elif hanchan.game_score >= 60000:
hanchan.kyu_points = 3
elif hanchan.game_score >= 30000:
@@ -560,46 +566,44 @@ class KyuDanRanking(models.Model):
if self.dan:
# Only substract so much points that player has 0 Points:
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)
self.dan_points += hanchan.dan_points
else:
# Only substract so much points that player has 0 Points:
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)
self.kyu_points += hanchan.kyu_points
# TODO: Merkwürdige Methode die zwar funktioniert aber nicht sehr
# aussagekräfig ist. Überarbeiten?
def update_rank(self):
if self.dan and self.dan_points < 0:
self.dan_points = 0
self.dan = 1
elif self.dan or self.dan_points > 0:
old_dan = self.dan
for min_points, dan_rank in settings.DAN_RANKS:
if self.dan_points > min_points:
self.dan = dan_rank
break
if old_dan is None or self.dan > old_dan:
self.wins_in_a_row = 0
elif self.kyu_points < 1:
self.kyu_points = 0
self.kyu = 10
# Update Dan ranking:
if self.dan or self.dan_points > 0:
if settings.DAN_ALLOW_DROP_DOWN:
self.dan = max((dan for min_points, dan in settings.DAN_RANKS
if self.dan_points > min_points))
else:
self.max_dan_points = max(self.max_dan_points, self.dan_points)
self.dan = max((dan for min_points, dan in settings.DAN_RANKS
if self.max_dan_points > min_points))
# jump from Kyu to Dan
elif self.kyu_points > 50:
self.dan = 1
self.kyu = 0
self.dan_points = 0
self.kyu = None
self.kyu_points = 0
self.wins_in_a_row = 0
# update Kyu ranking_
else:
for min_points, kyu_rank in settings.KYU_RANKS:
if self.kyu_points > min_points:
self.kyu = kyu_rank
break
self.kyu = min((kyu for min_points, kyu in settings.KYU_RANKS
if self.kyu_points > min_points))
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'))
placement = models.PositiveIntegerField(blank=True, null=True)
avg_placement = models.FloatField(blank=True, null=True)

View File

@@ -0,0 +1,120 @@
{% extends "events/event_detail.html" %}{% load i18n humanize thumbnail %}
{% block title %}Hanchans: {{ event.name }}{% endblock %}
{% block extra_head %}
<script type="text/javascript" src="{{ STATIC_URL }}js/jquery.min.js"></script>
<script type="text/javascript" src="{{ STATIC_URL }}js/jquery.formset.js"></script>
{% endblock %}
{% block maincontent %}
<h2 class="grid_12">{% trans 'Edit Hanchans' %}</h2>
<form method="post" action="" id="eventhanchan_form">
{% csrf_token %}
{{ formset.management_form }}
{% for form in formset %}
<fieldset class="hanchan">
{% for hidden in form.hidden_fields %} {{ hidden }} {% endfor %}
<p>
<label for="id_{{ form.start.html_name }}_0" class="field_name {{ form.start.css_classes }}">{{ form.start.label }}:</label>
{{ form.start }}
{{ form.start.errors }}
</p>
<table>
<thead>
<tr>
<th>{{form.player1.label}}</th>
<th>{{form.player2.label}}</th>
<th>{{form.player3.label}}</th>
<th>{{form.player4.label}}</th>
</tr>
</thead>
<tr>
<td>{{ form.player1 }}</td>
<td>{{ form.player2 }}</td>
<td>{{ form.player3 }}</td>
<td>{{ form.player4 }}</td>
</tr>
<tr>
<td>{{ form.player1_input_score }}</td>
<td>{{ form.player2_input_score }}</td>
<td>{{ form.player3_input_score }}</td>
<td>{{ form.player4_input_score }}</td>
</tr>
</table>
<p>
<label class="field_name {{ form.comment.css_classes }}">{% trans 'Total' %}:</label>
<input type="number" value="0" name="total_score" disabled>
<label>{% trans 'Difference' %}:</label>
<span class="difference"></span>
</p>
<p><label for="id_{{ form.comment.html_name }}" class="field_name {{ form.comment.css_classes }}">{{ form.comment.label }}:</label>
{{ form.comment }}
{{ form.comment.errors }}
</p>
{% if form.instance.pk %}
<p>
<label for="id_{{ form.DELETE.html_name }}" class="field_name {{ form.DELETE.css_classes }}">{{ form.DELETE.label }}:</label>
{{ form.DELETE }} {{form.DELETE.help_text}}
{{ form.DELETE.errors }}
</p>
{% endif %}
{% if form.non_field_errors %}
<p> {{ form.non_field_errors }}</p>
{% endif %}
</fieldset>
{% endfor %}
</form>
<script type="text/javascript">
function autofill(row) {
row.find("input[id$='start_0']").val('{{ event.start|date:"SHORT_DATE_FORMAT"}}');
row.find("input[id$='start_1']").val('{{ event.start|time:'TIME_FORMAT'}}');
}
function recalculate_score(element) {
var difference = 100000
var total = 0;
score_fields = $(element).closest('fieldset').find('input[name$="input_score"]')
total_field = $(element).closest('fieldset').find('input[name$="total_score"]')
difference_field = $(element).closest('fieldset').find('span[class="difference"]')
score_fields.each(function() {total += Number($(this).val());});
total_field.val(total)
difference = 100000 - total
if (difference > 0) {
differnence_text = difference + ' offen'
} else if (difference < 0) {
differnence_text = (0 - difference) + ' zu viel'
} else {
differnence_text = 'Ok'
}
difference_field.text(differnence_text)
}
$(function() {
$('.hanchan').formset({
prefix: '{{ formset.prefix }}',
added: autofill,
addText: '<span class="fa fa-plus-circle"></span> {% trans 'Add Hanchan' %}',
addCssClass: 'button',
deleteText:'<span class="fa fa-trash"></span> {% trans 'Delete Hanchan' %}',
deleteCssClass: 'button'
});
})
$('input[name$="_input_score"]').change(function() {recalculate_score(this);});
$('input[name$="_input_score"]').keyup(function() {recalculate_score(this);});
$('input[name$="total_score"]').each(function() {recalculate_score(this);});
</script>
{% endblock %}
{% block comments %}{% endblock %}
{% block buttonbar %}
<a class="button" href="{% url 'event-hanchan-list' event.pk %}"><span class="fa fa-undo"></span> {% trans 'back' %}</a>
<button type="submit" form="eventhanchan_form"><span class="fa fa-hdd-o"></span> {% trans 'save' %}</button>
{% endblock %}

View File

@@ -52,6 +52,7 @@
{% block buttonbar %}
{% if perms.mahjong_ranking.add_hanchan %}
<a class="button" href="{{event.get_edit_url}}"><span class="fa fa-pencil"></span> {% trans 'Edit Event' %}</a>
<a class="button" href="{% url 'event-hanchan-form' event.id %}"><span class="fa fa-pencil"></span> {% trans 'Edit Hanchans' %}</a>
<a class="button" href="{% url 'add-hanchan-form' event.id %}"><span class="fa fa-plus-circle"></span> {% trans 'Add Hanchan' %}</a>
{% endif %}
{% endblock %}

View File

@@ -50,4 +50,12 @@
</tr>
{% endfor %}
</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 %}

View File

@@ -40,3 +40,7 @@
{% endfor %}
</table>
{% endblock %}
{% block buttonbar %}
<a href="?download=xlsx" class="button"><span class="fa fa-table"></span> Download</a>
{% endblock %}

View File

@@ -48,3 +48,7 @@
{% endfor %}
</table>
{% endblock %}
{% block buttonbar %}
<a href="?download=xlsx" class="button"><span class="fa fa-table"></span> Download</a>
{% endblock %}

View File

@@ -72,5 +72,8 @@
</form>
</td></tr></tfoot>
</table>
{% endblock %}
{% block buttonbar %}
<a href="?download=xlsx" class="button"><span class="fa fa-table"></span> Download</a>
{% endblock %}

View File

@@ -4,14 +4,15 @@ from django.conf.urls import url
from django.views.generic import RedirectView
from . import views
urlpatterns = [ # Ignore PyLintBear (C0103)
url(r'^$',
RedirectView.as_view(url='/ranking/mahjong-ladder/', permanent=True)),
url(r'^event/(?P<event>[\d]+)/mahjong/$',
views.EventHanchanList.as_view(), name="event-hanchan-list"),
url(r'^event/(?P<event>[\d]+)/add-hanchan/$',
views.HanchanForm.as_view(), name="add-hanchan-form"),
url(r'^event/(?P<event>[\d]+)/edit/$',
views.EventHanchanForm.as_view(), name="event-hanchan-form"),
url(r'^event/(?P<event>[\d]+)/mahjong/$',
views.EventHanchanList.as_view(), name="event-hanchan-list"),
url(r'^event/(?P<event>[\d]+)/mahjong-ranking/$',
views.EventRankingList.as_view(), name="event-ranking"),
url(r'^hanchan/(?P<hanchan>[\d]+)/edit/$',
@@ -32,6 +33,7 @@ urlpatterns = [ # Ignore PyLintBear (C0103)
views.PlayerLadderScore.as_view(), name="player-ladder-score"),
url(r'^mahjong/$', views.KyuDanRankingList.as_view(),
name="kyudanranking-list"),
url(r'^mahjong/(?P<order_by>[\+\-\w]+)/$',
url(r'^mahjong/(?P<order_by>[\+\-][a-z_]+)/$',
views.KyuDanRankingList.as_view(), name="kyudanranking-list"),
]

View File

@@ -7,14 +7,16 @@ from django.contrib import auth
from django.contrib.auth.mixins import LoginRequiredMixin, \
PermissionRequiredMixin
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.views import generic
from events.mixins import EventDetailMixin
from kasu import xlsx
from . import forms, models
from .mixins import MahjongMixin
DEFAULT_KYU_DAN_ORDER = '-score'
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'),
@@ -106,6 +108,47 @@ class HanchanForm(SuccessMessageMixin, EventDetailMixin,
'one.') % self.object
class EventHanchanForm(EventDetailMixin, PermissionRequiredMixin,
generic.TemplateView):
"""Display a Formset to add and Edit Hanchans of the specific Event."""
permission_required = 'mahjong_ranking.add_hanchan'
template_name = 'mahjong_ranking/eventhanchan_form.html'
model=models.Hanchan
def get_context_data(self, **kwargs):
self.event = models.Event.objects.get(pk=self.kwargs['event'])
context = super(EventHanchanForm, self).get_context_data()
context['formset'] = self.formset
return context
def get(self, request, *args, **kwargs):
self.get_queryset()
self.formset = forms.HanchanFormset(
instance=self.event,
initial=[{'start': self.event.start}]
)
context = self.get_context_data(**kwargs)
return self.render_to_response(context)
def post(self, request, *args, **kwargs):
print("ICH WURDE GEPOSTET!!!!")
self.get_queryset()
self.formset = forms.HanchanFormset(
self.request.POST,
self.request.FILES,
instance=self.event,
initial=[{'start': self.event.start}]
)
if self.formset.is_valid():
self.formset.save()
return django.http.HttpResponseRedirect(
reverse('event-hanchan-form', kwargs={'event': self.event.pk})
)
context = self.get_context_data(**kwargs)
return self.render_to_response(context)
class EventHanchanList(EventDetailMixin, generic.ListView):
"List all hanchans played on a given event."
model = models.Hanchan
@@ -117,23 +160,19 @@ class EventRankingList(EventDetailMixin, generic.ListView):
model = models.EventRanking
class KyuDanRankingList(MahjongMixin, generic.ListView):
"""List all Players with an Kyu or Dan score. """
default_order = '-score'
order_by = ''
order_by = None
paginate_by = 25
def dispatch(self, request, *args, **kwargs):
"""Set the order_by settings, revert to default_order if necessary."""
self.order_by = KYU_DAN_ORDER[
kwargs.get('order_by', self.default_order)
]
if kwargs.get('order_by') in KYU_DAN_ORDER.keys():
self.order_by = KYU_DAN_ORDER[kwargs.get('order_by')]
else:
self.order_by = KYU_DAN_ORDER[DEFAULT_KYU_DAN_ORDER]
return super(KyuDanRankingList, self).dispatch(request, *args, **kwargs)
def get_queryset(self):
queryset = models.KyuDanRanking.objects.filter(
hanchan_count__gt=0).order_by(*self.order_by)
@@ -160,7 +199,6 @@ class PlayerScore(LoginRequiredMixin, generic.ListView):
def get(self, request, *args, **kwargs):
user_model = auth.get_user_model()
username = kwargs.get('username')
try:
self.user = user_model.objects.get(
username=self.kwargs.get('username'))
@@ -168,6 +206,9 @@ class PlayerScore(LoginRequiredMixin, generic.ListView):
raise django.http.Http404(
_("No user found matching the name {}").format(
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)
def get_context_data(self, **kwargs):
@@ -186,20 +227,76 @@ class PlayerScore(LoginRequiredMixin, generic.ListView):
context['ladder_ranking'] = models.SeasonRanking(user=self.user)
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):
template_name = 'mahjong_ranking/player_dan_score.html'
def get_queryset(self):
kyu_dan_ranking = models.KyuDanRanking.objects.get(user=self.user)
return models.Hanchan.objects.dan_hanchans(user=self.user,
since=kyu_dan_ranking.legacy_date)
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):
template_name = 'mahjong_ranking/player_invalid_score.html'
def get_queryset(self):
self.xlsx_filename = "{username}_invalid_score.xlsx".format(
username=self.user.username)
return models.Hanchan.objects.unconfirmed(user=self.user)
@@ -207,7 +304,47 @@ class PlayerKyuScore(PlayerScore):
template_name = 'mahjong_ranking/player_kyu_score.html'
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):
@@ -229,3 +366,39 @@ class PlayerLadderScore(PlayerScore):
season=self.season
)
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
)

View File

@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: kasu.mahjong_ranking\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2018-04-27 09:49+0200\n"
"POT-Creation-Date: 2018-01-11 22:50+0100\n"
"PO-Revision-Date: 2016-09-28 00:24+0200\n"
"Last-Translator: Christian Berg <xeniac.at@gmail.com>\n"
"Language-Team: Kasu <verein@kasu.at>\n"
@@ -19,205 +19,208 @@ msgstr ""
"X-Generator: Poedit 1.8.9\n"
"X-Translated-Using: django-rosetta 0.7.2\n"
#: admin.py:24
#: src/maistar_ranking/admin.py:24
msgid "Recalculate"
msgstr "Neuberechnen"
#: forms.py:35
#: src/maistar_ranking/forms.py:35
#, python-format
msgid "%s may only participate once."
msgstr "%s darf nur einmal teilnehmen."
#: models.py:20
#: src/maistar_ranking/models.py:21
msgid "Comment"
msgstr "Kommentar"
#: models.py:22
#: src/maistar_ranking/models.py:24
msgid "Player 1"
msgstr "Spieler 1"
#: models.py:24 models.py:30 models.py:36 models.py:42 models.py:48
#: models.py:54 templates/maistar_ranking/ranking_list.html:19
#: src/maistar_ranking/models.py:26 src/maistar_ranking/models.py:33
#: src/maistar_ranking/models.py:40 src/maistar_ranking/models.py:47
#: src/maistar_ranking/models.py:54 src/maistar_ranking/models.py:61
#: src/maistar_ranking/templates/maistar_ranking/ranking_list.html:19
msgid "Score"
msgstr "Punkte"
#: models.py:28
#: src/maistar_ranking/models.py:31
msgid "Player 2"
msgstr "Spieler 2"
#: models.py:34
#: src/maistar_ranking/models.py:38
msgid "Player 3"
msgstr "Spieler 3"
#: models.py:40
#: src/maistar_ranking/models.py:45
msgid "Player 4"
msgstr "Spieler 4"
#: models.py:46
#: src/maistar_ranking/models.py:52
msgid "Player 5"
msgstr "Spieler 5"
#: models.py:52
#: src/maistar_ranking/models.py:59
msgid "Player 6"
msgstr "Spieler 6"
#: models.py:58
#: src/maistar_ranking/models.py:65
msgid "Has been confirmed"
msgstr "Wurde bestätigt"
#: models.py:60
#: src/maistar_ranking/models.py:67
msgid "the game only counts whe it has been confirmed"
msgstr "das Spiel zählt nur wenn es bestätigt wurde"
#: models.py:63 models.py:147 templates/maistar_ranking/player_game_list.html:6
#: templates/maistar_ranking/ranking_list.html:4
#: templates/maistar_ranking/ranking_list.html:72
#: src/maistar_ranking/models.py:70 src/maistar_ranking/models.py:153
#: src/maistar_ranking/templates/maistar_ranking/player_game_list.html:6
#: src/maistar_ranking/templates/maistar_ranking/ranking_list.html:4
#: src/maistar_ranking/templates/maistar_ranking/ranking_list.html:72
msgid "Season"
msgstr "Saison"
#: models.py:74
#: src/maistar_ranking/models.py:80
msgid "Mai-Star Game with {0} from {1:%Y-%m-%d}"
msgstr "Mai-Star Spiel mit {0} vom {1:%Y-%m-%d}"
#: templates/maistar_ranking/game_form.html:5
#: templates/maistar_ranking/game_form.html:16
#: templates/maistar_ranking/game_list.html:27
#: templates/maistar_ranking/player_game_list.html:44
#: src/maistar_ranking/templates/maistar_ranking/game_form.html:5
#: src/maistar_ranking/templates/maistar_ranking/game_form.html:16
#: src/maistar_ranking/templates/maistar_ranking/game_list.html:27
#: src/maistar_ranking/templates/maistar_ranking/player_game_list.html:44
msgid "Edit Game"
msgstr "Spiel bearbeiten"
#: templates/maistar_ranking/game_form.html:5
#: templates/maistar_ranking/game_form.html:16
#: templates/maistar_ranking/game_list.html:41
#: src/maistar_ranking/templates/maistar_ranking/game_form.html:5
#: src/maistar_ranking/templates/maistar_ranking/game_form.html:16
#: src/maistar_ranking/templates/maistar_ranking/game_list.html:41
msgid "Add Game"
msgstr "Spiel hinzufügen"
#: templates/maistar_ranking/game_form.html:76
#: src/maistar_ranking/templates/maistar_ranking/game_form.html:76
msgid "Back"
msgstr "Zurück"
#: templates/maistar_ranking/game_form.html:77
#: src/maistar_ranking/templates/maistar_ranking/game_form.html:77
msgid "Save"
msgstr "Speichern"
#: templates/maistar_ranking/game_list.html:4
#: templates/maistar_ranking/player_game_list.html:6
#: src/maistar_ranking/templates/maistar_ranking/game_list.html:4
#: src/maistar_ranking/templates/maistar_ranking/player_game_list.html:6
msgid "Mai-Star Games"
msgstr "Mai-Star Spiele"
#: templates/maistar_ranking/game_list.html:7
#: src/maistar_ranking/templates/maistar_ranking/game_list.html:7
msgid "Played Mai-Star Games"
msgstr "Gespielte Mai-Star Spiele"
#: templates/maistar_ranking/game_list.html:11
#: src/maistar_ranking/templates/maistar_ranking/game_list.html:11
msgid "Game"
msgstr "Spiel"
#: templates/maistar_ranking/game_list.html:14
#: src/maistar_ranking/templates/maistar_ranking/game_list.html:14
msgid "Place"
msgstr "Platz"
#: templates/maistar_ranking/game_list.html:19
#: templates/maistar_ranking/player_game_list.html:36
#: src/maistar_ranking/templates/maistar_ranking/game_list.html:19
#: src/maistar_ranking/templates/maistar_ranking/player_game_list.html:36
msgid "Points"
msgstr "Punkte"
#: templates/maistar_ranking/game_list.html:24
#: templates/maistar_ranking/player_game_list.html:41
#: src/maistar_ranking/templates/maistar_ranking/game_list.html:24
#: src/maistar_ranking/templates/maistar_ranking/player_game_list.html:41
msgid "Delete Game"
msgstr "Spiel löschen"
#: templates/maistar_ranking/game_list.html:33
#: src/maistar_ranking/templates/maistar_ranking/game_list.html:33
msgid "No Mai-Star games have been added to this event yet."
msgstr "Für diese Veranstaltung wurden noch keine Mai-Star Spiele erfasst."
#: templates/maistar_ranking/game_list.html:40
#: src/maistar_ranking/templates/maistar_ranking/game_list.html:40
msgid "Edit Event"
msgstr "Veranstaltung bearbeiten"
#: templates/maistar_ranking/hanchan_confirm_delete.html:4
#: templates/maistar_ranking/hanchan_confirm_delete.html:10
#: src/maistar_ranking/templates/maistar_ranking/hanchan_confirm_delete.html:4
#: src/maistar_ranking/templates/maistar_ranking/hanchan_confirm_delete.html:10
msgid "Delete game"
msgstr "Spiel löschen"
#: templates/maistar_ranking/hanchan_confirm_delete.html:13
#: src/maistar_ranking/templates/maistar_ranking/hanchan_confirm_delete.html:13
msgid "Cancel"
msgstr "Abbrechen"
#: templates/maistar_ranking/hanchan_confirm_delete.html:14
#: src/maistar_ranking/templates/maistar_ranking/hanchan_confirm_delete.html:14
msgid "Delete"
msgstr "Löschen"
#: templates/maistar_ranking/page.html:5
#: src/maistar_ranking/templates/maistar_ranking/page.html:5
msgid "Archive"
msgstr "Archiv"
#: templates/maistar_ranking/page.html:7
#: src/maistar_ranking/templates/maistar_ranking/page.html:7
msgid "Add Event"
msgstr "Veranstaltung hinzufügen"
#: templates/maistar_ranking/player_game_list.html:4
#: src/maistar_ranking/templates/maistar_ranking/player_game_list.html:4
msgid "Ladder Score for"
msgstr "Ladder Wertung für"
#: templates/maistar_ranking/player_game_list.html:9
#: src/maistar_ranking/templates/maistar_ranking/player_game_list.html:9
msgid "Mai-Star Games with"
msgstr "Mai-Star Spiele mit"
#: templates/maistar_ranking/player_game_list.html:14
#: src/maistar_ranking/templates/maistar_ranking/player_game_list.html:14
msgid "Date"
msgstr "Datum"
#: templates/maistar_ranking/player_game_list.html:15
#: src/maistar_ranking/templates/maistar_ranking/player_game_list.html:15
msgid "Event"
msgstr "Veranstaltung"
#: templates/maistar_ranking/player_game_list.html:16
#: src/maistar_ranking/templates/maistar_ranking/player_game_list.html:16
msgid "Players"
msgstr "Spieler"
#: templates/maistar_ranking/ranking_list.html:4
#: src/maistar_ranking/templates/maistar_ranking/ranking_list.html:4
msgid "Mai-Star Ranking"
msgstr "Mai-Star Platzierung"
#: templates/maistar_ranking/ranking_list.html:10
#: templates/maistar_ranking/ranking_list.html:18
#: src/maistar_ranking/templates/maistar_ranking/ranking_list.html:10
#: src/maistar_ranking/templates/maistar_ranking/ranking_list.html:18
msgid "Placement"
msgstr "Platzierung"
#: templates/maistar_ranking/ranking_list.html:11
#: src/maistar_ranking/templates/maistar_ranking/ranking_list.html:11
msgid "Avatar"
msgstr "Avatar"
#: templates/maistar_ranking/ranking_list.html:12
#: src/maistar_ranking/templates/maistar_ranking/ranking_list.html:12
msgid "Nickname"
msgstr "Spitzname"
#: templates/maistar_ranking/ranking_list.html:13
#: src/maistar_ranking/templates/maistar_ranking/ranking_list.html:13
msgid "Name"
msgstr "Name"
#: templates/maistar_ranking/ranking_list.html:14
#: src/maistar_ranking/templates/maistar_ranking/ranking_list.html:14
msgid "Average"
msgstr "Durchschnitt"
#: templates/maistar_ranking/ranking_list.html:15
#: src/maistar_ranking/templates/maistar_ranking/ranking_list.html:15
msgid "Games"
msgstr "Spiele"
#: templates/maistar_ranking/ranking_list.html:20
#: src/maistar_ranking/templates/maistar_ranking/ranking_list.html:20
msgid "count"
msgstr "Anzahl"
#: templates/maistar_ranking/ranking_list.html:21
#: src/maistar_ranking/templates/maistar_ranking/ranking_list.html:21
msgid "good"
msgstr "Gut"
#: templates/maistar_ranking/ranking_list.html:22
#: src/maistar_ranking/templates/maistar_ranking/ranking_list.html:22
msgid "won"
msgstr "Gewonnen"
#: templates/maistar_ranking/ranking_list.html:43
#: src/maistar_ranking/templates/maistar_ranking/ranking_list.html:43
msgid ""
"Unfortunately, nobody has it been done in the ranking.\n"
" A player must have 6 games done, to be added to the ranking."
@@ -226,15 +229,15 @@ msgstr ""
"als 6 Spiele innerhalb einer Saison absolviert haben, werden für das "
"Endergebnis nicht gewertet."
#: templates/maistar_ranking/ranking_list.html:52
#: src/maistar_ranking/templates/maistar_ranking/ranking_list.html:52
msgid "Latest Games"
msgstr "Letzten Spiele"
#: templates/maistar_ranking/ranking_list.html:63
#: src/maistar_ranking/templates/maistar_ranking/ranking_list.html:63
msgid "Latest Events"
msgstr "Letzten Veranstaltungen"
#: templates/maistar_ranking/ranking_list.html:70
#: src/maistar_ranking/templates/maistar_ranking/ranking_list.html:70
msgid "Ladder Archive"
msgstr "Archiv"

View File

@@ -37,19 +37,19 @@ class Migration(migrations.Migration):
('season', models.PositiveSmallIntegerField(
verbose_name='Saison', editable=False, db_index=True)),
('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='+',
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='+',
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='+',
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='+',
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='+',
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='+',
verbose_name='Spieler 6', to=settings.AUTH_USER_MODEL)),
verbose_name='Spieler 6', to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE)),
],
),
migrations.CreateModel(
@@ -65,7 +65,7 @@ class Migration(migrations.Migration):
('games_count', models.PositiveSmallIntegerField(default=0)),
('games_good', 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={
'ordering': ('-season', 'placement', 'avg_placement', '-avg_score'),

View 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),
),
]

View File

@@ -2,11 +2,11 @@
import logging
from django.core.urlresolvers import reverse
from django.db import models
from django.db.models.signals import post_delete, post_save
from django.utils.translation import ugettext as _
from django.dispatch import receiver
from django.urls import reverse
from django.utils.translation import ugettext as _
from events.models import Event
from . import settings, managers
@@ -16,40 +16,47 @@ class Game(models.Model):
"""to record a complete game with 6 different players."""
_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)
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_placement = models.PositiveSmallIntegerField(editable=False)
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_placement = models.PositiveSmallIntegerField(editable=False)
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_placement = models.PositiveSmallIntegerField(editable=False)
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_placement = models.PositiveSmallIntegerField(editable=False)
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_placement = models.PositiveSmallIntegerField(editable=False)
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_placement = models.PositiveSmallIntegerField(editable=False)
@@ -69,7 +76,6 @@ class Game(models.Model):
"""Display rankings by placement, best players first."""
ordering = ('-event__start', '-id')
def __str__(self):
return _("Mai-Star Game with {0} from {1:%Y-%m-%d}").format(
self.player_names, self.event.start
@@ -143,7 +149,7 @@ class Game(models.Model):
class Ranking(models.Model):
"""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"))
placement = models.PositiveIntegerField(blank=True, null=True)
avg_placement = models.PositiveSmallIntegerField(blank=True, null=True)

View File

@@ -3,7 +3,7 @@
from datetime import date
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.views import generic

View File

@@ -7,8 +7,8 @@ msgid ""
msgstr ""
"Project-Id-Version: kasu.membership\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2018-04-27 09:49+0200\n"
"PO-Revision-Date: 2018-04-27 10:30+0105\n"
"POT-Creation-Date: 2018-01-11 22:50+0100\n"
"PO-Revision-Date: 2018-01-12 15:22+0105\n"
"Last-Translator: b'Christian Berg <kasu@xendynastie.at>'\n"
"Language-Team: Kasu <verein@kasu.at>\n"
"Language: de\n"
@@ -19,163 +19,165 @@ msgstr ""
"X-Generator: Poedit 1.8.9\n"
"X-Translated-Using: django-rosetta 0.7.14\n"
#: __init__.py:11
#: src/membership/__init__.py:11
msgid "Male"
msgstr "Männlich"
#: __init__.py:12
#: src/membership/__init__.py:12
msgid "Female"
msgstr "Weiblich"
#: admin.py:22
#: src/membership/admin.py:22
msgid "Activate selected User"
msgstr "Ausgewählte Benutzer freischalten"
#: admin.py:38
#: src/membership/admin.py:38
msgid "Cleanup selected Activation Requests"
msgstr "Ausgewählte Aktivierungsanfragen bereinigen"
#: admin.py:47
#: src/membership/admin.py:47
msgid "Group"
msgstr "Gruppe"
#: admin.py:48
#: src/membership/admin.py:48
msgid "Groups"
msgstr "Gruppen"
#: admin.py:72 models.py:162 models.py:215
#: templates/membership/register_form.html:32
#: src/membership/admin.py:72 src/membership/models.py:163
#: src/membership/models.py:216
#: src/membership/templates/membership/register_form.html:32
msgid "Membership"
msgstr "Mitgliedschaft"
#: admin.py:77
#: src/membership/admin.py:77
msgid "Permissions"
msgstr "Berechtigung"
#: admin.py:79
#: src/membership/admin.py:79
msgid "Important dates"
msgstr "Wichtige Daten"
#: forms.py:23
#: src/membership/forms.py:23
msgid "birthday"
msgstr "Geburtstag"
#: forms.py:25
#: src/membership/forms.py:25
msgid "Input format: yyyy-mm-dd"
msgstr "Eingabeformat: tt.mm.jjjj"
#: forms.py:27
#: src/membership/forms.py:27
msgid "Email"
msgstr "E-Mail"
#: forms.py:42 forms.py:50 forms.py:58
#: src/membership/forms.py:42 src/membership/forms.py:50
#: src/membership/forms.py:58
msgid ""
"For your membership, we need this. Please fill out this field "
"yet."
msgstr "Diese Angabe wird für eine Mitgliedschaft benötigt, bitte ausfüllen."
#: forms.py:65
#: src/membership/forms.py:65
msgid ""
"For your membership, we need this. Please fill out this field "
"yet."
msgstr "Diese Angabe wird für eine Mitgliedschaft benötigt, bitte ausfüllen."
#: forms.py:78
#: src/membership/forms.py:78
msgid "password"
msgstr "Passwort"
#: forms.py:80
#: src/membership/forms.py:80
msgid "password (again)"
msgstr "Passwort (wiederholen)"
#: forms.py:102
#: src/membership/forms.py:102
msgid "This username is already taken. Please choose another."
msgstr ""
"Diesen Benutzername ist schon vergeben. Bitte einen anderen auswählen."
#: forms.py:109
#: src/membership/forms.py:109
msgid ""
"This email address is already in use. Please supply a different "
"email address."
msgstr "Die E-Mail Adresse wird schon verwendet. Bitte eine andere angeben."
#: forms.py:119
#: src/membership/forms.py:119
msgid "The two password fields didn't match."
msgstr "Die beiden Passwörter passen nicht."
#: models.py:83
#: src/membership/models.py:84
msgid "user"
msgstr "Benutzer"
#: models.py:85
#: src/membership/models.py:86
msgid "activation key"
msgstr "Aktivierungsschlüssel"
#: models.py:89
#: src/membership/models.py:90
msgid "pending activation"
msgstr "Ausstehende Aktivierung"
#: models.py:90
#: src/membership/models.py:91
msgid "pending activations"
msgstr "Wartende Aktivierungen"
#: models.py:93
#: src/membership/models.py:94
#, python-format
msgid "user registration for %s"
msgstr "Benutzerregistrierung für %s"
#: models.py:148
#: src/membership/models.py:149
msgid "Gender"
msgstr "Geschlecht"
#: models.py:164
#: src/membership/models.py:165
msgid ""
"Yes, I confirm that I am in agreement with the statutes and would "
"like to become a member."
msgstr "Ja, ich bin mit den Statuen einverstanden und möchte Mitglied werden."
#: models.py:168
#: src/membership/models.py:169
msgid "Birthday Date"
msgstr "Geburtstag"
#: models.py:172
#: src/membership/models.py:173
msgid "Telephone"
msgstr "Telefon"
#: models.py:178
#: src/membership/models.py:179
msgid "Address"
msgstr "Adresse"
#: models.py:184
#: src/membership/models.py:185
msgid "Postcode"
msgstr "Postleitzahl"
#: models.py:189
#: src/membership/models.py:190
msgid "Town/City"
msgstr "Ort"
#: models.py:197
#: src/membership/models.py:198
msgid "Paid until"
msgstr "Bezahlt bis"
#: models.py:203
#: src/membership/models.py:204
msgid "Confirmed"
msgstr "Bestätigt"
#: models.py:205
#: src/membership/models.py:206
msgid "This person has paid the membership fee."
msgstr "Diese Person hat ihre Mitgliedschaft bezahlt"
#: models.py:216
#: src/membership/models.py:217
msgid "Memberships"
msgstr "Mitgliedschaften"
#: templates/membership/email/activation_email.txt:2
#: src/membership/templates/membership/email/activation_email.txt:2
#, python-format
msgid "Welcome %(user)s,"
msgstr "Herzlich willkommen %(user)s,"
#: templates/membership/email/activation_email.txt:4
#: src/membership/templates/membership/email/activation_email.txt:4
#, python-format
msgid ""
"We received an account request on %(site.domain)s for your email address.\n"
@@ -184,7 +186,7 @@ msgstr ""
"Jemand (hoffentlich du selbst) möchte mit dieser E-Mail Adresse einen neuen Benutzer Account für %(site.domain)s anlegen.\n"
"Solltest du diesen Account aktivieren wollen, klicke bitte auf den unten stehenden Link, oder kopiere diesen in die Adresszeile deines Browsers:"
#: templates/membership/email/activation_email.txt:9
#: src/membership/templates/membership/email/activation_email.txt:9
#, python-format
msgid ""
"If you do not want to open an account on %(site.domain)s, please ignore this email.\n"
@@ -193,7 +195,7 @@ msgstr ""
"Wenn du keinen Zugang für %(site.domain)s eröffnen willst, ignoriere diese E-Mail bitte.\n"
"Die Zugangsdaten werden dann in ein paar Tagen automatisch gelöscht."
#: templates/membership/email/activation_email.txt:12
#: src/membership/templates/membership/email/activation_email.txt:12
#, python-format
msgid ""
"Best Regards,\n"
@@ -202,166 +204,170 @@ msgstr ""
"mit den besten Wünschen\n"
"Das Team von %(site.domain)s"
#: templates/membership/email/password_reset_email.html:2
#: src/membership/templates/membership/email/password_reset_email.html:2
msgid "You're receiving this e-mail because you requested a password reset"
msgstr ""
"Du hast diese E-Mail erhalten, weil jemand die das Passwort zurücksetzen "
"möchte. "
#: templates/membership/email/password_reset_email.html:3
#: src/membership/templates/membership/email/password_reset_email.html:3
#, python-format
msgid "for your user account at %(site_name)s"
msgstr "Für deinen Benutzerzugang auf %(site_name)s"
#: templates/membership/email/password_reset_email.html:5
#: src/membership/templates/membership/email/password_reset_email.html:5
msgid "Please go to the following page and choose a new password:"
msgstr "Bitte gehe auf folgende Seite und wähle ein neues Passwort aus:"
#: templates/membership/email/password_reset_email.html:9
#: src/membership/templates/membership/email/password_reset_email.html:9
msgid "Your username, in case you've forgotten:"
msgstr "Dein Benutzername, für den Fall das du diesen vergessen hast:"
#: templates/membership/email/password_reset_email.html:11
#: src/membership/templates/membership/email/password_reset_email.html:11
msgid "Thanks for using our site!"
msgstr "Danke das du unsere Seite verwendest!"
#: templates/membership/email/password_reset_email.html:13
#: src/membership/templates/membership/email/password_reset_email.html:13
#, python-format
msgid "The %(site_name)s team"
msgstr "Das %(site_name)s Team"
#: templates/membership/email/password_reset_subject.txt:2
#: src/membership/templates/membership/email/password_reset_subject.txt:2
#, python-format
msgid "Password reset on %(site_name)s"
msgstr "Passwort auf %(site_name)s zurücksetzen"
#: templates/membership/hanchan_table.html:5
#: src/membership/templates/membership/hanchan_table.html:5
msgid "Start"
msgstr "Beginn"
#: templates/membership/hanchan_table.html:6
#: src/membership/templates/membership/hanchan_table.html:6
msgid "Event"
msgstr "Termin"
#: templates/membership/hanchan_table.html:7
#: src/membership/templates/membership/hanchan_table.html:7
msgid "Players"
msgstr "Spieler"
#: templates/membership/hanchan_table.html:8
#: src/membership/templates/membership/hanchan_table.html:8
msgid "Kyu Points"
msgstr "Kyū Punkte"
#: templates/membership/hanchan_table.html:9
#: src/membership/templates/membership/hanchan_table.html:9
msgid "Dan Points"
msgstr "Dan Punkte"
#: templates/membership/hanchan_table.html:10
#: src/membership/templates/membership/hanchan_table.html:10
msgid "Bonus Points"
msgstr "Bonus Punkte"
#: templates/membership/hanchan_table.html:11
#: src/membership/templates/membership/hanchan_table.html:11
msgid "Comment"
msgstr "Anmerkung"
#: templates/membership/hanchan_table.html:26
#: src/membership/templates/membership/hanchan_table.html:26
msgid "This Hanchan does not validate"
msgstr "Diese Hanchan ist ungültig"
#: templates/membership/membership_detail.html:6
#: src/membership/templates/membership/membership_detail.html:6
msgid "profile for"
msgstr "Profil für"
#: templates/membership/membership_detail.html:10
#: src/membership/templates/membership/membership_detail.html:10
msgid "Ladder Hanchans"
msgstr "Ladder Hanchans"
#: templates/membership/membership_detail.html:11
#: src/membership/templates/membership/membership_detail.html:11
msgid "Kyu Hanchans"
msgstr "Kyū Hanchans"
#: templates/membership/membership_detail.html:12
#: src/membership/templates/membership/membership_detail.html:12
msgid "Dan Hanchans"
msgstr "Dan Hanchans"
#: templates/membership/membership_detail.html:13
#: src/membership/templates/membership/membership_detail.html:13
msgid "Invalid Hanchans"
msgstr "Ungültige Hanchans"
#: templates/membership/membership_detail.html:14
#: src/membership/templates/membership/membership_detail.html:14
msgid "Mai-Star Games"
msgstr "Mai-Star Spiele"
#: templates/membership/membership_detail.html:20
#: src/membership/templates/membership/membership_detail.html:20
msgid "Profile Image"
msgstr "Profilbild"
#: templates/membership/membership_detail.html:28
#: src/membership/templates/membership/membership_detail.html:28
msgid "Member Since"
msgstr "Mitglied seit"
#: templates/membership/membership_detail.html:29
#: src/membership/templates/membership/membership_detail.html:29
msgid "Last Login"
msgstr "Letzte Anmeldung"
#: templates/membership/membership_detail.html:38
#: templates/membership/membership_detail.html:40
#: src/membership/templates/membership/membership_detail.html:39
#: src/membership/templates/membership/membership_detail.html:43
msgid "Points"
msgstr "Punkte"
#: templates/membership/membership_detail.html:42
#: src/membership/templates/membership/membership_detail.html:40
msgid "Maximum"
msgstr "Maximum"
#: src/membership/templates/membership/membership_detail.html:45
msgid "Games Total"
msgstr "Spiele gesamt"
#: templates/membership/membership_detail.html:43
#: templates/membership/membership_detail.html:45
#: src/membership/templates/membership/membership_detail.html:46
#: src/membership/templates/membership/membership_detail.html:48
msgid "Won"
msgstr "Gewonnen"
#: templates/membership/membership_detail.html:43
#: templates/membership/membership_detail.html:45
#: src/membership/templates/membership/membership_detail.html:46
#: src/membership/templates/membership/membership_detail.html:48
msgid "Good"
msgstr "Gut"
#: templates/membership/membership_detail.html:45
#: src/membership/templates/membership/membership_detail.html:48
msgid "Current Season"
msgstr "Aktuelle Saison"
#: templates/membership/membership_detail.html:55
#: src/membership/templates/membership/membership_detail.html:58
msgid "Edit Profile"
msgstr "Profil bearbeiten"
#: templates/membership/membership_detail.html:59
#: templates/registration/password_change_form.html:23
#: src/membership/templates/membership/membership_detail.html:62
#: src/membership/templates/registration/password_change_form.html:23
msgid "Change Password"
msgstr "Passwort ändern"
#: templates/membership/membership_detail.html:63
#: templates/membership/membership_detail.html:67
#: templates/membership/membership_detail.html:71
#: src/membership/templates/membership/membership_detail.html:66
#: src/membership/templates/membership/membership_detail.html:70
#: src/membership/templates/membership/membership_detail.html:74
#, python-format
msgid "Associate with %(name)s"
msgstr "Verbinde mit %(name)s"
#: templates/membership/membership_form.html:4
#: templates/membership/membership_form.html:6
#: templates/membership/membership_form.html:11
#: src/membership/templates/membership/membership_form.html:4
#: src/membership/templates/membership/membership_form.html:6
#: src/membership/templates/membership/membership_form.html:11
msgid "Edit Userprofile"
msgstr "Profil bearbeiten"
#: templates/membership/membership_form.html:15
#: src/membership/templates/membership/membership_form.html:15
msgid "Reset"
msgstr "Zurücksetzen"
#: templates/membership/membership_form.html:16
#: src/membership/templates/membership/membership_form.html:16
msgid "Save"
msgstr "Speichern"
#: templates/membership/register_form.html:4
#: templates/membership/register_form.html:7
#: src/membership/templates/membership/register_form.html:4
#: src/membership/templates/membership/register_form.html:7
msgid "Registration"
msgstr "Registrieren"
#: templates/membership/register_form.html:9
#: src/membership/templates/membership/register_form.html:9
msgid ""
"After you've provided your account data, you'll receive\n"
" an email asking you to verify your email address. You have to click on the\n"
@@ -371,41 +377,42 @@ msgstr ""
"Nach dem du deine Daten eingegeben hast, wirst du eine E-Mail zur Bestätigung bekommen.\n"
"Bitte klicke auf den Link in dieser E-Mail zur Verifizierung, erst dann ist die Anmeldung möglich."
#: templates/membership/register_form.html:20
#: src/membership/templates/membership/register_form.html:20
msgid "name"
msgstr "Name"
#: templates/membership/register_form.html:26
#: templates/registration/login.html:41
#: src/membership/templates/membership/register_form.html:26
#: src/membership/templates/registration/login.html:41
msgid "login"
msgstr "Anmelden"
#: templates/membership/register_form.html:39
#: src/membership/templates/membership/register_form.html:39
msgid "reset"
msgstr "Zurücksetzen"
#: templates/membership/register_form.html:41
#: templates/registration/login.html:35
#: src/membership/templates/membership/register_form.html:41
#: src/membership/templates/registration/login.html:35
msgid "register"
msgstr "Registrieren"
#: templates/membership/register_successful.html:5
#: templates/membership/register_successful.html:7
#: templates/membership/register_successful.html:10
#: src/membership/templates/membership/register_successful.html:5
#: src/membership/templates/membership/register_successful.html:7
#: src/membership/templates/membership/register_successful.html:10
msgid "Activation sent"
msgstr "Aktivierung wurde zugesendet"
#: templates/registration/login.html:4 templates/registration/login.html:11
#: templates/registration/login.html:53
#: templates/registration/password_reset_complete.html:13
#: src/membership/templates/registration/login.html:4
#: src/membership/templates/registration/login.html:11
#: src/membership/templates/registration/login.html:53
#: src/membership/templates/registration/password_reset_complete.html:13
msgid "Login"
msgstr "Anmelden"
#: templates/registration/login.html:17
#: src/membership/templates/registration/login.html:17
msgid "Have you already registered?"
msgstr "Bereits registriert?"
#: templates/registration/login.html:18
#: src/membership/templates/registration/login.html:18
#| msgid ""
#| "\n"
#| "<p>As a registered member you can:</p>\n"
@@ -435,7 +442,7 @@ msgstr ""
" <li>Vereinsmitglieder haben auch vollen Zugang zu unserem Ranking System</li>\n"
"</ul>"
#: templates/registration/login.html:27
#: src/membership/templates/registration/login.html:27
#| msgid ""
#| "\n"
#| "<p>You can register here with your Google, or Facebook account.\n"
@@ -454,7 +461,7 @@ msgstr ""
"Wenn du so etwas nicht besitzt, oder nicht verwenden möchtest, \n"
"kannst du auch das Registrierungsformular ausfüllen.</p>"
#: templates/registration/login.html:45
#: src/membership/templates/registration/login.html:45
#| msgid "Your username and password didn't match. Please try again."
msgid ""
"Your username and password didn't match. Please try\n"
@@ -463,42 +470,42 @@ msgstr ""
"Benutzername und Passwort stimmen nicht überein. Bitte noch einmal "
"versuchen."
#: templates/registration/login.html:50
#: src/membership/templates/registration/login.html:50
msgid "Forgot your Password?"
msgstr "Passwort vergessen?"
#: templates/registration/login.html:60
#: src/membership/templates/registration/login.html:60
msgid "or login with an existing Account"
msgstr "oder über einen existierenden Account anmelden"
#: templates/registration/login.html:63
#: src/membership/templates/registration/login.html:63
msgid "Login with Facebook"
msgstr "Über Facebook anmelden"
#: templates/registration/login.html:66
#: src/membership/templates/registration/login.html:66
msgid "Login with Twitter"
msgstr "Über Twitter anmelden"
#: templates/registration/login.html:69
#: src/membership/templates/registration/login.html:69
msgid "Login with Google"
msgstr "Über Google Anmelden"
#: templates/registration/password_change_done.html:4
#: templates/registration/password_change_done.html:7
#: src/membership/templates/registration/password_change_done.html:4
#: src/membership/templates/registration/password_change_done.html:7
msgid "Password change successful"
msgstr "Benutzerprofil erfolgreich geändert."
#: templates/registration/password_change_done.html:8
#: src/membership/templates/registration/password_change_done.html:8
msgid "Your password was changed."
msgstr "Passwort geändet"
#: templates/registration/password_change_form.html:4
#: templates/registration/password_change_form.html:9
#: templates/registration/password_change_form.html:16
#: src/membership/templates/registration/password_change_form.html:4
#: src/membership/templates/registration/password_change_form.html:9
#: src/membership/templates/registration/password_change_form.html:16
msgid "Password change"
msgstr "Passwort wechseln"
#: templates/registration/password_change_form.html:10
#: src/membership/templates/registration/password_change_form.html:10
msgid ""
"Please enter your old password, for security's sake, and then enter your new"
" password twice so we can verify you typed it in correctly."
@@ -506,23 +513,23 @@ msgstr ""
"Zur Sicherheit bitte altes Passwort einmal und das gewünschte neue Passwort"
" zweimal angeben, so können Tippfehler abgefangen werden."
#: templates/registration/password_reset_complete.html:4
#: templates/registration/password_reset_complete.html:6
#: templates/registration/password_reset_complete.html:9
#: src/membership/templates/registration/password_reset_complete.html:4
#: src/membership/templates/registration/password_reset_complete.html:6
#: src/membership/templates/registration/password_reset_complete.html:9
msgid "Password reset complete"
msgstr "Das Rücksetzen des Passwortes ist abgeschlossen."
#: templates/registration/password_reset_complete.html:10
#: src/membership/templates/registration/password_reset_complete.html:10
msgid "Your password has been set. You may go ahead and log in now."
msgstr "Das Passwort wurde gesetzt, Du kannst dich nun damit anmelden."
#: templates/registration/password_reset_confirm.html:4
#: templates/registration/password_reset_confirm.html:6
#: templates/registration/password_reset_confirm.html:15
#: src/membership/templates/registration/password_reset_confirm.html:4
#: src/membership/templates/registration/password_reset_confirm.html:6
#: src/membership/templates/registration/password_reset_confirm.html:15
msgid "Enter new password"
msgstr "Neues Passwort eingeben"
#: templates/registration/password_reset_confirm.html:12
#: src/membership/templates/registration/password_reset_confirm.html:12
msgid ""
"Please enter your new password twice so we can verify you typed it in "
"correctly."
@@ -530,15 +537,15 @@ msgstr ""
"Bitte das Passwort zweimal eingeben, um sicher zu stellen das es korrekt "
"eingetippt wurde."
#: templates/registration/password_reset_confirm.html:18
#: src/membership/templates/registration/password_reset_confirm.html:18
msgid "Change my password"
msgstr "Passwort ändern"
#: templates/registration/password_reset_confirm.html:26
#: src/membership/templates/registration/password_reset_confirm.html:26
msgid "Password reset unsuccessful"
msgstr "Passwort rücksetzen fehlgeschlagen"
#: templates/registration/password_reset_confirm.html:27
#: src/membership/templates/registration/password_reset_confirm.html:27
msgid ""
"The password reset link was invalid, possibly because it has already been "
"used. Please request a new password reset."
@@ -546,22 +553,22 @@ msgstr ""
"Der Link für die Rücksetzung des Passwortes war ungültig, vermutlich wurde "
"er schon einmal benutzt. Bitte eine neue Rücksetzung beantragen."
#: templates/registration/password_reset_done.html:4
#: templates/registration/password_reset_done.html:6
#: templates/registration/password_reset_done.html:12
#: src/membership/templates/registration/password_reset_done.html:4
#: src/membership/templates/registration/password_reset_done.html:6
#: src/membership/templates/registration/password_reset_done.html:12
msgid "Password reset successful"
msgstr "Passwort erfolgreich zurückgesetzt."
#: templates/registration/password_reset_form.html:4
#: templates/registration/password_reset_form.html:6
#: src/membership/templates/registration/password_reset_form.html:4
#: src/membership/templates/registration/password_reset_form.html:6
msgid "Password reset"
msgstr "Passwort zurücksetzen"
#: templates/registration/password_reset_form.html:21
#: src/membership/templates/registration/password_reset_form.html:21
msgid "Transmit"
msgstr "Übermitteln"
#: views.py:61
#: src/membership/views.py:61
msgid ""
"Activation successful. You can now login anytime with you username "
"and password."
@@ -569,14 +576,14 @@ msgstr ""
"Die Aktivierung war erfolgreich. Du kannst dich ab jetzt jederzeit mit "
"deinem Benutzernamen und Passwort anmelden."
#: views.py:88
#: src/membership/views.py:88
msgid "User Profile changed successfully"
msgstr "Benutzerprofil erfolgreich geändert."
#: views.py:112
#: src/membership/views.py:112
#| msgid "No %(verbose_name)s found matching the query"
msgid "No Membership found matching the query"
msgstr "Keine Mitgliedschaft gefunden welche der Anfrage entspricht"
msgstr "Kein Mitglied gefunden welche der Anfrage entspricht"
#~ msgid "Given Name"
#~ msgstr "Vorname"

View File

@@ -1,17 +1,17 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
import django.core.validators
import django.contrib.auth.models
from django.conf import settings
import django.core.validators
import django.utils.timezone
from django.conf import settings
from django.db import models, migrations
import membership.models
import utils
class Migration(migrations.Migration):
dependencies = [
('auth', '0006_require_contenttypes_0002'),
]
@@ -21,56 +21,94 @@ class Migration(migrations.Migration):
name='Membership',
fields=[
('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(
max_length=128, verbose_name='password')),
('last_login', models.DateTimeField(
null=True, verbose_name='last login', blank=True)),
('is_superuser', models.BooleanField(default=False,
help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')),
('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')),
help_text='Designates that this user has all permissions without explicitly assigning them.',
verbose_name='superuser status')),
('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,
verbose_name='first name', blank=True)),
verbose_name='first name',
blank=True)),
('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,
verbose_name='email address', blank=True)),
verbose_name='email address',
blank=True)),
('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(
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(
default=django.utils.timezone.now, verbose_name='date joined')),
('gender', models.CharField(max_length=1, verbose_name='Geschlecht', choices=[
(b'm', 'M\xe4nnlich'), (b'f', 'Weiblich')])),
default=django.utils.timezone.now,
verbose_name='date joined')),
('gender',
models.CharField(max_length=1, verbose_name='Geschlecht',
choices=[
(b'm', 'M\xe4nnlich'),
(b'f', 'Weiblich')])),
('website', models.URLField(blank=True)),
('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,
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,
verbose_name='Geburtstag', blank=True)),
verbose_name='Geburtstag',
blank=True)),
('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,
null=True, verbose_name='Adresse', blank=True)),
null=True,
verbose_name='Adresse',
blank=True)),
('post_code', models.PositiveSmallIntegerField(
null=True, verbose_name='Postleitzahl', blank=True)),
('city', models.CharField(max_length=75,
null=True, verbose_name='Ort', blank=True)),
null=True, verbose_name='Ort',
blank=True)),
('deposit', models.PositiveSmallIntegerField(
default=0, editable=False)),
('registration_date', models.DateField(auto_now_add=True)),
('paid_until', models.DateField(null=True,
verbose_name='Bezahlt bis', blank=True)),
verbose_name='Bezahlt bis',
blank=True)),
('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)),
('groups', models.ManyToManyField(related_query_name='user', related_name='user_set', to='auth.Group', blank=True,
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')),
('groups', models.ManyToManyField(related_query_name='user',
related_name='user_set',
to='auth.Group', blank=True,
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={
'ordering': ('last_name', 'first_name'),
@@ -86,11 +124,13 @@ class Migration(migrations.Migration):
name='ActivationRequest',
fields=[
('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(
max_length=40, verbose_name='Aktivierungsschl\xfcssel')),
('user', models.OneToOneField(
verbose_name='Benutzer', to=settings.AUTH_USER_MODEL)),
verbose_name='Benutzer', to=settings.AUTH_USER_MODEL,
on_delete=models.CASCADE)),
],
options={
'verbose_name': 'Ausstehende Aktivierung',

View File

@@ -7,7 +7,7 @@ from os import path
from django.conf import settings
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.utils import timezone
from django.utils.translation import ugettext as _
@@ -80,6 +80,7 @@ class ActivationRequest(models.Model):
"""
user = models.OneToOneField(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
verbose_name=_('user')
)
activation_key = models.CharField(_('activation key'), max_length=40)
@@ -215,6 +216,10 @@ class Membership(AbstractUser):
verbose_name = _('Membership')
verbose_name_plural = _('Memberships')
@property
def full_name(self):
return " ".join([self.last_name, self.first_name])
def __str__(self):
return self.username

View File

@@ -35,7 +35,10 @@
<h3>Mahjong</h3>
<ul>
{% 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%}
<li><strong>{{kyu_dan_ranking.kyu}}. Kyu: </strong> {{ kyu_dan_ranking.kyu_points }} {% trans 'Points' %}</li>
{% endif %}

View File

@@ -7,7 +7,7 @@ from django import http
from django.conf import settings
from django.contrib import auth, messages
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.utils.decorators import method_decorator
from django.utils.translation import ugettext as _

File diff suppressed because it is too large Load Diff

View File

@@ -31,3 +31,15 @@ class CompressHtmlMiddleware(object):
response.content = strip_spaces_between_tags(
response.content).strip()
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