Compare commits
156 Commits
css3_redes
...
django2
| Author | SHA1 | Date | |
|---|---|---|---|
| 0d3c47d8b3 | |||
| 95f5e0b920 | |||
| b12203911e | |||
| a733130b65 | |||
| 483e6f215d | |||
| 2bdf8c9692 | |||
| 36e24b7da8 | |||
| 79a5cdb868 | |||
| 5ed8976074 | |||
| 12930c2ec6 | |||
| e792da7459 | |||
| b01dc33cb4 | |||
| 1bdce47375 | |||
| bb7d4c8a08 | |||
| a3ac722acf | |||
| 1313d96e8f | |||
| f2836baae5 | |||
| d67719cc17 | |||
| 12773463a6 | |||
| a8536fc776 | |||
| ba4620705f | |||
| 4db5c09387 | |||
| 40d2120d7e | |||
| 0768641184 | |||
| f7e9585932 | |||
| fae5f30cfa | |||
| 99ceb54db1 | |||
| e95ad8bf0a | |||
| f841c1736e | |||
| 4b68f62929 | |||
| 436a77d036 | |||
| fc8daaaf25 | |||
| a10b2706c3 | |||
| c3d213934e | |||
| afc471d27f | |||
| baa9660b7c | |||
| 1d18356c8c | |||
| 3dcd1aeffa | |||
| 5a32dbf0af | |||
| 5affdb0f5c | |||
| 65b8208987 | |||
| 9f0126be54 | |||
| 4f61b1e73b | |||
| 0f71bb5360 | |||
| df4d8c83a6 | |||
| 3e64f4d757 | |||
| f2533273e9 | |||
| 121167f1fe | |||
| 595341a53b | |||
| ac9b3e06c0 | |||
| af46768e7d | |||
| 9fdf725702 | |||
| 10bb990539 | |||
| 2f5f834bba | |||
| 90cefd8739 | |||
| 722c155c17 | |||
| a4c4f96c06 | |||
| 3b162fc59c | |||
| 19bbb5a226 | |||
| e9e997e38d | |||
| 3a3c8ec0c5 | |||
| 4aab611026 | |||
| d5995bc612 | |||
| 3ef947f128 | |||
| 28f7292c9d | |||
| 7b631230da | |||
| 6804319c28 | |||
| 0a793b7954 | |||
| a2df81d62e | |||
| 4b0a5c9c82 | |||
| afd163298c | |||
| 6796b58d4c | |||
| cce6ac6014 | |||
| 0b2e040fc9 | |||
| 192721452e | |||
| fdbf819092 | |||
| 10c27784ee | |||
| 9f6fffa4f4 | |||
| 8ab99ec039 | |||
| b7fab97715 | |||
| 1fdf88c6d2 | |||
| c030a31e2b | |||
| b20b988e5d | |||
| ade2a568f7 | |||
| cf0e5e778c | |||
| c5781246fe | |||
| c7b714c41b | |||
| f51155cfac | |||
| 1315bc4225 | |||
| 97749bfd2e | |||
| bb5081a78b | |||
| 84880281c6 | |||
| 854fd38740 | |||
| cffbd30d7e | |||
| 6de1ecb102 | |||
| 3a611ca9da | |||
| bf12060c3b | |||
| 68c484afc9 | |||
| 5ad628f33a | |||
| 92470514c4 | |||
| 36272c60d6 | |||
| d33e5fc8c6 | |||
| c428f6ed1f | |||
| bb110da5a2 | |||
| 9276e97c36 | |||
| 35a51091bf | |||
| fd244f10e8 | |||
| c0c48f950a | |||
| 0a45cf1fd8 | |||
| d9e0d5596c | |||
| a3c02ae73a | |||
| e6f2528a0e | |||
| 638ec96c25 | |||
| 34d327f183 | |||
| 002eb40ea5 | |||
| d97c9db539 | |||
| 7ef69849a4 | |||
| 8719c2377a | |||
| abeb86d48f | |||
| 42a6ebedf9 | |||
| b3ab9798b5 | |||
| ce218080b2 | |||
| 63e099b7c5 | |||
| a26a91c360 | |||
| cf0bbb4c8f | |||
| 321531c160 | |||
| 5437b7b8de | |||
| 10bdaaa98c | |||
| 9b4ab374c6 | |||
| ae87414584 | |||
| 120b4ea17f | |||
| fcb15c7e7e | |||
| 299418ee62 | |||
| e5f0d7f6fe | |||
| 53974dcd48 | |||
| 222dd060b1 | |||
| bbe16b2d13 | |||
| ba44e97e9a | |||
| 8595959872 | |||
| 9b0967adc8 | |||
| ac78dc3d75 | |||
| 57b7d5a84b | |||
| 6b37aa84b0 | |||
|
|
0c7bd466c7 | ||
| 0bdd409dc2 | |||
|
|
9c4ec20394 | ||
|
|
6f1512906d | ||
|
|
12c9a6e0e9 | ||
|
|
8cac20dd2b | ||
|
|
b1586efbab | ||
|
|
088efe2f39 | ||
|
|
b9ec418e5e | ||
|
|
836eee983d | ||
|
|
79eaeb34ad | ||
|
|
bafbf38612 | ||
|
|
b96b485b61 |
2
.coafile
2
.coafile
@@ -5,4 +5,4 @@ language = python
|
||||
bears = PEP8Bear,PyLintBear,PyUnusedCodeBear
|
||||
use_spaces = True
|
||||
pylint_cli_options = --load-plugins pylint_django
|
||||
pylint_disable = E1101,R0201,R0901,R0903
|
||||
#pylint_disable = E1101,R0201,R0901,R0903
|
||||
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -62,6 +62,7 @@ docs/_build/
|
||||
target/
|
||||
|
||||
#Django Development
|
||||
backup/
|
||||
/bower_components/
|
||||
/media/
|
||||
/node_modules/
|
||||
@@ -70,3 +71,4 @@ target/
|
||||
.[a-zA-Z]*
|
||||
local_settings.py
|
||||
sample.xlsx
|
||||
venv/
|
||||
|
||||
@@ -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: {
|
||||
|
||||
80
Makefile
Normal file
80
Makefile
Normal file
@@ -0,0 +1,80 @@
|
||||
ASSESTS=requirements static
|
||||
DEV_REQUIREMENTS=requirements/development.txt
|
||||
DJANGO_SETTINGS_MODULE="kasu.settings"
|
||||
EXCLUDE_FILES=*.pyc
|
||||
PROJECT_PATH=$(CURDIR)
|
||||
PYTHON=${VENV_PATH}/bin/python3
|
||||
REQUIREMENTS=requirements/base.txt
|
||||
SRC_PATH=${PROJECT_PATH}/src
|
||||
MANAGE_PY=${PYTHON} -Wa ${SRC_PATH}/manage.py
|
||||
SSH_LOGIN=kasu@kasu.at
|
||||
VENV_PATH=$(PROJECT_PATH)/venv
|
||||
|
||||
.PHONY: clean venv
|
||||
|
||||
all: cleanup migrate testserver
|
||||
|
||||
venv: $(VENV_PATH)/bin/activate
|
||||
$(VENV_PATH)/bin/activate:
|
||||
@test -d $(VENV_PATH) || python3 -m venv --clear $(VENV_PATH)
|
||||
|
||||
dev-requirements: venv ${DEV_REQUIREMENTS}
|
||||
${PYTHON} -m pip install -qU pip
|
||||
${PYTHON} -m pip install -qUr ${DEV_REQUIREMENTS}
|
||||
|
||||
requirements: venv ${REQUIREMENTS}
|
||||
${PYTHON} -m pip install -qU pip
|
||||
${PYTHON} -m pip install -qUr ${REQUIREMENTS}
|
||||
|
||||
grunt:
|
||||
-grunt
|
||||
|
||||
requirements_remote:
|
||||
@echo "Installing requirements"
|
||||
ssh ${SSH_LOGIN} "virtualenv/bin/pip install -qUr ${REQUIREMENTS}"
|
||||
|
||||
sync_assets:
|
||||
@echo "Syncing project assets ..."
|
||||
rsync -qr --copy-links --delete ${ASSESTS} ${SSH_LOGIN}:~/
|
||||
|
||||
sync_src:
|
||||
@echo "Syncing Sourcecode ..."
|
||||
find . -name EXCLUDE_FILES -exec rm -rf {} \;
|
||||
rsync -qr --copy-links --delete ${SRC_PATH} ${SSH_LOGIN}:~/ --exclude 'src/kasu/local_settings.py'
|
||||
|
||||
restart_remote:
|
||||
@echo "Rebuild and reload django..."
|
||||
ssh ${SSH_LOGIN} "~/virtualenv/bin/python ~/src/manage.py collectstatic -l --noinput -v0"
|
||||
ssh ${SSH_LOGIN} "~/init/kasu restart"
|
||||
|
||||
sync: sync_assets requirements_remote sync_src restart_remote
|
||||
|
||||
testserver: venv
|
||||
${MANAGE_PY} runserver 127.0.0.1:8000
|
||||
|
||||
messages: venv
|
||||
@echo "aktualisiere Übersetzungen..."
|
||||
@(for d in ${SRC_PATH}/*; do \
|
||||
test -d $$d/locale && cd $$d && ${VENV_PATH}/bin/django-admin.py makemessages -l de;\
|
||||
done)
|
||||
${MANAGE_PY} compilemessages -v0
|
||||
|
||||
cleanup-pyc:
|
||||
@echo "lösche den Python Compiler Cache..."
|
||||
find ${SRC_PATH} -name ${EXCLUDE_FILES} -exec rm -rf {} \;
|
||||
|
||||
collectstatic: venv
|
||||
@echo "aktualisiere Statics"
|
||||
${MANAGE_PY} collectstatic --noinput -c -v0
|
||||
|
||||
thumbnails: venv
|
||||
@echo "Erstelle Vorschaubilder"
|
||||
${MANAGE_PY} thumbnail_cleanup
|
||||
|
||||
cleanup: requirements messages cleanup-pyc collectstatic thumbnails
|
||||
|
||||
migrate: venv
|
||||
${MANAGE_PY} migrate
|
||||
|
||||
migrations: venv
|
||||
${MANAGE_PY} makemigrations
|
||||
@@ -1,26 +0,0 @@
|
||||
#!/bin/bash
|
||||
source .virtualenv/bin/activate
|
||||
echo "aktualisiere Übersetzungen..."
|
||||
cd src
|
||||
unset DJANGO_SETTINGS_MODULE
|
||||
for dir in *
|
||||
do
|
||||
if [ -d ${dir}/locale ]
|
||||
then
|
||||
echo -n "$dir: "
|
||||
cd ${dir}
|
||||
django-admin.py makemessages -l de
|
||||
cd ..
|
||||
fi
|
||||
done
|
||||
sleep 5s
|
||||
export DJANGO_SETTINGS_MODULE="kasu.settings"
|
||||
./manage.py compilemessages
|
||||
|
||||
echo "lösche den Python Compiler Cache..."
|
||||
find . -name "*.pyc" -exec rm -rf {} \;
|
||||
echo "Aktualisiere Statics"
|
||||
./manage.py collectstatic --noinput -c
|
||||
echo "Erstelle Vorschaubilder"
|
||||
./manage.py thumbnail_cleanup
|
||||
touch kasu/wsgi.py
|
||||
23
bin/sync.sh
23
bin/sync.sh
@@ -1,23 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
SSH_LOGIN="kasu@s21.wservices.ch"
|
||||
SYNC_ASSESTS="requirements"
|
||||
SYNC_SOURCECODE="src"
|
||||
EXCLUDE_FILES="*.pyc"
|
||||
|
||||
grunt
|
||||
|
||||
echo "Syncing project assets ..."
|
||||
rsync -r --copy-links --delete ${SYNC_ASSESTS} ${SSH_LOGIN}:~/
|
||||
|
||||
echo "Installing dependecies"
|
||||
ssh ${SSH_LOGIN} "virtualenv/bin/pip install --upgrade -r requirements/base.txt"
|
||||
|
||||
echo "Syncing Sourcecode ..."
|
||||
find . -name "*.pyc" -exec rm -rf {} \;
|
||||
rsync -r --copy-links --delete ${SYNC_SOURCECODE} ${SSH_LOGIN}:~/ --exclude 'src/kasu/local_settings.py'
|
||||
|
||||
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} "~/init/kasu restart"
|
||||
@@ -1,6 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
export DJANGO_SETTINGS_MODULE=kasu.settings
|
||||
source .virtualenv/bin/activate
|
||||
./manage.py runserver 0.0.0.0:8000
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
beautifulsoup4
|
||||
django<2.0
|
||||
django < 3.0
|
||||
django-appconf
|
||||
django-ckeditor
|
||||
django-contrib-comments
|
||||
@@ -8,14 +8,13 @@ django-compressor
|
||||
django-extra-views
|
||||
django-markdown
|
||||
django-recaptcha
|
||||
easy-thumbnails
|
||||
git+https://github.com/SmileyChris/easy-thumbnails.git
|
||||
icalendar
|
||||
openpyxl
|
||||
markdown
|
||||
pillow
|
||||
psycopg2
|
||||
psycopg2-binary
|
||||
PyJWT
|
||||
pytz
|
||||
requests
|
||||
requests-oauthlib
|
||||
social-auth-app-django
|
||||
social-auth-core
|
||||
|
||||
@@ -4,5 +4,5 @@ django-rosetta
|
||||
sqlparse
|
||||
|
||||
# Code Linting
|
||||
coala-bears
|
||||
pylint>=2.0
|
||||
pylint-django
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
from django.core.cache import cache
|
||||
|
||||
from . import models
|
||||
from utils import STATUS_PUBLISHED
|
||||
|
||||
|
||||
def content_menus(request):
|
||||
@@ -18,8 +19,8 @@ def content_menus(request):
|
||||
:param request: a Django request object
|
||||
:return: a dict with the template variables mentioned above
|
||||
"""
|
||||
current_page = None
|
||||
current_top_page = None
|
||||
current_page = models.Page()
|
||||
current_top_page = models.Page()
|
||||
current_path = request.path_info[1:request.path_info.rfind('.')]
|
||||
|
||||
# erzeuge das Top-Level Menü
|
||||
@@ -50,7 +51,7 @@ def content_menus(request):
|
||||
break
|
||||
current_path = current_path[0:current_path.rfind('.')]
|
||||
|
||||
return {'top_menu_items': top_level_pages,
|
||||
return {'top_menu_items': top_level_pages.filter(status=STATUS_PUBLISHED),
|
||||
'current_top_page': current_top_page,
|
||||
'current_path': current_path,
|
||||
'current_page': current_page}
|
||||
|
||||
Binary file not shown.
@@ -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: 2019-12-13 23:38+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"
|
||||
@@ -32,130 +32,125 @@ msgstr "Neueste Kommentare auf Kasu.at "
|
||||
msgid "Kasu - latest comments"
|
||||
msgstr "Kasu - neue Kommentare"
|
||||
|
||||
#: forms.py:57 models.py:315
|
||||
#: forms.py:57 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
|
||||
#: models.py:76
|
||||
msgid "Headline"
|
||||
msgstr "Schlagzeile"
|
||||
|
||||
#: models.py:70
|
||||
#: models.py:78
|
||||
msgid "Content"
|
||||
msgstr "Inhalt"
|
||||
|
||||
#: models.py:72 models.py:143 templates/content/article_detail.html:25
|
||||
#: models.py:82 models.py:150 templates/content/article_detail.html:28
|
||||
msgid "Category"
|
||||
msgstr "Kategorie"
|
||||
|
||||
#: models.py:73 models.py:136
|
||||
#: models.py:83 models.py:143
|
||||
msgid "Image"
|
||||
msgstr "Bild"
|
||||
|
||||
#: models.py:75 models.py:138
|
||||
#: models.py:85 models.py:145
|
||||
msgid "Slug"
|
||||
msgstr "Slug"
|
||||
|
||||
#: models.py:77 templates/content/article_detail.html:23
|
||||
#: models.py:88 templates/content/article_detail.html:21
|
||||
msgid "Author"
|
||||
msgstr "Autor"
|
||||
|
||||
#: models.py:78
|
||||
#: models.py:89
|
||||
msgid "Status"
|
||||
msgstr "Status"
|
||||
|
||||
#: models.py:80
|
||||
#: models.py:91
|
||||
msgid "Created"
|
||||
msgstr "Erstellt"
|
||||
|
||||
#: models.py:81
|
||||
#: models.py:92
|
||||
msgid "Modified"
|
||||
msgstr "Bearbeitet"
|
||||
|
||||
#: models.py:86
|
||||
#: models.py:97
|
||||
msgid "Article"
|
||||
msgstr "Artikel"
|
||||
|
||||
#: models.py:87
|
||||
#: models.py:98
|
||||
msgid "Articles"
|
||||
msgstr "Artikel"
|
||||
|
||||
#: models.py:132 models.py:133
|
||||
#: models.py:139 models.py:140
|
||||
msgid "Name"
|
||||
msgstr "Name"
|
||||
|
||||
#: models.py:134 models.py:135
|
||||
#: models.py:141 models.py:142
|
||||
msgid "Description"
|
||||
msgstr "Beschreibung"
|
||||
|
||||
#: models.py:144
|
||||
#: models.py:151
|
||||
msgid "Categories"
|
||||
msgstr "Kategorien"
|
||||
|
||||
#: models.py:176 models.py:182
|
||||
#: models.py:182 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
|
||||
#: models.py:193 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
|
||||
#: models.py:200
|
||||
msgid "slug"
|
||||
msgstr "Slug"
|
||||
|
||||
#: models.py:197
|
||||
#: 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]/"
|
||||
"The name of the page as it will appear in URLs e.g http://domain.com/blog/"
|
||||
"[my-slug]/"
|
||||
msgstr "Wie die Seite in der URL aufscheint also http://domain.com/blog/[slug]"
|
||||
|
||||
#: models.py:206
|
||||
#: models.py:212
|
||||
msgid "Path"
|
||||
msgstr "Pfad"
|
||||
|
||||
#: models.py:218
|
||||
#: models.py:224
|
||||
msgid "Position"
|
||||
msgstr "Position"
|
||||
|
||||
#: models.py:223
|
||||
#: models.py:229
|
||||
msgid "status"
|
||||
msgstr "Status"
|
||||
|
||||
#: models.py:226 models.py:228
|
||||
#| msgid "Description"
|
||||
#: models.py:232 models.py:234
|
||||
msgid "search description"
|
||||
msgstr "Suchbeschreibung"
|
||||
msgstr "Beschreibung für Suchfunktion"
|
||||
|
||||
#: models.py:231
|
||||
#| msgid "Content"
|
||||
#: models.py:237
|
||||
msgid "content type"
|
||||
msgstr "Inhaltstyp"
|
||||
|
||||
#: models.py:236
|
||||
#: models.py:242
|
||||
msgid "enable comments"
|
||||
msgstr "Kommentare möglich"
|
||||
|
||||
#: models.py:241
|
||||
#: models.py:247
|
||||
msgid "Template"
|
||||
msgstr "Vorlage"
|
||||
|
||||
#: models.py:249
|
||||
#| msgid "created on"
|
||||
#: models.py:255
|
||||
msgid "first created at"
|
||||
msgstr "erstellt am"
|
||||
|
||||
#: models.py:254
|
||||
#: models.py:260
|
||||
msgid "latest updated at"
|
||||
msgstr "letzte Änderung"
|
||||
msgstr "letzte Aktualisierung am"
|
||||
|
||||
#: models.py:328
|
||||
#: models.py:331
|
||||
msgid "Page"
|
||||
msgstr "Seite"
|
||||
|
||||
#: models.py:329
|
||||
#: models.py:332
|
||||
msgid "Pages"
|
||||
msgstr "Seiten"
|
||||
|
||||
@@ -203,15 +198,15 @@ msgstr "neuer Artikel "
|
||||
msgid "back"
|
||||
msgstr "Zurück"
|
||||
|
||||
#: templates/content/article_detail.html:24
|
||||
#: templates/content/article_detail.html:25
|
||||
msgid "Created on"
|
||||
msgstr "Erstellt am"
|
||||
|
||||
#: templates/content/article_detail.html:36
|
||||
#: templates/content/article_detail.html:39
|
||||
msgid "share on"
|
||||
msgstr "Teile auf"
|
||||
|
||||
#: templates/content/article_detail.html:51 views.py:156
|
||||
#: templates/content/article_detail.html:48 views.py:156
|
||||
msgid "Edit Article"
|
||||
msgstr "Artikel bearbeiten"
|
||||
|
||||
@@ -258,12 +253,12 @@ msgstr "Diese Kategorie existiert nicht."
|
||||
msgid "Create Article"
|
||||
msgstr "Artikel erstellen"
|
||||
|
||||
#: views.py:233
|
||||
#: views.py:237
|
||||
#, python-format
|
||||
msgid "No Page found matching the Path %s"
|
||||
msgstr "Keine Seite unter dem Pfad %s gefunden"
|
||||
|
||||
#: views.py:262
|
||||
#: views.py:266
|
||||
#, python-format
|
||||
msgid "No PDF Document found matching the Path %s"
|
||||
msgstr "Kein PDF Dokument unter dem Pfad %s gefunden."
|
||||
|
||||
@@ -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',
|
||||
|
||||
27
src/content/migrations/0007_auto_20171214_1215.py
Normal file
27
src/content/migrations/0007_auto_20171214_1215.py
Normal file
@@ -0,0 +1,27 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11.8 on 2017-12-14 11:15
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('content', '0006_auto_20171115_0653'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='article',
|
||||
name='author',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL, verbose_name='Autor'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='article',
|
||||
name='category',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='content.Category', verbose_name='Kategorie'),
|
||||
),
|
||||
]
|
||||
58
src/content/migrations/0008_auto_20190106_1954.py
Normal file
58
src/content/migrations/0008_auto_20190106_1954.py
Normal file
@@ -0,0 +1,58 @@
|
||||
# Generated by Django 2.1.5 on 2019-01-06 18:54
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('content', '0007_auto_20171214_1215'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='article',
|
||||
name='status',
|
||||
field=models.SmallIntegerField(choices=[(-1, 'Rejected'), (0, 'Waiting...'), (1, 'Published')], default=1, verbose_name='Status'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='page',
|
||||
name='date_created',
|
||||
field=models.DateTimeField(auto_now_add=True, db_index=True, verbose_name='erstellt am'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='page',
|
||||
name='date_modified',
|
||||
field=models.DateTimeField(auto_now=True, verbose_name='letzte Aktualisierung am'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='page',
|
||||
name='description_de',
|
||||
field=models.TextField(blank=True, verbose_name='Beschreibung für Suchfunktion'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='page',
|
||||
name='description_en',
|
||||
field=models.TextField(blank=True, verbose_name='Beschreibung für Suchfunktion'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='page',
|
||||
name='slug',
|
||||
field=models.SlugField(help_text='Wie die Seite in der URL aufscheint also http://domain.com/blog/[slug]', max_length=100, verbose_name='Slug'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='page',
|
||||
name='status',
|
||||
field=models.SmallIntegerField(choices=[(-1, 'Rejected'), (0, 'Waiting...'), (1, 'Published')], default=0, verbose_name='Status'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='page',
|
||||
name='title_de',
|
||||
field=models.CharField(help_text='Der Seitentitel der öffentlich angezeigt werden soll', max_length=255, verbose_name='Titel'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='page',
|
||||
name='title_en',
|
||||
field=models.CharField(blank=True, help_text='Der Seitentitel der öffentlich angezeigt werden soll', max_length=255, verbose_name='Title'),
|
||||
),
|
||||
]
|
||||
@@ -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."""
|
||||
|
||||
@@ -69,7 +69,7 @@
|
||||
<h3><a href="{{article.get_absolute_url}}">{{article.headline}}</a></h3>
|
||||
<ul class="info">
|
||||
<li><span class="fa fa-calendar-o" title="{% trans 'created on' %}"></span> <time
|
||||
datetime="{{article.date_created|date:'c'}}">{{ article.date_created|date }}</time></li>
|
||||
datetime="{{article.date_created|date:'Y-m-d\TH:i:sO'}}">{{ article.date_created|date }}</time></li>
|
||||
<li><span class="fa fa-user" title="{% trans 'by' %}"></span> {{ article.author }}</li>
|
||||
<li><span class="fa fa-comments" title="{% trans 'comments' %}"></span> <a
|
||||
href="{{article.get_absolute_url}}#comments" >{{comment_count}} {% trans "comments" %}</a></li>
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
{% extends "base.html" %}
|
||||
{% load i18n comments thumbnail %}
|
||||
{% extends "base.html" %}{% load i18n comments thumbnail %}
|
||||
|
||||
{% block title %}{{ article.headline }}{% endblock %}
|
||||
|
||||
@@ -11,8 +10,7 @@
|
||||
<meta property="og:url" content="http://www.kasu.at{{ article.get_absolute_url }}"/>
|
||||
<meta property="og:image" content="http://www.kasu.at{{article.get_image|thumbnail_url:'article'}}"/>
|
||||
<meta property="og:description" content="{{article.content|striptags|truncatewords:25|force_escape}}"/>
|
||||
<link rel="image_src" type="image/jpeg" href="{{article.get_image|thumbnail_url:'article'}}" />
|
||||
{% endblock %}
|
||||
<link rel="image_src" type="image/jpeg" href="{{article.get_image|thumbnail_url:'article'}}"/>{% endblock %}
|
||||
|
||||
{% block itemscope %}itemscope itemtype="http://schema.org/Article"{% endblock %}
|
||||
|
||||
@@ -20,34 +18,31 @@
|
||||
<h1 itemprop="name">{{article.headline}}</h1>
|
||||
<div id="teaser_text">
|
||||
<ul class="info">
|
||||
<li><span class="fa fa-user"></span> <strong>{% trans 'Author' %}:</strong> <a href="{% url 'membership-details' article.author %}" itemprop="author">{{article.author}}</a></li>
|
||||
<li><span class="fa fa-calendar-o"></span> <strong>{% trans 'Created on' %}: </strong><time datetime="{{article.date_created|date:'Y-m-d H:i'}}">{{ article.date_created|date:'DATE_FORMAT' }}</time></li>
|
||||
<li><span class="fa fa-tag"></span> <strong>{% trans "Category"%}: </strong><a href="{{ article.category.get_absolute_url }}" itemprop="articleSection">{{article.category.name}}</a></li>
|
||||
<li><span class="fa fa-user"></span> <strong>{% trans 'Author' %}:</strong>
|
||||
<a href="{% url 'membership-details' article.author %}" itemprop="author">{{article.author}}</a>
|
||||
</li>
|
||||
<li><span class="fa fa-calendar-o"></span>
|
||||
<strong>{% trans 'Created on' %}:</strong>
|
||||
<time datetime="{{article.date_created|date:'Y-m-d\TH:i:sO'}}">{{ article.date_created|date:'DATE_FORMAT' }}</time>
|
||||
</li>
|
||||
<li><span class="fa fa-tag"></span> <strong>{% trans "Category"%}:</strong>
|
||||
<a href="{{ article.category.get_absolute_url }}" itemprop="articleSection">{{article.category.name}}</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
{% endblock %}
|
||||
</div>{% endblock %}
|
||||
|
||||
{% block maincontent %}
|
||||
<div itemprop="articleBody" class="grid_12">
|
||||
<img alt="{{article.category.name}}" src="{{article.get_image|thumbnail_url:'article'}}" class="posting_image" itemprop="image"/>
|
||||
{{ article.content }}
|
||||
<img alt="{{article.category.name}}" src="{{article.get_image|thumbnail_url:'article'}}" class="posting_image" itemprop="image"/> {{ article.content }}
|
||||
</div>
|
||||
<p class="right">
|
||||
<strong>{% trans 'share on' %}:</strong>
|
||||
<a class="button" href="https://plus.google.com/share?url=http%3A%2F%2Fwww.kasu.at{{article.get_absolute_url|urlencode}}"
|
||||
onclick="javascript:window.open(this.href,'', 'menubar=no,toolbar=no,resizable=yes,scrollbars=yes,height=600,width=600');return false;"><span class="fa fa-google-plus"></span> Google+</a>
|
||||
<a class="button" href="https://plus.google.com/share?url=http%3A%2F%2Fwww.kasu.at{{article.get_absolute_url|urlencode}}" onclick="javascript:window.open(this.href,'', 'menubar=no,toolbar=no,resizable=yes,scrollbars=yes,height=600,width=600');return false;"><span class="fa fa-google-plus"></span> Google+</a>
|
||||
<a class="button" href="https://twitter.com/share?url=http%3A%2F%2Fwww.kasu.at{{article.get_absolute_url|urlencode}}" target='_blank'><span class="fa fa-twitter"></span> Twitter</a>
|
||||
<a class="button" href="http://facebook.com/sharer.php?u=http%3A%2F%2Fwww.kasu.at{{article.get_absolute_url|urlencode}}?t={{article.headline|urlencode}}" target="_blank"><span class="fa fa-facebook"></span> Facebook</a>
|
||||
</p>
|
||||
{% endblock %}
|
||||
</p>{% endblock %}
|
||||
|
||||
{% block comments%}
|
||||
{% render_comment_list for article %}
|
||||
{% render_comment_form for article %}
|
||||
{% endblock %}
|
||||
{% block comments%}{% render_comment_list for article %}{% render_comment_form for article %}{% endblock %}
|
||||
|
||||
{% block buttonbar %}
|
||||
{% if perms.content.change_article %}
|
||||
<a href="{% url 'edit-article' article.id %}" class="button"><span class="fa fa-pencil"></span> {% trans "Edit Article" %}</a>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
{% block buttonbar %}{% if perms.content.change_article %}
|
||||
<a href="{% url 'edit-article' article.id %}" class="button"><span class="fa fa-pencil"></span> {% trans "Edit Article" %}</a>{% endif %}{% endblock %}
|
||||
|
||||
@@ -179,6 +179,7 @@ class PageAddForm(WYSIWYGEditorMixin, generic.CreateView):
|
||||
|
||||
def get_initial(self):
|
||||
"""Try to get the path of the parent page as initial data."""
|
||||
if self.kwargs['path']:
|
||||
path = os.path.splitext(self.kwargs['path'])[0]
|
||||
if path.startswith('/'):
|
||||
path = path[1:]
|
||||
@@ -186,6 +187,8 @@ class PageAddForm(WYSIWYGEditorMixin, generic.CreateView):
|
||||
path = path[:-1]
|
||||
parent = models.Page.objects.get(path=path)
|
||||
return {'parent': parent}
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
class PageEditForm(WYSIWYGEditorMixin, generic.UpdateView):
|
||||
@@ -204,7 +207,8 @@ class PageEditForm(WYSIWYGEditorMixin, generic.UpdateView):
|
||||
:param queryset: Get the single item from this filtered queryset.
|
||||
:return:
|
||||
"""
|
||||
path = os.path.splitext(self.kwargs['path'])[0]
|
||||
path = self.kwargs['path'] or 'index.html'
|
||||
path = os.path.splitext(path)[0]
|
||||
queryset = queryset or self.get_queryset()
|
||||
if path.startswith('/'):
|
||||
path = path[1:]
|
||||
|
||||
Binary file not shown.
@@ -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: 2019-12-13 23:38+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"
|
||||
@@ -35,7 +35,7 @@ msgstr "Beginn"
|
||||
msgid "end"
|
||||
msgstr "Ende"
|
||||
|
||||
#: mixins.py:57 views.py:149
|
||||
#: mixins.py:76
|
||||
msgid "Event does not exist"
|
||||
msgstr "Veranstaltung gibt es nicht"
|
||||
|
||||
@@ -92,13 +92,13 @@ 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
|
||||
#: models.py:92 models.py:195 models.py:248
|
||||
msgid "first created at"
|
||||
msgstr "Erstellt am"
|
||||
msgstr "erstellt am"
|
||||
|
||||
#: models.py:97 models.py:200 models.py:252
|
||||
#: models.py:97 models.py:200 models.py:253
|
||||
msgid "latest updated at"
|
||||
msgstr "Geändert am"
|
||||
msgstr "letzte Aktualisierung am"
|
||||
|
||||
#: models.py:103
|
||||
msgid "Event"
|
||||
@@ -136,28 +136,28 @@ msgstr "Veranstaltungsort"
|
||||
msgid "Venues"
|
||||
msgstr "Veranstaltungsorte"
|
||||
|
||||
#: models.py:231
|
||||
#: models.py:232
|
||||
msgid "Startpage"
|
||||
msgstr "Startseite"
|
||||
|
||||
#: models.py:234
|
||||
#: models.py:235
|
||||
msgid "Display this Photo on the Startpage Teaser"
|
||||
msgstr "Foto als Teaser auf der Startseite verwenden."
|
||||
|
||||
#: models.py:236
|
||||
#: models.py:237
|
||||
msgid "Published on"
|
||||
msgstr "Veröffentlicht am"
|
||||
|
||||
#: models.py:238
|
||||
#: models.py:239
|
||||
msgid "Number of views"
|
||||
msgstr "Wie oft gesehen"
|
||||
|
||||
#: models.py:262 templates/events/event_archive.html:38
|
||||
#: models.py:263 templates/events/event_archive.html:38
|
||||
#: templates/events/event_list.html:18
|
||||
msgid "Event Image"
|
||||
msgstr "Veranstaltungsbild"
|
||||
|
||||
#: models.py:263
|
||||
#: models.py:264
|
||||
msgid "Event Images"
|
||||
msgstr "Veranstaltungsbilder"
|
||||
|
||||
@@ -165,9 +165,8 @@ msgstr "Veranstaltungsbilder"
|
||||
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
|
||||
#: templates/events/event_archive.html:42 templates/events/event_detail.html:85
|
||||
#: templates/events/event_list.html:22 templates/events/photo_detail.html:53
|
||||
msgid "Date"
|
||||
msgstr "Datum"
|
||||
|
||||
@@ -175,19 +174,17 @@ msgstr "Datum"
|
||||
msgid "Time"
|
||||
msgstr "Zeit"
|
||||
|
||||
#: templates/events/event_archive.html:49
|
||||
#: templates/events/photo_upload.html:16
|
||||
#: templates/events/event_archive.html:49 templates/events/photo_upload.html:16
|
||||
msgid "from"
|
||||
msgstr "von"
|
||||
|
||||
#: templates/events/event_archive.html:49
|
||||
#: templates/events/photo_upload.html:16
|
||||
#: templates/events/event_archive.html:49 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
|
||||
#: 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
|
||||
msgid "Location"
|
||||
msgstr "Ort"
|
||||
|
||||
@@ -196,16 +193,15 @@ msgstr "Ort"
|
||||
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
|
||||
#: 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
|
||||
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
|
||||
#: templates/events/event_archive.html:61 templates/events/event_detail.html:35
|
||||
#: templates/events/event_detail.html:51
|
||||
msgid "Hanchans"
|
||||
msgstr "Hanchans"
|
||||
|
||||
|
||||
@@ -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),
|
||||
),
|
||||
]
|
||||
|
||||
@@ -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),
|
||||
),
|
||||
]
|
||||
|
||||
32
src/events/migrations/0009_auto_20171214_1215.py
Normal file
32
src/events/migrations/0009_auto_20171214_1215.py
Normal file
@@ -0,0 +1,32 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11.8 on 2017-12-14 11:15
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('events', '0008_auto_20171115_0653'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='event',
|
||||
name='location',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='events.Location'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='photo',
|
||||
name='event',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='events.Event'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='photo',
|
||||
name='photographer',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL),
|
||||
),
|
||||
]
|
||||
48
src/events/migrations/0010_auto_20190106_1954.py
Normal file
48
src/events/migrations/0010_auto_20190106_1954.py
Normal file
File diff suppressed because one or more lines are too long
@@ -1,5 +1,8 @@
|
||||
"""Mixins for Events."""
|
||||
from django.db.models import Q
|
||||
from django.http import Http404
|
||||
from django.shortcuts import get_object_or_404
|
||||
from django.utils.translation import ugettext as _
|
||||
|
||||
from . import models
|
||||
|
||||
@@ -34,12 +37,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 +54,23 @@ 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)
|
||||
return queryset.prefetch_related()
|
||||
|
||||
|
||||
class EventPhotoMixin(EventDetailMixin):
|
||||
|
||||
def get_queryset(self):
|
||||
try:
|
||||
self.event = models.Event.objects.get(id=self.kwargs['event'])
|
||||
return models.Photo.objects.filter(
|
||||
Q(event=self.event) |
|
||||
Q(event__event_series=self.event)
|
||||
)
|
||||
except models.Event.DoesNotExist:
|
||||
raise Http404(_('Event does not exist'))
|
||||
return queryset.prefetch_related()
|
||||
|
||||
@@ -4,10 +4,10 @@ import os
|
||||
from ckeditor.fields import RichTextField
|
||||
from 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,
|
||||
|
||||
@@ -40,7 +40,7 @@
|
||||
<h4><a href="{{ event.get_absolute_url }}">{{ event.name }}</a></h4>
|
||||
<p>
|
||||
<span class="fa fa-calendar-o" title="{% trans 'Date' %}" aria-label="{% trans 'Date' %}"></span>
|
||||
<time datetime="{{event.start|date:'c'}}">
|
||||
<time datetime="{{event.start|date:'Y-m-d\TH:i:sO'}}">
|
||||
{{ event.start|date:'D' }}
|
||||
{{ event.start|date:'SHORT_DATE_FORMAT' }} {{hanchan.start|time:'H:i'}}
|
||||
</time>
|
||||
@@ -52,7 +52,7 @@
|
||||
{% endif %}
|
||||
</p>
|
||||
|
||||
<p>{{event.description|truncatewords_html:20|safe}}</p>
|
||||
{{event.description|truncatewords_html:20|safe}}
|
||||
<ul class="info">
|
||||
<li><span class="fa fa-map-marker" title="{% trans 'Location' %}"></span> {{ event.location }}</li>
|
||||
<li><span class="fa fa-comments" title="{% trans 'Comments' %}"></span> <a href="{{event.get_absolute_url}}#comments">{{ comment_count }}</a></li>
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
<h4><a href="{{ event.get_absolute_url }}">{{ event.name }}</a></h4>
|
||||
<ul class="info">
|
||||
<li><span class="fa fa-calendar-o" title="{% trans 'Date' %}" aria-label="{% trans 'Date' %}"></span>
|
||||
<time datetime="{{event.start|date:'c'}}">
|
||||
<time datetime="{{event.start|date:'Y-m-d\TH:i:sO'}}">
|
||||
{{ event.start|date:'D' }}
|
||||
{{ event.start|date:'SHORT_DATE_FORMAT' }}
|
||||
</time>
|
||||
@@ -36,10 +36,9 @@
|
||||
<a href="{{event.get_absolute_url}}#comments">{{ comment_count }}</a>
|
||||
</li>
|
||||
</ul>
|
||||
<p>{{event.description|truncatewords_html:25|safe}}</p>
|
||||
{{event.description|truncatewords_html:25|safe}}
|
||||
</div>
|
||||
{% if forloop.counter|divisibleby:2 %}<br class="clear">{% endif %}
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
{% if page_obj.has_other_pages %}{% include 'paginator.html' %}{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
@@ -3,9 +3,10 @@ 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.http import Http404
|
||||
from django.http import HttpResponse
|
||||
from django.shortcuts import redirect
|
||||
from django.utils import timezone
|
||||
from django.utils.translation import ugettext as _
|
||||
@@ -75,13 +76,16 @@ class EventForm(PermissionRequiredMixin, mixins.EventDetailMixin,
|
||||
class EventGallery(generic.ListView):
|
||||
"""Display a overview of all event photo albums."""
|
||||
template_name = 'events/photo_gallery.html'
|
||||
paginate_by = 24
|
||||
|
||||
def get_queryset(self):
|
||||
queryset = models.Event.objects.filter(
|
||||
start__lt=timezone.now(),
|
||||
event_series__isnull=True,
|
||||
photo_count__gt=0
|
||||
)
|
||||
queryset = queryset.order_by('-start')
|
||||
paginate_by = 24
|
||||
return queryset
|
||||
|
||||
|
||||
class EventListIcal(generic.View):
|
||||
@@ -110,7 +114,7 @@ class EventListIcal(generic.View):
|
||||
return response
|
||||
|
||||
|
||||
class EventPhoto(mixins.EventDetailMixin, generic.UpdateView):
|
||||
class EventPhoto(mixins.EventPhotoMixin, generic.UpdateView):
|
||||
"""Display the requested Photo and allows rotation if the user has change
|
||||
permissions."""
|
||||
form_class = forms.EditPhotoForm
|
||||
@@ -127,7 +131,7 @@ class EventPhoto(mixins.EventDetailMixin, generic.UpdateView):
|
||||
return generic.UpdateView.post(self, request, *args, **kwargs)
|
||||
|
||||
|
||||
class EventPhotoList(mixins.EventDetailMixin, generic.ListView):
|
||||
class EventPhotoList(mixins.EventPhotoMixin, generic.ListView):
|
||||
"""List all Photos of the event or event series in an album."""
|
||||
context_object_name = 'photo_list'
|
||||
event = None
|
||||
@@ -139,16 +143,6 @@ class EventPhotoList(mixins.EventDetailMixin, generic.ListView):
|
||||
initial={'event': self.event, 'photographer': self.request.user})
|
||||
return context
|
||||
|
||||
def get_queryset(self):
|
||||
try:
|
||||
self.event = models.Event.objects.get(id=self.kwargs['event'])
|
||||
return models.Photo.objects.filter(
|
||||
Q(event=self.event) |
|
||||
Q(event__event_series=self.event)
|
||||
)
|
||||
except models.Event.DoesNotExist:
|
||||
raise Http404(_('Event does not exist'))
|
||||
|
||||
|
||||
class EventPhotoUpload(mixins.EventDetailMixin, generic.FormView):
|
||||
form_class = forms.PhotoUploadForm
|
||||
@@ -187,10 +181,9 @@ class EventSeriesForm(mixins.EventDetailMixin, PermissionRequiredMixin,
|
||||
InlineFormSetView):
|
||||
model = models.Event
|
||||
inline_model = models.Event
|
||||
fk_name = 'event_series'
|
||||
fields = ('start', 'end')
|
||||
form_class = forms.EventForm
|
||||
extra = 3
|
||||
factory_kwargs = {'extra': 3, 'fk_name': 'event_series'}
|
||||
permission_required = 'events.add_event'
|
||||
template_name = 'events/eventseries_form.html'
|
||||
|
||||
@@ -202,5 +195,7 @@ class EventSeriesForm(mixins.EventDetailMixin, PermissionRequiredMixin,
|
||||
|
||||
|
||||
class UpcomingEvents(generic.ListView):
|
||||
queryset = models.Event.objects.upcoming(limit=None)
|
||||
paginate_by = 16
|
||||
|
||||
def get_queryset(self):
|
||||
return models.Event.objects.upcoming(limit=None)
|
||||
|
||||
Binary file not shown.
@@ -7,9 +7,9 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: kasu.utils\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2018-04-27 09:49+0200\n"
|
||||
"PO-Revision-Date: 2016-09-28 00:24+0200\n"
|
||||
"Last-Translator: Christian Berg <xeniac.at@gmail.com>\n"
|
||||
"POT-Creation-Date: 2019-12-13 23:38+0100\n"
|
||||
"PO-Revision-Date: 2018-12-30 11:14+0105\n"
|
||||
"Last-Translator: b' <kasu@xendynastie.at>'\n"
|
||||
"Language-Team: Kasu <verein@kasu.at>\n"
|
||||
"Language: de\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
@@ -17,13 +17,13 @@ msgstr ""
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
"X-Generator: Poedit 1.8.9\n"
|
||||
"X-Translated-Using: django-rosetta 0.7.6\n"
|
||||
"X-Translated-Using: django-rosetta 0.9.0\n"
|
||||
|
||||
#: settings.py:157
|
||||
#: settings.py:140
|
||||
msgid "German"
|
||||
msgstr "Deutsch"
|
||||
|
||||
#: settings.py:157
|
||||
#: settings.py:140
|
||||
msgid "English"
|
||||
msgstr "Englisch"
|
||||
|
||||
@@ -43,101 +43,90 @@ msgstr "Kürzliche Kommentare"
|
||||
msgid "Menu"
|
||||
msgstr "Menü"
|
||||
|
||||
#: templates/base.html:69 templates/redbox.html:3
|
||||
#: templates/base.html:69
|
||||
msgid "Current Event"
|
||||
msgstr "Aktuelle Veranstaltung"
|
||||
|
||||
#: templates/base.html:72 templates/redbox.html:6
|
||||
#: templates/base.html:72
|
||||
msgid "Since"
|
||||
msgstr "seit"
|
||||
|
||||
#: templates/base.html:73 templates/base.html:85 templates/redbox.html:8
|
||||
#: templates/redbox.html:22
|
||||
#: templates/base.html:73 templates/base.html:87
|
||||
msgid "Start"
|
||||
msgstr "Beginn"
|
||||
|
||||
#: templates/base.html:76 templates/base.html:88 templates/redbox.html:11
|
||||
#: templates/redbox.html:25
|
||||
#: templates/base.html:76 templates/base.html:90
|
||||
msgid "Location"
|
||||
msgstr "Ort"
|
||||
|
||||
#: templates/base.html:79 templates/base.html:90 templates/redbox.html:14
|
||||
#: templates/redbox.html:28
|
||||
#: templates/base.html:81 templates/base.html:94
|
||||
msgid "More Details"
|
||||
msgstr "Mehr Details"
|
||||
|
||||
#: templates/base.html:81 templates/redbox.html:17
|
||||
#: templates/base.html:83
|
||||
msgid "Next Event"
|
||||
msgstr "Nächste Veranstaltung"
|
||||
|
||||
#: templates/base.html:84 templates/redbox.html:20
|
||||
#: templates/base.html:86
|
||||
msgid "in"
|
||||
msgstr "in"
|
||||
|
||||
#: templates/base.html:93 templates/redbox.html:30
|
||||
#: templates/base.html:97
|
||||
msgid "Upcoming events"
|
||||
msgstr "Bevorstehende Veranstaltungen"
|
||||
|
||||
#: templates/base.html:143
|
||||
#: templates/base.html:107
|
||||
msgid "No events found"
|
||||
msgstr "Keine Veranstaltungen gefunden"
|
||||
|
||||
#: templates/base.html:149
|
||||
msgid "Add Subpage"
|
||||
msgstr "Unterseite Hinzufügen"
|
||||
|
||||
#: templates/base.html:148
|
||||
#: templates/base.html:154
|
||||
msgid "Edit Page"
|
||||
msgstr "Seite bearbeiten"
|
||||
|
||||
#: templates/base.html:156
|
||||
#: templates/base.html:162
|
||||
msgid "Imprint"
|
||||
msgstr "Impressum"
|
||||
|
||||
#: templates/base.html:157
|
||||
#: templates/base.html:163
|
||||
msgid "contact"
|
||||
msgstr "Kontakt"
|
||||
|
||||
#: templates/base.html:162
|
||||
#: templates/base.html:168
|
||||
msgid "Language"
|
||||
msgstr "Sprache"
|
||||
|
||||
#: templates/base.html:171
|
||||
#: templates/base.html:177
|
||||
msgid "Go"
|
||||
msgstr "Los"
|
||||
|
||||
#: templates/base.html:176
|
||||
#: templates/base.html:182
|
||||
msgid "Logged in as"
|
||||
msgstr "Angemeldet als"
|
||||
|
||||
#: templates/base.html:178
|
||||
#: templates/base.html:184
|
||||
msgid "Admin"
|
||||
msgstr "Admin"
|
||||
|
||||
#: templates/base.html:179
|
||||
#: templates/base.html:185
|
||||
msgid "Logout"
|
||||
msgstr "Abmelden"
|
||||
|
||||
#: templates/base.html:181
|
||||
#: templates/base.html:187
|
||||
msgid "no user logged in"
|
||||
msgstr "Niemand angemeldet"
|
||||
|
||||
#: templates/base.html:182 templates/comments/form.html:43
|
||||
#: templates/base.html:188 templates/comments/form.html:43
|
||||
msgid "register"
|
||||
msgstr "Registrieren"
|
||||
|
||||
#: templates/base.html:183 templates/comments/form.html:44
|
||||
#: templates/base.html:189 templates/comments/form.html:44
|
||||
msgid "login"
|
||||
msgstr "anmelden"
|
||||
|
||||
#: templates/base.html:185
|
||||
msgid "Login with Facebook"
|
||||
msgstr "über Facebook anmelden"
|
||||
|
||||
#: templates/base.html:187
|
||||
msgid "Login with Twitter"
|
||||
msgstr "über Twitter anmelden"
|
||||
|
||||
#: templates/base.html:189
|
||||
msgid "Login with Google"
|
||||
msgstr "über Google anmelden"
|
||||
|
||||
#: templates/comments/form.html:5
|
||||
msgid "New Comment"
|
||||
msgstr "Neuer Kommentar"
|
||||
@@ -189,13 +178,22 @@ msgid "Read More"
|
||||
msgstr "Mehr lesen"
|
||||
|
||||
#: templates/index.html:47
|
||||
#, python-format
|
||||
#, fuzzy, python-format
|
||||
#| msgid ""
|
||||
#| "\n"
|
||||
#| " From <a href=\"%(user_link)s\">%(author)s</a> in\n"
|
||||
#| " <a href=\"%(comment_link)s\">“%(object)s”</"
|
||||
#| "a>\n"
|
||||
#| " since\n"
|
||||
#| " <time datetime=\"%(submit_date)s\">%(since)s</time>\n"
|
||||
#| " "
|
||||
msgid ""
|
||||
"\n"
|
||||
" From <a href=\"%(user_link)s\">%(author)s</a> in\n"
|
||||
" <a href=\"%(comment_link)s\">“%(object)s”</a>\n"
|
||||
" since\n"
|
||||
" <time datetime=\"%(submit_date)s\">%(since)s</time>\n"
|
||||
" <time datetime=\"%(submit_date|date:'Y-m-d\\TH:i:sO')s\">"
|
||||
"%(since)s</time>\n"
|
||||
" "
|
||||
msgstr ""
|
||||
"\n"
|
||||
@@ -225,6 +223,15 @@ msgstr "Vorherige"
|
||||
msgid "Next"
|
||||
msgstr "Nächste"
|
||||
|
||||
#~ msgid "Login with Facebook"
|
||||
#~ msgstr "über Facebook anmelden"
|
||||
|
||||
#~ msgid "Login with Twitter"
|
||||
#~ msgstr "über Twitter anmelden"
|
||||
|
||||
#~ msgid "Login with Google"
|
||||
#~ msgstr "über Google anmelden"
|
||||
|
||||
#~ msgid "United Kingdom"
|
||||
#~ msgstr "Vereinigtes Königreich"
|
||||
|
||||
|
||||
@@ -49,7 +49,6 @@ PREREQ_APPS = [
|
||||
'ckeditor',
|
||||
'ckeditor_uploader',
|
||||
'easy_thumbnails',
|
||||
'social_django',
|
||||
]
|
||||
PROJECT_APPS = [
|
||||
'kasu',
|
||||
@@ -69,17 +68,17 @@ CACHES = {
|
||||
}
|
||||
|
||||
# Request Middleware
|
||||
MIDDLEWARE_CLASSES = [
|
||||
MIDDLEWARE = [
|
||||
'csp.middleware.CSPMiddleware',
|
||||
'django.middleware.security.SecurityMiddleware',
|
||||
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||
'django.middleware.common.CommonMiddleware',
|
||||
'django.middleware.csrf.CsrfViewMiddleware',
|
||||
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
||||
'django.contrib.auth.middleware.SessionAuthenticationMiddleware',
|
||||
'django.middleware.locale.LocaleMiddleware',
|
||||
'django.contrib.messages.middleware.MessageMiddleware',
|
||||
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
||||
'django.middleware.locale.LocaleMiddleware',
|
||||
'utils.middleware.SetRemoteAddrFromForwardedFor',
|
||||
'mahjong_ranking.middleware.DenormalizationUpdateMiddleware',
|
||||
]
|
||||
|
||||
@@ -102,8 +101,6 @@ TEMPLATES = [
|
||||
'django.contrib.messages.context_processors.messages',
|
||||
'django.contrib.messages.context_processors.messages',
|
||||
'events.context_processors.events_overview',
|
||||
'social_django.context_processors.backends',
|
||||
'social_django.context_processors.login_redirect'
|
||||
],
|
||||
'loaders': [
|
||||
('django.template.loaders.cached.Loader', [
|
||||
@@ -131,24 +128,10 @@ DEFAULT_FROM_EMAIL = ""
|
||||
# Login Settings
|
||||
ACCOUNT_ACTIVATION_DAYS = 5
|
||||
AUTH_USER_MODEL = 'membership.Membership'
|
||||
AUTHENTICATION_BACKENDS = ('social_core.backends.facebook.FacebookOAuth2',
|
||||
'social_core.backends.google.GoogleOAuth2',
|
||||
'social_core.backends.twitter.TwitterOAuth',
|
||||
'django.contrib.auth.backends.ModelBackend',)
|
||||
AUTHENTICATION_BACKENDS = ('django.contrib.auth.backends.ModelBackend',)
|
||||
LOGIN_URL = '/membership/login/'
|
||||
LOGIN_ERROR_URL = '/membership/login/error/'
|
||||
LOGIN_REDIRECT_URL = '/users/'
|
||||
SOCIAL_AUTH_CHANGE_SIGNAL_ONLY = False
|
||||
SOCIAL_AUTH_ENABLED_BACKENDS = ('facebook-oauth2', 'google-oauth2', 'twitter')
|
||||
SOCIAL_AUTH_NEW_USER_REDIRECT_URL = '/users/'
|
||||
SOCIAL_AUTH_SLUGIFY_USERNAMES = True
|
||||
SOCIAL_AUTH_FACEBOOK_KEY = ''
|
||||
SOCIAL_AUTH_FACEBOOK_SECRET = ''
|
||||
SOCIAL_AUTH_FACEBOOK_SCOPE = ['user_about_me', 'email']
|
||||
SOCIAL_AUTH_GOOGLE_OAUTH2_KEY = ''
|
||||
SOCIAL_AUTH_GOOGLE_OAUTH2_SECRET = ''
|
||||
SOCIAL_AUTH_TWITTER_KEY = ''
|
||||
SOCIAL_AUTH_TWITTER_SECRET = ''
|
||||
|
||||
# Localization
|
||||
USE_I18N = True
|
||||
@@ -229,7 +212,7 @@ LOGGING = {
|
||||
'loggers': {
|
||||
'django': {
|
||||
'handlers': ['console'],
|
||||
'level': 'INFO',
|
||||
'level': 'DEBUG',
|
||||
'propagate': True,
|
||||
},
|
||||
'django.request': {
|
||||
@@ -246,6 +229,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 +254,7 @@ DAN_RANKS = (
|
||||
(30, 4),
|
||||
(20, 3),
|
||||
(10, 2),
|
||||
(0, 1),
|
||||
(-1, 1),
|
||||
)
|
||||
|
||||
KYU_RANKS = (
|
||||
@@ -268,13 +267,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
231
src/kasu/static/js/jquery.formset.js
Normal file
231
src/kasu/static/js/jquery.formset.js
Normal 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);
|
||||
@@ -4,9 +4,11 @@
|
||||
_paq.push(['trackPageView']);
|
||||
_paq.push(['enableLinkTracking']);
|
||||
(function() {
|
||||
var u="//kasu.at/piwik/";
|
||||
var u="/piwik/";
|
||||
_paq.push(['setTrackerUrl', u+'piwik.php']);
|
||||
_paq.push(['setSiteId', '1']);
|
||||
_paq.push(['disableCookies']);
|
||||
_paq.push(['trackPageView']);
|
||||
var d=document, g=d.createElement('script'), s=d.getElementsByTagName('script')[0];
|
||||
g.type='text/javascript'; g.async=true; g.defer=true; g.src=u+'piwik.js'; s.parentNode.insertBefore(g,s);
|
||||
})();
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -71,7 +71,7 @@
|
||||
<ul class="fa-ul">
|
||||
<li><span class="fa-li fa fa-clock-o"></span><strong>{% trans "Since" %}:</strong> {{current_event.start|timesince}}</li>
|
||||
<li><span class="fa-li fa fa-calendar"></span><strong>{% trans "Start" %}:</strong>
|
||||
<time datetime="{{current_event.start|date:'c'}}">{{current_event.start|date:'DATETIME_FORMAT'}}</time>
|
||||
<time datetime="{{current_event.start|date:'Y-m-d\TH:i:sO'}}">{{current_event.start|date:'DATETIME_FORMAT'}}</time>
|
||||
</li>
|
||||
<li><span class="fa-li fa fa-map-marker"></span><strong>{% trans "Location" %}:</strong> {{ current_event.location }}
|
||||
- {{current_event.location.street_address}}, {{current_event.location.postal_code}} {{current_event.location.locality}}
|
||||
@@ -79,13 +79,13 @@
|
||||
</ul>
|
||||
<div class="right"><a class="button" href="{{current_event.get_absolute_url}}">
|
||||
{% trans "More Details" %} <span class="fa fa-arrow-right"></span></a></div>
|
||||
{% else %}
|
||||
{% elif next_event %}
|
||||
<h2>{% trans "Next Event" %}</h2>
|
||||
<h3>{{ next_event.name}}</h3>
|
||||
<ul class="fa-ul">
|
||||
<li><span class="fa-li fa fa-clock-o"></span><strong>{% trans "in" %}:</strong> {{next_event.start|timeuntil}}</li>
|
||||
<li><span class="fa-li fa fa-calendar"></span><strong>{% trans "Start" %}:</strong>
|
||||
<time datetime="{{next_event.start|date:'c'}}">{{next_event.start|date:'DATETIME_FORMAT' }}</time>
|
||||
<time datetime="{{next_event.start|date:'Y-m-d\TH:i:sO'}}">{{next_event.start|date:'DATETIME_FORMAT' }}</time>
|
||||
</li>
|
||||
<li><span class="fa-li fa fa-map-marker"></span><strong>{% trans "Location" %}:</strong> {{ next_event.location }}
|
||||
- {{next_event.location.street_address}}, {{next_event.location.postal_code}} {{next_event.location.locality}}
|
||||
@@ -101,8 +101,10 @@
|
||||
{% for event in upcoming_events %}
|
||||
<li><span class="fa-li fa fa-calendar-o"></span>
|
||||
<a href="{{ event.get_absolute_url}}">
|
||||
<time datetime="{{event.start|date:'c'}}">{{event.start|date:'D d. M:'}}</time>
|
||||
<time datetime="{{event.start|date:'Y-m-d\TH:i:sO'}}">{{event.start|date:'D d. M:'}}</time>
|
||||
{{event.name}}</a></li>
|
||||
{% empty %}
|
||||
<li>{% trans 'No events found' %}.</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endblock %}
|
||||
@@ -185,12 +187,6 @@
|
||||
{% trans "no user logged in" %} -
|
||||
<a rel="nofollow" href="{% url 'membership-register' %}">{% trans "register" %}</a>
|
||||
<a rel="nofollow" href="{% url 'login' %}?next={{ request.path_info }}">{% trans "login" %}</a>
|
||||
<a rel="nofollow" href="{% url 'social:begin' 'facebook' %}" class="fa fa-facebook"
|
||||
title="{% trans 'Login with Facebook' %}" aria-label="{% trans 'Login with Facebook' %}"></a>
|
||||
<a rel="nofollow" href="{% url 'social:begin' 'twitter' %}" class="fa fa-twitter"
|
||||
title="{% trans 'Login with Twitter' %}" aria-label="{% trans 'Login with Twitter' %}"></a>
|
||||
<a rel="nofollow" href="{% url 'social:begin' 'google-oauth2' %}" class="fa fa-google"
|
||||
title="{% trans 'Login with Google' %}" aria-label="{% trans 'Login with Google' %}"></a>
|
||||
{% endif %}
|
||||
</nav>
|
||||
</body>
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
</div>
|
||||
<header class="comment_header">
|
||||
<h3><a href="{{ comment.user.get_profile.get_absolute_url }}" class="user">{{comment.user}}</a></h3>
|
||||
<time datetime="{{comment.submit_date|date:'Y-m-d'}}" class="submit_date">{{comment.submit_date|timesince}}</time>
|
||||
<time datetime="{{comment.submit_date|date:'Y-m-d\TH:i:sO'}}" class="submit_date">{{comment.submit_date|timesince}}</time>
|
||||
</header>
|
||||
<div class="comment_text">{{comment.comment}}</div>
|
||||
</article>
|
||||
|
||||
4
src/kasu/templates/django/forms/widgets/date.html
Normal file
4
src/kasu/templates/django/forms/widgets/date.html
Normal file
@@ -0,0 +1,4 @@
|
||||
{% with type="date" %}
|
||||
{% include "django/forms/widgets/html5input.html" %}
|
||||
{% endwith %}
|
||||
|
||||
4
src/kasu/templates/django/forms/widgets/datetime.html
Normal file
4
src/kasu/templates/django/forms/widgets/datetime.html
Normal file
@@ -0,0 +1,4 @@
|
||||
{% with type="datetime-local" %}
|
||||
{% include "django/forms/widgets/html5input.html" %}
|
||||
{% endwith %}
|
||||
|
||||
1
src/kasu/templates/django/forms/widgets/html5input.html
Normal file
1
src/kasu/templates/django/forms/widgets/html5input.html
Normal file
@@ -0,0 +1 @@
|
||||
<input type="{{ type|default:'text' }}" name="{{ widget.name }}"{% if widget.value != None %} value="{{ widget.value|stringformat:'s' }}"{% endif %}{% include "django/forms/widgets/attrs.html" %} />
|
||||
4
src/kasu/templates/django/forms/widgets/time.html
Normal file
4
src/kasu/templates/django/forms/widgets/time.html
Normal file
@@ -0,0 +1,4 @@
|
||||
{% with type="time" %}
|
||||
{% include "django/forms/widgets/html5input.html" %}
|
||||
{% endwith %}
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
<label {% if field.html_name != 'recaptcha' %} for="id_{{ field.html_name}}" {% endif %}
|
||||
class="field_name {{ field.css_classes }}">{{ field.label}}</label>
|
||||
{{ field }}
|
||||
{% if field.help_text and not field.field.widget.input_type %}
|
||||
{{field.help_text}}
|
||||
{% if field.field.widget.input_type == 'checkbox' %}
|
||||
<label for="id_{{field.name}}">{{field.help_text|safe}}</label>
|
||||
{% elif field.help_text %}
|
||||
<p class="help_text">{{field.help_text}}</p>
|
||||
{% endif %}
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
<h2><a href="{{article.get_absolute_url}}">{{article.headline}}</a></h2>
|
||||
<ul class="info">
|
||||
<li><span class="fa fa-calendar"></span>
|
||||
<time datetime="{{article.date_created|date:'Y-m-d H:i'}}">{{ article.date_created|date:'DATE_FORMAT' }}</time>
|
||||
<time datetime="{{article.date_created|date:'Y-m-d\TH:i:sO'}}">{{ article.date_created|date:'DATE_FORMAT' }}</time>
|
||||
</li>
|
||||
<li><span class="fa fa-user"></span> {{ article.author }}
|
||||
</li>
|
||||
@@ -48,7 +48,7 @@
|
||||
From <a href="{{user_link}}">{{author}}</a> in
|
||||
<a href="{{comment_link}}">“{{object}}”</a>
|
||||
since
|
||||
<time datetime="{{submit_date}}">{{since}}</time>
|
||||
<time datetime="{{submit_date|date:'Y-m-d\TH:i:sO'}}">{{since}}</time>
|
||||
{% endblocktrans %}
|
||||
</li>
|
||||
{% endfor %}
|
||||
|
||||
@@ -29,13 +29,13 @@ sitemaps = {
|
||||
urlpatterns = [ # Ignore PyLintBear (C0103)
|
||||
url(r'^$', views.StartPage.as_view()),
|
||||
url(r'^404/$', TemplateView.as_view(template_name='404.html')),
|
||||
url(r'^add_page/(?P<path>[\+\.\-\d\w\/]+)/$',
|
||||
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\/]+)/$',
|
||||
url(r'^edit_page/(?P<path>[\+\.\-\d\w\/]*)$',
|
||||
views.PageEditForm.as_view(), name='edit-page'),
|
||||
url(r'^events/', include('events.urls')),
|
||||
url(r'^events.ics$', EventListIcal.as_view(), name='events-ical'),
|
||||
@@ -62,7 +62,6 @@ urlpatterns = [ # Ignore PyLintBear (C0103)
|
||||
url(r'^(?P<path>[\-\d\w\/]+)\.html$',
|
||||
views.PageHtml.as_view(), name='view-page'),
|
||||
url(r'^(?P<path>[\-\d\w\/]+)\.pdf$', views.PagePdf.as_view()),
|
||||
url('', include('social_django.urls', namespace='social'))
|
||||
]
|
||||
|
||||
if settings.DEBUG:
|
||||
|
||||
@@ -14,8 +14,8 @@ if VENV_PATH not in sys.path:
|
||||
if SOURCE_PATH not in sys.path:
|
||||
sys.path.append(SOURCE_PATH)
|
||||
|
||||
from django.core.wsgi import get_wsgi_application # Ignore PyLintBear (C0413) # Ignore PyLintBear (C0413)
|
||||
from django.core.wsgi import get_wsgi_application
|
||||
|
||||
os.environ['DJANGO_SETTINGS_MODULE'] = 'kasu.settings'
|
||||
|
||||
application = get_wsgi_application() # Ignore PyLintBear (C0103) # Ignore PyLintBear (C0103)
|
||||
application = get_wsgi_application()
|
||||
|
||||
149
src/kasu/xlsx.py
Normal file
149
src/kasu/xlsx.py
Normal file
@@ -0,0 +1,149 @@
|
||||
"""
|
||||
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)
|
||||
@@ -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)
|
||||
|
||||
Binary file not shown.
@@ -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: 2019-12-13 23:38+0100\n"
|
||||
"PO-Revision-Date: 2018-05-08 00:20+0105\n"
|
||||
"Last-Translator: b'Christian Berg <kasu@xendynastie.at>'\n"
|
||||
"Language-Team: Kasu <verein@kasu.at>\n"
|
||||
"Language: de\n"
|
||||
@@ -17,21 +17,21 @@ msgstr ""
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
"X-Generator: Poedit 1.8.9\n"
|
||||
"X-Translated-Using: django-rosetta 0.7.14\n"
|
||||
"X-Translated-Using: django-rosetta 0.8.1\n"
|
||||
|
||||
#: admin.py:26
|
||||
#: admin.py:24
|
||||
msgid "Recalculate"
|
||||
msgstr "Neuberechnen"
|
||||
|
||||
#: admin.py:36
|
||||
#: admin.py:34
|
||||
msgid "Confirm"
|
||||
msgstr "Bestätigen"
|
||||
|
||||
#: admin.py:46
|
||||
#: admin.py:44
|
||||
msgid "Set unconfirmed"
|
||||
msgstr "Als unbestätigt markieren"
|
||||
|
||||
#: forms.py:21
|
||||
#: forms.py:22
|
||||
msgid "start"
|
||||
msgstr "Beginn"
|
||||
|
||||
@@ -57,8 +57,8 @@ msgstr "Spieler 1"
|
||||
#: 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
|
||||
#: templates/mahjong_ranking/kyudanranking_list.html:30
|
||||
#: templates/mahjong_ranking/seasonranking_list.html:31
|
||||
msgid "Score"
|
||||
msgstr "Punkte"
|
||||
|
||||
@@ -89,7 +89,7 @@ msgstr "Wurde bestätigt"
|
||||
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
|
||||
#: models.py:179 models.py:607 templates/mahjong_ranking/ladder_redbox.html:29
|
||||
#: templates/mahjong_ranking/player_ladder_score.html:63
|
||||
msgid "Season"
|
||||
msgstr "Saison"
|
||||
@@ -127,14 +127,57 @@ msgstr "Spielstand ist weniger als 100.000 Punkte"
|
||||
msgid "Gamescore is over 100.000 Pt."
|
||||
msgstr "Spielstand ist über 100.000 Punkte."
|
||||
|
||||
#: models.py:356
|
||||
#: models.py:362
|
||||
msgid "Kyū/Dan Ranking"
|
||||
msgstr "Kyū/Dan Wertung"
|
||||
|
||||
#: models.py:357
|
||||
#: models.py:363
|
||||
msgid "Kyū/Dan Rankings"
|
||||
msgstr "Kyū/Dan Wertungen"
|
||||
|
||||
#: templates/mahjong_ranking/eventhanchan_form.html:11
|
||||
#: templates/mahjong_ranking/eventhanchan_list.html:55
|
||||
msgid "Edit Hanchans"
|
||||
msgstr "Hanchans bearbeiten"
|
||||
|
||||
#: templates/mahjong_ranking/eventhanchan_form.html:49
|
||||
#: templates/mahjong_ranking/hanchan_form.html:58
|
||||
msgid "Total"
|
||||
msgstr "Total"
|
||||
|
||||
#: templates/mahjong_ranking/eventhanchan_form.html:51
|
||||
msgid "Difference"
|
||||
msgstr "Unterschied"
|
||||
|
||||
#: templates/mahjong_ranking/eventhanchan_form.html:103
|
||||
#: templates/mahjong_ranking/eventhanchan_list.html:56
|
||||
#: templates/mahjong_ranking/eventranking_list.html:52
|
||||
#: templates/mahjong_ranking/hanchan_form.html:4
|
||||
#: templates/mahjong_ranking/hanchan_form.html:14
|
||||
msgid "Add Hanchan"
|
||||
msgstr "Hanchan hinzufügen"
|
||||
|
||||
#: templates/mahjong_ranking/eventhanchan_form.html:105
|
||||
#: 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_form.html:118
|
||||
#: templates/mahjong_ranking/hanchan_form.html:71
|
||||
msgid "back"
|
||||
msgstr "Zurück"
|
||||
|
||||
#: templates/mahjong_ranking/eventhanchan_form.html:119
|
||||
#: templates/mahjong_ranking/hanchan_form.html:72
|
||||
msgid "save"
|
||||
msgstr "Speichern"
|
||||
|
||||
#: templates/mahjong_ranking/eventhanchan_list.html:7
|
||||
msgid "Played Hanchans"
|
||||
msgstr "Gespielte Hanchans"
|
||||
@@ -157,16 +200,6 @@ msgstr "Dan Punkte"
|
||||
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
|
||||
@@ -186,20 +219,13 @@ msgstr "Für diese Veranstaltung wurde noch keine Hanchan eingetragen."
|
||||
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
|
||||
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"
|
||||
|
||||
#: templates/mahjong_ranking/eventranking_list.html:12
|
||||
#: templates/mahjong_ranking/kyudanranking_list.html:30
|
||||
#: templates/mahjong_ranking/kyudanranking_list.html:25
|
||||
#: templates/mahjong_ranking/seasonranking_list.html:23
|
||||
msgid "Rank"
|
||||
msgstr "Rang"
|
||||
@@ -217,12 +243,11 @@ 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
|
||||
#: templates/mahjong_ranking/seasonranking_list.html:26
|
||||
msgid "Average"
|
||||
msgstr "Durchschnitt"
|
||||
|
||||
@@ -231,22 +256,22 @@ msgstr "Durchschnitt"
|
||||
#: 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
|
||||
#: templates/mahjong_ranking/seasonranking_list.html:30
|
||||
msgid "Placement"
|
||||
msgstr "Platzierung"
|
||||
|
||||
#: templates/mahjong_ranking/eventranking_list.html:22
|
||||
#: templates/mahjong_ranking/seasonranking_list.html:33
|
||||
#: templates/mahjong_ranking/seasonranking_list.html:32
|
||||
msgid "count"
|
||||
msgstr "Anzahl"
|
||||
|
||||
#: templates/mahjong_ranking/eventranking_list.html:23
|
||||
#: templates/mahjong_ranking/seasonranking_list.html:34
|
||||
#: templates/mahjong_ranking/seasonranking_list.html:33
|
||||
msgid "good"
|
||||
msgstr "gut"
|
||||
|
||||
#: templates/mahjong_ranking/eventranking_list.html:24
|
||||
#: templates/mahjong_ranking/seasonranking_list.html:35
|
||||
#: templates/mahjong_ranking/seasonranking_list.html:34
|
||||
msgid "won"
|
||||
msgstr "gewonnen"
|
||||
|
||||
@@ -262,32 +287,12 @@ msgstr "Löschen"
|
||||
msgid "Player"
|
||||
msgstr "Spieler"
|
||||
|
||||
#: templates/mahjong_ranking/hanchan_form.html:58
|
||||
msgid "Total"
|
||||
msgstr "Total"
|
||||
|
||||
#: templates/mahjong_ranking/hanchan_form.html:71
|
||||
msgid "back"
|
||||
msgstr "Zurück"
|
||||
|
||||
#: templates/mahjong_ranking/hanchan_form.html:72
|
||||
msgid "save"
|
||||
msgstr "Speichern"
|
||||
|
||||
#: templates/mahjong_ranking/kyudanranking_list.html:4
|
||||
#| msgid "Player List"
|
||||
#: templates/mahjong_ranking/kyudanranking_list.html:9
|
||||
msgid "Players list"
|
||||
msgstr "Spielerliste"
|
||||
|
||||
#: templates/mahjong_ranking/kyudanranking_list.html:9
|
||||
msgid "Player List"
|
||||
msgstr "Spieler Liste"
|
||||
|
||||
#: templates/mahjong_ranking/kyudanranking_list.html:25
|
||||
msgid "Full Name"
|
||||
msgstr "Voller Name"
|
||||
|
||||
#: templates/mahjong_ranking/kyudanranking_list.html:40
|
||||
#: templates/mahjong_ranking/kyudanranking_list.html:35
|
||||
msgid "Games Total"
|
||||
msgstr "Spiele total"
|
||||
|
||||
@@ -371,20 +376,23 @@ msgstr "Ende"
|
||||
msgid "Participants"
|
||||
msgstr "Teilnehmer"
|
||||
|
||||
#: views.py:102
|
||||
#: views.py:116
|
||||
#, python-format
|
||||
msgid "%s has been updated successfully."
|
||||
msgstr "%s wurde erfolgreich aktualisiert."
|
||||
|
||||
#: views.py:105
|
||||
#: views.py:119
|
||||
#, 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
|
||||
#: views.py:219
|
||||
msgid "No user found matching the name {}"
|
||||
msgstr "Kein Benutzer mit dem Namen %s gefunden"
|
||||
|
||||
#~ msgid "Full Name"
|
||||
#~ msgstr "Voller Name"
|
||||
|
||||
#~ msgid "Event does not exist"
|
||||
#~ msgstr "Veranstaltung existiert nicht"
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
126
src/mahjong_ranking/management/commands/exportranking.py
Normal file
126
src/mahjong_ranking/management/commands/exportranking.py
Normal file
@@ -0,0 +1,126 @@
|
||||
"""Export Mahjong Rankings as excel files."""
|
||||
|
||||
import os
|
||||
from datetime import date, time, datetime
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.mail import EmailMessage
|
||||
from django.core.management.base import BaseCommand
|
||||
from django.utils import timezone
|
||||
from django.utils.dateparse import parse_date
|
||||
|
||||
from kasu import xlsx
|
||||
from mahjong_ranking.models import SeasonRanking, KyuDanRanking
|
||||
|
||||
MAIL_BODY = """
|
||||
Hallo! Ich bin's dein Server.
|
||||
|
||||
Ich habe gerade die Mahjong Rankings als Excel exportiert und dachte mir das
|
||||
ich sie dir am besten gleich schicke.
|
||||
|
||||
Bitte versuche nicht auf diese E-Mail zu antworten.
|
||||
Ich bin nur ein dummes Programm.
|
||||
|
||||
mit lieben Grüßen
|
||||
|
||||
Der Kasu Server
|
||||
"""
|
||||
|
||||
|
||||
def export_season_rankings(workbook, until):
|
||||
SeasonRanking.objects.update(until=until)
|
||||
season = until.year if until else date.today().year
|
||||
object_list = SeasonRanking.objects.season_rankings()
|
||||
title = "Mahjong Ladder - {}".format(season)
|
||||
columns_settings = (
|
||||
{'col': 'A', 'title': 'Rang', 'attr': 'placement', 'style': 'Integer',
|
||||
'width': 8},
|
||||
{'col': 'B', 'title': 'Spitzname', 'attr': 'user.username',
|
||||
'style': 'Content',
|
||||
'width': 25},
|
||||
{'col': 'C', 'title': '⌀ Platz', 'attr': 'avg_placement',
|
||||
'style': 'Float', 'width': 8},
|
||||
{'col': 'D', 'title': '⌀ Punkte', 'attr': 'avg_score',
|
||||
'style': 'Float', 'width': 12},
|
||||
{'col': 'E', 'title': 'Hanchans', 'attr': 'hanchan_count',
|
||||
'style': 'Integer', 'width': 10},
|
||||
{'col': 'F', 'title': 'Gut', 'attr': 'good_hanchans',
|
||||
'style': 'Integer', 'width': 5},
|
||||
{'col': 'G', 'title': 'Gewonnen', 'attr': 'won_hanchans',
|
||||
'style': 'Integer', 'width': 10},
|
||||
)
|
||||
workbook.generate_sheet(
|
||||
title=title,
|
||||
columns_settings=columns_settings,
|
||||
object_list=object_list)
|
||||
|
||||
|
||||
def export_kyu_dan_rankings(workbook, until):
|
||||
KyuDanRanking.objects.update(until=until, force_recalc=True)
|
||||
object_list = KyuDanRanking.objects.all()
|
||||
title = "Kyū & Dan Rankings"
|
||||
columns_settings = (
|
||||
{'col': 'A', 'title': 'Spitzname', 'attr': 'user.username',
|
||||
'style': 'Content', 'width': 14},
|
||||
{'col': 'B', 'title': 'Voller Name', 'attr': 'user.full_name',
|
||||
'style': 'Content', 'width': 20},
|
||||
{'col': 'C', 'title': 'Rang', 'attr': 'rank',
|
||||
'style': 'Content', 'width': 8},
|
||||
{'col': 'D', 'title': 'Punkte', 'attr': 'points',
|
||||
'style': 'Integer', 'width': 8},
|
||||
{'col': 'E', 'title': 'Hanchans', 'attr': 'hanchan_count',
|
||||
'style': 'Integer', 'width': 10},
|
||||
{'col': 'F', 'title': 'Gut', 'attr': 'good_hanchans',
|
||||
'style': 'Integer', 'width': 5},
|
||||
{'col': 'G', 'title': 'Gewonnen', 'attr': 'won_hanchans',
|
||||
'style': 'Integer', 'width': 10},
|
||||
{'col': 'H', 'title': 'letzte Hanchan', 'attr': 'last_hanchan_date',
|
||||
'style': 'Date', 'width': 16},
|
||||
)
|
||||
workbook.generate_sheet(
|
||||
title=title,
|
||||
columns_settings=columns_settings,
|
||||
object_list=object_list)
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
"""Exports the SeasonRankings"""
|
||||
filename = str()
|
||||
until = datetime
|
||||
|
||||
def add_arguments(self, parser):
|
||||
parser.add_argument(
|
||||
'--until', nargs='?', type=parse_date,
|
||||
default=date.today(), metavar='YYYY-MM-DD',
|
||||
help='Calculate and export rankings until the given date.')
|
||||
parser.add_argument(
|
||||
'--mail', nargs='*', type=str, metavar='user@example.com',
|
||||
help='Send the spreadsheet via eMail to the given recipient.')
|
||||
|
||||
def handle(self, *args, **options):
|
||||
"""Exports the current ladder ranking in a spreadsheet.
|
||||
This is useful as a backup in form of a hardcopy."""
|
||||
self.until = timezone.make_aware(datetime.combine(
|
||||
options['until'], time(23, 59, 59)
|
||||
))
|
||||
|
||||
self.filename = os.path.join(
|
||||
settings.RANKING_EXPORT_PATH,
|
||||
'mahjong_rankings_{:%Y-%m-%d}.xlsx'.format(self.until)
|
||||
)
|
||||
workbook = xlsx.Workbook()
|
||||
export_season_rankings(workbook, until=self.until)
|
||||
export_kyu_dan_rankings(workbook, until=self.until)
|
||||
os.makedirs(settings.RANKING_EXPORT_PATH, exist_ok=True)
|
||||
workbook.save(self.filename)
|
||||
if options['mail']:
|
||||
self.send_mail(options['mail'])
|
||||
|
||||
def send_mail(self, recipients):
|
||||
mail = EmailMessage(
|
||||
subject='Mahjong Rankings vom {:%d.%m.%Y}'.format(self.until),
|
||||
body=MAIL_BODY,
|
||||
from_email=settings.DEFAULT_FROM_EMAIL,
|
||||
to=recipients)
|
||||
mail.attach_file(self.filename)
|
||||
mail.send()
|
||||
@@ -3,11 +3,13 @@ Rest all dan points to 0 at a given date.
|
||||
"""
|
||||
|
||||
from django.core.management.base import BaseCommand
|
||||
from datetime import date, datetime, time
|
||||
from datetime import datetime
|
||||
from datetime import time
|
||||
from mahjong_ranking import models
|
||||
from django.utils.dateparse import parse_date
|
||||
from django.utils import timezone
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
""" Recalculate all Kyu/Dan Rankings """
|
||||
|
||||
@@ -17,16 +19,23 @@ 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()
|
||||
|
||||
|
||||
|
||||
@@ -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)
|
||||
@@ -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')
|
||||
|
||||
@@ -1,12 +1,27 @@
|
||||
"""Middleware to defer slow denormalization at the end of a request."""
|
||||
from django.core.cache import cache
|
||||
|
||||
from mahjong_ranking import models
|
||||
from . import LOGGER
|
||||
from . import LOGGER
|
||||
|
||||
|
||||
class DenormalizationUpdateMiddleware(object): # Ignore PyLintBear (R0903)
|
||||
class DenormalizationUpdateMiddleware:
|
||||
"""To recalculate everything in the queues at the end of a POST request."""
|
||||
|
||||
def __init__(self, get_response=None):
|
||||
self.get_response = get_response
|
||||
super().__init__()
|
||||
|
||||
def __call__(self, request):
|
||||
response = None
|
||||
if hasattr(self, 'process_request'):
|
||||
response = self.process_request(request)
|
||||
response = response or self.get_response(request)
|
||||
if hasattr(self, 'process_response'):
|
||||
response = self.process_response(request, response)
|
||||
return response
|
||||
|
||||
def process_response(self, request, response): # Ignore PyLintBear (R0201)
|
||||
"""Check and process the recalculation queues on each POST request.
|
||||
|
||||
@@ -16,7 +31,6 @@ class DenormalizationUpdateMiddleware(object): # Ignore PyLintBear (R0903)
|
||||
"""
|
||||
event_queue = set()
|
||||
season_queue = set()
|
||||
|
||||
if request.method != 'POST':
|
||||
return response
|
||||
|
||||
|
||||
@@ -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'),
|
||||
|
||||
87
src/mahjong_ranking/migrations/0006_auto_20171214_1318.py
Normal file
87
src/mahjong_ranking/migrations/0006_auto_20171214_1318.py
Normal file
@@ -0,0 +1,87 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11.8 on 2017-12-14 12:18
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('mahjong_ranking', '0005_auto_20171115_0653'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='kyudanranking',
|
||||
name='legacy_dan',
|
||||
field=models.PositiveSmallIntegerField(blank=True, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='kyudanranking',
|
||||
name='legacy_good_hanchans',
|
||||
field=models.PositiveIntegerField(blank=True, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='kyudanranking',
|
||||
name='legacy_kyu',
|
||||
field=models.PositiveSmallIntegerField(blank=True, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='kyudanranking',
|
||||
name='legacy_won_hanchans',
|
||||
field=models.PositiveIntegerField(blank=True, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='kyudanranking',
|
||||
name='max_dan_points',
|
||||
field=models.PositiveIntegerField(default=0),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='eventranking',
|
||||
name='user',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='hanchan',
|
||||
name='player1',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='user_hanchan+', to=settings.AUTH_USER_MODEL, verbose_name='Spieler 1'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='hanchan',
|
||||
name='player2',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='user_hanchan+', to=settings.AUTH_USER_MODEL, verbose_name='Spieler 2'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='hanchan',
|
||||
name='player3',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='user_hanchan+', to=settings.AUTH_USER_MODEL, verbose_name='Spieler 3'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='hanchan',
|
||||
name='player4',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='user_hanchan+', to=settings.AUTH_USER_MODEL, verbose_name='Spieler 4'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='kyudanranking',
|
||||
name='legacy_dan_points',
|
||||
field=models.PositiveIntegerField(blank=True, null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='kyudanranking',
|
||||
name='legacy_hanchan_count',
|
||||
field=models.PositiveIntegerField(blank=True, null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='kyudanranking',
|
||||
name='legacy_kyu_points',
|
||||
field=models.PositiveIntegerField(blank=True, null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='seasonranking',
|
||||
name='user',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL),
|
||||
),
|
||||
]
|
||||
@@ -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,9 @@ 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 +87,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 +95,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 +114,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 +133,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 +152,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 +336,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 +363,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 +386,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 +414,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 +428,21 @@ 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.
|
||||
if hanchan != last_hanchan_this_event:
|
||||
return False
|
||||
else:
|
||||
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 +472,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 +499,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 +530,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 +554,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 +568,45 @@ 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)
|
||||
|
||||
120
src/mahjong_ranking/templates/mahjong_ranking/eventhanchan_form.html
Executable file
120
src/mahjong_ranking/templates/mahjong_ranking/eventhanchan_form.html
Executable 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 %}
|
||||
@@ -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 %}
|
||||
@@ -20,11 +20,6 @@
|
||||
{% trans 'Nickname' %}
|
||||
<a href="{% url 'kyudanranking-list' order_by='-username' %}?page={{page_obj.number}}" class="fa fa-sort-desc" rel="nofollow"></a>
|
||||
</th>
|
||||
<th>
|
||||
<a href="{% url 'kyudanranking-list' order_by='+full_name'%}?page={{page_obj.number}}" class="fa fa-sort-asc" rel="nofollow"></a>
|
||||
{% trans 'Full Name' %}
|
||||
<a href="{% url 'kyudanranking-list' order_by='-full_name' %}?page={{page_obj.number}}" class="fa fa-sort-desc" rel="nofollow"></a>
|
||||
</th>
|
||||
<th>
|
||||
<a href="{% url 'kyudanranking-list' order_by='+rank' %}?page={{page_obj.number}}" class="fa fa-sort-asc" rel="nofollow"></a>
|
||||
{% trans 'Rank' %}
|
||||
@@ -51,7 +46,6 @@
|
||||
width="70" height="70" alt="" /></a>
|
||||
</td>
|
||||
<td><a href="{{ ranking.get_absolute_url }}">{{ ranking.user }}</a></td>
|
||||
<td>{% if user.is_authenticated %}{{ranking.user.last_name}} {{ranking.user.first_name}}{% else %}---{% endif %}</td>
|
||||
<td>{% if ranking.dan %} {{ranking.dan}}. Dan {% else %} {{ranking.kyu}} Kyu {% endif %}</td>
|
||||
<td class="right">{% if ranking.dan %} {{ranking.dan_points}} {% else %} {{ranking.kyu_points}} {% endif %}</td>
|
||||
<td class="right">{{ranking.hanchan_count}}</td>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
{% for hanchan in latest_hanchan_list %}
|
||||
<li><span class="fa-li fa fa-table"></span>
|
||||
<a href="{% url 'event-hanchan-list' hanchan.event_id %}">
|
||||
<time datetime="{{ hanchan.start|date:'c' }}">{{ hanchan.start|date:'D' }}
|
||||
<time datetime="{{ hanchan.start|date:'Y-m-d\TH:i:sO' }}">{{ hanchan.start|date:'D' }}
|
||||
{{ hanchan.start|date:'SHORT_DATE_FORMAT' }} {{hanchan.start|time:'H:i'}}
|
||||
</time></a>:<br />
|
||||
<small>{{hanchan.player_names}}</small>
|
||||
@@ -18,7 +18,7 @@
|
||||
<li>
|
||||
<span class="fa-li fa fa-calendar-o"></span>
|
||||
<a href="{% url 'event-hanchan-list' event.pk %}">
|
||||
<time datetime="{{event.start|date:'c'}}">{{ event.start|date:'D' }} {{ event.start|date:'SHORT_DATE_FORMAT' }}</time>:
|
||||
<time datetime="{{event.start|date:'Y-m-d\TH:i:sO'}}">{{ event.start|date:'D' }} {{ event.start|date:'SHORT_DATE_FORMAT' }}</time>:
|
||||
{{event.name}}</a></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
|
||||
@@ -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 %}
|
||||
@@ -40,3 +40,7 @@
|
||||
{% endfor %}
|
||||
</table>
|
||||
{% endblock %}
|
||||
|
||||
{% block buttonbar %}
|
||||
<a href="?download=xlsx" class="button"><span class="fa fa-table"></span> Download</a>
|
||||
{% endblock %}
|
||||
|
||||
@@ -48,3 +48,7 @@
|
||||
{% endfor %}
|
||||
</table>
|
||||
{% endblock %}
|
||||
|
||||
{% block buttonbar %}
|
||||
<a href="?download=xlsx" class="button"><span class="fa fa-table"></span> Download</a>
|
||||
{% endblock %}
|
||||
|
||||
@@ -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 %}
|
||||
@@ -23,7 +23,6 @@
|
||||
<th rowspan="2">{% trans "Rank" %}</th>
|
||||
<th rowspan="2">{% trans "Avatar" %}</th>
|
||||
<th rowspan="2">{% trans "Nickname" %}</th>
|
||||
<th rowspan="2">{% trans "Name" %}</th>
|
||||
<th colspan="2">{% trans 'Average' %}</th>
|
||||
<th colspan="3">Hanchans</th>
|
||||
</tr>
|
||||
@@ -42,7 +41,6 @@
|
||||
src="{% thumbnail player.user.avatar|default:'unknown_profile.jpg' 'avatar' %}"
|
||||
width="70" height="70" class="avatar" alt=""/></a></td>
|
||||
<td><a href="{{ player.get_absolute_url }}?season={{season}}">{{player.user}}</a></td>
|
||||
<td>{% if user.is_authenticated %}{{player.user.last_name}} {{player.user.first_name}}{% else %}---{% endif %}</td>
|
||||
<td class="center">{{player.avg_placement|floatformat:2 }}</td>
|
||||
<td class="right">{{player.avg_score|floatformat:0|intcomma }}</td>
|
||||
<td class="right">{{player.hanchan_count}}</td>
|
||||
@@ -51,7 +49,7 @@
|
||||
</tr>
|
||||
{% empty %}
|
||||
<tr>
|
||||
<td colspan="9">Leider hat es noch niemand in das Ranking geschafft.
|
||||
<td colspan="8">Leider hat es noch niemand in das Ranking geschafft.
|
||||
Ein Spieler wird erst ins Ranking genommen wenn er 5 Hanchans absolviert hat.
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
@@ -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,6 @@ 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"),
|
||||
]
|
||||
|
||||
@@ -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'),
|
||||
@@ -29,6 +31,18 @@ KYU_DAN_ORDER = { # map sort URL args to Django ORM order_by args
|
||||
}
|
||||
|
||||
|
||||
def get_kyu_dan_ranking(user=None):
|
||||
"""
|
||||
get the KyuDanRanking from the Database, or return a blank one
|
||||
:param user: user model
|
||||
:return: KyuDanRanking object
|
||||
"""
|
||||
try:
|
||||
return models.KyuDanRanking.objects.get(user=user)
|
||||
except models.KyuDanRanking.DoesNotExist:
|
||||
return models.KyuDanRanking(user=user)
|
||||
|
||||
|
||||
class DeleteHanchan(EventDetailMixin, PermissionRequiredMixin,
|
||||
generic.DeleteView):
|
||||
"""Deletes a Hanchan if confimration has been answerd with 'yes'."""
|
||||
@@ -106,6 +120,46 @@ 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 +171,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 +210,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 +217,8 @@ class PlayerScore(LoginRequiredMixin, generic.ListView):
|
||||
raise django.http.Http404(
|
||||
_("No user found matching the name {}").format(
|
||||
self.kwargs.get('username')))
|
||||
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 +237,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 = get_kyu_dan_ranking(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 +314,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 = get_kyu_dan_ranking(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 +376,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
|
||||
)
|
||||
|
||||
Binary file not shown.
@@ -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: 2019-12-13 23:38+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"
|
||||
@@ -28,54 +28,54 @@ msgstr "Neuberechnen"
|
||||
msgid "%s may only participate once."
|
||||
msgstr "%s darf nur einmal teilnehmen."
|
||||
|
||||
#: models.py:20
|
||||
#: models.py:21
|
||||
msgid "Comment"
|
||||
msgstr "Kommentar"
|
||||
|
||||
#: models.py:22
|
||||
#: 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
|
||||
#: models.py:26 models.py:33 models.py:40 models.py:47 models.py:54
|
||||
#: models.py:61 templates/maistar_ranking/ranking_list.html:19
|
||||
msgid "Score"
|
||||
msgstr "Punkte"
|
||||
|
||||
#: models.py:28
|
||||
#: models.py:31
|
||||
msgid "Player 2"
|
||||
msgstr "Spieler 2"
|
||||
|
||||
#: models.py:34
|
||||
#: models.py:38
|
||||
msgid "Player 3"
|
||||
msgstr "Spieler 3"
|
||||
|
||||
#: models.py:40
|
||||
#: models.py:45
|
||||
msgid "Player 4"
|
||||
msgstr "Spieler 4"
|
||||
|
||||
#: models.py:46
|
||||
#: models.py:52
|
||||
msgid "Player 5"
|
||||
msgstr "Spieler 5"
|
||||
|
||||
#: models.py:52
|
||||
#: models.py:59
|
||||
msgid "Player 6"
|
||||
msgstr "Spieler 6"
|
||||
|
||||
#: models.py:58
|
||||
#: models.py:65
|
||||
msgid "Has been confirmed"
|
||||
msgstr "Wurde bestätigt"
|
||||
|
||||
#: models.py:60
|
||||
#: 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
|
||||
#: models.py:70 models.py:153 templates/maistar_ranking/player_game_list.html:6
|
||||
#: templates/maistar_ranking/ranking_list.html:4
|
||||
#: templates/maistar_ranking/ranking_list.html:72
|
||||
msgid "Season"
|
||||
msgstr "Saison"
|
||||
|
||||
#: models.py:74
|
||||
#: 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}"
|
||||
|
||||
|
||||
@@ -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'),
|
||||
|
||||
52
src/maistar_ranking/migrations/0007_auto_20171214_1215.py
Normal file
52
src/maistar_ranking/migrations/0007_auto_20171214_1215.py
Normal file
@@ -0,0 +1,52 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11.8 on 2017-12-14 11:15
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('maistar_ranking', '0006_auto_20171115_0653'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='game',
|
||||
name='player1',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='+', to=settings.AUTH_USER_MODEL, verbose_name='Spieler 1'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='game',
|
||||
name='player2',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='+', to=settings.AUTH_USER_MODEL, verbose_name='Spieler 2'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='game',
|
||||
name='player3',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='+', to=settings.AUTH_USER_MODEL, verbose_name='Spieler 3'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='game',
|
||||
name='player4',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='+', to=settings.AUTH_USER_MODEL, verbose_name='Spieler 4'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='game',
|
||||
name='player5',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='+', to=settings.AUTH_USER_MODEL, verbose_name='Spieler 5'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='game',
|
||||
name='player6',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='+', to=settings.AUTH_USER_MODEL, verbose_name='Spieler 6'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='ranking',
|
||||
name='user',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL),
|
||||
),
|
||||
]
|
||||
@@ -2,11 +2,11 @@
|
||||
|
||||
import logging
|
||||
|
||||
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,8 @@ 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)
|
||||
|
||||
@@ -5,7 +5,6 @@ from django.urls import reverse
|
||||
from events.models import Event
|
||||
|
||||
|
||||
|
||||
class MaistarGamesSitemap(GenericSitemap):
|
||||
@staticmethod
|
||||
def items():
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
""" Admin Interface to manage the memberships."""
|
||||
# import stuff we need from django
|
||||
from django.contrib import admin
|
||||
from django.contrib import admin, messages
|
||||
from django.contrib.auth.admin import UserAdmin, GroupAdmin
|
||||
from django.contrib.auth.models import Group
|
||||
from django.core.exceptions import PermissionDenied
|
||||
from django.utils.translation import ugettext as _
|
||||
from easy_thumbnails import fields, widgets
|
||||
|
||||
from membership.models import Membership, ActivationRequest
|
||||
|
||||
|
||||
@@ -22,7 +22,8 @@ def activate_user(modeladmin, request, queryset):
|
||||
activate_user.short_description = _('Activate selected User')
|
||||
|
||||
|
||||
def cleanup_activation(modeladmin, request, queryset): # Ignore PyLintBear (W0613)
|
||||
def cleanup_activation(modeladmin, request,
|
||||
queryset): # Ignore PyLintBear (W0613)
|
||||
"""Delete every selected activation request that has been expired.
|
||||
|
||||
:param modeladmin: The ModelAdmin that triggered this action.
|
||||
@@ -38,6 +39,38 @@ cleanup_activation.short_description = _(
|
||||
"Cleanup selected Activation Requests")
|
||||
|
||||
|
||||
def clear_personal_data(modeladmin, request, queryset):
|
||||
"""deactivates the account and removes all personal user information.
|
||||
|
||||
:param modeladmin: The ModelAdmin that triggered this action.
|
||||
:param request: An HttpRequest representing the current request.
|
||||
:param queryset: A QuerySet containing the objects selected by the user.
|
||||
"""
|
||||
cleared_memberships = 0
|
||||
if not modeladmin.has_delete_permission(request):
|
||||
raise PermissionDenied
|
||||
for membership in queryset:
|
||||
if membership.membership == False:
|
||||
[setattr(membership, fieldname, None)
|
||||
for fieldname in membership.nullable_personal_data]
|
||||
[setattr(membership, fieldname, "")
|
||||
for fieldname in membership.blankable_personal_data]
|
||||
membership.is_active = False
|
||||
membership.confirmed = False
|
||||
membership.membership = False
|
||||
membership.save()
|
||||
cleared_memberships += 1
|
||||
else:
|
||||
modeladmin.message_user(request, _(
|
||||
"Can't remove personal data from active member %s.") % membership.username, messages.ERROR)
|
||||
if cleared_memberships > 0:
|
||||
modeladmin.message_user(request, _(
|
||||
"Cleared %d personal data profiles.") % cleared_memberships, messages.INFO)
|
||||
|
||||
|
||||
clear_personal_data.short_description = _("Clear personal Data")
|
||||
|
||||
|
||||
class ProxyGroup(Group):
|
||||
"""A Proxy to list Usergroups from django.contrib.auth here."""
|
||||
|
||||
@@ -50,6 +83,8 @@ class ProxyGroup(Group):
|
||||
|
||||
class MembershipAdmin(UserAdmin):
|
||||
"""Admin interface to manage membership. e.g. users."""
|
||||
actions = [clear_personal_data, ]
|
||||
|
||||
formfield_overrides = {
|
||||
fields.ThumbnailerImageField: {
|
||||
'widget': widgets.ImageClearableFileInput},
|
||||
@@ -62,9 +97,9 @@ class MembershipAdmin(UserAdmin):
|
||||
'is_active',
|
||||
'membership',
|
||||
'confirmed',
|
||||
'paid_until',
|
||||
'last_login',
|
||||
)
|
||||
list_editable = ('confirmed', 'paid_until',)
|
||||
list_editable = ('confirmed', )
|
||||
list_display_links = ('username',)
|
||||
fieldsets = (
|
||||
(None, {'fields': (('username', 'password'), 'gender', 'avatar',
|
||||
|
||||
@@ -3,16 +3,16 @@ Created on 03.10.2011
|
||||
|
||||
@author: Christian
|
||||
"""
|
||||
|
||||
from datetime import date
|
||||
from captcha.fields import ReCaptchaField
|
||||
from django import forms
|
||||
from django.conf import settings
|
||||
from django.contrib import auth
|
||||
from django.contrib.sites.models import Site
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from utils.massmailer import MassMailer
|
||||
from . import models
|
||||
from content.models import Page
|
||||
|
||||
|
||||
class MembershipForm(forms.ModelForm):
|
||||
@@ -25,46 +25,32 @@ class MembershipForm(forms.ModelForm):
|
||||
help_text=_('Input format: yyyy-mm-dd')
|
||||
)
|
||||
email = forms.EmailField(label=_('Email'), required=True)
|
||||
required_membership_fields = ('first_name', 'last_name', 'birthday')
|
||||
|
||||
class Meta:
|
||||
"""get the user model dyamicly"""
|
||||
model = auth.get_user_model()
|
||||
fields = (
|
||||
'username', 'gender', 'first_name', 'last_name', 'email', 'avatar',
|
||||
'website', 'membership', 'birthday', 'telephone', 'street_name',
|
||||
'post_code', 'city'
|
||||
'username', 'email', 'avatar',
|
||||
'membership', 'first_name', 'last_name', 'birthday',
|
||||
)
|
||||
|
||||
def clean_birthday(self):
|
||||
"""If the user wants to be a member the birthday field is mandatory."""
|
||||
if self.cleaned_data['membership'] \
|
||||
and not self.cleaned_data['birthday']:
|
||||
raise forms.ValidationError(_('For your membership, we need this. \
|
||||
Please fill out this field yet.'))
|
||||
return self.cleaned_data['birthday']
|
||||
|
||||
def clean_street_name(self):
|
||||
"""If the user wants to be a member the address is mandatory."""
|
||||
if self.cleaned_data['membership'] \
|
||||
and not self.cleaned_data['street_name']:
|
||||
raise forms.ValidationError(_('For your membership, we need this. \
|
||||
Please fill out this field yet.'))
|
||||
return self.cleaned_data['street_name']
|
||||
|
||||
def clean_post_code(self):
|
||||
"""If the user wants to be a member the address is mandatory."""
|
||||
if self.cleaned_data['membership'] \
|
||||
and not self.cleaned_data['post_code']:
|
||||
raise forms.ValidationError(_('For your membership, we need this. \
|
||||
Please fill out this field yet.'))
|
||||
return self.cleaned_data['post_code']
|
||||
|
||||
def clean_city(self):
|
||||
"""If the user wants to be a member the address is mandatory."""
|
||||
if self.cleaned_data['membership'] and not self.cleaned_data['city']:
|
||||
raise forms.ValidationError(_('For your membership, we need this. \
|
||||
Please fill out this field yet.'))
|
||||
return self.cleaned_data['city']
|
||||
def clean(self):
|
||||
cleaned_data = super().clean()
|
||||
errormsg = _('For your membership, we need this. \
|
||||
Please fill out this field yet.')
|
||||
membership = cleaned_data.get('membership')
|
||||
for fieldname in self.required_membership_fields:
|
||||
if membership and not cleaned_data.get(fieldname):
|
||||
self.add_error(fieldname, errormsg)
|
||||
if membership and cleaned_data.get('birthday'):
|
||||
birthday = cleaned_data.get('birthday')
|
||||
today = date.today()
|
||||
age = today.year - birthday.year - (
|
||||
(today.month, today.day) < (birthday.month, birthday.day))
|
||||
if age < 16:
|
||||
self.add_error('birthday',
|
||||
'Midestalter für Mitlieder ist 16 Jahre!')
|
||||
|
||||
|
||||
class RegistrationForm(MembershipForm):
|
||||
@@ -78,15 +64,26 @@ class RegistrationForm(MembershipForm):
|
||||
widget=forms.PasswordInput(), label=_('password'))
|
||||
password2 = forms.CharField(
|
||||
widget=forms.PasswordInput(), label=_('password (again)'))
|
||||
privacy_policy = forms.BooleanField(
|
||||
label=_('Privacy policy'),
|
||||
help_text=_(
|
||||
'I have read and understood the <a href="%s">privacy policy</a>') %
|
||||
Page.objects.get(slug='privacypolicy').get_absolute_url())
|
||||
membership = forms.BooleanField(
|
||||
label=_('Membership'),
|
||||
help_text=_(
|
||||
'Yes, I confirm that I am in agreement with the '
|
||||
'<a href="%s">statutes</a> and would like to become a member.') %
|
||||
Page.objects.get(slug='statutes').get_absolute_url())
|
||||
|
||||
recaptcha = ReCaptchaField()
|
||||
|
||||
class Meta:
|
||||
"""Metadata to localize and customize the ModelForm."""
|
||||
model = auth.get_user_model()
|
||||
fields = ('first_name', 'last_name', 'username', 'email',
|
||||
'username', 'gender', 'first_name', 'last_name', 'email',
|
||||
'avatar',
|
||||
'website', 'membership', 'birthday', 'telephone',
|
||||
'membership', 'birthday', 'telephone',
|
||||
'street_name',
|
||||
'post_code', 'city'
|
||||
)
|
||||
|
||||
Binary file not shown.
@@ -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: 2019-12-13 23:38+0100\n"
|
||||
"PO-Revision-Date: 2018-05-08 00:19+0105\n"
|
||||
"Last-Translator: b'Christian Berg <kasu@xendynastie.at>'\n"
|
||||
"Language-Team: Kasu <verein@kasu.at>\n"
|
||||
"Language: de\n"
|
||||
@@ -17,7 +17,7 @@ msgstr ""
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
"X-Generator: Poedit 1.8.9\n"
|
||||
"X-Translated-Using: django-rosetta 0.7.14\n"
|
||||
"X-Translated-Using: django-rosetta 0.8.1\n"
|
||||
|
||||
#: __init__.py:11
|
||||
msgid "Male"
|
||||
@@ -31,32 +31,46 @@ msgstr "Weiblich"
|
||||
msgid "Activate selected User"
|
||||
msgstr "Ausgewählte Benutzer freischalten"
|
||||
|
||||
#: admin.py:38
|
||||
#: admin.py:39
|
||||
msgid "Cleanup selected Activation Requests"
|
||||
msgstr "Ausgewählte Aktivierungsanfragen bereinigen"
|
||||
|
||||
#: admin.py:47
|
||||
#: admin.py:62
|
||||
#, python-format
|
||||
msgid "Can't remove personal data from active member %s."
|
||||
msgstr ""
|
||||
"Persönliche Daten von aktiven Mitglied %s können nicht entfernt werden."
|
||||
|
||||
#: admin.py:64
|
||||
#, python-format
|
||||
msgid "Cleared %d personal data profiles."
|
||||
msgstr "Persönliche Daten in %d Profilen entfernt."
|
||||
|
||||
#: admin.py:66
|
||||
msgid "Clear personal Data"
|
||||
msgstr "Persönliche Daten bereinigen"
|
||||
|
||||
#: admin.py:75
|
||||
msgid "Group"
|
||||
msgstr "Gruppe"
|
||||
|
||||
#: admin.py:48
|
||||
#: admin.py:76
|
||||
msgid "Groups"
|
||||
msgstr "Gruppen"
|
||||
|
||||
#: admin.py:72 models.py:162 models.py:215
|
||||
#: templates/membership/register_form.html:32
|
||||
#: admin.py:102 forms.py:73 models.py:163 models.py:218
|
||||
msgid "Membership"
|
||||
msgstr "Mitgliedschaft"
|
||||
|
||||
#: admin.py:77
|
||||
#: admin.py:107
|
||||
msgid "Permissions"
|
||||
msgstr "Berechtigung"
|
||||
|
||||
#: admin.py:79
|
||||
#: admin.py:109
|
||||
msgid "Important dates"
|
||||
msgstr "Wichtige Daten"
|
||||
|
||||
#: forms.py:23
|
||||
#: forms.py:23 templates/membership/membership_detail.html:46
|
||||
msgid "birthday"
|
||||
msgstr "Geburtstag"
|
||||
|
||||
@@ -64,109 +78,121 @@ msgstr "Geburtstag"
|
||||
msgid "Input format: yyyy-mm-dd"
|
||||
msgstr "Eingabeformat: tt.mm.jjjj"
|
||||
|
||||
#: forms.py:27
|
||||
#: forms.py:27 templates/membership/membership_detail.html:48
|
||||
msgid "Email"
|
||||
msgstr "E-Mail"
|
||||
|
||||
#: forms.py:42 forms.py:50 forms.py:58
|
||||
#: forms.py:40
|
||||
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
|
||||
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
|
||||
#: forms.py:64
|
||||
msgid "password"
|
||||
msgstr "Passwort"
|
||||
|
||||
#: forms.py:80
|
||||
#: forms.py:66
|
||||
msgid "password (again)"
|
||||
msgstr "Passwort (wiederholen)"
|
||||
|
||||
#: 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:68
|
||||
msgid "Privacy policy"
|
||||
msgstr "Datenschutzerklärung"
|
||||
|
||||
#: forms.py:109
|
||||
#: forms.py:70
|
||||
#, python-format
|
||||
msgid "I have read and understood the <a href=\"%s\">privacy policy</a>"
|
||||
msgstr ""
|
||||
"Ich habe die <a href=\"%s\"> Datenschutzerklärung</a> gelesen und verstanden"
|
||||
|
||||
#: forms.py:75
|
||||
#, python-format
|
||||
msgid ""
|
||||
"Yes, I confirm that I am in agreement with the <a href=\"%s\">statutes</a> "
|
||||
"and would like to become a member."
|
||||
msgstr ""
|
||||
"Ja, ich bin mit den <a href=\"%s\">Statuen</a> einverstanden und möchte "
|
||||
"Mitglied werden."
|
||||
|
||||
#: forms.py:99
|
||||
msgid "This username is already taken. Please choose another."
|
||||
msgstr "Diesen Benutzername ist schon vergeben. Bitte einen anderen auswählen."
|
||||
|
||||
#: forms.py:106
|
||||
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
|
||||
#: forms.py:116
|
||||
msgid "The two password fields didn't match."
|
||||
msgstr "Die beiden Passwörter passen nicht."
|
||||
|
||||
#: models.py:83
|
||||
#: models.py:84
|
||||
msgid "user"
|
||||
msgstr "Benutzer"
|
||||
|
||||
#: models.py:85
|
||||
#: models.py:86
|
||||
msgid "activation key"
|
||||
msgstr "Aktivierungsschlüssel"
|
||||
|
||||
#: models.py:89
|
||||
#: models.py:90
|
||||
msgid "pending activation"
|
||||
msgstr "Ausstehende Aktivierung"
|
||||
|
||||
#: models.py:90
|
||||
#: models.py:91
|
||||
msgid "pending activations"
|
||||
msgstr "Wartende Aktivierungen"
|
||||
|
||||
#: models.py:93
|
||||
#: models.py:94
|
||||
#, python-format
|
||||
msgid "user registration for %s"
|
||||
msgstr "Benutzerregistrierung für %s"
|
||||
|
||||
#: models.py:148
|
||||
#: models.py:149
|
||||
msgid "Gender"
|
||||
msgstr "Geschlecht"
|
||||
|
||||
#: models.py:164
|
||||
#: 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
|
||||
#: models.py:169
|
||||
msgid "Birthday Date"
|
||||
msgstr "Geburtstag"
|
||||
|
||||
#: models.py:172
|
||||
#: models.py:173 templates/membership/membership_detail.html:49
|
||||
msgid "Telephone"
|
||||
msgstr "Telefon"
|
||||
|
||||
#: models.py:178
|
||||
#: models.py:179 templates/membership/membership_detail.html:47
|
||||
msgid "Address"
|
||||
msgstr "Adresse"
|
||||
|
||||
#: models.py:184
|
||||
#: models.py:185
|
||||
msgid "Postcode"
|
||||
msgstr "Postleitzahl"
|
||||
|
||||
#: models.py:189
|
||||
#: models.py:190
|
||||
msgid "Town/City"
|
||||
msgstr "Ort"
|
||||
|
||||
#: models.py:197
|
||||
#: models.py:198
|
||||
msgid "Paid until"
|
||||
msgstr "Bezahlt bis"
|
||||
|
||||
#: models.py:203
|
||||
#: models.py:204
|
||||
msgid "Confirmed"
|
||||
msgstr "Bestätigt"
|
||||
|
||||
#: models.py:205
|
||||
#: models.py:206
|
||||
msgid "This person has paid the membership fee."
|
||||
msgstr "Diese Person hat ihre Mitgliedschaft bezahlt"
|
||||
|
||||
#: models.py:216
|
||||
#: models.py:219
|
||||
msgid "Memberships"
|
||||
msgstr "Mitgliedschaften"
|
||||
|
||||
@@ -181,16 +207,20 @@ msgid ""
|
||||
"We received an account request on %(site.domain)s for your email address.\n"
|
||||
"To activate your account please visit the following link:"
|
||||
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:"
|
||||
"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
|
||||
#, python-format
|
||||
msgid ""
|
||||
"If you do not want to open an account on %(site.domain)s, please ignore this email.\n"
|
||||
"If you do not want to open an account on %(site.domain)s, please ignore this "
|
||||
"email.\n"
|
||||
"Your information will then be deleted in a few days time."
|
||||
msgstr ""
|
||||
"Wenn du keinen Zugang für %(site.domain)s eröffnen willst, ignoriere diese E-Mail bitte.\n"
|
||||
"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
|
||||
@@ -267,81 +297,92 @@ msgstr "Anmerkung"
|
||||
msgid "This Hanchan does not validate"
|
||||
msgstr "Diese Hanchan ist ungültig"
|
||||
|
||||
#: templates/membership/membership_detail.html:6
|
||||
#: templates/membership/membership_detail.html:5
|
||||
msgid "profile for"
|
||||
msgstr "Profil für"
|
||||
|
||||
#: templates/membership/membership_detail.html:10
|
||||
#: templates/membership/membership_detail.html:9
|
||||
msgid "Ladder Hanchans"
|
||||
msgstr "Ladder Hanchans"
|
||||
|
||||
#: templates/membership/membership_detail.html:11
|
||||
#: templates/membership/membership_detail.html:10
|
||||
msgid "Kyu Hanchans"
|
||||
msgstr "Kyū Hanchans"
|
||||
|
||||
#: templates/membership/membership_detail.html:12
|
||||
#: templates/membership/membership_detail.html:11
|
||||
msgid "Dan Hanchans"
|
||||
msgstr "Dan Hanchans"
|
||||
|
||||
#: templates/membership/membership_detail.html:13
|
||||
#: templates/membership/membership_detail.html:12
|
||||
msgid "Invalid Hanchans"
|
||||
msgstr "Ungültige Hanchans"
|
||||
|
||||
#: templates/membership/membership_detail.html:14
|
||||
#: templates/membership/membership_detail.html:13
|
||||
msgid "Mai-Star Games"
|
||||
msgstr "Mai-Star Spiele"
|
||||
|
||||
#: templates/membership/membership_detail.html:20
|
||||
#: templates/membership/membership_detail.html:17
|
||||
msgid "Profile Image"
|
||||
msgstr "Profilbild"
|
||||
|
||||
#: templates/membership/membership_detail.html:28
|
||||
msgid "Member Since"
|
||||
msgstr "Mitglied seit"
|
||||
|
||||
#: templates/membership/membership_detail.html:26
|
||||
#: templates/membership/membership_detail.html:29
|
||||
msgid "Last Login"
|
||||
msgstr "Letzte Anmeldung"
|
||||
|
||||
#: templates/membership/membership_detail.html:38
|
||||
#: templates/membership/membership_detail.html:40
|
||||
msgid "Points"
|
||||
msgstr "Punkte"
|
||||
|
||||
#: templates/membership/membership_detail.html:42
|
||||
#: templates/membership/membership_detail.html:26
|
||||
msgid "Maximum"
|
||||
msgstr "Maximum"
|
||||
|
||||
#: templates/membership/membership_detail.html:32
|
||||
msgid "Games Total"
|
||||
msgstr "Spiele gesamt"
|
||||
|
||||
#: templates/membership/membership_detail.html:43
|
||||
#: templates/membership/membership_detail.html:45
|
||||
#: templates/membership/membership_detail.html:32
|
||||
#: templates/membership/membership_detail.html:35
|
||||
msgid "Won"
|
||||
msgstr "Gewonnen"
|
||||
|
||||
#: templates/membership/membership_detail.html:43
|
||||
#: templates/membership/membership_detail.html:45
|
||||
#: templates/membership/membership_detail.html:32
|
||||
#: templates/membership/membership_detail.html:35
|
||||
msgid "Good"
|
||||
msgstr "Gut"
|
||||
|
||||
#: templates/membership/membership_detail.html:45
|
||||
#: templates/membership/membership_detail.html:35
|
||||
msgid "Current Season"
|
||||
msgstr "Aktuelle Saison"
|
||||
|
||||
#: templates/membership/membership_detail.html:55
|
||||
#: templates/membership/membership_detail.html:42
|
||||
msgid "private data"
|
||||
msgstr "Private Daten"
|
||||
|
||||
#: templates/membership/membership_detail.html:43
|
||||
msgid "This data can only be seen by yourself and members of the board."
|
||||
msgstr ""
|
||||
"Diese Angaben können nur von dir selbst und von Mitgliedern des Vorstandes "
|
||||
"eingesehen werden."
|
||||
|
||||
#: templates/membership/membership_detail.html:45
|
||||
msgid "name"
|
||||
msgstr "Name"
|
||||
|
||||
#: templates/membership/membership_detail.html:50
|
||||
msgid "Member Since"
|
||||
msgstr "Mitglied seit"
|
||||
|
||||
#: templates/membership/membership_detail.html:51
|
||||
msgid "Last Login"
|
||||
msgstr "Letzte Anmeldung"
|
||||
|
||||
#: templates/membership/membership_detail.html:54
|
||||
msgid "Edit Profile"
|
||||
msgstr "Profil bearbeiten"
|
||||
|
||||
#: templates/membership/membership_detail.html:59
|
||||
#: templates/membership/membership_detail.html:55
|
||||
#: 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
|
||||
#, 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
|
||||
@@ -357,35 +398,22 @@ msgid "Save"
|
||||
msgstr "Speichern"
|
||||
|
||||
#: templates/membership/register_form.html:4
|
||||
#: templates/membership/register_form.html:7
|
||||
msgid "Registration"
|
||||
msgstr "Registrieren"
|
||||
|
||||
#: 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"
|
||||
" link in this verification email to confirm your email address, otherwise\n"
|
||||
" your can't login."
|
||||
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:10
|
||||
msgid "Login crendentials"
|
||||
msgstr "Anmeldedaten"
|
||||
|
||||
#: templates/membership/register_form.html:20
|
||||
msgid "name"
|
||||
msgstr "Name"
|
||||
#: templates/membership/register_form.html:16
|
||||
msgid "Club membership"
|
||||
msgstr "Vereinsmitgliedschaft"
|
||||
|
||||
#: templates/membership/register_form.html:26
|
||||
#: templates/registration/login.html:41
|
||||
msgid "login"
|
||||
msgstr "Anmelden"
|
||||
|
||||
#: templates/membership/register_form.html:39
|
||||
#: templates/membership/register_form.html:27
|
||||
msgid "reset"
|
||||
msgstr "Zurücksetzen"
|
||||
|
||||
#: templates/membership/register_form.html:41
|
||||
#: templates/registration/login.html:35
|
||||
#: templates/membership/register_form.html:29
|
||||
msgid "register"
|
||||
msgstr "Registrieren"
|
||||
|
||||
@@ -395,93 +423,22 @@ msgstr "Registrieren"
|
||||
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
|
||||
msgid "Login"
|
||||
#: templates/registration/login.html:17
|
||||
msgid "login"
|
||||
msgstr "Anmelden"
|
||||
|
||||
#: templates/registration/login.html:17
|
||||
msgid "Have you already registered?"
|
||||
msgstr "Bereits registriert?"
|
||||
#: templates/registration/login.html:19
|
||||
msgid "Your username and password didn't match. Please try again."
|
||||
msgstr "Anmeldung fehlgeschlagen, bitte Benutzername und Passwort überprüfen."
|
||||
|
||||
#: templates/registration/login.html:18
|
||||
#| msgid ""
|
||||
#| "\n"
|
||||
#| "<p>As a registered member you can:</p>\n"
|
||||
#| "<ul>\n"
|
||||
#| " <li>leave comments on this page.</li>\n"
|
||||
#| " <li>subscribe to our Newsletter</li>\n"
|
||||
#| " <li>apply to a membership to our club</li>\n"
|
||||
#| " <li>club-members have access to our ranking-system</li>\n"
|
||||
#| "</ul>\n"
|
||||
msgid ""
|
||||
"\n"
|
||||
" <p>As a registered member you can:</p>\n"
|
||||
" <ul>\n"
|
||||
" <li>leave comments on this page.</li>\n"
|
||||
" <li>subscribe to our Newsletter</li>\n"
|
||||
" <li>apply to a membership to our club</li>\n"
|
||||
" <li>club-members have access to our ranking-system</li>\n"
|
||||
" </ul>\n"
|
||||
" "
|
||||
msgstr ""
|
||||
"\n"
|
||||
"<p>Als registrierter auf dieser Seite kannst du:</p>\n"
|
||||
"<ul>\n"
|
||||
" <li>Kommentare auf dieser Seite hinterlassen.</li>\n"
|
||||
" <li>Dich für unseren Newsletter anmelden</li>\n"
|
||||
" <li>Mitglied in unserem Verein werden</li>\n"
|
||||
" <li>Vereinsmitglieder haben auch vollen Zugang zu unserem Ranking System</li>\n"
|
||||
"</ul>"
|
||||
|
||||
#: templates/registration/login.html:27
|
||||
#| msgid ""
|
||||
#| "\n"
|
||||
#| "<p>You can register here with your Google, or Facebook account.\n"
|
||||
#| "If you don't own such an account, or do not want to use it for authentication,\n"
|
||||
#| "you can fill out our registration form.</p>\n"
|
||||
msgid ""
|
||||
"\n"
|
||||
" <p>You can register here with your Google, or Facebook account.\n"
|
||||
" If you don't own such an account, or do not want to use it for\n"
|
||||
" authentication,\n"
|
||||
" you can fill out our registration form.</p>\n"
|
||||
" "
|
||||
msgstr ""
|
||||
"\n"
|
||||
"<p>Du kannst dich auch über deinen Facebook, Google, oder Twitter Account anmelden.\n"
|
||||
"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
|
||||
#| msgid "Your username and password didn't match. Please try again."
|
||||
msgid ""
|
||||
"Your username and password didn't match. Please try\n"
|
||||
" again."
|
||||
msgstr ""
|
||||
"Benutzername und Passwort stimmen nicht überein. Bitte noch einmal "
|
||||
"versuchen."
|
||||
|
||||
#: templates/registration/login.html:50
|
||||
#: templates/registration/login.html:22
|
||||
msgid "Forgot your Password?"
|
||||
msgstr "Passwort vergessen?"
|
||||
|
||||
#: templates/registration/login.html:60
|
||||
msgid "or login with an existing Account"
|
||||
msgstr "oder über einen existierenden Account anmelden"
|
||||
|
||||
#: templates/registration/login.html:63
|
||||
msgid "Login with Facebook"
|
||||
msgstr "Über Facebook anmelden"
|
||||
|
||||
#: templates/registration/login.html:66
|
||||
msgid "Login with Twitter"
|
||||
msgstr "Über Twitter anmelden"
|
||||
|
||||
#: templates/registration/login.html:69
|
||||
msgid "Login with Google"
|
||||
msgstr "Über Google Anmelden"
|
||||
#: templates/registration/login.html:26
|
||||
#: templates/registration/password_reset_complete.html:13
|
||||
msgid "Login"
|
||||
msgstr "Anmelden"
|
||||
|
||||
#: templates/registration/password_change_done.html:4
|
||||
#: templates/registration/password_change_done.html:7
|
||||
@@ -574,9 +531,83 @@ msgid "User Profile changed successfully"
|
||||
msgstr "Benutzerprofil erfolgreich geändert."
|
||||
|
||||
#: 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 "Associate with %(name)s"
|
||||
#~ msgstr "Verbinde mit %(name)s"
|
||||
|
||||
#~ msgid "or"
|
||||
#~ msgstr "oder"
|
||||
|
||||
#~ msgid "Login with Facebook"
|
||||
#~ msgstr "Über Facebook anmelden"
|
||||
|
||||
#~ msgid "Login with Twitter"
|
||||
#~ msgstr "Über Twitter anmelden"
|
||||
|
||||
#~ msgid "Login with Google"
|
||||
#~ msgstr "Über Google Anmelden"
|
||||
|
||||
#~ 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"
|
||||
#~ " link in this verification email to confirm your email address, "
|
||||
#~ "otherwise\n"
|
||||
#~ " your can't login."
|
||||
#~ 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."
|
||||
|
||||
#~ msgid "Have you already registered?"
|
||||
#~ msgstr "Bereits registriert?"
|
||||
|
||||
#~ msgid ""
|
||||
#~ "\n"
|
||||
#~ " <p>As a registered member you can:</p>\n"
|
||||
#~ " <ul>\n"
|
||||
#~ " <li>leave comments on this page.</li>\n"
|
||||
#~ " <li>subscribe to our Newsletter</li>\n"
|
||||
#~ " <li>apply to a membership to our club</li>\n"
|
||||
#~ " <li>club-members have access to our ranking-system</li>\n"
|
||||
#~ " </ul>\n"
|
||||
#~ " "
|
||||
#~ msgstr ""
|
||||
#~ "\n"
|
||||
#~ "<p>Als registrierter auf dieser Seite kannst du:</p>\n"
|
||||
#~ "<ul>\n"
|
||||
#~ " <li>Kommentare auf dieser Seite hinterlassen.</li>\n"
|
||||
#~ " <li>Dich für unseren Newsletter anmelden</li>\n"
|
||||
#~ " <li>Mitglied in unserem Verein werden</li>\n"
|
||||
#~ " <li>Vereinsmitglieder haben auch vollen Zugang zu unserem Ranking "
|
||||
#~ "System</li>\n"
|
||||
#~ "</ul>"
|
||||
|
||||
#~ msgid ""
|
||||
#~ "\n"
|
||||
#~ " <p>You can register here with your Google, or Facebook account.\n"
|
||||
#~ " If you don't own such an account, or do not want to use it for\n"
|
||||
#~ " authentication,\n"
|
||||
#~ " you can fill out our registration form.</p>\n"
|
||||
#~ " "
|
||||
#~ msgstr ""
|
||||
#~ "\n"
|
||||
#~ "<p>Du kannst dich auch über deinen Facebook, Google, oder Twitter Account "
|
||||
#~ "anmelden.\n"
|
||||
#~ "Wenn du so etwas nicht besitzt, oder nicht verwenden möchtest, \n"
|
||||
#~ "kannst du auch das Registrierungsformular ausfüllen.</p>"
|
||||
|
||||
#~ msgid "or login with an existing Account"
|
||||
#~ msgstr "oder über einen existierenden Account anmelden"
|
||||
|
||||
#~ 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."
|
||||
|
||||
#~ msgid "Given Name"
|
||||
#~ msgstr "Vorname"
|
||||
@@ -585,9 +616,9 @@ msgstr "Keine Mitgliedschaft gefunden welche der Anfrage entspricht"
|
||||
#~ msgstr "Nachname"
|
||||
|
||||
#~ msgid ""
|
||||
#~ "The Username can only contain the letters from A to Z, Numbers, and "
|
||||
#~ "the underscore. It must be at least 2 characters long, and cannot be"
|
||||
#~ " longer the 30. The first character must be a letter."
|
||||
#~ "The Username can only contain the letters from A to Z, Numbers, "
|
||||
#~ "and the underscore. It must be at least 2 characters long, and "
|
||||
#~ "cannot be longer the 30. The first character must be a letter."
|
||||
#~ msgstr ""
|
||||
#~ "Der Benutzername kann aus den Buchstaben A-Z, Ziffern und dem Unterstrich "
|
||||
#~ "bestehen. Es sollte wenigstens 2, aber maximal 30 Zeichen lang sein. Das "
|
||||
|
||||
@@ -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',
|
||||
|
||||
18
src/membership/migrations/0008_auto_20190106_1954.py
Normal file
18
src/membership/migrations/0008_auto_20190106_1954.py
Normal file
@@ -0,0 +1,18 @@
|
||||
# Generated by Django 2.1.5 on 2019-01-06 18:54
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('membership', '0007_auto_20171115_0653'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='membership',
|
||||
name='last_name',
|
||||
field=models.CharField(blank=True, max_length=150, verbose_name='last name'),
|
||||
),
|
||||
]
|
||||
@@ -7,8 +7,8 @@ from os import path
|
||||
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.models import AbstractUser
|
||||
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 _
|
||||
from easy_thumbnails.fields import ThumbnailerImageField
|
||||
@@ -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)
|
||||
@@ -149,7 +150,7 @@ class Membership(AbstractUser):
|
||||
max_length=1,
|
||||
choices=GENDER_CHOICES,
|
||||
blank=True,
|
||||
null=True
|
||||
null=True,
|
||||
)
|
||||
website = models.URLField(blank=True)
|
||||
avatar = ThumbnailerImageField(
|
||||
@@ -205,8 +206,10 @@ class Membership(AbstractUser):
|
||||
help_text=_('This person has paid the membership fee.')
|
||||
)
|
||||
|
||||
# comment = models.TextField(blank=True)
|
||||
# objects = MembershipManager()
|
||||
nullable_personal_data = (
|
||||
'gender', 'birthday', 'telephone', 'street_name', 'post_code', 'city')
|
||||
blankable_personal_data = (
|
||||
'email', 'password', 'first_name', 'last_name', 'website',)
|
||||
|
||||
class Meta(object):
|
||||
"""To manage object ordering and dispalynames on the admin interface."""
|
||||
@@ -215,6 +218,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
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
{% extends "base.html" %}
|
||||
{% load i18n comments thumbnail %}
|
||||
{% extends "base.html" %}{% load i18n comments thumbnail %}
|
||||
|
||||
{% block title %}{{ membership.first_name }} {{membership.last_name}}{% endblock %}
|
||||
|
||||
@@ -12,76 +11,56 @@
|
||||
<li><a href="{% url 'player-dan-score' membership.username %}">{% trans "Dan Hanchans" %}</a></li>
|
||||
<li><a href="{% url 'player-invalid-score' membership.username %}">{% trans "Invalid Hanchans" %}</a></li>
|
||||
<li><a href="{% url 'maistar-player-games' membership.username %}">{% trans "Mai-Star Games" %}</a></li>
|
||||
</ul>
|
||||
{% endblock %}
|
||||
</ul>{% endblock %}
|
||||
|
||||
{% block maincontent %}
|
||||
{% if membership.avatar %}
|
||||
<img class="grid_3" src="{% thumbnail membership.avatar '220x220' crop='smart' %}" alt="{% trans 'Profile Image' %}"/>
|
||||
{% else %}
|
||||
<div class="grid_3"> Noch kein Foto hoch geladen</div>
|
||||
{% endif %}
|
||||
{% block maincontent %}{% if membership.avatar %}
|
||||
<img class="grid_3" src="{% thumbnail membership.avatar '220x220' crop='smart' %}" alt="{% trans 'Profile Image' %}"/>{% else %}
|
||||
<div class="grid_3"> Noch kein Foto hoch geladen</div>{% endif %}
|
||||
|
||||
<div class="grid_6">
|
||||
<ul>
|
||||
<li><strong>Name:</strong> {{membership.first_name}} {{membership.last_name}}</li>
|
||||
<li><strong>{% trans "Member Since" %}:</strong> {{membership.date_joined}}</li>
|
||||
<li><strong>{% trans "Last Login" %}:</strong> {{membership.last_login}}</li>
|
||||
{% if website %}
|
||||
<li><strong>Homepage:</strong> <a href="{{website}}">{{website}}</a></li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
<div class="{% if membership == user or perms.membership.change_membership%}grid_4{% else %}grid_9{% endif %}" >
|
||||
{% if kyu_dan_ranking %}
|
||||
<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 %}
|
||||
<li><strong>{% trans 'Games Total' %}: </strong> {{ kyu_dan_ranking.hanchan_count }} Hanchans -
|
||||
{{kyu_dan_ranking.won_hanchans }} {% trans 'Won' %} / {{ kyu_dan_ranking.good_hanchans }} {% trans 'Good' %}
|
||||
<li>
|
||||
<strong>{% trans 'Games Total' %}: </strong> {{ kyu_dan_ranking.hanchan_count }} Hanchans - {{kyu_dan_ranking.won_hanchans }} {% trans 'Won' %} / {{ kyu_dan_ranking.good_hanchans }} {% trans 'Good' %}
|
||||
</li>
|
||||
<li><strong>{% trans 'Current Season' %}: </strong> {{ ladder_ranking.hanchan_count }} Hanchans - {{ ladder_ranking.won_hanchans }} {% trans 'Won' %} / {{ ladder_ranking.good_hanchans }} {% trans 'Good' %}
|
||||
<li>
|
||||
<strong>{% trans 'Current Season' %}: </strong> {{ ladder_ranking.hanchan_count }} Hanchans - {{ ladder_ranking.won_hanchans }} {% trans 'Won' %} / {{ ladder_ranking.good_hanchans }} {% trans 'Good' %}
|
||||
</li>
|
||||
</ul>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{% if membership == user or perms.membership.change_membership%}
|
||||
<div class="grid_5">
|
||||
<h3>{% trans 'private data' %}</h3>
|
||||
<p>{% blocktrans %}This data can only be seen by yourself and members of the board.{% endblocktrans %} </p>
|
||||
<ul>
|
||||
{% if membership.first_name %}<li><strong>{% trans "name" %}:</strong> {{membership.first_name}} {{membership.last_name}}</li>{% endif %}
|
||||
{% if membership.birthday %}<li><strong>{% trans "birthday" %}:</strong> {{membership.birthday}}</li>{% endif %}
|
||||
{% if membership.street_name %}<li><strong>{% trans "Address" %}:</strong> <address>{{ membership.street_name }}<br/> {{ membership.post_code }} {{ membership.city }}</address></li>{% endif %}
|
||||
{% if membership.email %}<li><strong>{% trans "Email" %}:</strong> <a href="mailto:{{ membership.email }}">{{membership.email}}</a></li>{% endif %}
|
||||
{% if membership.telephone %}<li><strong>{% trans "Telephone" %}:</strong> <a href="tel:{{ membership.telephone }}">{{membership.telephone}}</a></li>{% endif %}
|
||||
<li><strong>{% trans "Member Since" %}:</strong> {{membership.date_joined}}</li>
|
||||
<li><strong>{% trans "Last Login" %}:</strong> {{membership.last_login}}</li>
|
||||
</ul>
|
||||
{% ifequal membership user %}
|
||||
<div class="grid_3">
|
||||
<a href="{% url 'membership-edit' membership.username %}" class="button">
|
||||
<span class="fa fa-pencil"></span>
|
||||
{% trans "Edit Profile" %}
|
||||
</a>
|
||||
<a href="{% url 'password_change' %}" class="button">
|
||||
<span class="fa fa-key"></span>
|
||||
{% trans 'Change Password' %}
|
||||
</a>
|
||||
<a class="button" href="{% url 'social:begin' 'facebook' %}" rel="nofollow">
|
||||
<span class="fa fa-facebook"></span>
|
||||
{% blocktrans with 'Facebook' as name %}Associate with {{ name }}{% endblocktrans %}
|
||||
</a>
|
||||
<a class="button" href="{% url 'social:begin' 'twitter' %}" rel="nofollow">
|
||||
<span class="fa fa-twitter"></span>
|
||||
{% blocktrans with 'Twitter' as name %}Associate with {{ name }}{% endblocktrans %}
|
||||
</a>
|
||||
<a class="button" href="{% url 'social:begin' 'google-oauth2' %}" rel="nofollow">
|
||||
<span class="fa fa-google-plus"></span>
|
||||
{% blocktrans with 'Google' as name %}Associate with {{ name }}{% endblocktrans %}
|
||||
</a>
|
||||
|
||||
</div>
|
||||
<a href="{% url 'membership-edit' membership.username %}" class="button"> <span class="fa fa-pencil"></span> {% trans "Edit Profile" %} </a>
|
||||
<a href="{% url 'password_change' %}" class="button"> <span class="fa fa-key"></span> {% trans 'Change Password' %}</a>
|
||||
{% endifequal %}
|
||||
|
||||
{% block score_list %} {% endblock %}
|
||||
{% endblock %}
|
||||
|
||||
{% block comments %}
|
||||
{% if membership %}
|
||||
{% render_comment_list for membership %}
|
||||
{% render_comment_form for membership %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
|
||||
|
||||
{% block score_list %} {% endblock %}{% endblock %}
|
||||
|
||||
{% block comments %}{% if membership %}{% render_comment_list for membership %}{% render_comment_form for membership %}{% endif %}{% endblock %}
|
||||
|
||||
{% block buttonbar%}{%endblock%}
|
||||
|
||||
@@ -17,18 +17,22 @@
|
||||
</p>
|
||||
</fieldset>
|
||||
</form>
|
||||
{% endblock %}
|
||||
|
||||
{% block javascript %}
|
||||
$(function() {
|
||||
$( "#id_birthday" ).datepicker({
|
||||
changeMonth: true,
|
||||
changeYear: true,
|
||||
dateFormat: 'yy-mm-dd',
|
||||
yearRange: '-50,0',
|
||||
firstDay: 1
|
||||
});
|
||||
});
|
||||
<script type="text/javascript">
|
||||
function togglePersonalData(event) {
|
||||
var membership = document.getElementById("id_membership").checked;
|
||||
var input_elements = ["id_first_name", "id_last_name", "id_birthday"];
|
||||
for (var i = 0; i < input_elements.length; i++) {
|
||||
element_id = input_elements[i]
|
||||
element = document.getElementById(element_id);
|
||||
element.disabled = !membership;
|
||||
if (membership == 0) {element.value = ""};
|
||||
};
|
||||
};
|
||||
|
||||
document.addEventListener('DOMContentLoaded',function() {document.querySelector('#id_membership').onchange=togglePersonalData;},false);
|
||||
togglePersonalData();
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
{% block buttonbar %}{% endblock %}
|
||||
|
||||
@@ -3,37 +3,25 @@
|
||||
|
||||
{% block title %}{% trans "Registration"%}{% endblock %}
|
||||
|
||||
{% block teaser%}
|
||||
<h1>{% trans "Registration"%}</h1>
|
||||
<div id="teaser_text">
|
||||
{% blocktrans %}After you've provided your account data, you'll receive
|
||||
an email asking you to verify your email address. You have to click on the
|
||||
link in this verification email to confirm your email address, otherwise
|
||||
your can't login.{% endblocktrans %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block maincontent %}
|
||||
<form method="post" action="{% url 'membership-register' %}">
|
||||
{% csrf_token %}
|
||||
<fieldset class="grid_5">
|
||||
<legend>{% trans "name"%}</legend>
|
||||
{% get_fieldset "gender, first_name, last_name, username" from form as form1 %}
|
||||
{% with form1 as form %}{% include "form.html" %}{% endwith %}
|
||||
</fieldset>
|
||||
|
||||
<fieldset class="grid_7">
|
||||
<legend>{% trans "login"%}</legend>
|
||||
{% get_fieldset "email, password1, password2, recaptcha" from form as form1 %}
|
||||
<legend>{% trans "Login crendentials"%}</legend>
|
||||
{% get_fieldset "username, email, password1, password2, privacy_policy, recaptcha" from form as form1 %}
|
||||
{% with form1 as form %}{% include "form.html" %}{% endwith %}
|
||||
</fieldset>
|
||||
|
||||
<fieldset class="grid_12">
|
||||
<legend>{% trans "Membership"%}</legend>
|
||||
{% get_fieldset "membership, birthday, street_name, post_code, city, telephone, website" from form as form2 %}
|
||||
<fieldset class="grid_5">
|
||||
<legend>{% trans "Club membership"%}</legend>
|
||||
{% get_fieldset "membership, first_name, last_name, birthday" from form as form2 %}
|
||||
{% with form2 as form %}{% include "form.html" %}{% endwith %}
|
||||
</fieldset>
|
||||
|
||||
<div class="grid_12">
|
||||
{{ current_page.content }}
|
||||
</div>
|
||||
|
||||
<div class="grid_12">
|
||||
<p class="buttonbar">
|
||||
<button type="reset"><span class="fa fa-undo"></span> {% trans 'reset' %}
|
||||
@@ -42,4 +30,19 @@
|
||||
</p>
|
||||
</div>
|
||||
</form>
|
||||
<script type="text/javascript">
|
||||
function togglePersonalData(event) {
|
||||
var membership = document.getElementById("id_membership").checked;
|
||||
var input_elements = ["id_first_name", "id_last_name", "id_birthday"];
|
||||
for (var i = 0; i < input_elements.length; i++) {
|
||||
element_id = input_elements[i]
|
||||
element = document.getElementById(element_id);
|
||||
element.disabled = !membership;
|
||||
if (membership == 0) {element.value = ""};
|
||||
};
|
||||
};
|
||||
|
||||
document.addEventListener('DOMContentLoaded',function() {document.querySelector('#id_membership').onchange=togglePersonalData;},false);
|
||||
togglePersonalData();
|
||||
</script>
|
||||
{% endblock %}
|
||||
6
src/membership/templates/registration/logged_out.html
Executable file
6
src/membership/templates/registration/logged_out.html
Executable file
@@ -0,0 +1,6 @@
|
||||
{% extends "base.html" %}{% load i18n %}
|
||||
|
||||
{% block content %}
|
||||
{{ current_page.content }}
|
||||
{% endblock %}
|
||||
|
||||
@@ -1,72 +1,32 @@
|
||||
{% extends "base.html" %}
|
||||
{% load i18n %}
|
||||
{% extends "base.html" %}{% load i18n %}
|
||||
|
||||
{% block title %}{% trans 'Login' %}{% endblock %}
|
||||
{% block description %}Anmelden auf Kasu.at{% endblock %}
|
||||
|
||||
{% block extra_head %}
|
||||
<link rel="canonical" href="{% url 'login' %}"/>
|
||||
{% endblock %}
|
||||
<link rel="canonical" href="{% url 'login' %}"/>{% endblock %}
|
||||
|
||||
|
||||
{% block teaser %}<h1>{% trans 'Login' %}</h1>{% endblock %}
|
||||
{% block maincontent %}
|
||||
<form method="post" action="{% url 'login' %}">
|
||||
<h2 class="grid_12">Auf der Seite Anmelden</h2>
|
||||
|
||||
<div class="grid_7">
|
||||
<h2>{% trans "Have you already registered?" %}</h2>
|
||||
{% blocktrans %}
|
||||
<p>As a registered member you can:</p>
|
||||
<ul>
|
||||
<li>leave comments on this page.</li>
|
||||
<li>subscribe to our Newsletter</li>
|
||||
<li>apply to a membership to our club</li>
|
||||
<li>club-members have access to our ranking-system</li>
|
||||
</ul>
|
||||
{% endblocktrans %}
|
||||
{% blocktrans %}
|
||||
<p>You can register here with your Google, or Facebook account.
|
||||
If you don't own such an account, or do not want to use it for
|
||||
authentication,
|
||||
you can fill out our registration form.</p>
|
||||
{% endblocktrans %}
|
||||
<p class="buttonbar">
|
||||
<a href="{% url 'membership-register' %}" class="button"><span
|
||||
class="fa fa-user-plus"></span> {%trans "register"%}</a></p>
|
||||
{% csrf_token %}
|
||||
<div class="grid_6">
|
||||
{{ current_page.content }}
|
||||
</div>
|
||||
|
||||
|
||||
{% csrf_token %}
|
||||
<fieldset class="grid_5">
|
||||
<div class="grid_6">
|
||||
<fieldset>
|
||||
<legend>{% trans 'login' %}</legend>
|
||||
{% csrf_token %}
|
||||
{% include 'form.html' %}
|
||||
{% if form.errors %}
|
||||
<p>{% blocktrans %}Your username and password didn't match. Please try
|
||||
again.{% endblocktrans %}</p>
|
||||
{% endif %}
|
||||
<input type="hidden" name="next" value="{{next}}"/>
|
||||
<p><a href="{% url 'password_reset' %}">
|
||||
{% trans 'Forgot your Password?'%}</a></p>
|
||||
{% csrf_token %} {% include 'form.html' %} {% if form.errors %}
|
||||
<p>{% blocktrans %}Your username and password didn't match. Please try again.{% endblocktrans %}</p>
|
||||
{% endif %} <input type="hidden" name="next" value="{{next}}"/>
|
||||
<p>
|
||||
<a href="{% url 'password_reset' %}"> {% trans 'Forgot your Password?'%}</a>
|
||||
</p>
|
||||
<div class="buttonbar">
|
||||
<button type="submit"><span class="fa fa-sign-in"></span>
|
||||
{% trans 'Login' %}
|
||||
<button type="submit">
|
||||
<span class="fa fa-sign-in"></span> {% trans 'Login' %}
|
||||
</button>
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
|
||||
<div class="grid_5">
|
||||
<h2>{% trans "or login with an existing Account" %}</h2>
|
||||
<a rel="nofollow" href="{% url 'social:begin' 'facebook' %}"><span
|
||||
class="fa fa-facebook fa-5x"
|
||||
title="{% trans 'Login with Facebook' %}"></span></a>
|
||||
<a rel="nofollow" href="{% url 'social:begin' 'twitter' %}"><span
|
||||
class="fa fa-twitter fa-5x"
|
||||
title="{% trans 'Login with Twitter' %}"></span></a>
|
||||
<a rel="nofollow" href="{% url 'social:begin' 'google-oauth2' %}"><span
|
||||
class="fa fa-google-plus fa-5x"
|
||||
title="{% trans 'Login with Google' %}"></span></a>
|
||||
</div>
|
||||
</form>
|
||||
{% endblock %}
|
||||
|
||||
@@ -9,6 +9,7 @@ from django.conf.urls import url
|
||||
from . import views
|
||||
|
||||
urlpatterns = [
|
||||
url(r'^$', views.MembershipDetail.as_view()),
|
||||
url(r'^activate/(?P<activation_key>[\d\w]+)/$',
|
||||
views.ActivateRegistration.as_view(),
|
||||
name='membership-activate-registration'),
|
||||
|
||||
@@ -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 _
|
||||
@@ -106,7 +106,7 @@ class MembershipDetail(LoginRequiredMixin, generic.DetailView):
|
||||
try:
|
||||
if self.kwargs.get('username'):
|
||||
return queryset.get(username=self.kwargs['username'])
|
||||
elif self.request.user.is_authenticated():
|
||||
else:
|
||||
return self.request.user
|
||||
except models.Membership.DoesNotExist:
|
||||
raise Http404(_("No Membership found matching the query"))
|
||||
|
||||
Binary file not shown.
@@ -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: 2019-12-13 23:38+0100\n"
|
||||
"PO-Revision-Date: 2016-09-28 00:24+0200\n"
|
||||
"Last-Translator: Christian Berg <xeniac@posteo.at>\n"
|
||||
"Language-Team: Kasu <verein@kasu.at>\n"
|
||||
|
||||
@@ -3,10 +3,9 @@ between Tags, an at the beginning and the end of the content."""
|
||||
from django.utils.html import strip_spaces_between_tags
|
||||
|
||||
|
||||
class CompressHtmlMiddleware(object):
|
||||
class CompressHtmlMiddleware:
|
||||
"""This Middleware compresses strips the spaces between tags, and at the
|
||||
beginning and the end of the content."""
|
||||
# TODO: Port to django 1.10 and upward
|
||||
|
||||
def __init__(self, get_response):
|
||||
"""
|
||||
@@ -14,7 +13,6 @@ class CompressHtmlMiddleware(object):
|
||||
:param get_response:
|
||||
"""
|
||||
self.get_response = get_response
|
||||
regex = ">[\s]*<"
|
||||
|
||||
def __call__(self, request):
|
||||
"""
|
||||
@@ -25,9 +23,36 @@ class CompressHtmlMiddleware(object):
|
||||
|
||||
# Code to be executed for each request before
|
||||
# the view (and later middleware) are called.
|
||||
|
||||
response = self.get_response(request)
|
||||
if 'text/html' in response['Content-Type']:
|
||||
response.content = strip_spaces_between_tags(
|
||||
response.content).strip()
|
||||
return response
|
||||
|
||||
|
||||
class SetRemoteAddrFromForwardedFor:
|
||||
def __init__(self, get_response):
|
||||
self.get_response = get_response
|
||||
# One-time configuration and initialization.
|
||||
|
||||
def __call__(self, request):
|
||||
# Code to be executed for each request before
|
||||
# the view (and later middleware) are called.
|
||||
|
||||
response = self.get_response(request)
|
||||
|
||||
# Code to be executed for each request/response after
|
||||
# the view is called.
|
||||
|
||||
return response
|
||||
|
||||
def process_request(self, request):
|
||||
try:
|
||||
real_ip = request.META['HTTP_X_FORWARDED_FOR']
|
||||
real_ip = real_ip.split(",")[0]
|
||||
except KeyError:
|
||||
pass
|
||||
else:
|
||||
# HTTP_X_FORWARDED_FOR can be a comma-separated list of IPs.
|
||||
# Take just the first one.
|
||||
request.META['REMOTE_ADDR'] = real_ip
|
||||
|
||||
Reference in New Issue
Block a user