Eine Menge Aufräumarbeiten.

* Eine Testsuite um Mahrjong Ranking Berechnungen zu testen
* Erste Arbeiten um die Workarounds aus dem "utils" Paket los zu werden.
* Vieles am Code umformatiert für PEP8 conformität
This commit is contained in:
2017-06-07 13:25:30 +02:00
parent cf0bbb4c8f
commit a26a91c360
93 changed files with 33531 additions and 2737 deletions

View File

@@ -8,7 +8,6 @@ django-csp
django-extra-views django-extra-views
django-markdown django-markdown
easy-thumbnails easy-thumbnails
flup-py3
icalendar icalendar
markdown markdown
pillow pillow

View File

@@ -1,33 +1,33 @@
"""
Created on 19.09.2011
@author: christian
"""
# import stuff we need from django
from django.contrib import admin from django.contrib import admin
from . import models from . import models
class PageTabularInline(admin.TabularInline): class PageTabularInline(admin.TabularInline):
""" Displays the sub-pages of an page element. """
fields = ('title_de', 'menu_name_de', "position",) fields = ('title_de', 'menu_name_de', "position",)
model = models.Page model = models.Page
sortable_field_name = "position" sortable_field_name = "position"
class ArticleAdmin(admin.ModelAdmin): class ArticleAdmin(admin.ModelAdmin):
""" Admin interface for the news-articles. """
prepopulated_fields = {"slug": ("headline_de",)} prepopulated_fields = {"slug": ("headline_de",)}
list_display = ('headline', 'category', 'date_created', 'author',) list_display = ('headline', 'category', 'date_created', 'author',)
list_editable = ('category', 'author') list_editable = ('category', 'author')
class CategoryAdmin(admin.ModelAdmin): class CategoryAdmin(admin.ModelAdmin):
""" Admin interface for categories of the news-aricles. """
list_display = ['name', 'slug', 'description'] list_display = ['name', 'slug', 'description']
list_display_links = ('name', 'slug',) list_display_links = ('name', 'slug',)
prepopulated_fields = {'slug': ('name_de',)} prepopulated_fields = {'slug': ('name_de',)}
class PageAdmin(admin.ModelAdmin): class PageAdmin(admin.ModelAdmin):
"""
Admin interface for static pages that can contain HTML or PDF Content.
"""
prepopulated_fields = {"slug": ('menu_name_de',)} prepopulated_fields = {"slug": ('menu_name_de',)}
inlines = [PageTabularInline, ] inlines = [PageTabularInline, ]
list_display = ('position', 'menu_name', 'title', 'parent', 'path',) list_display = ('position', 'menu_name', 'title', 'parent', 'path',)
@@ -54,6 +54,7 @@ class PageAdmin(admin.ModelAdmin):
}), }),
) )
admin.site.register(models.Article, ArticleAdmin) admin.site.register(models.Article, ArticleAdmin)
admin.site.register(models.Page, PageAdmin) admin.site.register(models.Page, PageAdmin)
admin.site.register(models.Category, CategoryAdmin) admin.site.register(models.Category, CategoryAdmin)

View File

@@ -3,11 +3,10 @@ Created on 04.10.2011
@author: christian @author: christian
""" """
import django.forms from django import forms
from django.template.defaultfilters import slugify from django.template.defaultfilters import slugify
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from utils.html5 import forms
from . import models from . import models
@@ -35,9 +34,9 @@ class ArticleForm(forms.ModelForm):
class PageForm(forms.ModelForm): class PageForm(forms.ModelForm):
error_css_class = 'error' error_css_class = 'error'
required_css_class = 'required' required_css_class = 'required'
content_type = django.forms.ChoiceField( content_type = forms.ChoiceField(
choices=models.CONTENT_CHOICES, choices=models.CONTENT_CHOICES,
widget=django.forms.RadioSelect widget=forms.RadioSelect
) )
class Meta(object): class Meta(object):
@@ -47,7 +46,7 @@ class PageForm(forms.ModelForm):
def clean(self): def clean(self):
cleaned_data = super(PageForm, self).clean() cleaned_data = super(PageForm, self).clean()
content_type = cleaned_data.get("content_type") content_type = cleaned_data.get("content_type")
pdf_de = cleaned_data.get("pdf_de") cleaned_data.get("pdf_de")
if content_type == "2" and not cleaned_data.get("pdf_de"): if content_type == "2" and not cleaned_data.get("pdf_de"):
msg = _('Please upload a PDF-File to this PDF-Page.') msg = _('Please upload a PDF-File to this PDF-Page.')
self._errors["content_type"] = self.error_class([msg]) self._errors["content_type"] = self.error_class([msg])

View File

@@ -8,7 +8,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: kasu.content\n" "Project-Id-Version: kasu.content\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2016-09-28 00:25+0200\n" "POT-Creation-Date: 2017-05-10 23:16+0200\n"
"PO-Revision-Date: 2016-09-28 00:24+0200\n" "PO-Revision-Date: 2016-09-28 00:24+0200\n"
"Last-Translator: Christian Berg <xeniac@posteo.at>\n" "Last-Translator: Christian Berg <xeniac@posteo.at>\n"
"Language-Team: Deutsch <>\n" "Language-Team: Deutsch <>\n"
@@ -20,235 +20,265 @@ msgstr ""
"X-Translated-Using: django-rosetta 0.7.2\n" "X-Translated-Using: django-rosetta 0.7.2\n"
"X-Generator: Poedit 1.8.9\n" "X-Generator: Poedit 1.8.9\n"
#: src/content/feeds.py:14 #: content/feeds.py:14
msgid "Current news from Kasu" msgid "Current news from Kasu"
msgstr "Aktuelle Nachrichten von Kasu" msgstr "Aktuelle Nachrichten von Kasu"
#: src/content/feeds.py:42 #: content/feeds.py:42
msgid "Latest comments on kasu.at" msgid "Latest comments on kasu.at"
msgstr "Neueste Kommentare auf Kasu.at " msgstr "Neueste Kommentare auf Kasu.at "
#: src/content/feeds.py:43 #: content/feeds.py:43
msgid "Kasu - latest comments" msgid "Kasu - latest comments"
msgstr "Kasu - neue Kommentare" msgstr "Kasu - neue Kommentare"
#: src/content/forms.py:52 src/content/models.py:204 #: content/forms.py:52 content/models.py:249
msgid "Please upload a PDF-File to this PDF-Page." msgid "Please upload a PDF-File to this PDF-Page."
msgstr "Bitte eine PDF Datei für diese PDF Seite hochladen." msgstr "Bitte eine PDF Datei für diese PDF Seite hochladen."
#: src/content/models.py:51 #: content/models.py:51
msgid "Headline" msgid "Headline"
msgstr "Schlagzeile" msgstr "Schlagzeile"
#: src/content/models.py:53 #: content/models.py:53
msgid "Content" msgid "Content"
msgstr "Inhalt" msgstr "Inhalt"
#: src/content/models.py:55 src/content/models.py:234 #: content/models.py:55 content/models.py:279
#: src/content/templates/content/article_detail.html:25 #: content/templates/content/article_detail.html:25
msgid "Category" msgid "Category"
msgstr "Kategorie" msgstr "Kategorie"
#: src/content/models.py:56 src/content/models.py:228 #: content/models.py:56 content/models.py:273
msgid "Image" msgid "Image"
msgstr "Bild" msgstr "Bild"
#: src/content/models.py:58 src/content/models.py:230 #: content/models.py:58 content/models.py:275
msgid "Slug" msgid "Slug"
msgstr "Slug" msgstr "Slug"
#: src/content/models.py:60 #: content/models.py:60 content/templates/content/article_detail.html:23
#: src/content/templates/content/article_detail.html:23
msgid "Author" msgid "Author"
msgstr "Autor" msgstr "Autor"
#: src/content/models.py:61 #: content/models.py:61
msgid "Status" msgid "Status"
msgstr "Status" msgstr "Status"
#: src/content/models.py:63 #: content/models.py:63
msgid "Created" msgid "Created"
msgstr "Erstellt" msgstr "Erstellt"
#: src/content/models.py:64 #: content/models.py:64
msgid "Modified" msgid "Modified"
msgstr "Bearbeitet" msgstr "Bearbeitet"
#: src/content/models.py:68 #: content/models.py:68
msgid "Article" msgid "Article"
msgstr "Artikel" msgstr "Artikel"
#: src/content/models.py:69 #: content/models.py:69
msgid "Articles" msgid "Articles"
msgstr "Artikel" msgstr "Artikel"
#: src/content/models.py:126 src/content/models.py:132 #: content/models.py:126 content/models.py:132
msgid "The short name for the menu-entry of this page" msgid "The short name for the menu-entry of this page"
msgstr "Ein kurzer Name für den Menüeintrag" msgstr "Ein kurzer Name für den Menüeintrag"
#: src/content/models.py:136 src/content/models.py:139 #: content/models.py:137 content/models.py:142
msgid "This title appears in the HTML header" msgid "The page title as you'd like it to be seen by the public"
msgstr "Der Titel erscheint im HTML Header" msgstr ""
#: src/content/models.py:140 #: content/models.py:144
msgid "slug" msgid "slug"
msgstr "Slug" msgstr "Slug"
#: src/content/models.py:141 #: content/models.py:146
msgid ""
"The name of the page as it will appear in URLs e.g http://domain.com/blog/"
"[my-slug]/"
msgstr ""
#: content/models.py:153
msgid "Path" msgid "Path"
msgstr "Pfad" msgstr "Pfad"
#: src/content/models.py:147 #: content/models.py:165
msgid "Position" msgid "Position"
msgstr "Position" msgstr "Position"
#: src/content/models.py:149 #: content/models.py:170
msgid "status" msgid "status"
msgstr "Status" msgstr "Status"
#: src/content/models.py:155 #: content/models.py:172 content/models.py:173
#, fuzzy
#| msgid "Description"
msgid "search description"
msgstr "Beschreibung"
#: content/models.py:176
#, fuzzy
#| msgid "Content"
msgid "content type"
msgstr "Inhalt"
#: content/models.py:181
msgid "enable comments" msgid "enable comments"
msgstr "Kommentare möglich" msgstr "Kommentare möglich"
#: src/content/models.py:156 #: content/models.py:186
msgid "Template" msgid "Template"
msgstr "Vorlage" msgstr "Vorlage"
#: src/content/models.py:219 #: content/models.py:194
#, fuzzy
#| msgid "created on"
msgid "first created at"
msgstr "erstellt am"
#: content/models.py:199
msgid "latest updated at"
msgstr ""
#: content/models.py:264
msgid "Page" msgid "Page"
msgstr "Seite" msgstr "Seite"
#: src/content/models.py:220 #: content/models.py:265
msgid "Pages" msgid "Pages"
msgstr "Seiten" msgstr "Seiten"
#: src/content/models.py:224 src/content/models.py:225 #: content/models.py:269 content/models.py:270
msgid "Name" msgid "Name"
msgstr "Name" msgstr "Name"
#: src/content/models.py:226 src/content/models.py:227 #: content/models.py:271 content/models.py:272
msgid "Description" msgid "Description"
msgstr "Beschreibung" msgstr "Beschreibung"
#: src/content/models.py:235 #: content/models.py:280
msgid "Categories" msgid "Categories"
msgstr "Kategorien" msgstr "Kategorien"
#: src/content/templates/content/article_archive.html:5 #: content/templates/content/article_archive.html:5
#: src/content/templates/content/article_archive.html:20 #: content/templates/content/article_archive.html:20
msgid "Article Archive" msgid "Article Archive"
msgstr "Nachrichtenarchiv" msgstr "Nachrichtenarchiv"
#: src/content/templates/content/article_archive.html:35 #: content/templates/content/article_archive.html:35
#: src/content/templates/content/article_archive_month.html:5 #: content/templates/content/article_archive_month.html:5
#: src/content/templates/content/article_archive_year.html:7 #: content/templates/content/article_archive_year.html:7
msgid "Archive" msgid "Archive"
msgstr "Archiv" msgstr "Archiv"
#: src/content/templates/content/article_archive.html:52 #: content/templates/content/article_archive.html:52
msgid "All Categories" msgid "All Categories"
msgstr "Alle Kategorien" msgstr "Alle Kategorien"
#: src/content/templates/content/article_archive.html:67 #: content/templates/content/article_archive.html:67
msgid "created on" msgid "created on"
msgstr "erstellt am" msgstr "erstellt am"
#: src/content/templates/content/article_archive.html:68 #: content/templates/content/article_archive.html:68
msgid "by" msgid "by"
msgstr "von" msgstr "von"
#: src/content/templates/content/article_archive.html:69 #: content/templates/content/article_archive.html:69
msgid "comments" msgid "comments"
msgstr "Kommentare" msgstr "Kommentare"
#: src/content/templates/content/article_archive.html:73 #: content/templates/content/article_archive.html:73
msgid "Read More" msgid "Read More"
msgstr "Mehr lesen" msgstr "Mehr lesen"
#: src/content/templates/content/article_archive.html:76 #: content/templates/content/article_archive.html:76
msgid "We're sorry. Your search yielded no results." msgid "We're sorry. Your search yielded no results."
msgstr "Es tut uns leid. Deine Suche ergab keine Treffer." msgstr "Es tut uns leid. Deine Suche ergab keine Treffer."
#: src/content/templates/content/article_archive.html:94 #: content/templates/content/article_archive.html:94
msgid "Add Article" msgid "Add Article"
msgstr "neuer Artikel " msgstr "neuer Artikel "
#: src/content/templates/content/article_archive_month.html:7 #: content/templates/content/article_archive_month.html:7
msgid "back" msgid "back"
msgstr "Zurück" msgstr "Zurück"
#: src/content/templates/content/article_detail.html:24 #: content/templates/content/article_detail.html:24
msgid "Created on" msgid "Created on"
msgstr "Erstellt am" msgstr "Erstellt am"
#: src/content/templates/content/article_detail.html:36 #: content/templates/content/article_detail.html:36
msgid "share on" msgid "share on"
msgstr "Teile auf" msgstr "Teile auf"
#: src/content/templates/content/article_detail.html:51 #: content/templates/content/article_detail.html:51
#: src/content/templates/content/article_form.html:17 src/content/views.py:88 #: content/templates/content/article_form.html:24 content/views.py:88
msgid "Edit Article" msgid "Edit Article"
msgstr "Artikel bearbeiten" msgstr "Artikel bearbeiten"
#: src/content/templates/content/article_form.html:17 src/content/views.py:90 #: content/templates/content/article_form.html:24 content/views.py:90
msgid "Create Article" msgid "Create Article"
msgstr "Artikel erstellen" msgstr "Artikel erstellen"
#: src/content/templates/content/article_form.html:22 #: content/templates/content/article_form.html:29
#: src/content/templates/content/page_form.html:39 #: content/templates/content/page_form.html:49
#: src/content/templates/content/page_form.html:46 #: content/templates/content/page_form.html:56
msgid "German" msgid "German"
msgstr "Deutsch" msgstr "Deutsch"
#: src/content/templates/content/article_form.html:23 #: content/templates/content/article_form.html:30
#: src/content/templates/content/page_form.html:40 #: content/templates/content/page_form.html:50
#: src/content/templates/content/page_form.html:54 #: content/templates/content/page_form.html:64
msgid "English" msgid "English"
msgstr "Englisch" msgstr "Englisch"
#: src/content/templates/content/article_form.html:42 #: content/templates/content/article_form.html:49
#: src/content/templates/content/page_form.html:63 #: content/templates/content/page_form.html:73
msgid "reset" msgid "reset"
msgstr "Zurücksetzen" msgstr "Zurücksetzen"
#: src/content/templates/content/article_form.html:43 #: content/templates/content/article_form.html:50
#: src/content/templates/content/page_form.html:64 #: content/templates/content/page_form.html:74
msgid "save" msgid "save"
msgstr "Speichern" msgstr "Speichern"
#: src/content/templates/content/page_form.html:5 #: content/templates/content/page_form.html:5
#: src/content/templates/content/page_form.html:32 #: content/templates/content/page_form.html:42
msgid "Edit Page" msgid "Edit Page"
msgstr "Seite bearbeiten" msgstr "Seite bearbeiten"
#: src/content/templates/content/page_form.html:5 #: content/templates/content/page_form.html:5
#: src/content/templates/content/page_form.html:18 #: content/templates/content/page_form.html:28
#: src/content/templates/content/page_form.html:32 #: content/templates/content/page_form.html:42
msgid "Add Page" msgid "Add Page"
msgstr "Seite hinzufügen" msgstr "Seite hinzufügen"
#: src/content/templates/content/page_form.html:17 #: content/templates/content/page_form.html:27
msgid "Edit" msgid "Edit"
msgstr "Bearbeiten" msgstr "Bearbeiten"
#: src/content/templates/content/page_form.html:34 #: content/templates/content/page_form.html:44
msgid "HTML Specific" msgid "HTML Specific"
msgstr "HTML spezifisch" msgstr "HTML spezifisch"
#: src/content/views.py:23 #: content/views.py:23
msgid "This Category does not exist." msgid "This Category does not exist."
msgstr "Diese Kategorie existiert nicht." msgstr "Diese Kategorie existiert nicht."
#: src/content/views.py:157 #: content/views.py:157
#, python-format #, python-format
msgid "No Page found matching the Path %s" msgid "No Page found matching the Path %s"
msgstr "Keine Seite unter dem Pfad %s gefunden" msgstr "Keine Seite unter dem Pfad %s gefunden"
#: src/content/views.py:172 #: content/views.py:172
#, python-format #, python-format
msgid "No PDF Document found matching the Path %s" msgid "No PDF Document found matching the Path %s"
msgstr "Kein PDF Dokument unter dem Pfad %s gefunden." msgstr "Kein PDF Dokument unter dem Pfad %s gefunden."
#~ msgid "This title appears in the HTML header"
#~ msgstr "Der Titel erscheint im HTML Header"
#~ msgid "Share on Google+" #~ msgid "Share on Google+"
#~ msgstr "Auf Google+ teilen" #~ msgstr "Auf Google+ teilen"

View File

@@ -148,6 +148,6 @@ class Migration(migrations.Migration):
), ),
migrations.AlterUniqueTogether( migrations.AlterUniqueTogether(
name='page', name='page',
unique_together=set([('slug', 'parent')]), unique_together={('slug', 'parent')},
), ),
] ]

View File

@@ -1,7 +1,8 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import unicode_literals from __future__ import unicode_literals
from django.db import models, migrations from django.db import migrations
from django.db import models
import ckeditor.fields import ckeditor.fields

View File

@@ -15,46 +15,55 @@ class Migration(migrations.Migration):
migrations.AddField( migrations.AddField(
model_name='page', model_name='page',
name='date_created', name='date_created',
field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='first created at', editable=False, db_index=True), field=models.DateTimeField(default=django.utils.timezone.now,
verbose_name='first created at', editable=False, db_index=True),
), ),
migrations.AddField( migrations.AddField(
model_name='page', model_name='page',
name='date_modified', name='date_modified',
field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='latest updated at', editable=False), field=models.DateTimeField(
default=django.utils.timezone.now, verbose_name='latest updated at', editable=False),
), ),
migrations.AlterField( migrations.AlterField(
model_name='article', model_name='article',
name='date_created', name='date_created',
field=models.DateTimeField(auto_now_add=True, verbose_name='Erstellt'), field=models.DateTimeField(
auto_now_add=True, verbose_name='Erstellt'),
), ),
migrations.AlterField( migrations.AlterField(
model_name='page', model_name='page',
name='content_type', name='content_type',
field=models.IntegerField(verbose_name='Inhaltstyp', choices=[(0, 'Django View'), (1, 'HTML'), (2, 'PDF')]), field=models.IntegerField(verbose_name='Inhaltstyp', choices=[
(0, 'Django View'), (1, 'HTML'), (2, 'PDF')]),
), ),
migrations.AlterField( migrations.AlterField(
model_name='page', model_name='page',
name='path', name='path',
field=models.CharField(verbose_name='Pfad', unique=True, max_length=255, editable=False, db_index=True), field=models.CharField(
verbose_name='Pfad', unique=True, max_length=255, editable=False, db_index=True),
), ),
migrations.AlterField( migrations.AlterField(
model_name='page', model_name='page',
name='slug', name='slug',
field=models.SlugField(help_text='The name of the page as it will appear in URLs e.g http://domain.com/blog/[my-slug]/', max_length=100, verbose_name='Slug'), field=models.SlugField(
help_text='The name of the page as it will appear in URLs e.g http://domain.com/blog/[my-slug]/', max_length=100, verbose_name='Slug'),
), ),
migrations.AlterField( migrations.AlterField(
model_name='page', model_name='page',
name='template', name='template',
field=models.CharField(default=b'content/page.html', max_length=255, verbose_name='Vorlage'), field=models.CharField(
default=b'content/page.html', max_length=255, verbose_name='Vorlage'),
), ),
migrations.AlterField( migrations.AlterField(
model_name='page', model_name='page',
name='title_de', name='title_de',
field=models.CharField(help_text="The page title as you'd like it to be seen by the public", max_length=255, verbose_name=b'Titel'), field=models.CharField(
help_text="The page title as you'd like it to be seen by the public", max_length=255, verbose_name=b'Titel'),
), ),
migrations.AlterField( migrations.AlterField(
model_name='page', model_name='page',
name='title_en', name='title_en',
field=models.CharField(help_text="The page title as you'd like it to be seen by the public", max_length=255, verbose_name=b'Title', blank=True), field=models.CharField(help_text="The page title as you'd like it to be seen by the public",
max_length=255, verbose_name=b'Title', blank=True),
), ),
] ]

View File

@@ -14,11 +14,13 @@ class Migration(migrations.Migration):
migrations.AlterField( migrations.AlterField(
model_name='page', model_name='page',
name='date_created', name='date_created',
field=models.DateTimeField(auto_now_add=True, verbose_name='first created at', db_index=True), field=models.DateTimeField(
auto_now_add=True, verbose_name='first created at', db_index=True),
), ),
migrations.AlterField( migrations.AlterField(
model_name='page', model_name='page',
name='date_modified', name='date_modified',
field=models.DateTimeField(auto_now=True, verbose_name='latest updated at'), field=models.DateTimeField(
auto_now=True, verbose_name='latest updated at'),
), ),
] ]

View File

@@ -14,11 +14,13 @@ class Migration(migrations.Migration):
migrations.AddField( migrations.AddField(
model_name='page', model_name='page',
name='description_de', name='description_de',
field=models.TextField(verbose_name='search description', blank=True), field=models.TextField(
verbose_name='search description', blank=True),
), ),
migrations.AddField( migrations.AddField(
model_name='page', model_name='page',
name='description_en', name='description_en',
field=models.TextField(verbose_name='search description', blank=True), field=models.TextField(
verbose_name='search description', blank=True),
), ),
] ]

View File

@@ -1,6 +1,4 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from os import path
from ckeditor.fields import RichTextField from ckeditor.fields import RichTextField
from django.conf import settings from django.conf import settings
from django.core.cache import cache from django.core.cache import cache
@@ -12,8 +10,7 @@ from django.utils import timezone
from django.utils.safestring import mark_safe from django.utils.safestring import mark_safe
from django.utils.translation import get_language, ugettext as _ from django.utils.translation import get_language, ugettext as _
from utils import STATUS_CHOICES, STATUS_WAITING, STATUS_PUBLISHED, \ from utils import STATUS_CHOICES, STATUS_WAITING, STATUS_PUBLISHED, CLEANER
cleaner
CONTENT_CHOICES = ( CONTENT_CHOICES = (
(0, u'Django View'), (0, u'Django View'),
@@ -44,7 +41,10 @@ class ArticleManager(models.Manager):
'author', 'category') 'author', 'category')
def published(self): def published(self):
return self.filter(status=STATUS_PUBLISHED, date_created__lte=timezone.now()) return self.filter(
status=STATUS_PUBLISHED,
date_created__lte=timezone.now()
)
class Article(models.Model): class Article(models.Model):
@@ -74,8 +74,8 @@ class Article(models.Model):
self.date_created = timezone.now() self.date_created = timezone.now()
if not self.slug: if not self.slug:
self.slug = slugify(self.headline_de)[:50] self.slug = slugify(self.headline_de)[:50]
self.content_de = cleaner.clean_html(self.content_de) self.content_de = CLEANER.clean_html(self.content_de)
self.content_en = cleaner.clean_html(self.content_en) self.content_en = CLEANER.clean_html(self.content_en)
def __str__(self): def __str__(self):
return self.headline return self.headline
@@ -120,6 +120,7 @@ class Page(models.Model):
Ist keine englische Übersetzung vorhanden, wird die deutsche Version Ist keine englische Übersetzung vorhanden, wird die deutsche Version
angeboten. angeboten.
""" """
menu_name_de = models.CharField( menu_name_de = models.CharField(
max_length=255, max_length=255,
verbose_name='Menü Name', verbose_name='Menü Name',
@@ -143,7 +144,10 @@ class Page(models.Model):
slug = models.SlugField( slug = models.SlugField(
verbose_name=_('slug'), verbose_name=_('slug'),
max_length=100, max_length=100,
help_text=_("The name of the page as it will appear in URLs e.g http://domain.com/blog/[my-slug]/") help_text=_(
'The name of the page as it will appear in URLs e.g '
'http://domain.com/blog/[my-slug]/'
)
) )
path = models.CharField( path = models.CharField(
max_length=255, max_length=255,
@@ -169,8 +173,10 @@ class Page(models.Model):
default=STATUS_WAITING, default=STATUS_WAITING,
verbose_name=_('status') verbose_name=_('status')
) )
description_de = models.TextField(verbose_name=_('search description'), blank=True) description_de = models.TextField(
description_en = models.TextField(verbose_name=_('search description'), blank=True) verbose_name=_('search description'), blank=True)
description_en = models.TextField(
verbose_name=_('search description'), blank=True)
content_type = models.IntegerField( content_type = models.IntegerField(
choices=CONTENT_CHOICES, choices=CONTENT_CHOICES,
verbose_name=_('content type')) verbose_name=_('content type'))
@@ -213,26 +219,34 @@ class Page(models.Model):
@property @property
def description(self): def description(self):
return getattr(self, "description_%s" % get_language()) or self.description_de lang_attr = "description_%s" % get_language()
return getattr(self, lang_attr, self.description_de)
@property @property
def menu_name(self): def menu_name(self):
return getattr(self, lang_attr = "menu_name_%s" % get_language()
"menu_name_%s" % get_language()) or self.menu_name_de return getattr(self, lang_attr, self.menu_name_de)
@property @property
def pdf_file(self): def pdf_file(self):
return getattr(self, "pdf_%s" % get_language()) or self.pdf_de lang_attr = "pdf_%s" % get_language()
return getattr(self, lang_attr, self.pdf_de)
@property @property
def title(self): def title(self):
return getattr(self, "title_%s" % get_language()) or self.title_de """ Return the translated title if available """
lang_attr = "title_%s" % get_language()
return getattr(self, lang_attr, self.title_de)
def clean(self): def clean(self):
"""
:return:
"""
if self.parent is None: if self.parent is None:
self.path = self.slug self.path = self.slug
else: else:
self.path = path.join(self.parent.path, self.slug) self.path = '/'.join([self.parent.path, self.slug])
if self.content_type is None: if self.content_type is None:
if self.pdf_de: if self.pdf_de:
@@ -242,8 +256,8 @@ class Page(models.Model):
else: else:
self.content_type = 0 self.content_type = 0
if self.content_type == 1: if self.content_type == 1:
self.content_de = cleaner.clean_html(self.content_de) self.content_de = CLEANER.clean_html(self.content_de)
self.content_en = cleaner.clean_html(self.content_en) self.content_en = CLEANER.clean_html(self.content_en)
elif self.content_type == 2 and not self.pdf_de.name: elif self.content_type == 2 and not self.pdf_de.name:
raise ValidationError( raise ValidationError(
_(u'Please upload a PDF-File to this PDF-Page.')) _(u'Please upload a PDF-File to this PDF-Page.'))

View File

@@ -1,6 +1,7 @@
from django.contrib.sitemaps import Sitemap from django.contrib.sitemaps import Sitemap
from .models import Article, Page from .models import Article, Page
class ArticleSitemap(Sitemap): class ArticleSitemap(Sitemap):
changefreq = "never" changefreq = "never"
priority = 0.6 priority = 0.6
@@ -21,4 +22,3 @@ class PageSitemap(Sitemap):
def lastmod(self, page): def lastmod(self, page):
return page.date_modified return page.date_modified

View File

@@ -1,5 +1,5 @@
{% extends "base.html" %} {% extends "base.html" %}
{% load i18n fieldset_extras %} {% load i18n %}
{% block extra_head %} {% block extra_head %}
<script type="text/javascript" src="{{ STATIC_URL }}js/jquery.min.js"></script> <script type="text/javascript" src="{{ STATIC_URL }}js/jquery.min.js"></script>
@@ -15,11 +15,7 @@ window.onload = function () {
{% endblock %} {% endblock %}
{% block maincontent %} {% block maincontent %}
{% get_fieldset "category, image" from form as fieldset_common %} <form action="" method="post" enctype="multipart/form-data">
{% get_fieldset "headline_en, content_en" from form as fieldset_en %}
{% get_fieldset "headline_en, content_en" from form as fieldset_en %}
<form action="" method="post" enctype="multipart/formdata">
<fieldset class="grid_12"> <fieldset class="grid_12">
<legend>{% if object.pk %}{% trans "Edit Article" %}{% else %}{% trans "Create Article" %}{% endif %}</legend> <legend>{% if object.pk %}{% trans "Edit Article" %}{% else %}{% trans "Create Article" %}{% endif %}</legend>
{% csrf_token %} {% csrf_token %}

View File

@@ -50,7 +50,7 @@ window.onload = function () {
<li><a href="#en">{% trans "English" %}</a></li> <li><a href="#en">{% trans "English" %}</a></li>
</ul> </ul>
<div class="tab_container""> <div class="tab_container">
<section id="de" class="tab_content"> <section id="de" class="tab_content">
<fieldset class="grid_12"> <fieldset class="grid_12">
<legend>{% trans "German" %}</legend> <legend>{% trans "German" %}</legend>

View File

@@ -60,7 +60,7 @@ body {
</div> </div>
<div id="page_footer"> <div id="page_footer">
{{ page.title }} Seite: {{ page.title }} Seite:
<pdf:pagenumber> <pdf:pagenumber />
</div> </div>
</body> </body>
</html> </html>

View File

@@ -1,14 +0,0 @@
# -*- encoding: utf-8 -*-
"""
Created on 03.10.2011
@author: christian
"""
from django.conf.urls import url
from .views import ImageList, PageList
urlpatterns = [
url(r'^image_list.js$', ImageList.as_view(), name='content-image-list'),
url(r'^link_list.js$', PageList.as_view(), name='content-page-list'),
]

View File

@@ -1,31 +1,48 @@
# Create your views here. import os
import django_comments as comments import django_comments as comments
import os from csp.decorators import csp_update
from django.conf import settings from django.conf import settings
from django.http import HttpResponse, Http404 from django.http import HttpResponse, Http404
from django.utils.decorators import method_decorator
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from django.views import generic from django.views import generic
from . import models, forms
from events.models import Photo from events.models import Photo
from utils.mixins import PermissionRequiredMixin from utils.mixins import PermissionRequiredMixin
from . import models, forms
class ArticleArchiveMixin(object): class ArticleArchiveMixin(object):
"""
Mixin View to add common context data to Views of the news article archive.
"""
def get_category(self, queryset): def get_category(self, queryset):
try: """
self.category = models.Category.objects.get( Filter the queryset by category if one has been specified in the URL
slug=self.kwargs['category'])
return queryset.filter(category=self.category) :param queryset: an model.Article.objects Queryset
except models.Category.DoesNotExist: :return: an model.Article.objects Queryset filterd by category
raise Http404(_("This Category does not exist.")) """
except KeyError:
category_slug = self.kwargs.get('category')
if not category_slug:
self.category = None self.category = None
return self.queryset return self.queryset
try:
self.category = models.Category.objects.get(slug=category_slug)
except models.Category.DoesNotExist:
raise Http404(_("This Category does not exist."))
return queryset.filter(category=self.category)
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
"""
Adds the categories and the active category to the template context.
:return: an django.template.context
"""
context = super(ArticleArchiveMixin, self).get_context_data(**kwargs) context = super(ArticleArchiveMixin, self).get_context_data(**kwargs)
context['categories'] = models.Category.objects.all() context['categories'] = models.Category.objects.all()
context['active_category'] = self.category context['active_category'] = self.category
@@ -33,6 +50,10 @@ class ArticleArchiveMixin(object):
class ArticleArchiveIndex(ArticleArchiveMixin, generic.ArchiveIndexView): class ArticleArchiveIndex(ArticleArchiveMixin, generic.ArchiveIndexView):
"""
Displays the latest news and the filters to browse the archives.
"""
queryset = models.Article.objects.filter(status=models.STATUS_PUBLISHED) queryset = models.Article.objects.filter(status=models.STATUS_PUBLISHED)
date_field = 'date_created' date_field = 'date_created'
paginate_by = 5 paginate_by = 5
@@ -40,12 +61,21 @@ class ArticleArchiveIndex(ArticleArchiveMixin, generic.ArchiveIndexView):
allow_empty = True allow_empty = True
def get_queryset(self): def get_queryset(self):
queryset = generic.ArchiveIndexView.get_queryset(self) """
queryset = self.get_category(queryset) Filter the Queryset by category.
return queryset
:return: models.Article.objects queryset
"""
return self.get_category(
super(ArticleArchiveIndex, self).get_queryset()
)
class ArticleYearArchive(ArticleArchiveMixin, generic.YearArchiveView): class ArticleYearArchive(ArticleArchiveMixin, generic.YearArchiveView):
"""
Displays the Articles filterd by a specific year
"""
queryset = models.Article.objects.filter(status=models.STATUS_PUBLISHED) queryset = models.Article.objects.filter(status=models.STATUS_PUBLISHED)
date_field = 'date_created' date_field = 'date_created'
paginate_by = 5 paginate_by = 5
@@ -54,12 +84,21 @@ class ArticleYearArchive(ArticleArchiveMixin, generic.YearArchiveView):
allow_empty = True allow_empty = True
def get_queryset(self): def get_queryset(self):
queryset = generic.YearArchiveView.get_queryset(self) """
queryset = self.get_category(queryset) Filter the Queryset by category.
return queryset
:return: models.Article.objects queryset
"""
return self.get_category(
super(ArticleYearArchive, self).get_queryset()
)
class ArticleMonthArchive(ArticleArchiveMixin, generic.MonthArchiveView): class ArticleMonthArchive(ArticleArchiveMixin, generic.MonthArchiveView):
"""
Displays the Articles filterd by a specific month
"""
queryset = models.Article.objects.filter(status=models.STATUS_PUBLISHED) queryset = models.Article.objects.filter(status=models.STATUS_PUBLISHED)
date_field = 'date_created' date_field = 'date_created'
month_format = '%m' month_format = '%m'
@@ -68,16 +107,28 @@ class ArticleMonthArchive(ArticleArchiveMixin, generic.MonthArchiveView):
allow_empty = True allow_empty = True
def get_queryset(self): def get_queryset(self):
queryset = generic.MonthArchiveView.get_queryset(self) """
queryset = self.get_category(queryset) Filter the Queryset by category.
return queryset
:return: models.Article.objects queryset
"""
return self.get_category(
super(ArticleMonthArchive, self).get_queryset()
)
class ArticleDetail(generic.DetailView): class ArticleDetail(generic.DetailView):
"""
Render the news Article, but only if it got published.
"""
queryset = models.Article.objects.filter(status=models.STATUS_PUBLISHED) queryset = models.Article.objects.filter(status=models.STATUS_PUBLISHED)
class ArticleForm(PermissionRequiredMixin, generic.UpdateView): class ArticleForm(PermissionRequiredMixin, generic.UpdateView):
"""
View to add or edit an Article
"""
model = models.Article model = models.Article
form_class = forms.ArticleForm form_class = forms.ArticleForm
permission_required = 'content.change_article' permission_required = 'content.change_article'
@@ -97,28 +148,11 @@ class ArticleForm(PermissionRequiredMixin, generic.UpdateView):
return models.Article(author=self.request.user) return models.Article(author=self.request.user)
class ImageList(generic.View):
# noinspection PyMethodMayBeStatic
def get(self, kwargs):
image_list = []
response = HttpResponse(content_type='text/javascript')
response.write('var tinyMCEImageList = new Array(')
os.chdir(settings.MEDIA_ROOT)
for dirpath, dirnames, filenames in os.walk(
'images'): # @UnusedVariable @IgnorePep8
filenames.sort()
for filename in filenames:
image_list.append('["%(name)s", "%(path)s"]' % {
'name': os.path.join(dirpath, filename),
'path': os.path.join(settings.MEDIA_URL, dirpath, filename)
})
response.write(', '.join(image_list))
response.write(');')
return response
class PageAddForm(PermissionRequiredMixin, generic.CreateView): class PageAddForm(PermissionRequiredMixin, generic.CreateView):
"""
Renders an Form to create a new page for users with conforming permissions.
"""
form_class = forms.PageForm form_class = forms.PageForm
template_name = 'content/page_form.html' template_name = 'content/page_form.html'
permission_required = 'content.add_page' permission_required = 'content.add_page'
@@ -132,8 +166,16 @@ class PageAddForm(PermissionRequiredMixin, generic.CreateView):
parent = models.Page.objects.get(path=path) parent = models.Page.objects.get(path=path)
return {'parent': parent} return {'parent': parent}
@method_decorator(csp_update(SCRIPT_SRC="'unsafe-eval'"))
def dispatch(self, *args, **kwargs):
return super(PageAddForm, self).dispatch(*args, **kwargs)
class PageEditForm(PermissionRequiredMixin, generic.UpdateView): class PageEditForm(PermissionRequiredMixin, generic.UpdateView):
"""
Renders an Form to edit a page for users with conforming permissions.
"""
form_class = forms.PageForm form_class = forms.PageForm
permission_required = 'content.change_page' permission_required = 'content.change_page'
@@ -145,6 +187,10 @@ class PageEditForm(PermissionRequiredMixin, generic.UpdateView):
path = path[:-1] path = path[:-1]
return models.Page.objects.get(path=path) return models.Page.objects.get(path=path)
@method_decorator(csp_update(SCRIPT_SRC="'unsafe-eval'"))
def dispatch(self, *args, **kwargs):
return super(PageEditForm, self).dispatch(*args, **kwargs)
class PageHtml(generic.DetailView): class PageHtml(generic.DetailView):
@@ -184,23 +230,6 @@ class PagePdf(generic.DeleteView):
raise Http404('File not Found %s.pdf' % self.kwargs['path']) raise Http404('File not Found %s.pdf' % self.kwargs['path'])
class PageList(generic.View):
# noinspection PyMethodMayBeStatic
def get(self, kwargs):
response = HttpResponse(content_type='text/javascript')
response.write('var tinyMCELinkList = new Array(')
page_list = []
for page in models.Page.objects.filter(status=models.STATUS_PUBLISHED):
page_list.append('["%(name)s", "%(path)s"]' % {
'name': page.menu_name,
'path': page.get_absolute_url()
})
response.write(', '.join(page_list))
response.write(');')
return response
class StartPage(generic.TemplateView): class StartPage(generic.TemplateView):
template_name = 'index.html' template_name = 'index.html'

View File

@@ -13,10 +13,10 @@ def upcoming_events(request):
next_event = cache.get('next_event', False) next_event = cache.get('next_event', False)
upcoming_events = cache.get('upcoming_events', False) upcoming_events = cache.get('upcoming_events', False)
if current_event == False: if not current_event:
current_event = Event.objects.current_event() current_event = Event.objects.current_event()
cache.set('current_event', current_event, 360) cache.set('current_event', current_event, 360)
if next_event == False: if not next_event:
next_event = Event.objects.next_event() next_event = Event.objects.next_event()
cache.set('next_event', next_event, 360) cache.set('next_event', next_event, 360)

File diff suppressed because it is too large Load Diff

View File

@@ -8,7 +8,6 @@ from django.utils.translation import ugettext as _
from django.contrib.auth import get_user_model from django.contrib.auth import get_user_model
from . import models from . import models
from utils.html5.widgets import DateTimeInput
user_query = get_user_model().objects.all() user_query = get_user_model().objects.all()
@@ -47,16 +46,17 @@ class EventForm(forms.ModelForm):
start = forms.DateTimeField( start = forms.DateTimeField(
label=_('start'), required=True, label=_('start'), required=True,
widget=DateTimeInput() # widget=SplitDateTimeWidget() widget=forms.SplitHiddenDateTimeWidget()
) )
end = forms.DateTimeField( end = forms.DateTimeField(
label=_('end'), required=False, label=_('end'), required=False,
widget=DateTimeInput() # widget=SplitDateTimeWidget() widget=forms.SplitDateTimeWidget()
) )
class Meta(object): class Meta(object):
model = models.Event model = models.Event
exclude = ('event_count', 'event_series', ) exclude = ('event_count', 'event_series', )
EventSeriesFormset = forms.inlineformset_factory( EventSeriesFormset = forms.inlineformset_factory(
models.Event, models.Event, fields=('start', 'end'), form=EventForm) models.Event, models.Event, fields=('start', 'end'), form=EventForm)

View File

@@ -7,7 +7,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: kasu.events\n" "Project-Id-Version: kasu.events\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2016-09-28 00:25+0200\n" "POT-Creation-Date: 2017-05-10 23:16+0200\n"
"PO-Revision-Date: 2016-09-28 00:24+0200\n" "PO-Revision-Date: 2016-09-28 00:24+0200\n"
"Last-Translator: Christian Berg <xeniac@posteo.at>\n" "Last-Translator: Christian Berg <xeniac@posteo.at>\n"
"Language-Team: Kasu <verein@kasu.at>\n" "Language-Team: Kasu <verein@kasu.at>\n"
@@ -19,58 +19,58 @@ msgstr ""
"X-Generator: Poedit 1.8.9\n" "X-Generator: Poedit 1.8.9\n"
"X-Translated-Using: django-rosetta 0.7.6\n" "X-Translated-Using: django-rosetta 0.7.6\n"
#: src/events/admin.py:16 src/events/models.py:109 #: events/admin.py:16 events/models.py:114
msgid "Event Series" msgid "Event Series"
msgstr "Veranstaltungsreihen" msgstr "Veranstaltungsreihen"
#: src/events/forms.py:23 #: events/forms.py:23
msgid "Images" msgid "Images"
msgstr "Bilder" msgstr "Bilder"
#: src/events/forms.py:49 #: events/forms.py:49
msgid "start" msgid "start"
msgstr "Beginn" msgstr "Beginn"
#: src/events/forms.py:53 #: events/forms.py:53
msgid "end" msgid "end"
msgstr "Ende" msgstr "Ende"
#: src/events/models.py:82 src/events/models.py:186 src/events/models.py:228 #: events/models.py:84 events/models.py:207 events/models.py:260
msgid "Name" msgid "Name"
msgstr "Name" msgstr "Name"
#: src/events/models.py:83 src/events/models.py:187 src/events/models.py:233 #: events/models.py:85 events/models.py:208 events/models.py:268
msgid "Description" msgid "Description"
msgstr "Beschreibung" msgstr "Beschreibung"
#: src/events/models.py:85 src/events/templates/events/event_detail.html:31 #: events/models.py:87 events/templates/events/event_detail.html:29
#: src/events/templates/events/event_detail.html:74 #: events/templates/events/event_detail.html:87
#: src/events/templates/events/event_list.html:28 #: events/templates/events/event_list.html:28
#: src/events/templates/events/photo_upload.html:13 #: events/templates/events/photo_upload.html:13
msgid "Start" msgid "Start"
msgstr "Beginn" msgstr "Beginn"
#: src/events/models.py:86 src/events/templates/events/event_detail.html:32 #: events/models.py:88 events/templates/events/event_detail.html:30
#: src/events/templates/events/event_detail.html:75 #: events/templates/events/event_detail.html:89
msgid "End" msgid "End"
msgstr "Ende" msgstr "Ende"
#: src/events/models.py:87 src/events/models.py:195 #: events/models.py:89 events/models.py:216
#: src/events/templates/events/event_detail.html:36 #: events/templates/events/event_detail.html:34
#: src/events/templates/events/event_detail.html:70 #: events/templates/events/event_detail.html:80
#: src/events/templates/events/event_detail.html:76 #: events/templates/events/event_detail.html:92
msgid "Homepage" msgid "Homepage"
msgstr "Homepage" msgstr "Homepage"
#: src/events/models.py:89 src/events/models.py:189 src/events/models.py:229 #: events/models.py:91 events/models.py:210 events/models.py:262
msgid "Image" msgid "Image"
msgstr "Bild" msgstr "Bild"
#: src/events/models.py:96 #: events/models.py:98
msgid "Mahjong Tournament" msgid "Mahjong Tournament"
msgstr "Mahjong Turnier" msgstr "Mahjong Turnier"
#: src/events/models.py:98 #: events/models.py:100
msgid "" msgid ""
"This event is a tournament, different rules apply for the kyu " "This event is a tournament, different rules apply for the kyu "
"ranking." "ranking."
@@ -78,11 +78,11 @@ msgstr ""
"Diese Veranstaltung ist ein Turnier, es gelten andere Regeln für das Kyu " "Diese Veranstaltung ist ein Turnier, es gelten andere Regeln für das Kyu "
"Ranking." "Ranking."
#: src/events/models.py:102 #: events/models.py:104
msgid "Mahjong Season" msgid "Mahjong Season"
msgstr "Mahjong Saison" msgstr "Mahjong Saison"
#: src/events/models.py:110 #: events/models.py:115
msgid "" msgid ""
"Wenn dieser Event zu einer Veranstaltungsreihe gehört werden Ort, " "Wenn dieser Event zu einer Veranstaltungsreihe gehört werden Ort, "
"Beschreibung, Bild und Homepage von dem hier angegebenen Event " "Beschreibung, Bild und Homepage von dem hier angegebenen Event "
@@ -91,236 +91,244 @@ msgstr ""
"Wenn dieser Termin zu einer Veranstaltungsreihe gehört werden Ort, " "Wenn dieser Termin zu einer Veranstaltungsreihe gehört werden Ort, "
"Beschreibung, Bild und Homepage von dem hier angegebenen Event übernommen." "Beschreibung, Bild und Homepage von dem hier angegebenen Event übernommen."
#: src/events/models.py:117 #: events/models.py:124 events/models.py:226 events/models.py:290
msgid "first created at"
msgstr ""
#: events/models.py:129 events/models.py:231 events/models.py:295
msgid "latest updated at"
msgstr ""
#: events/models.py:134
msgid "Event" msgid "Event"
msgstr "Termin" msgstr "Termin"
#: src/events/models.py:118 #: events/models.py:135
msgid "Events" msgid "Events"
msgstr "Termine" msgstr "Termine"
#: src/events/models.py:131 #: events/models.py:148
msgid "A event can't end before it had started" msgid "A event can't end before it had started"
msgstr "Eine Veranstaltung kann nicht enden bevor sie begonnen hat" msgstr "Eine Veranstaltung kann nicht enden bevor sie begonnen hat"
#: src/events/models.py:196 #: events/models.py:217
msgid "Postal Code" msgid "Postal Code"
msgstr "Postleitzahl" msgstr "Postleitzahl"
#: src/events/models.py:197 #: events/models.py:218
msgid "Street Address" msgid "Street Address"
msgstr "Straße" msgstr "Straße"
#: src/events/models.py:198 #: events/models.py:219
msgid "Locality" msgid "Locality"
msgstr "Ort" msgstr "Ort"
#: src/events/models.py:199 #: events/models.py:220
msgid "Country" msgid "Country"
msgstr "Land" msgstr "Land"
#: src/events/models.py:202 #: events/models.py:235
msgid "Venue" msgid "Venue"
msgstr "Veranstaltungsort" msgstr "Veranstaltungsort"
#: src/events/models.py:203 #: events/models.py:236
msgid "Venues" msgid "Venues"
msgstr "Veranstaltungsorte" msgstr "Veranstaltungsorte"
#: src/events/models.py:239 #: events/models.py:274
msgid "Startpage" msgid "Startpage"
msgstr "Startseite" msgstr "Startseite"
#: src/events/models.py:242 #: events/models.py:277
msgid "Display this Photo on the Startpage Teaser" msgid "Display this Photo on the Startpage Teaser"
msgstr "Foto als Teaser auf der Startseite verwenden." msgstr "Foto als Teaser auf der Startseite verwenden."
#: src/events/models.py:244 #: events/models.py:279
msgid "Published on" msgid "Published on"
msgstr "Veröffentlicht am" msgstr "Veröffentlicht am"
#: src/events/models.py:246 #: events/models.py:281
msgid "Number of views" msgid "Number of views"
msgstr "Wie oft gesehen" msgstr "Wie oft gesehen"
#: src/events/models.py:258 src/events/templates/events/event_archive.html:38 #: events/models.py:306 events/templates/events/event_archive.html:38
#: src/events/templates/events/event_list.html:18 #: events/templates/events/event_list.html:18
msgid "Event Image" msgid "Event Image"
msgstr "Veranstaltungsbild" msgstr "Veranstaltungsbild"
#: src/events/models.py:259 #: events/models.py:307
msgid "Event Images" msgid "Event Images"
msgstr "Veranstaltungsbilder" msgstr "Veranstaltungsbilder"
#: src/events/templates/events/event_archive.html:5 #: events/templates/events/event_archive.html:5
#: src/events/templates/events/event_archive.html:9 #: events/templates/events/event_archive.html:9
msgid "Event Archive" msgid "Event Archive"
msgstr "Veranstaltungsarchiv" msgstr "Veranstaltungsarchiv"
#: src/events/templates/events/event_archive.html:42 #: events/templates/events/event_archive.html:42
#: src/events/templates/events/event_detail.html:72 #: events/templates/events/event_detail.html:85
#: src/events/templates/events/event_list.html:22 #: events/templates/events/event_list.html:22
#: src/events/templates/events/photo_detail.html:48 #: events/templates/events/photo_detail.html:53
msgid "Date" msgid "Date"
msgstr "Datum" msgstr "Datum"
#: src/events/templates/events/event_archive.html:47 #: events/templates/events/event_archive.html:47
msgid "Time" msgid "Time"
msgstr "Zeit" msgstr "Zeit"
#: src/events/templates/events/event_archive.html:49 #: events/templates/events/event_archive.html:49
#: src/events/templates/events/photo_upload.html:16 #: events/templates/events/photo_upload.html:16
msgid "from" msgid "from"
msgstr "von" msgstr "von"
#: src/events/templates/events/event_archive.html:49 #: events/templates/events/event_archive.html:49
#: src/events/templates/events/photo_upload.html:16 #: events/templates/events/photo_upload.html:16
msgid "to" msgid "to"
msgstr "bis" msgstr "bis"
#: src/events/templates/events/event_archive.html:57 #: events/templates/events/event_archive.html:57
#: src/events/templates/events/event_detail.html:33 #: events/templates/events/event_detail.html:31
#: src/events/templates/events/event_detail.html:62 #: events/templates/events/event_detail.html:72
#: src/events/templates/events/event_list.html:32 #: events/templates/events/event_list.html:32
#: src/events/templates/events/photo_upload.html:23 #: events/templates/events/photo_upload.html:23
msgid "Location" msgid "Location"
msgstr "Ort" msgstr "Ort"
#: src/events/templates/events/event_archive.html:58 #: events/templates/events/event_archive.html:58
#: src/events/templates/events/event_list.html:35 #: events/templates/events/event_list.html:35
#: src/events/templates/events/photo_upload.html:25 #: events/templates/events/photo_upload.html:25
#: src/events/templates/events/photo_upload.html:26 #: events/templates/events/photo_upload.html:26
msgid "Comments" msgid "Comments"
msgstr "Kommentare" msgstr "Kommentare"
#: src/events/templates/events/event_archive.html:59 #: events/templates/events/event_archive.html:59
#: src/events/templates/events/event_detail.html:38 #: events/templates/events/event_detail.html:36
#: src/events/templates/events/event_detail.html:48 #: events/templates/events/event_detail.html:48
#: src/events/templates/events/photo_upload.html:28 #: events/templates/events/photo_upload.html:28
#: src/events/templates/events/photo_upload.html:29 #: events/templates/events/photo_upload.html:29
msgid "Photos" msgid "Photos"
msgstr "Fotos" msgstr "Fotos"
#: src/events/templates/events/event_archive.html:60 #: events/templates/events/event_archive.html:60
#: src/events/templates/events/event_archive.html:61 #: events/templates/events/event_archive.html:61
#: src/events/templates/events/event_detail.html:37 #: events/templates/events/event_detail.html:35
#: src/events/templates/events/event_detail.html:49 #: events/templates/events/event_detail.html:51
msgid "Hanchans" msgid "Hanchans"
msgstr "Hanchans" msgstr "Hanchans"
#: src/events/templates/events/event_detail.html:39 #: events/templates/events/event_detail.html:37
msgid "tourney" msgid "tourney"
msgstr "Turnier" msgstr "Turnier"
#: src/events/templates/events/event_detail.html:39 #: events/templates/events/event_detail.html:37
msgid "other rules apply here" msgid "other rules apply here"
msgstr "hier gelten andere Regeln" msgstr "hier gelten andere Regeln"
#: src/events/templates/events/event_detail.html:47 #: events/templates/events/event_detail.html:45
msgid "Info" msgid "Info"
msgstr "Info" msgstr "Info"
#: src/events/templates/events/event_detail.html:50 #: events/templates/events/event_detail.html:54
msgid "Mai-Star Games" msgid "Mai-Star Games"
msgstr "Mai-Star Spiele" msgstr "Mai-Star Spiele"
#: src/events/templates/events/event_detail.html:52 #: events/templates/events/event_detail.html:57
msgid "Event Ranking" msgid "Event Ranking"
msgstr "Veranstaltungs Wertung" msgstr "Veranstaltungs Wertung"
#: src/events/templates/events/event_detail.html:90 #: events/templates/events/event_detail.html:100
msgid "Share on Facebook" msgid "Share on Facebook"
msgstr "Auf Facebook teilen" msgstr "Auf Facebook teilen"
#: src/events/templates/events/event_detail.html:96 #: events/templates/events/event_detail.html:104
msgid "Share on Google+" msgid "Share on Google+"
msgstr "Auf Google+ teilen" msgstr "Auf Google+ teilen"
#: src/events/templates/events/event_detail.html:100 #: events/templates/events/event_detail.html:109
msgid "Share on Twitter" msgid "Share on Twitter"
msgstr "Auf Twitter teilen" msgstr "Auf Twitter teilen"
#: src/events/templates/events/event_detail.html:104 #: events/templates/events/event_detail.html:113
msgid "Show on Google Maps" msgid "Show on Google Maps"
msgstr "Auf Google Maps zeigen" msgstr "Auf Google Maps zeigen"
#: src/events/templates/events/event_detail.html:121 #: events/templates/events/event_detail.html:127
#: src/events/templates/events/event_form.html:9 src/events/views.py:106 #: events/templates/events/event_form.html:9 events/views.py:106
msgid "Edit Event" msgid "Edit Event"
msgstr "Termin bearbeiten" msgstr "Termin bearbeiten"
#: src/events/templates/events/event_detail.html:124 #: events/templates/events/event_detail.html:131
msgid "Add Dates" msgid "Add Dates"
msgstr "Termine hinzufügen" msgstr "Termine hinzufügen"
#: src/events/templates/events/event_form.html:9 #: events/templates/events/event_form.html:9
#: src/events/templates/events/page.html:9 src/events/views.py:108 #: events/templates/events/page.html:9 events/views.py:108
msgid "Add Event" msgid "Add Event"
msgstr "Neuer Termin" msgstr "Neuer Termin"
#: src/events/templates/events/event_form.html:19 #: events/templates/events/event_form.html:19
#: src/events/templates/events/photo_list.html:35 #: events/templates/events/photo_list.html:35
msgid "reset" msgid "reset"
msgstr "Zurücksetzen" msgstr "Zurücksetzen"
#: src/events/templates/events/event_form.html:20 #: events/templates/events/event_form.html:20
#: src/events/templates/events/eventseries_form.html:23 #: events/templates/events/eventseries_form.html:25
msgid "save" msgid "save"
msgstr "Speichern" msgstr "Speichern"
#: src/events/templates/events/event_list.html:4 #: events/templates/events/event_list.html:4
#: src/events/templates/events/event_list.html:5 #: events/templates/events/event_list.html:5
msgid "Upcoming Events" msgid "Upcoming Events"
msgstr "Bevorstehende Veranstaltungen" msgstr "Bevorstehende Veranstaltungen"
#: src/events/templates/events/eventseries_form.html:22 #: events/templates/events/eventseries_form.html:24
msgid "back" msgid "back"
msgstr "Zurück" msgstr "Zurück"
#: src/events/templates/events/photo_confirm_delete.html:17 #: events/templates/events/photo_confirm_delete.html:17
msgid "Cancel" msgid "Cancel"
msgstr "Abbrechen" msgstr "Abbrechen"
#: src/events/templates/events/photo_confirm_delete.html:21 #: events/templates/events/photo_confirm_delete.html:21
#: src/events/templates/events/photo_list.html:21 #: events/templates/events/photo_list.html:21
msgid "Delete" msgid "Delete"
msgstr "Löschen" msgstr "Löschen"
#: src/events/templates/events/photo_detail.html:39 #: events/templates/events/photo_detail.html:44
msgid "previous" msgid "previous"
msgstr "Zurück" msgstr "Zurück"
#: src/events/templates/events/photo_detail.html:47 #: events/templates/events/photo_detail.html:52
msgid "Photographer" msgid "Photographer"
msgstr "Fotograf" msgstr "Fotograf"
#: src/events/templates/events/photo_detail.html:53 #: events/templates/events/photo_detail.html:58
msgid "share on" msgid "share on"
msgstr "Teile auf" msgstr "Teile auf"
#: src/events/templates/events/photo_detail.html:76 #: events/templates/events/photo_detail.html:81
msgid "download" msgid "download"
msgstr "Herunterladen" msgstr "Herunterladen"
#: src/events/templates/events/photo_detail.html:77 #: events/templates/events/photo_detail.html:82
msgid "Rotate counter clockwise" msgid "Rotate counter clockwise"
msgstr "mit dem Uhrzeiger drehen" msgstr "mit dem Uhrzeiger drehen"
#: src/events/templates/events/photo_detail.html:78 #: events/templates/events/photo_detail.html:83
msgid "Rotate clockwise" msgid "Rotate clockwise"
msgstr "gegen den Uhrzeiger drehen" msgstr "gegen den Uhrzeiger drehen"
#: src/events/templates/events/photo_detail.html:79 #: events/templates/events/photo_detail.html:84
msgid "Save" msgid "Save"
msgstr "Speichern" msgstr "Speichern"
#: src/events/templates/events/photo_list.html:36 #: events/templates/events/photo_list.html:36
#: src/events/templates/events/photo_upload.html:35 #: events/templates/events/photo_upload.html:35
#: src/events/templates/events/photo_upload.html:49 #: events/templates/events/photo_upload.html:49
msgid "Upload" msgid "Upload"
msgstr "Hochladen" msgstr "Hochladen"
#: src/events/views.py:209 #: events/views.py:209
msgid "Event does not exist" msgid "Event does not exist"
msgstr "Veranstaltung gibt es nicht" msgstr "Veranstaltung gibt es nicht"

View File

@@ -13,12 +13,14 @@ class Migration(migrations.Migration):
operations = [ operations = [
migrations.AlterModelOptions( migrations.AlterModelOptions(
name='photo', name='photo',
options={'ordering': ['created_date'], 'get_latest_by': 'created_date', 'verbose_name': 'Veranstaltungsbild', 'verbose_name_plural': 'Veranstaltungsbilder'}, options={'ordering': ['created_date'], 'get_latest_by': 'created_date',
'verbose_name': 'Veranstaltungsbild', 'verbose_name_plural': 'Veranstaltungsbilder'},
), ),
migrations.AlterField( migrations.AlterField(
model_name='event', model_name='event',
name='mahjong_season', name='mahjong_season',
field=models.PositiveSmallIntegerField(null=True, verbose_name='Mahjong Saison', blank=True), field=models.PositiveSmallIntegerField(
null=True, verbose_name='Mahjong Saison', blank=True),
), ),
migrations.AlterField( migrations.AlterField(
model_name='photo', model_name='photo',
@@ -28,11 +30,13 @@ class Migration(migrations.Migration):
migrations.AlterField( migrations.AlterField(
model_name='photo', model_name='photo',
name='on_startpage', name='on_startpage',
field=models.BooleanField(default=False, help_text='Foto als Teaser auf der Startseite verwenden.', db_index=True, verbose_name='Startseite'), field=models.BooleanField(
default=False, help_text='Foto als Teaser auf der Startseite verwenden.', db_index=True, verbose_name='Startseite'),
), ),
migrations.AlterField( migrations.AlterField(
model_name='photo', model_name='photo',
name='views', name='views',
field=models.PositiveIntegerField(default=0, verbose_name='Wie oft gesehen', editable=False), field=models.PositiveIntegerField(
default=0, verbose_name='Wie oft gesehen', editable=False),
), ),
] ]

View File

@@ -16,34 +16,40 @@ class Migration(migrations.Migration):
migrations.AddField( migrations.AddField(
model_name='event', model_name='event',
name='date_created', name='date_created',
field=models.DateTimeField(auto_now_add=True, null=True, verbose_name='first created at', db_index=True), field=models.DateTimeField(
auto_now_add=True, null=True, verbose_name='first created at', db_index=True),
), ),
migrations.AddField( migrations.AddField(
model_name='event', model_name='event',
name='date_modified', name='date_modified',
field=models.DateTimeField(default=datetime.datetime(2016, 10, 12, 20, 24, 39, 910492, tzinfo=utc), verbose_name='latest updated at', auto_now=True), field=models.DateTimeField(default=datetime.datetime(
2016, 10, 12, 20, 24, 39, 910492, tzinfo=utc), verbose_name='latest updated at', auto_now=True),
preserve_default=False, preserve_default=False,
), ),
migrations.AddField( migrations.AddField(
model_name='location', model_name='location',
name='date_created', name='date_created',
field=models.DateTimeField(auto_now_add=True, null=True, verbose_name='first created at', db_index=True), field=models.DateTimeField(
auto_now_add=True, null=True, verbose_name='first created at', db_index=True),
), ),
migrations.AddField( migrations.AddField(
model_name='location', model_name='location',
name='date_modified', name='date_modified',
field=models.DateTimeField(default=datetime.datetime(2016, 10, 12, 20, 24, 44, 566305, tzinfo=utc), verbose_name='latest updated at', auto_now=True), field=models.DateTimeField(default=datetime.datetime(
2016, 10, 12, 20, 24, 44, 566305, tzinfo=utc), verbose_name='latest updated at', auto_now=True),
preserve_default=False, preserve_default=False,
), ),
migrations.AddField( migrations.AddField(
model_name='photo', model_name='photo',
name='date_created', name='date_created',
field=models.DateTimeField(auto_now_add=True, null=True, verbose_name='first created at', db_index=True), field=models.DateTimeField(
auto_now_add=True, null=True, verbose_name='first created at', db_index=True),
), ),
migrations.AddField( migrations.AddField(
model_name='photo', model_name='photo',
name='date_modified', name='date_modified',
field=models.DateTimeField(default=datetime.datetime(2016, 10, 12, 20, 24, 50, 509970, tzinfo=utc), verbose_name='latest updated at', auto_now=True), field=models.DateTimeField(default=datetime.datetime(
2016, 10, 12, 20, 24, 50, 509970, tzinfo=utc), verbose_name='latest updated at', auto_now=True),
preserve_default=False, preserve_default=False,
), ),
] ]

View File

@@ -346,7 +346,6 @@ class Photo(models.Model):
)[0] )[0]
except IndexError: except IndexError:
return None return None
return self.get_next_by_created_date(event=self.event)
@property @property
def previous_photo(self): def previous_photo(self):
@@ -366,4 +365,3 @@ class Photo(models.Model):
)[0] )[0]
except IndexError: except IndexError:
return None return None
return self.get_previous_by_created_date(event=self.event)

View File

@@ -1,11 +1,11 @@
from django.contrib.sitemaps import Sitemap from django.contrib.sitemaps import Sitemap
from django.utils import timezone from django.utils import timezone
from .models import Event, Photo from .models import Event
from .models import Photo
class EventSitemap(Sitemap): class EventSitemap(Sitemap):
changefreq = "never" changefreq = "never"
priority = 0.6
protocol = 'https' protocol = 'https'
def items(self): def items(self):
@@ -18,4 +18,3 @@ class EventSitemap(Sitemap):
def lastmod(self, event): def lastmod(self, event):
return event.date_modified return event.date_modified

View File

@@ -12,7 +12,6 @@
<p class="warning"> <p class="warning">
<strong>Achtung! Das ist eine Veranstaltungsreihe!</strong> Diese kann man im Moment nur im Admin-Interface vernünfig bearbeiten.<br /> <strong>Achtung! Das ist eine Veranstaltungsreihe!</strong> Diese kann man im Moment nur im Admin-Interface vernünfig bearbeiten.<br />
Du bearbeitest hier den "Hauptevent" der Reihe ({{event.event_set.count}}). Alle Änderungen (abgesehen von Name, Start und Ende) werden von den darauf folgendem Veranstaltungen übernommen. Du bearbeitest hier den "Hauptevent" der Reihe ({{event.event_set.count}}). Alle Änderungen (abgesehen von Name, Start und Ende) werden von den darauf folgendem Veranstaltungen übernommen.
</strong>
</p> </p>
{% endif %} {% endif %}
<p class="buttonbar"> <p class="buttonbar">

View File

@@ -35,7 +35,7 @@
{%trans "Upload" %}</a> {%trans "Upload" %}</a>
{% endif %} {% endif %}
</p> </p>
</div></div> </div>
{% endfor %} {% endfor %}
<form method="post" enctype="multipart/form-data"> <form method="post" enctype="multipart/form-data">

View File

@@ -6,7 +6,8 @@ from django.db.models import Q
from django.contrib.auth.decorators import permission_required from django.contrib.auth.decorators import permission_required
from django.contrib.auth import get_user_model from django.contrib.auth import get_user_model
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from extra_views import ModelFormSetView, InlineFormSetView from extra_views import InlineFormSetView
from extra_views import ModelFormSetView
from django.http import HttpResponse, Http404 from django.http import HttpResponse, Http404
from django.shortcuts import redirect from django.shortcuts import redirect
from django.utils import timezone from django.utils import timezone

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,9 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from os import path from os import path
gettext = lambda s: s
def gettext(s): return s
ADMINS = (('Max Mustermann', 'email@example.com'),) ADMINS = (('Max Mustermann', 'email@example.com'),)
ALLOWED_HOSTS = ['.kasu.at'] ALLOWED_HOSTS = ['.kasu.at']
@@ -39,6 +41,7 @@ PREREQ_APPS = [
'django.contrib.sitemaps', 'django.contrib.sitemaps',
'django.contrib.staticfiles', 'django.contrib.staticfiles',
'django_comments', 'django_comments',
'captcha',
'ckeditor', 'ckeditor',
'ckeditor_uploader', 'ckeditor_uploader',
'easy_thumbnails', 'easy_thumbnails',
@@ -108,16 +111,6 @@ TEMPLATES = [
}, },
] ]
#Settings for Security Middleware
CSP_DEFAULT_SRC = ("'self'",)
CSP_IMG_SRC = ("'self'", "'unsafe-eval'")
CSP_SCRIPT_SRC = ("'self'", "'unsafe-inline'", "'unsafe-eval'")
CSP_STYLE_SRC = ("'self'", "'unsafe-inline'")
SECURE_BROWSER_XSS_FILTER = True
SECURE_CONTENT_TYPE_NOSNIFF = True
SECURE_HSTS_INCLUDE_SUBDOMAINS = True
SECURE_HSTS_SECONDS = 31536000
SECURE_SSL_REDIRECT = True
SESSION_COOKIE_DOMAIN = 'kasu.at' # Die ganze Domain Kasu SESSION_COOKIE_DOMAIN = 'kasu.at' # Die ganze Domain Kasu
SESSION_COOKIE_AGE = 15768000 # Session dauer: 4 Wochen SESSION_COOKIE_AGE = 15768000 # Session dauer: 4 Wochen
SESSION_COOKIE_SECURE = True SESSION_COOKIE_SECURE = True
@@ -191,11 +184,9 @@ CKEDITOR_CONFIGS = {'default': {
FACEBOOK_APP_ID = '' FACEBOOK_APP_ID = ''
FACEBOOK_ACCESS_TOKEN = '' FACEBOOK_ACCESS_TOKEN = ''
# Settings for the redactor WYSIWYG Editor
REDACTOR_OPTIONS = {'lang': 'de', 'linebreaks': True}
REDACTOR_UPLOAD = 'uploads/'
# crendetials for reCaptcha (set in local_settings) # crendetials for reCaptcha (set in local_settings)
NOCAPTCHA = True
RECAPTCHA_USE_SSL = True
RECAPTCHA_PUBLIC_KEY = '' RECAPTCHA_PUBLIC_KEY = ''
RECAPTCHA_PRIVATE_KEY = '' RECAPTCHA_PRIVATE_KEY = ''
RECAPTCHA_THEME = 'red' RECAPTCHA_THEME = 'red'

View File

@@ -1,8 +1,14 @@
var idSite = 1; /* Piwik */
var piwikTrackingApiUrl = 'https://kasu.at/piwik/piwik.php';
var _paq = _paq || []; var _paq = _paq || [];
_paq.push(['setTrackerUrl', piwikTrackingApiUrl]); /* tracker methods like "setCustomDimension" should be called before "trackPageView" */
_paq.push(['setSiteId', idSite]);
_paq.push(['trackPageView']); _paq.push(['trackPageView']);
_paq.push(['enableLinkTracking']); _paq.push(['enableLinkTracking']);
(function() {
var u="//kasu.at/piwik/";
_paq.push(['setTrackerUrl', u+'piwik.php']);
_paq.push(['setSiteId', '1']);
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);
})();
</script>
/* End Piwik Code */

View File

@@ -4,7 +4,7 @@
{% block title %}404 - Seite nicht gefunden{% endblock %} {% block title %}404 - Seite nicht gefunden{% endblock %}
{% block teaser %} {% block teaser %}
<h1">404 - Nōten!</h1> <h1>404 - Nōten!</h1>
<div id="teaser_text"><p>{% trans 'The page your requested does not exist on this server.' %}</p></div> <div id="teaser_text"><p>{% trans 'The page your requested does not exist on this server.' %}</p></div>
{% endblock %} {% endblock %}

View File

@@ -35,21 +35,7 @@
<meta property="og:image" content="http://www.kasu.at/static/img/logo.png"/> <meta property="og:image" content="http://www.kasu.at/static/img/logo.png"/>
{% endblock %} {% endblock %}
{% block extra_head %}{% endblock %} {% block extra_head %}{% endblock %}
<!-- Piwik --> <script src="{{ STATIC_URL }}js/piwik.js"></script>
<script type="text/javascript">
var _paq = _paq || [];
// tracker methods like "setCustomDimension" should be called before "trackPageView"
_paq.push(['trackPageView']);
_paq.push(['enableLinkTracking']);
(function() {
var u="/piwik/";
_paq.push(['setTrackerUrl', u+'piwik.php']);
_paq.push(['setSiteId', '1']);
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);
})();
</script>
<!-- End Piwik Code -->
</head> </head>
<body id="body" {% block itemscope %}{% endblock %}> <body id="body" {% block itemscope %}{% endblock %}>
<header id="siteheader"> <header id="siteheader">
@@ -63,7 +49,10 @@
class="{%if item.active %}active{% endif %}">{{item.menu_name}}</a> class="{%if item.active %}active{% endif %}">{{item.menu_name}}</a>
{% if item.subpages.all %} {% if item.subpages.all %}
<ul class="main_dropdown"> <ul class="main_dropdown">
{% for subpage in item.subpages.all %}<li><a href="{{subpage.get_absolute_url}}" {% ifequal subpage current_page %}class="active"{% endifequal %}>{{subpage.menu_name}}</a></li>{% endfor %} {% for subpage in item.subpages.all %}<li><a
href="{{subpage.get_absolute_url}}"
{% ifequal subpage current_page %}class="active"{% endifequal %}>{{subpage.menu_name}}</a></li>
{% endfor %}
</ul> </ul>
{% endif %} {% endif %}
</li> </li>

View File

@@ -2,7 +2,8 @@
{% for hidden in form.hidden_fields %}{{ hidden }}{% endfor %} {% for hidden in form.hidden_fields %}{{ hidden }}{% endfor %}
{% for field in form.visible_fields %} {% for field in form.visible_fields %}
<div> <div>
<label {% if field.html_name != 'recaptcha' %}for="id_{{ field.html_name}}"{% endif %} class="field_name {{ field.css_classes }}">{{ field.label}}</label> <label {% if field.html_name != 'recaptcha' %} for="id_{{ field.html_name}}" {% endif %}
class="field_name {{ field.css_classes }}">{{ field.label}}</label>
{{ field }} {{ field }}
{% if field.help_text and not field.field.widget.input_type %} {% if field.help_text and not field.field.widget.input_type %}
{{field.help_text}} {{field.help_text}}

View File

@@ -1,6 +1,10 @@
{% load i18n %} {% load i18n %}
<nav class="grid_12 pagination"> <nav class="grid_12 pagination">
<a {% if page_obj.has_previous %}class="previous" href="?page={{ page_obj.previous_page_number }}"{% else %}class="previous disabled" {% endif %}> <a {% if page_obj.has_previous %}
class="previous" href="?page={{ page_obj.previous_page_number }}"
{% else %}
class="previous disabled"
{% endif %}>
<span class="fa fa-arrow-left"></span>{% trans "Previous" %} <span class="fa fa-arrow-left"></span>{% trans "Previous" %}
</a> </a>
@@ -8,8 +12,11 @@
<a {% ifequal page_obj.number page %}class="active"{% else %}href="?page={{page}}"{% endifequal %}>{{page}}</a> <a {% ifequal page_obj.number page %}class="active"{% else %}href="?page={{page}}"{% endifequal %}>{{page}}</a>
{% endfor %} {% endfor %}
<a {% if page_obj.has_next %}class="next" href="?page={{ page_obj.next_page_number }}"{% else %}class="next disabled"{% endif %}> <a {% if page_obj.has_next %}
class="next" href="?page={{ page_obj.next_page_number }}"
{% else %}
class="next disabled"
{% endif %}>
{% trans "Next" %} <span class="fa fa-arrow-right"></span> {% trans "Next" %} <span class="fa fa-arrow-right"></span>
</a> </a>
</nav> </nav>

View File

@@ -1,8 +1,3 @@
"""
Created on 10.06.2012
@author: christian
"""
import copy import copy
from collections import OrderedDict from collections import OrderedDict

View File

@@ -23,19 +23,21 @@ sitemaps = {
urlpatterns = [ urlpatterns = [
url(r'^$', StartPage.as_view()), url(r'^$', StartPage.as_view()),
url(r'^404/$', TemplateView.as_view(template_name='404.html')), url(r'^404/$', TemplateView.as_view(template_name='404.html')),
url(r'^add_page/(?P<path>[\+\.\-\d\w\/]+)/$', PageAddForm.as_view(), name='add-page'), url(r'^add_page/(?P<path>[\+\.\-\d\w\/]+)/$',
PageAddForm.as_view(), name='add-page'),
url(r'^admin/doc/', include('django.contrib.admindocs.urls')), url(r'^admin/doc/', include('django.contrib.admindocs.urls')),
url(r'^admin/', include(admin.site.urls)), url(r'^admin/', include(admin.site.urls)),
url(r'^ckeditor/', include('ckeditor_uploader.urls')), url(r'^ckeditor/', include('ckeditor_uploader.urls')),
url(r'^comments/', include('django_comments.urls')), url(r'^comments/', include('django_comments.urls')),
url(r'^content/', include('content.urls')), url(r'^edit_page/(?P<path>[\+\.\-\d\w\/]+)/$',
url(r'^edit_page/(?P<path>[\+\.\-\d\w\/]+)/$', PageEditForm.as_view(), name='edit-page'), PageEditForm.as_view(), name='edit-page'),
url(r'^events/', include('events.urls')), url(r'^events/', include('events.urls')),
url(r'^events.ics$', EventListIcal.as_view(), name='events-ical'), url(r'^events.ics$', EventListIcal.as_view(), name='events-ical'),
url(r'^feeds/latest/$', LatestNews(), name='feed-latest-news'), url(r'^feeds/latest/$', LatestNews(), name='feed-latest-news'),
url(r'^feeds/comments/$', LatestComments(), name='feed-latest-comments'), url(r'^feeds/comments/$', LatestComments(), name='feed-latest-comments'),
url(r'^gallery/', include('events.gallery_urls')), url(r'^gallery/', include('events.gallery_urls')),
url(r'^google25dabc1a49a9ef03.html$', TemplateView.as_view(template_name='google25dabc1a49a9ef03.html')), url(r'^google25dabc1a49a9ef03.html$', TemplateView.as_view(
template_name='google25dabc1a49a9ef03.html')),
url(r'^i18n/', include('django.conf.urls.i18n'), name='start-page'), url(r'^i18n/', include('django.conf.urls.i18n'), name='start-page'),
url(r'^index.html$', StartPage.as_view()), url(r'^index.html$', StartPage.as_view()),
url(r'^manifest.json$', TemplateView.as_view(template_name='manifest.json')), url(r'^manifest.json$', TemplateView.as_view(template_name='manifest.json')),
@@ -43,19 +45,24 @@ urlpatterns = [
url(r'^news/', include('content.news_urls')), url(r'^news/', include('content.news_urls')),
url(r'^ranking/', include('mahjong_ranking.urls')), url(r'^ranking/', include('mahjong_ranking.urls')),
url(r'^ranking/', include('maistar_ranking.urls')), url(r'^ranking/', include('maistar_ranking.urls')),
url(r'^sitemap\.xml$', sitemap, {'sitemaps': sitemaps}, name='django.contrib.sitemaps.views.sitemap'), url(r'^sitemap\.xml$', sitemap, {
'sitemaps': sitemaps}, name='django.contrib.sitemaps.views.sitemap'),
url(r'^robots.txt$', TemplateView.as_view(template_name='robots.txt')), url(r'^robots.txt$', TemplateView.as_view(template_name='robots.txt')),
url(r'^users/$', MembershipDetail.as_view(), name='membership-details'), url(r'^users/$', MembershipDetail.as_view(), name='membership-details'),
url(r'^users/(?P<username>[\-\.\d\w]+)/$', MembershipDetail.as_view(), name='membership-details'), url(r'^users/(?P<username>[\-\.\d\w]+)/$',
url(r'^(?P<path>[\-\d\w\/]+)\.html$', PageHtml.as_view(), name='view-page'), MembershipDetail.as_view(), name='membership-details'),
url(r'^(?P<path>[\-\d\w\/]+)\.html$',
PageHtml.as_view(), name='view-page'),
url(r'^(?P<path>[\-\d\w\/]+)\.pdf$', PagePdf.as_view()), url(r'^(?P<path>[\-\d\w\/]+)\.pdf$', PagePdf.as_view()),
url('', include('social_django.urls', namespace='social')) url('', include('social_django.urls', namespace='social'))
] ]
if settings.DEBUG: if settings.DEBUG:
from django.conf.urls.static import static from django.conf.urls.static import static
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) urlpatterns += static(settings.MEDIA_URL,
urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT) document_root=settings.MEDIA_ROOT)
urlpatterns += static(settings.STATIC_URL,
document_root=settings.STATIC_ROOT)
if 'rosetta' in settings.INSTALLED_APPS: if 'rosetta' in settings.INSTALLED_APPS:
urlpatterns += url(r'^rosetta/', include('rosetta.urls')) urlpatterns += url(r'^rosetta/', include('rosetta.urls'))

View File

@@ -326,7 +326,7 @@ class MassMailer(object):
if isinstance(recipient, USER_MODEL): if isinstance(recipient, USER_MODEL):
self.recipients.add(recipient) self.recipients.add(recipient)
else: else:
self.log.warn('%s is not a User Object!', recipient) self.log.warning('%s is not a User Object!', recipient)
def add_recipients(self, user_list): def add_recipients(self, user_list):
for user in user_list: for user in user_list:
@@ -334,7 +334,7 @@ class MassMailer(object):
self.log.debug('Adding %s as Recipient' % user) self.log.debug('Adding %s as Recipient' % user)
self.recipients.add(user) self.recipients.add(user)
else: else:
self.log.warn('%s is not a User Object!', user) self.log.warning('%s is not a User Object!', user)
def process_mails(self): def process_mails(self):
mail_context = Context(self.context) mail_context = Context(self.context)
@@ -381,7 +381,7 @@ class MassMailer(object):
try: try:
mail.send() mail.send()
except: except:
self.log.warn("Mail failed for: %s", mail.to) self.log.warning("Mail failed for: %s", mail.to)
else: else:
self.log.info("Mail sent successful to: %s" % mail.to) self.log.info("Mail sent successful to: %s" % mail.to)
self.close_smtp_connection() self.close_smtp_connection()

View File

@@ -2,15 +2,16 @@
import os import os
import sys import sys
src_path = '/var/www/virtual/kasu.at/' src_path = '/home/kasu/src'
virtpy_path = '/var/www/virtual/kasu.at/virtenv/lib/python2.6/site-packages' virtpy_path = '/home/kasu/virtualenv/lib/python3.4/site-packages'
if virtpy_path not in sys.path: if virtpy_path not in sys.path:
sys.path.insert(1, virtpy_path) sys.path.insert(1, virtpy_path)
if src_path not in sys.path: if src_path not in sys.path:
sys.path.append(src_path) sys.path.append(src_path)
os.environ['DJANGO_SETTINGS_MODULE'] = 'kasu.settings.production'
import django.core.handlers.wsgi from django.core.wsgi import get_wsgi_application
application = django.core.handlers.wsgi.WSGIHandler() os.environ['DJANGO_SETTINGS_MODULE'] = 'kasu.settings'
application = get_wsgi_application()

View File

@@ -33,6 +33,9 @@ logger = logging.getLogger('kasu.mahjong_ranking')
def set_dirty(event=None, season=None, user=None, hanchan_date=None): def set_dirty(event=None, season=None, user=None, hanchan_date=None):
key_to_add = None
queue_name = None
if season and user: if season and user:
key_to_add = (season, user) key_to_add = (season, user)
queue_name = 'ladder_ranking_queue' queue_name = 'ladder_ranking_queue'

View File

@@ -26,6 +26,8 @@ def recalculate(modeladmin, request, queryset):
for ladder_ranking in queryset: for ladder_ranking in queryset:
set_dirty(user=ladder_ranking.user_id, set_dirty(user=ladder_ranking.user_id,
season=ladder_ranking.season) season=ladder_ranking.season)
recalculate.short_description = _("Recalculate") recalculate.short_description = _("Recalculate")
@@ -33,6 +35,8 @@ def confirm(modeladmin, request, queryset):
for hanchan in queryset: for hanchan in queryset:
hanchan.confirmed = True hanchan.confirmed = True
hanchan.save() hanchan.save()
confirm.short_description = _("Confirm") confirm.short_description = _("Confirm")
@@ -40,6 +44,8 @@ def unconfirm(modeladmin, request, queryset):
for hanchan in queryset: for hanchan in queryset:
hanchan.confirmed = False hanchan.confirmed = False
hanchan.save() hanchan.save()
unconfirm.short_description = _('Set unconfirmed') unconfirm.short_description = _('Set unconfirmed')

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -6,11 +6,9 @@ Created on 04.10.2011
@author: christian @author: christian
""" """
from django.contrib.auth import get_user_model from django.contrib.auth import get_user_model
import django.forms from django import forms
from django.utils import timezone
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from utils.html5 import forms
from . import models from . import models
USER_MODEL = get_user_model() USER_MODEL = get_user_model()
@@ -19,7 +17,7 @@ USER_MODEL = get_user_model()
class HanchanForm(forms.ModelForm): class HanchanForm(forms.ModelForm):
error_css_class = 'error' error_css_class = 'error'
required_css_class = 'required' required_css_class = 'required'
start = forms.DateTimeField(label=_('start'), required=True) start = forms.SplitDateTimeField(label=_('start'), required=True)
class Meta(object): class Meta(object):
model = models.Hanchan model = models.Hanchan
@@ -29,7 +27,8 @@ class HanchanForm(forms.ModelForm):
'player3', 'player3_input_score', 'player3', 'player3_input_score',
'player4', 'player4_input_score', 'player4', 'player4_input_score',
'comment') 'comment')
widgets = {'event': forms.HiddenInput(), widgets = {
'event': forms.HiddenInput(),
'comment': forms.widgets.Textarea(attrs={'rows': 4, 'cols': 40}) 'comment': forms.widgets.Textarea(attrs={'rows': 4, 'cols': 40})
} }
@@ -45,7 +44,6 @@ class HanchanForm(forms.ModelForm):
self.fields[player].queryset = player_queryset self.fields[player].queryset = player_queryset
class HanchanAdminForm(HanchanForm): class HanchanAdminForm(HanchanForm):
class Meta(object): class Meta(object):
@@ -53,12 +51,12 @@ class HanchanAdminForm(HanchanForm):
fields = HanchanForm.Meta.fields + ('confirmed',) fields = HanchanForm.Meta.fields + ('confirmed',)
class SeasonSelectForm(django.forms.Form): class SeasonSelectForm(forms.Form):
season = django.forms.ChoiceField(label='', choices=('a', 'b', 'c')) season = forms.ChoiceField(label='', choices=('a', 'b', 'c'))
def __init__(self, user, *args, **kwargs): def __init__(self, user, *args, **kwargs):
super(SeasonSelectForm, self).__init__(args, kwargs) super(SeasonSelectForm, self).__init__(args, kwargs)
season_list = models.LadderRanking.objects.filter(user=user) season_list = models.LadderRanking.objects.filter(user=user)
season_list = season_list.select_related('user') season_list = season_list.select_related('user')
season_list = season_list.values_list('season__id', 'season__name') season_list = season_list.values_list('season__id', 'season__name')
self.fields['season'] = django.forms.ChoiceField(choices=season_list) self.fields['season'] = forms.ChoiceField(choices=season_list)

View File

@@ -7,7 +7,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: kasu.mahjong_ranking\n" "Project-Id-Version: kasu.mahjong_ranking\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2016-09-28 00:25+0200\n" "POT-Creation-Date: 2017-05-10 23:16+0200\n"
"PO-Revision-Date: 2016-09-28 00:24+0200\n" "PO-Revision-Date: 2016-09-28 00:24+0200\n"
"Last-Translator: Christian Berg <xeniac.at@gmail.com>\n" "Last-Translator: Christian Berg <xeniac.at@gmail.com>\n"
"Language-Team: Kasu <verein@kasu.at>\n" "Language-Team: Kasu <verein@kasu.at>\n"
@@ -19,376 +19,376 @@ msgstr ""
"X-Generator: Poedit 1.8.9\n" "X-Generator: Poedit 1.8.9\n"
"X-Translated-Using: django-rosetta 0.7.6\n" "X-Translated-Using: django-rosetta 0.7.6\n"
#: src/mahjong_ranking/admin.py:29 #: mahjong_ranking/admin.py:29
msgid "Recalculate" msgid "Recalculate"
msgstr "Neuberechnen" msgstr "Neuberechnen"
#: src/mahjong_ranking/admin.py:36 #: mahjong_ranking/admin.py:36
msgid "Confirm" msgid "Confirm"
msgstr "Bestätigen" msgstr "Bestätigen"
#: src/mahjong_ranking/admin.py:43 #: mahjong_ranking/admin.py:43
msgid "Set unconfirmed" msgid "Set unconfirmed"
msgstr "Als unbestätigt markieren" msgstr "Als unbestätigt markieren"
#: src/mahjong_ranking/forms.py:22 #: mahjong_ranking/forms.py:22
msgid "start" msgid "start"
msgstr "Beginn" msgstr "Beginn"
#: src/mahjong_ranking/models.py:91 #: mahjong_ranking/models.py:90
#: src/mahjong_ranking/templates/mahjong_ranking/player_dan_score.html:14 #: mahjong_ranking/templates/mahjong_ranking/player_dan_score.html:14
#: src/mahjong_ranking/templates/mahjong_ranking/player_invalid_score.html:13 #: mahjong_ranking/templates/mahjong_ranking/player_invalid_score.html:13
#: src/mahjong_ranking/templates/mahjong_ranking/player_kyu_score.html:15 #: mahjong_ranking/templates/mahjong_ranking/player_kyu_score.html:15
#: src/mahjong_ranking/templates/mahjong_ranking/player_ladder_score.html:15 #: mahjong_ranking/templates/mahjong_ranking/player_ladder_score.html:15
#: src/mahjong_ranking/templates/mahjong_ranking/seasonranking_list.html:10 #: mahjong_ranking/templates/mahjong_ranking/seasonranking_list.html:10
msgid "Start" msgid "Start"
msgstr "Beginn" msgstr "Beginn"
#: src/mahjong_ranking/models.py:92 #: mahjong_ranking/models.py:91
msgid "This is crucial to get the right Hanchans that scores" msgid "This is crucial to get the right Hanchans that scores"
msgstr "Wichtig damit die richtigen Hanchans in die Wertung kommen." msgstr "Wichtig damit die richtigen Hanchans in die Wertung kommen."
#: src/mahjong_ranking/models.py:97 #: mahjong_ranking/models.py:98
msgid "Player 1" msgid "Player 1"
msgstr "Spieler 1" msgstr "Spieler 1"
#: src/mahjong_ranking/models.py:98 src/mahjong_ranking/models.py:100 #: mahjong_ranking/models.py:99 mahjong_ranking/models.py:101
#: src/mahjong_ranking/models.py:115 src/mahjong_ranking/models.py:117 #: mahjong_ranking/models.py:118 mahjong_ranking/models.py:120
#: src/mahjong_ranking/models.py:132 src/mahjong_ranking/models.py:134 #: mahjong_ranking/models.py:137 mahjong_ranking/models.py:139
#: src/mahjong_ranking/models.py:149 src/mahjong_ranking/models.py:151 #: mahjong_ranking/models.py:156 mahjong_ranking/models.py:158
#: src/mahjong_ranking/templates/mahjong_ranking/eventhanchan_list.html:19 #: mahjong_ranking/templates/mahjong_ranking/eventhanchan_list.html:19
#: src/mahjong_ranking/templates/mahjong_ranking/eventranking_list.html:21 #: mahjong_ranking/templates/mahjong_ranking/eventranking_list.html:21
#: src/mahjong_ranking/templates/mahjong_ranking/hanchan_confirm_delete.html:16 #: mahjong_ranking/templates/mahjong_ranking/hanchan_confirm_delete.html:16
#: src/mahjong_ranking/templates/mahjong_ranking/hanchan_form.html:43 #: mahjong_ranking/templates/mahjong_ranking/hanchan_form.html:19
#: src/mahjong_ranking/templates/mahjong_ranking/kyudanranking_list.html:31 #: mahjong_ranking/templates/mahjong_ranking/kyudanranking_list.html:35
#: src/mahjong_ranking/templates/mahjong_ranking/seasonranking_list.html:32 #: mahjong_ranking/templates/mahjong_ranking/seasonranking_list.html:32
msgid "Score" msgid "Score"
msgstr "Punkte" msgstr "Punkte"
#: src/mahjong_ranking/models.py:110 src/mahjong_ranking/models.py:127 #: mahjong_ranking/models.py:111 mahjong_ranking/models.py:130
#: src/mahjong_ranking/models.py:144 src/mahjong_ranking/models.py:161 #: mahjong_ranking/models.py:149 mahjong_ranking/models.py:168
#: src/mahjong_ranking/models.py:163 #: mahjong_ranking/models.py:170
#: src/mahjong_ranking/templates/mahjong_ranking/hanchan_form.html:44 #: mahjong_ranking/templates/mahjong_ranking/hanchan_form.html:20
#: src/mahjong_ranking/templates/mahjong_ranking/player_dan_score.html:18 #: mahjong_ranking/templates/mahjong_ranking/player_dan_score.html:18
#: src/mahjong_ranking/templates/mahjong_ranking/player_invalid_score.html:17 #: mahjong_ranking/templates/mahjong_ranking/player_invalid_score.html:17
msgid "Comment" msgid "Comment"
msgstr "Kommentar" msgstr "Kommentar"
#: src/mahjong_ranking/models.py:114 #: mahjong_ranking/models.py:117
msgid "Player 2" msgid "Player 2"
msgstr "Spieler 2" msgstr "Spieler 2"
#: src/mahjong_ranking/models.py:131 #: mahjong_ranking/models.py:136
msgid "Player 3" msgid "Player 3"
msgstr "Spieler 3" msgstr "Spieler 3"
#: src/mahjong_ranking/models.py:148 #: mahjong_ranking/models.py:155
msgid "Player 4" msgid "Player 4"
msgstr "Spieler 4" msgstr "Spieler 4"
#: src/mahjong_ranking/models.py:164 #: mahjong_ranking/models.py:171
msgid "Has been Confirmed" msgid "Has been Confirmed"
msgstr "Wurde bestätigt" msgstr "Wurde bestätigt"
#: src/mahjong_ranking/models.py:166 #: mahjong_ranking/models.py:173
msgid "Only valid and confirmed Hanchans will be counted in the rating." msgid "Only valid and confirmed Hanchans will be counted in the rating."
msgstr "Nur gültige und bestätigte Hanchans kommen in die Wertung." msgstr "Nur gültige und bestätigte Hanchans kommen in die Wertung."
#: src/mahjong_ranking/models.py:171 src/mahjong_ranking/models.py:547 #: mahjong_ranking/models.py:178 mahjong_ranking/models.py:565
#: src/mahjong_ranking/templates/mahjong_ranking/ladder_redbox.html:29 #: mahjong_ranking/templates/mahjong_ranking/ladder_redbox.html:29
#: src/mahjong_ranking/templates/mahjong_ranking/player_ladder_score.html:63 #: mahjong_ranking/templates/mahjong_ranking/player_ladder_score.html:63
msgid "Season" msgid "Season"
msgstr "Saison" msgstr "Saison"
#: src/mahjong_ranking/models.py:176 #: mahjong_ranking/models.py:183
msgid "Hanchan" msgid "Hanchan"
msgstr "Hanchan" msgstr "Hanchan"
#: src/mahjong_ranking/models.py:177 #: mahjong_ranking/models.py:184
#: src/mahjong_ranking/templates/mahjong_ranking/eventranking_list.html:17 #: mahjong_ranking/templates/mahjong_ranking/eventranking_list.html:17
msgid "Hanchans" msgid "Hanchans"
msgstr "Hanchans" msgstr "Hanchans"
#: src/mahjong_ranking/models.py:180 #: mahjong_ranking/models.py:187
msgid "Hanchan from {0:%Y-%m-%d} at {0:%H:%M} with {1}" msgid "Hanchan from {0:%Y-%m-%d} at {0:%H:%M} with {1}"
msgstr "Hanchan vom {0:%Y-%m-%d} um {0:%H:%M} mit {1}" msgstr "Hanchan vom {0:%Y-%m-%d} um {0:%H:%M} mit {1}"
#: src/mahjong_ranking/models.py:207 #: mahjong_ranking/models.py:214
#, python-format #, python-format
msgid "%s can't attend the same game multiple times" msgid "%s can't attend the same game multiple times"
msgstr "%s kann an einem Spiel nicht mehrfach teilnehmen." msgstr "%s kann an einem Spiel nicht mehrfach teilnehmen."
#: src/mahjong_ranking/models.py:215 #: mahjong_ranking/models.py:222
msgid "Games in the future may not be added, Dr. Brown" msgid "Games in the future may not be added, Dr. Brown"
msgstr "Spiele aus der Zukunft dürfen noch nicht erfasst werden. Dr. Brown." msgstr "Spiele aus der Zukunft dürfen noch nicht erfasst werden. Dr. Brown."
#: src/mahjong_ranking/models.py:217 #: mahjong_ranking/models.py:224
msgid "Only games during the event are allowed" msgid "Only games during the event are allowed"
msgstr "Nur Spiele während der Veranstaltung zählen." msgstr "Nur Spiele während der Veranstaltung zählen."
#: src/mahjong_ranking/models.py:220 #: mahjong_ranking/models.py:227
msgid "Gamescore is lower then 100.000 Pt." msgid "Gamescore is lower then 100.000 Pt."
msgstr "Spielstand ist weniger als 100.000 Punkte" msgstr "Spielstand ist weniger als 100.000 Punkte"
#: src/mahjong_ranking/models.py:222 #: mahjong_ranking/models.py:229
msgid "Gamescore is over 100.000 Pt." msgid "Gamescore is over 100.000 Pt."
msgstr "Spielstand ist über 100.000 Punkte." msgstr "Spielstand ist über 100.000 Punkte."
#: src/mahjong_ranking/models.py:344 #: mahjong_ranking/models.py:353
msgid "Kyū/Dan Ranking" msgid "Kyū/Dan Ranking"
msgstr "Kyū/Dan Wertung" msgstr "Kyū/Dan Wertung"
#: src/mahjong_ranking/models.py:345 #: mahjong_ranking/models.py:354
msgid "Kyū/Dan Rankings" msgid "Kyū/Dan Rankings"
msgstr "Kyū/Dan Wertungen" msgstr "Kyū/Dan Wertungen"
#: src/mahjong_ranking/templates/mahjong_ranking/eventhanchan_list.html:7 #: mahjong_ranking/templates/mahjong_ranking/eventhanchan_list.html:7
msgid "Played Hanchans" msgid "Played Hanchans"
msgstr "Gespielte Hanchans" msgstr "Gespielte Hanchans"
#: src/mahjong_ranking/templates/mahjong_ranking/eventhanchan_list.html:18 #: mahjong_ranking/templates/mahjong_ranking/eventhanchan_list.html:18
#: src/mahjong_ranking/templates/mahjong_ranking/hanchan_confirm_delete.html:15 #: mahjong_ranking/templates/mahjong_ranking/hanchan_confirm_delete.html:15
msgid "Place" msgid "Place"
msgstr "Platz" msgstr "Platz"
#: src/mahjong_ranking/templates/mahjong_ranking/eventhanchan_list.html:21 #: mahjong_ranking/templates/mahjong_ranking/eventhanchan_list.html:21
#: src/mahjong_ranking/templates/mahjong_ranking/hanchan_confirm_delete.html:18 #: mahjong_ranking/templates/mahjong_ranking/hanchan_confirm_delete.html:18
#: src/mahjong_ranking/templates/mahjong_ranking/player_dan_score.html:17 #: mahjong_ranking/templates/mahjong_ranking/player_dan_score.html:17
msgid "Dan Points" msgid "Dan Points"
msgstr "Dan Punkte" msgstr "Dan Punkte"
#: src/mahjong_ranking/templates/mahjong_ranking/eventhanchan_list.html:23 #: mahjong_ranking/templates/mahjong_ranking/eventhanchan_list.html:23
#: src/mahjong_ranking/templates/mahjong_ranking/hanchan_confirm_delete.html:20 #: mahjong_ranking/templates/mahjong_ranking/hanchan_confirm_delete.html:20
#: src/mahjong_ranking/templates/mahjong_ranking/player_invalid_score.html:16 #: mahjong_ranking/templates/mahjong_ranking/player_invalid_score.html:16
#: src/mahjong_ranking/templates/mahjong_ranking/player_kyu_score.html:18 #: mahjong_ranking/templates/mahjong_ranking/player_kyu_score.html:18
msgid "Kyu Points" msgid "Kyu Points"
msgstr "Kyu Punkte" msgstr "Kyu Punkte"
#: src/mahjong_ranking/templates/mahjong_ranking/eventhanchan_list.html:37 #: mahjong_ranking/templates/mahjong_ranking/eventhanchan_list.html:37
#: src/mahjong_ranking/templates/mahjong_ranking/hanchan_confirm_delete.html:4 #: mahjong_ranking/templates/mahjong_ranking/hanchan_confirm_delete.html:4
#: src/mahjong_ranking/templates/mahjong_ranking/hanchan_confirm_delete.html:33 #: mahjong_ranking/templates/mahjong_ranking/hanchan_confirm_delete.html:33
#: src/mahjong_ranking/templates/mahjong_ranking/player_dan_score.html:44 #: mahjong_ranking/templates/mahjong_ranking/player_dan_score.html:44
#: src/mahjong_ranking/templates/mahjong_ranking/player_invalid_score.html:33 #: mahjong_ranking/templates/mahjong_ranking/player_invalid_score.html:33
#: src/mahjong_ranking/templates/mahjong_ranking/player_kyu_score.html:41 #: mahjong_ranking/templates/mahjong_ranking/player_kyu_score.html:41
#: src/mahjong_ranking/templates/mahjong_ranking/player_ladder_score.html:52 #: mahjong_ranking/templates/mahjong_ranking/player_ladder_score.html:52
msgid "Delete Hanchan" msgid "Delete Hanchan"
msgstr "Hanchan löschen" msgstr "Hanchan löschen"
#: src/mahjong_ranking/templates/mahjong_ranking/eventhanchan_list.html:43 #: mahjong_ranking/templates/mahjong_ranking/eventhanchan_list.html:43
#: src/mahjong_ranking/templates/mahjong_ranking/hanchan_form.html:4 #: mahjong_ranking/templates/mahjong_ranking/hanchan_form.html:4
#: src/mahjong_ranking/templates/mahjong_ranking/hanchan_form.html:38 #: mahjong_ranking/templates/mahjong_ranking/hanchan_form.html:14
#: src/mahjong_ranking/templates/mahjong_ranking/player_dan_score.html:47 #: mahjong_ranking/templates/mahjong_ranking/player_dan_score.html:47
#: src/mahjong_ranking/templates/mahjong_ranking/player_invalid_score.html:36 #: mahjong_ranking/templates/mahjong_ranking/player_invalid_score.html:36
#: src/mahjong_ranking/templates/mahjong_ranking/player_kyu_score.html:44 #: mahjong_ranking/templates/mahjong_ranking/player_kyu_score.html:44
#: src/mahjong_ranking/templates/mahjong_ranking/player_ladder_score.html:55 #: mahjong_ranking/templates/mahjong_ranking/player_ladder_score.html:55
msgid "Edit Hanchan" msgid "Edit Hanchan"
msgstr "Hanchan bearbeiten" msgstr "Hanchan bearbeiten"
#: src/mahjong_ranking/templates/mahjong_ranking/eventhanchan_list.html:48 #: mahjong_ranking/templates/mahjong_ranking/eventhanchan_list.html:48
msgid "No Hanchan has been added to this event yet." msgid "No Hanchan has been added to this event yet."
msgstr "Für diese Veranstaltung wurde noch keine Hanchan eingetragen." msgstr "Für diese Veranstaltung wurde noch keine Hanchan eingetragen."
#: src/mahjong_ranking/templates/mahjong_ranking/eventhanchan_list.html:54 #: mahjong_ranking/templates/mahjong_ranking/eventhanchan_list.html:54
#: src/mahjong_ranking/templates/mahjong_ranking/eventranking_list.html:51 #: mahjong_ranking/templates/mahjong_ranking/eventranking_list.html:51
msgid "Edit Event" msgid "Edit Event"
msgstr "Veranstaltung bearbeiten" msgstr "Veranstaltung bearbeiten"
#: src/mahjong_ranking/templates/mahjong_ranking/eventhanchan_list.html:55 #: mahjong_ranking/templates/mahjong_ranking/eventhanchan_list.html:55
#: src/mahjong_ranking/templates/mahjong_ranking/eventranking_list.html:52 #: mahjong_ranking/templates/mahjong_ranking/eventranking_list.html:52
#: src/mahjong_ranking/templates/mahjong_ranking/hanchan_form.html:4 #: mahjong_ranking/templates/mahjong_ranking/hanchan_form.html:4
#: src/mahjong_ranking/templates/mahjong_ranking/hanchan_form.html:38 #: mahjong_ranking/templates/mahjong_ranking/hanchan_form.html:14
msgid "Add Hanchan" msgid "Add Hanchan"
msgstr "Hanchan hinzufügen" msgstr "Hanchan hinzufügen"
#: src/mahjong_ranking/templates/mahjong_ranking/eventranking_list.html:4 #: mahjong_ranking/templates/mahjong_ranking/eventranking_list.html:4
#: src/mahjong_ranking/templates/mahjong_ranking/eventranking_list.html:5 #: mahjong_ranking/templates/mahjong_ranking/eventranking_list.html:5
msgid "Tournament Ranking" msgid "Tournament Ranking"
msgstr "Turnierwertung" msgstr "Turnierwertung"
#: src/mahjong_ranking/templates/mahjong_ranking/eventranking_list.html:12 #: mahjong_ranking/templates/mahjong_ranking/eventranking_list.html:12
#: src/mahjong_ranking/templates/mahjong_ranking/kyudanranking_list.html:26 #: mahjong_ranking/templates/mahjong_ranking/kyudanranking_list.html:30
#: src/mahjong_ranking/templates/mahjong_ranking/seasonranking_list.html:23 #: mahjong_ranking/templates/mahjong_ranking/seasonranking_list.html:23
msgid "Rank" msgid "Rank"
msgstr "Rang" msgstr "Rang"
#: src/mahjong_ranking/templates/mahjong_ranking/eventranking_list.html:13 #: mahjong_ranking/templates/mahjong_ranking/eventranking_list.html:13
#: src/mahjong_ranking/templates/mahjong_ranking/kyudanranking_list.html:13 #: mahjong_ranking/templates/mahjong_ranking/kyudanranking_list.html:17
#: src/mahjong_ranking/templates/mahjong_ranking/seasonranking_list.html:24 #: mahjong_ranking/templates/mahjong_ranking/seasonranking_list.html:24
msgid "Avatar" msgid "Avatar"
msgstr "Avatar" msgstr "Avatar"
#: src/mahjong_ranking/templates/mahjong_ranking/eventranking_list.html:14 #: mahjong_ranking/templates/mahjong_ranking/eventranking_list.html:14
#: src/mahjong_ranking/templates/mahjong_ranking/kyudanranking_list.html:16 #: mahjong_ranking/templates/mahjong_ranking/kyudanranking_list.html:20
#: src/mahjong_ranking/templates/mahjong_ranking/seasonranking_list.html:25 #: mahjong_ranking/templates/mahjong_ranking/seasonranking_list.html:25
msgid "Nickname" msgid "Nickname"
msgstr "Spitzname" msgstr "Spitzname"
#: src/mahjong_ranking/templates/mahjong_ranking/eventranking_list.html:15 #: mahjong_ranking/templates/mahjong_ranking/eventranking_list.html:15
#: src/mahjong_ranking/templates/mahjong_ranking/seasonranking_list.html:26 #: mahjong_ranking/templates/mahjong_ranking/seasonranking_list.html:26
msgid "Name" msgid "Name"
msgstr "Name" msgstr "Name"
#: src/mahjong_ranking/templates/mahjong_ranking/eventranking_list.html:16 #: mahjong_ranking/templates/mahjong_ranking/eventranking_list.html:16
#: src/mahjong_ranking/templates/mahjong_ranking/seasonranking_list.html:27 #: mahjong_ranking/templates/mahjong_ranking/seasonranking_list.html:27
msgid "Average" msgid "Average"
msgstr "Durchschnitt" msgstr "Durchschnitt"
#: src/mahjong_ranking/templates/mahjong_ranking/eventranking_list.html:20 #: mahjong_ranking/templates/mahjong_ranking/eventranking_list.html:20
#: src/mahjong_ranking/templates/mahjong_ranking/player_dan_score.html:15 #: mahjong_ranking/templates/mahjong_ranking/player_dan_score.html:15
#: src/mahjong_ranking/templates/mahjong_ranking/player_invalid_score.html:15 #: mahjong_ranking/templates/mahjong_ranking/player_invalid_score.html:15
#: src/mahjong_ranking/templates/mahjong_ranking/player_kyu_score.html:16 #: mahjong_ranking/templates/mahjong_ranking/player_kyu_score.html:16
#: src/mahjong_ranking/templates/mahjong_ranking/player_ladder_score.html:16 #: mahjong_ranking/templates/mahjong_ranking/player_ladder_score.html:16
#: src/mahjong_ranking/templates/mahjong_ranking/seasonranking_list.html:31 #: mahjong_ranking/templates/mahjong_ranking/seasonranking_list.html:31
msgid "Placement" msgid "Placement"
msgstr "Platzierung" msgstr "Platzierung"
#: src/mahjong_ranking/templates/mahjong_ranking/eventranking_list.html:22 #: mahjong_ranking/templates/mahjong_ranking/eventranking_list.html:22
#: src/mahjong_ranking/templates/mahjong_ranking/seasonranking_list.html:33 #: mahjong_ranking/templates/mahjong_ranking/seasonranking_list.html:33
msgid "count" msgid "count"
msgstr "Anzahl" msgstr "Anzahl"
#: src/mahjong_ranking/templates/mahjong_ranking/eventranking_list.html:23 #: mahjong_ranking/templates/mahjong_ranking/eventranking_list.html:23
#: src/mahjong_ranking/templates/mahjong_ranking/seasonranking_list.html:34 #: mahjong_ranking/templates/mahjong_ranking/seasonranking_list.html:34
msgid "good" msgid "good"
msgstr "gut" msgstr "gut"
#: src/mahjong_ranking/templates/mahjong_ranking/eventranking_list.html:24 #: mahjong_ranking/templates/mahjong_ranking/eventranking_list.html:24
#: src/mahjong_ranking/templates/mahjong_ranking/seasonranking_list.html:35 #: mahjong_ranking/templates/mahjong_ranking/seasonranking_list.html:35
msgid "won" msgid "won"
msgstr "gewonnen" msgstr "gewonnen"
#: src/mahjong_ranking/templates/mahjong_ranking/hanchan_confirm_delete.html:39 #: mahjong_ranking/templates/mahjong_ranking/hanchan_confirm_delete.html:39
msgid "Cancel" msgid "Cancel"
msgstr "Abbruch" msgstr "Abbruch"
#: src/mahjong_ranking/templates/mahjong_ranking/hanchan_confirm_delete.html:40 #: mahjong_ranking/templates/mahjong_ranking/hanchan_confirm_delete.html:40
msgid "Delete" msgid "Delete"
msgstr "Löschen" msgstr "Löschen"
#: src/mahjong_ranking/templates/mahjong_ranking/hanchan_form.html:42 #: mahjong_ranking/templates/mahjong_ranking/hanchan_form.html:18
msgid "Player" msgid "Player"
msgstr "Spieler" msgstr "Spieler"
#: src/mahjong_ranking/templates/mahjong_ranking/hanchan_form.html:82 #: mahjong_ranking/templates/mahjong_ranking/hanchan_form.html:58
msgid "Total" msgid "Total"
msgstr "Total" msgstr "Total"
#: src/mahjong_ranking/templates/mahjong_ranking/hanchan_form.html:94 #: mahjong_ranking/templates/mahjong_ranking/hanchan_form.html:71
msgid "back" msgid "back"
msgstr "Zurück" msgstr "Zurück"
#: src/mahjong_ranking/templates/mahjong_ranking/hanchan_form.html:95 #: mahjong_ranking/templates/mahjong_ranking/hanchan_form.html:72
msgid "save" msgid "save"
msgstr "Speichern" msgstr "Speichern"
#: src/mahjong_ranking/templates/mahjong_ranking/kyudanranking_list.html:4 #: mahjong_ranking/templates/mahjong_ranking/kyudanranking_list.html:4
#: src/mahjong_ranking/templates/mahjong_ranking/kyudanranking_list.html:5 #: mahjong_ranking/templates/mahjong_ranking/kyudanranking_list.html:9
msgid "Player List" msgid "Player List"
msgstr "Spieler Liste" msgstr "Spieler Liste"
#: src/mahjong_ranking/templates/mahjong_ranking/kyudanranking_list.html:21 #: mahjong_ranking/templates/mahjong_ranking/kyudanranking_list.html:25
msgid "Full Name" msgid "Full Name"
msgstr "Voller Name" msgstr "Voller Name"
#: src/mahjong_ranking/templates/mahjong_ranking/kyudanranking_list.html:36 #: mahjong_ranking/templates/mahjong_ranking/kyudanranking_list.html:40
msgid "Games Total" msgid "Games Total"
msgstr "Spiele total" msgstr "Spiele total"
#: src/mahjong_ranking/templates/mahjong_ranking/ladder_redbox.html:3 #: mahjong_ranking/templates/mahjong_ranking/ladder_redbox.html:3
msgid "Latest Hanchans" msgid "Latest Hanchans"
msgstr "Letzten Hanchans" msgstr "Letzten Hanchans"
#: src/mahjong_ranking/templates/mahjong_ranking/ladder_redbox.html:15 #: mahjong_ranking/templates/mahjong_ranking/ladder_redbox.html:15
msgid "Latest Events" msgid "Latest Events"
msgstr "Letzte Veranstaltungen" msgstr "Letzte Veranstaltungen"
#: src/mahjong_ranking/templates/mahjong_ranking/ladder_redbox.html:27 #: mahjong_ranking/templates/mahjong_ranking/ladder_redbox.html:27
msgid "Ladder Archive" msgid "Ladder Archive"
msgstr "Ladder Archiv" msgstr "Ladder Archiv"
#: src/mahjong_ranking/templates/mahjong_ranking/player_dan_score.html:4 #: mahjong_ranking/templates/mahjong_ranking/player_dan_score.html:4
#: src/mahjong_ranking/templates/mahjong_ranking/player_dan_score.html:5 #: mahjong_ranking/templates/mahjong_ranking/player_dan_score.html:5
msgid "Dan Score for" msgid "Dan Score for"
msgstr "Dan Wertung für" msgstr "Dan Wertung für"
#: src/mahjong_ranking/templates/mahjong_ranking/player_dan_score.html:8 #: mahjong_ranking/templates/mahjong_ranking/player_dan_score.html:8
msgid "Hanchans that apply to the Dan Score" msgid "Hanchans that apply to the Dan Score"
msgstr "Hanchans welche zur Dan Wertung zählen" msgstr "Hanchans welche zur Dan Wertung zählen"
#: src/mahjong_ranking/templates/mahjong_ranking/player_dan_score.html:12 #: mahjong_ranking/templates/mahjong_ranking/player_dan_score.html:12
#: src/mahjong_ranking/templates/mahjong_ranking/player_kyu_score.html:13 #: mahjong_ranking/templates/mahjong_ranking/player_kyu_score.html:13
#: src/mahjong_ranking/templates/mahjong_ranking/player_ladder_score.html:13 #: mahjong_ranking/templates/mahjong_ranking/player_ladder_score.html:13
msgid "Date" msgid "Date"
msgstr "Datum" msgstr "Datum"
#: src/mahjong_ranking/templates/mahjong_ranking/player_dan_score.html:13 #: mahjong_ranking/templates/mahjong_ranking/player_dan_score.html:13
#: src/mahjong_ranking/templates/mahjong_ranking/player_invalid_score.html:12 #: mahjong_ranking/templates/mahjong_ranking/player_invalid_score.html:12
#: src/mahjong_ranking/templates/mahjong_ranking/player_kyu_score.html:14 #: mahjong_ranking/templates/mahjong_ranking/player_kyu_score.html:14
#: src/mahjong_ranking/templates/mahjong_ranking/player_ladder_score.html:14 #: mahjong_ranking/templates/mahjong_ranking/player_ladder_score.html:14
msgid "Event" msgid "Event"
msgstr "Veranstaltung" msgstr "Veranstaltung"
#: src/mahjong_ranking/templates/mahjong_ranking/player_dan_score.html:16 #: mahjong_ranking/templates/mahjong_ranking/player_dan_score.html:16
#: src/mahjong_ranking/templates/mahjong_ranking/player_invalid_score.html:14 #: mahjong_ranking/templates/mahjong_ranking/player_invalid_score.html:14
#: src/mahjong_ranking/templates/mahjong_ranking/player_kyu_score.html:17 #: mahjong_ranking/templates/mahjong_ranking/player_kyu_score.html:17
#: src/mahjong_ranking/templates/mahjong_ranking/player_ladder_score.html:17 #: mahjong_ranking/templates/mahjong_ranking/player_ladder_score.html:17
msgid "Players" msgid "Players"
msgstr "Spieler" msgstr "Spieler"
#: src/mahjong_ranking/templates/mahjong_ranking/player_invalid_score.html:4 #: mahjong_ranking/templates/mahjong_ranking/player_invalid_score.html:4
#: src/mahjong_ranking/templates/mahjong_ranking/player_invalid_score.html:6 #: mahjong_ranking/templates/mahjong_ranking/player_invalid_score.html:6
msgid "Unconfirmed Hanchans from" msgid "Unconfirmed Hanchans from"
msgstr "Nicht bestätigte Hanchans von" msgstr "Nicht bestätigte Hanchans von"
#: src/mahjong_ranking/templates/mahjong_ranking/player_invalid_score.html:9 #: mahjong_ranking/templates/mahjong_ranking/player_invalid_score.html:9
msgid "Invalid hanchans with" msgid "Invalid hanchans with"
msgstr "Ungültige Hanchans mit" msgstr "Ungültige Hanchans mit"
#: src/mahjong_ranking/templates/mahjong_ranking/player_kyu_score.html:4 #: mahjong_ranking/templates/mahjong_ranking/player_kyu_score.html:4
#: src/mahjong_ranking/templates/mahjong_ranking/player_kyu_score.html:6 #: mahjong_ranking/templates/mahjong_ranking/player_kyu_score.html:6
msgid "Kyu Score for" msgid "Kyu Score for"
msgstr "Kyu Wertung für" msgstr "Kyu Wertung für"
#: src/mahjong_ranking/templates/mahjong_ranking/player_kyu_score.html:9 #: mahjong_ranking/templates/mahjong_ranking/player_kyu_score.html:9
msgid "Hanchans that apply to the Kyu Score" msgid "Hanchans that apply to the Kyu Score"
msgstr "Hanchans welche zur Kyu Wertung zählen" msgstr "Hanchans welche zur Kyu Wertung zählen"
#: src/mahjong_ranking/templates/mahjong_ranking/player_ladder_score.html:4 #: mahjong_ranking/templates/mahjong_ranking/player_ladder_score.html:4
#: src/mahjong_ranking/templates/mahjong_ranking/player_ladder_score.html:5 #: mahjong_ranking/templates/mahjong_ranking/player_ladder_score.html:5
msgid "Ladder Score for" msgid "Ladder Score for"
msgstr "Ladder Wertung für" msgstr "Ladder Wertung für"
#: src/mahjong_ranking/templates/mahjong_ranking/player_ladder_score.html:8 #: mahjong_ranking/templates/mahjong_ranking/player_ladder_score.html:8
msgid "Hanchans that apply to the Ladder Score" msgid "Hanchans that apply to the Ladder Score"
msgstr "Hanchans welche in der Ladder zählen" msgstr "Hanchans welche in der Ladder zählen"
#: src/mahjong_ranking/templates/mahjong_ranking/player_ladder_score.html:69 #: mahjong_ranking/templates/mahjong_ranking/player_ladder_score.html:69
msgid "Go" msgid "Go"
msgstr "Los" msgstr "Los"
#: src/mahjong_ranking/templates/mahjong_ranking/seasonranking_list.html:11 #: mahjong_ranking/templates/mahjong_ranking/seasonranking_list.html:11
msgid "End" msgid "End"
msgstr "Ende" msgstr "Ende"
#: src/mahjong_ranking/templates/mahjong_ranking/seasonranking_list.html:12 #: mahjong_ranking/templates/mahjong_ranking/seasonranking_list.html:12
msgid "Participants" msgid "Participants"
msgstr "Teilnehmer" msgstr "Teilnehmer"
#: src/mahjong_ranking/views.py:97 #: mahjong_ranking/views.py:97
#, python-format #, python-format
msgid "%s has been updated successfully." msgid "%s has been updated successfully."
msgstr "%s wurde erfolgreich aktualisiert." msgstr "%s wurde erfolgreich aktualisiert."
#: src/mahjong_ranking/views.py:100 #: mahjong_ranking/views.py:100
#, python-format #, python-format
msgid "%s has been added successfully. You can now add a new one." msgid "%s has been added successfully. You can now add a new one."
msgstr "%s wurde erfolgreich hinzugefügt. Du kannst eine neue eintragen." msgstr "%s wurde erfolgreich hinzugefügt. Du kannst eine neue eintragen."
#: src/mahjong_ranking/views.py:118 src/mahjong_ranking/views.py:134 #: mahjong_ranking/views.py:118 mahjong_ranking/views.py:134
msgid "Event does not exist" msgid "Event does not exist"
msgstr "Veranstaltung existiert nicht" msgstr "Veranstaltung existiert nicht"
#: src/mahjong_ranking/views.py:199 #: mahjong_ranking/views.py:199
msgid "No user found matching the name {}" msgid "No user found matching the name {}"
msgstr "Kein Benutzer mit dem Namen %s gefunden" msgstr "Kein Benutzer mit dem Namen %s gefunden"

View File

@@ -10,11 +10,7 @@ from mahjong_ranking.models import SeasonRanking
from openpyxl import Workbook from openpyxl import Workbook
class Command(BaseCommand): def geneate_seasonexcel(json_data):
help = "Exports the SeasonRankings"
def geneate_seasonexcel(self, json_data):
wb = Workbook() wb = Workbook()
worksheet = wb.active worksheet = wb.active
@@ -35,6 +31,11 @@ class Command(BaseCommand):
]) ])
wb.save("sample.xlsx") wb.save("sample.xlsx")
class Command(BaseCommand):
help = "Exports the SeasonRankings"
def handle(self, *args, **options): def handle(self, *args, **options):
season_json = SeasonRanking.objects.json_data() season_json = SeasonRanking.objects.json_data()
self.geneate_seasonexcel(season_json) geneate_seasonexcel(season_json)

View File

@@ -14,4 +14,3 @@ class Command(BaseCommand):
def handle(self, *args, **options): def handle(self, *args, **options):
for ranking in models.KyuDanRanking.objects.all(): for ranking in models.KyuDanRanking.objects.all():
ranking.recalculate() ranking.recalculate()

View File

@@ -42,14 +42,22 @@ class HanchanManager(models.Manager):
season = season or date.today().year season = season or date.today().year
return self.confirmed_hanchans(user=user, season=season) return self.confirmed_hanchans(user=user, season=season)
def user_hanchans(self, user, **kwargs): def user_hanchans(self, user, since=None, **kwargs):
"""
:param user: User Object, or an player_id as integer
:param since: optional a date value since when you want to hanchans
:return:
"""
queryset = self.filter( queryset = self.filter(
models.Q(player1=user) | models.Q(player2=user) | models.Q(player1=user) | models.Q(player2=user) |
models.Q(player3=user) | models.Q(player4=user) models.Q(player3=user) | models.Q(player4=user)
) )
queryset = queryset.select_related().order_by('-start') if since:
queryset = queryset.filter(start__gte=since)
if kwargs: if kwargs:
queryset = queryset.filter(**kwargs) queryset = queryset.filter(**kwargs)
queryset = queryset.select_related().order_by('-start')
for hanchan in queryset: for hanchan in queryset:
hanchan.get_playerdata(user) hanchan.get_playerdata(user)
return queryset return queryset

View File

@@ -20,9 +20,12 @@ class DenormalizationUpdateMiddleware(object):
def process_response(self, request, response): def process_response(self, request, response):
# We only do this in POST request, to speedup the responsetime. # We only do this in POST request, to speedup the responsetime.
if request.method == 'POST': if request.method == 'POST':
print('event_ranking_queue', cache.get('event_ranking_queue', set())) print('event_ranking_queue', cache.get(
print('kyu_dan_ranking_queue', cache.get('kyu_dan_ranking_queue', set())) 'event_ranking_queue', set()))
print('ladder_ranking_queue', cache.get('ladder_ranking_queue', set())) print('kyu_dan_ranking_queue', cache.get(
'kyu_dan_ranking_queue', set()))
print('ladder_ranking_queue', cache.get(
'ladder_ranking_queue', set()))
queue = cache.get('event_ranking_queue', set()) queue = cache.get('event_ranking_queue', set())
if len(queue) > 0: if len(queue) > 0:
self.recalculate_event_rankings(queue) self.recalculate_event_rankings(queue)

View File

@@ -19,21 +19,25 @@ class Migration(migrations.Migration):
migrations.AlterField( migrations.AlterField(
model_name='hanchan', model_name='hanchan',
name='player1_comment', name='player1_comment',
field=models.CharField(verbose_name='Kommentar', max_length=255, editable=False, blank=True), field=models.CharField(
verbose_name='Kommentar', max_length=255, editable=False, blank=True),
), ),
migrations.AlterField( migrations.AlterField(
model_name='hanchan', model_name='hanchan',
name='player2_comment', name='player2_comment',
field=models.CharField(verbose_name='Kommentar', max_length=255, editable=False, blank=True), field=models.CharField(
verbose_name='Kommentar', max_length=255, editable=False, blank=True),
), ),
migrations.AlterField( migrations.AlterField(
model_name='hanchan', model_name='hanchan',
name='player3_comment', name='player3_comment',
field=models.CharField(verbose_name='Kommentar', max_length=255, editable=False, blank=True), field=models.CharField(
verbose_name='Kommentar', max_length=255, editable=False, blank=True),
), ),
migrations.AlterField( migrations.AlterField(
model_name='hanchan', model_name='hanchan',
name='player4_comment', name='player4_comment',
field=models.CharField(verbose_name='Kommentar', max_length=255, editable=False, blank=True), field=models.CharField(
verbose_name='Kommentar', max_length=255, editable=False, blank=True),
), ),
] ]

View File

@@ -19,21 +19,25 @@ class Migration(migrations.Migration):
migrations.AlterField( migrations.AlterField(
model_name='hanchan', model_name='hanchan',
name='player1_comment', name='player1_comment',
field=models.CharField(verbose_name='Anmerkung', max_length=255, editable=False, blank=True), field=models.CharField(
verbose_name='Anmerkung', max_length=255, editable=False, blank=True),
), ),
migrations.AlterField( migrations.AlterField(
model_name='hanchan', model_name='hanchan',
name='player2_comment', name='player2_comment',
field=models.CharField(verbose_name='Anmerkung', max_length=255, editable=False, blank=True), field=models.CharField(
verbose_name='Anmerkung', max_length=255, editable=False, blank=True),
), ),
migrations.AlterField( migrations.AlterField(
model_name='hanchan', model_name='hanchan',
name='player3_comment', name='player3_comment',
field=models.CharField(verbose_name='Anmerkung', max_length=255, editable=False, blank=True), field=models.CharField(
verbose_name='Anmerkung', max_length=255, editable=False, blank=True),
), ),
migrations.AlterField( migrations.AlterField(
model_name='hanchan', model_name='hanchan',
name='player4_comment', name='player4_comment',
field=models.CharField(verbose_name='Anmerkung', max_length=255, editable=False, blank=True), field=models.CharField(
verbose_name='Anmerkung', max_length=255, editable=False, blank=True),
), ),
] ]

View File

@@ -4,7 +4,6 @@
# werden dürfen. # werden dürfen.
from __future__ import division from __future__ import division
from datetime import date
from django.conf import settings from django.conf import settings
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
@@ -371,7 +370,8 @@ class KyuDanRanking(models.Model):
self.wins_in_a_row = 0 self.wins_in_a_row = 0
if self.dan and self.wins_in_a_row >= 3 and self.dan < 9: if self.dan and self.wins_in_a_row >= 3 and self.dan < 9:
logger.info('adding bonuspoints for 3 wins in a row for %s', self.user) logger.info(
'adding bonuspoints for 3 wins in a row for %s', self.user)
new_dan_rank = self.dan + 1 new_dan_rank = self.dan + 1
new_dan_points = DAN_RANKS_DICT[new_dan_rank] + 1 new_dan_points = DAN_RANKS_DICT[new_dan_rank] + 1
bonus_points = new_dan_points - self.dan_points bonus_points = new_dan_points - self.dan_points
@@ -379,7 +379,8 @@ class KyuDanRanking(models.Model):
logger.debug("Stats for %s:", self.user) logger.debug("Stats for %s:", self.user)
logger.debug("current dan_points: %d", self.dan_points) logger.debug("current dan_points: %d", self.dan_points)
logger.debug("current dan: %d", self.dan) logger.debug("current dan: %d", self.dan)
logger.debug("min required points for the next dan: %d", new_dan_points) logger.debug(
"min required points for the next dan: %d", new_dan_points)
logger.debug("bonus points to add: %d", bonus_points) logger.debug("bonus points to add: %d", bonus_points)
hanchan.dan_points += bonus_points hanchan.dan_points += bonus_points
@@ -404,7 +405,8 @@ class KyuDanRanking(models.Model):
).order_by('-start') ).order_by('-start')
last_hanchan_this_event = hanchans_this_event[0] last_hanchan_this_event = hanchans_this_event[0]
if hanchan != last_hanchan_this_event: if hanchan != last_hanchan_this_event:
return False # Das braucht nur am Ende eines Turnieres gemacht werden. # Das braucht nur am Ende eines Turnieres gemacht werden.
return False
else: else:
event_ranking = EventRanking.objects.get( event_ranking = EventRanking.objects.get(
user=self.user, user=self.user,
@@ -462,7 +464,6 @@ class KyuDanRanking(models.Model):
valid_hanchans = valid_hanchans.filter(start__gte=hanchan_start) valid_hanchans = valid_hanchans.filter(start__gte=hanchan_start)
self.hanchan_count += valid_hanchans.count() self.hanchan_count += valid_hanchans.count()
for hanchan in valid_hanchans: for hanchan in valid_hanchans:
logger.debug('Update Kyu/Dan points for Hanchan started on %s', str(hanchan.start))
hanchan.get_playerdata(self.user) hanchan.get_playerdata(self.user)
hanchan.bonus_points = 0 hanchan.bonus_points = 0
hanchan.player_comment = u"" hanchan.player_comment = u""
@@ -476,6 +477,16 @@ class KyuDanRanking(models.Model):
self.good_hanchans += 1 if hanchan.placement == 2 else 0 self.good_hanchans += 1 if hanchan.placement == 2 else 0
hanchan.update_playerdata(self.user) hanchan.update_playerdata(self.user)
hanchan.save(recalculate=False) hanchan.save(recalculate=False)
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) self.save(force_update=True)
def update_hanchan_points(self, hanchan): def update_hanchan_points(self, hanchan):
@@ -543,7 +554,7 @@ class KyuDanRanking(models.Model):
if self.dan_points > min_points: if self.dan_points > min_points:
self.dan = dan_rank self.dan = dan_rank
break break
if old_dan == None or self.dan > old_dan: if old_dan is None or self.dan > old_dan:
self.wins_in_a_row = 0 self.wins_in_a_row = 0
elif self.kyu_points < 1: elif self.kyu_points < 1:
self.kyu_points = 0 self.kyu_points = 0
@@ -622,5 +633,6 @@ def update_ranking(sender, instance, **kwargs):
logger.debug("marking event %s for recalculation.", instance.event) logger.debug("marking event %s for recalculation.", instance.event)
set_dirty(event=instance.event_id, user=user.id) set_dirty(event=instance.event_id, user=user.id)
models.signals.pre_delete.connect(update_ranking, sender=Hanchan) models.signals.pre_delete.connect(update_ranking, sender=Hanchan)
# models.signals.post_save.connect(update_ranking, sender=Hanchan) # models.signals.post_save.connect(update_ranking, sender=Hanchan)

View File

@@ -29,7 +29,10 @@
<label for="season">{% trans 'Season' %}</label> <label for="season">{% trans 'Season' %}</label>
<select id="season" name="season" size="1" onChange="window.location.href = document.season_select.season.options[document.season_select.season.selectedIndex].value;"> <select id="season" name="season" size="1" onChange="window.location.href = document.season_select.season.options[document.season_select.season.selectedIndex].value;">
{% for season_link in season_list%} {% for season_link in season_list%}
<option value="{% url 'mahjong-ladder' season_link %}" {% ifequal season season_link %}selected="selected"{% endifequal %}>{{ season_link }}</option> <option
value="{% url 'mahjong-ladder' season_link %}"
{% ifequal season season_link %} selected="selected"{% endifequal %}>{{ season_link }}
</option>
{% endfor %} {% endfor %}
</select> </select>
</form> </form>

View File

@@ -63,7 +63,9 @@
<label for="season">{% trans 'Season' %}:</label> <label for="season">{% trans 'Season' %}:</label>
<select id="season" name="season" size="1"> <select id="season" name="season" size="1">
{% for season_link in season_list%} {% for season_link in season_list%}
<option value="{{ season_link }}" {% ifequal season season_link %}selected="selected"{% endifequal %}>{{ season_link }}</option> <option
{% ifequal season season_link %} selected="selected"{% endifequal %}
value="{{ season_link }}">{{ season_link }}</option>
{% endfor %} {% endfor %}
</select> </select>
<button type="submit">{% trans 'Go' %}</button> <button type="submit">{% trans 'Go' %}</button>

View File

@@ -4,14 +4,101 @@ when you run "manage.py test".
Replace this with more appropriate tests for your application. Replace this with more appropriate tests for your application.
""" """
import random
from django.test import TestCase from django.test import TestCase
from mahjong_ranking.models import Hanchan, KyuDanRanking
class SimpleTest(TestCase): class KyuDanTest(TestCase):
def test_basic_addition(self):
""" """
Tests that 1 + 1 always equals 2. Unittest to check if the hanchan calculation works against the rulebook.
""" """
self.assertEqual(1 + 1, 2) equal_attrs = ('dan', 'dan_points', 'kyu', 'kyu_points', 'good_hanchans',
'won_hanchans', 'hanchan_count')
fixtures = [
'test_membership.json',
'test_events.json',
'test_hanchans.json',
'test_event_rankings.json',
'test_kyu_dan_rankings.json'
]
def recalc(self):
"""
Test if a Kyu/Dan Ranking recalculation gives the same result as stored.
:return:
"""
for ranking in KyuDanRanking.objects.all():
original = {a: getattr(ranking, a) for a in self.equal_attrs}
ranking.recalculate()
for attr in self.equal_attrs:
self.assertEqual(
original[attr],
getattr(ranking, attr),
"Recalculation was different! user: {user}, attribute: "
"{attr}, original: {original}, recalc: {recalc}".format(
user=ranking.user, attr=attr, original=original[attr],
recalc=getattr(ranking, attr))
)
def test_partial_recalc(self):
"""
Test if partial recalclulation gives the same results.
:return:
"""
for ranking in KyuDanRanking.objects.all():
original = {a: getattr(ranking, a) for a in self.equal_attrs}
confirmed_hanchans = Hanchan.objects.confirmed_hanchans(
user=ranking.user,
since=ranking.legacy_date
)
rnd = random.randrange(confirmed_hanchans.count())
since = confirmed_hanchans[rnd].start
print(since)
ranking.recalculate(hanchan_start=since)
for attr in self.equal_attrs:
self.assertEqual(
original[attr],
getattr(ranking, attr),
"partial Recalculation was different! user: {user}, "
"attribute: {attr}, original: {original}, recalc: "
"{recalc}".format(
user=ranking.user, attr=attr, original=original[attr],
recalc=getattr(ranking, attr))
)
def test_points_sum(self):
"""
Test if the sum of the kyu / dan points equals the value in the Ranking.
:return: None
"""
for ranking in KyuDanRanking.objects.all():
dan_kyu = 'dan_points' if ranking.dan else 'kyu_points'
points = {
'dan_points': ranking.legacy_dan_points or 0,
'kyu_points': ranking.legacy_kyu_points or 0
}
confirmed_hanchans = Hanchan.objects.confirmed_hanchans(
user=ranking.user,
since=ranking.legacy_date
)
for hanchan in confirmed_hanchans:
hanchan.get_playerdata(ranking.user)
points[dan_kyu] += getattr(hanchan, dan_kyu) or 0
self.assertEqual(
points[dan_kyu],
getattr(ranking, dan_kyu),
"{dan_kyu} for {player} won't compute! ranking: {ranking}, sum: {sum}".format(
dan_kyu=dan_kyu, player=ranking.user,
ranking=getattr(ranking, dan_kyu), sum=points[dan_kyu])
)

View File

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

View File

@@ -30,7 +30,6 @@
<tr> <tr>
<td class="center">{{ game.event.start|date:'SHORT_DATE_FORMAT' }}</td> <td class="center">{{ game.event.start|date:'SHORT_DATE_FORMAT' }}</td>
<td><a href="{{ game.get_absolute_url }}">{{ game.event.name }}</a></td> <td><a href="{{ game.get_absolute_url }}">{{ game.event.name }}</a></td>
</td>
{% for player in game.player_list %} {% for player in game.player_list %}
<td class="center"> <td class="center">
<a href="{% url 'maistar-player-games' username=player.user.username season=season %}">{{ player.user.username }}</a> <a href="{% url 'maistar-player-games' username=player.user.username season=season %}">{{ player.user.username }}</a>

View File

@@ -72,7 +72,9 @@
<label for="season">{% trans 'Season' %}</label> <label for="season">{% trans 'Season' %}</label>
<select id="season" name="season" size="1" onChange="window.location.href = document.season_select.season.options[document.season_select.season.selectedIndex].value;"> <select id="season" name="season" size="1" onChange="window.location.href = document.season_select.season.options[document.season_select.season.selectedIndex].value;">
{% for season_link in season_list%} {% for season_link in season_list%}
<option value="{% url 'maistar-ranking' season=season_link %}" {% ifequal season season_link %}selected="selected"{% endifequal %}>{{ season_link }}</option> <option
{% ifequal season season_link %} selected="selected"{% endifequal %}
value="{% url 'maistar-ranking' season=season_link %}" >{{ season_link }}</option>
{% endfor %} {% endfor %}
</select> </select>
</form> </form>

View File

@@ -11,7 +11,8 @@ from django.views import generic
from events.models import Event from events.models import Event
from events.views import EventDetailMixin from events.views import EventDetailMixin
from membership.models import Membership from membership.models import Membership
from utils.mixins import LoginRequiredMixin, PermissionRequiredMixin from utils.mixins import LoginRequiredMixin
from utils.mixins import PermissionRequiredMixin
from mahjong_ranking.views import PlayerScore from mahjong_ranking.views import PlayerScore
from . import forms, models from . import forms, models

View File

@@ -3,15 +3,18 @@ Created on 03.10.2011
@author: Christian @author: Christian
""" """
import sys
from django import forms
from django.conf import settings from django.conf import settings
from django.contrib import auth from django.contrib import auth
from django.contrib.sites.models import Site from django.contrib.sites.models import Site
from django.utils.safestring import mark_safe
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from . import models
from utils.html5 import forms
from utils.massmailer import MassMailer from utils.massmailer import MassMailer
from captcha.fields import ReCaptchaField
from . import models
class MembershipForm(forms.ModelForm): class MembershipForm(forms.ModelForm):
error_css_class = 'error' error_css_class = 'error'
@@ -65,9 +68,11 @@ class RegistrationForm(MembershipForm):
requires the password to be entered twice to catch typos. requires the password to be entered twice to catch typos.
sends an activation request per mail, to validate the eMail. sends an activation request per mail, to validate the eMail.
""" """
password1 = forms.CharField(widget=forms.PasswordInput(), label=_('password')) password1 = forms.CharField(
password2 = forms.CharField(widget=forms.PasswordInput(), label=_('password (again)')) widget=forms.PasswordInput(), label=_('password'))
recaptcha = forms.ReCaptchaField() password2 = forms.CharField(
widget=forms.PasswordInput(), label=_('password (again)'))
recaptcha = ReCaptchaField()
class Meta: class Meta:
model = auth.get_user_model() model = auth.get_user_model()

View File

@@ -7,7 +7,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: kasu.membership\n" "Project-Id-Version: kasu.membership\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2016-09-28 00:25+0200\n" "POT-Creation-Date: 2017-05-10 23:16+0200\n"
"PO-Revision-Date: 2016-09-28 00:24+0200\n" "PO-Revision-Date: 2016-09-28 00:24+0200\n"
"Last-Translator: Christian Berg <xeniac@posteo.at>\n" "Last-Translator: Christian Berg <xeniac@posteo.at>\n"
"Language-Team: Kasu <verein@kasu.at>\n" "Language-Team: Kasu <verein@kasu.at>\n"
@@ -19,164 +19,162 @@ msgstr ""
"X-Translated-Using: django-rosetta 0.7.2\n" "X-Translated-Using: django-rosetta 0.7.2\n"
"X-Generator: Poedit 1.8.9\n" "X-Generator: Poedit 1.8.9\n"
#: src/membership/admin.py:23 #: membership/admin.py:23
msgid "Activate selected User" msgid "Activate selected User"
msgstr "Ausgewählte Benutzer freischalten" msgstr "Ausgewählte Benutzer freischalten"
#: src/membership/admin.py:33 #: membership/admin.py:33
msgid "Cleanup selected Activation Requests" msgid "Cleanup selected Activation Requests"
msgstr "Ausgewählte Aktivierungsanfragen bereinigen" msgstr "Ausgewählte Aktivierungsanfragen bereinigen"
#: src/membership/admin.py:40 #: membership/admin.py:40
msgid "Group" msgid "Group"
msgstr "Gruppe" msgstr "Gruppe"
#: src/membership/admin.py:41 #: membership/admin.py:41
msgid "Groups" msgid "Groups"
msgstr "Gruppen" msgstr "Gruppen"
#: src/membership/admin.py:62 src/membership/models.py:164 #: membership/admin.py:62 membership/models.py:164 membership/models.py:215
#: src/membership/models.py:215 #: membership/templates/membership/register_form.html:31
#: src/membership/templates/membership/register_form.html:32
msgid "Membership" msgid "Membership"
msgstr "Mitgliedschaft" msgstr "Mitgliedschaft"
#: src/membership/admin.py:65 #: membership/admin.py:65
msgid "Permissions" msgid "Permissions"
msgstr "Berechtigung" msgstr "Berechtigung"
#: src/membership/admin.py:67 #: membership/admin.py:67
msgid "Important dates" msgid "Important dates"
msgstr "Wichtige Daten" msgstr "Wichtige Daten"
#: src/membership/forms.py:20 #: membership/forms.py:20
msgid "birthday" msgid "birthday"
msgstr "Geburtstag" msgstr "Geburtstag"
#: src/membership/forms.py:22 #: membership/forms.py:22
msgid "Input format: yyyy-mm-dd" msgid "Input format: yyyy-mm-dd"
msgstr "Eingabeformat: tt.mm.jjjj" msgstr "Eingabeformat: tt.mm.jjjj"
#: src/membership/forms.py:24 #: membership/forms.py:24
msgid "Email" msgid "Email"
msgstr "E-Mail" msgstr "E-Mail"
#: src/membership/forms.py:36 src/membership/forms.py:43 #: membership/forms.py:36 membership/forms.py:43 membership/forms.py:50
#: src/membership/forms.py:50
msgid "" msgid ""
"For your membership, we need this. Please fill out this field " "For your membership, we need this. Please fill out this field "
"yet." "yet."
msgstr "Diese Angabe wird für eine Mitgliedschaft benötigt, bitte ausfüllen." msgstr "Diese Angabe wird für eine Mitgliedschaft benötigt, bitte ausfüllen."
#: src/membership/forms.py:56 #: membership/forms.py:56
msgid "" msgid ""
"For your membership, we need this. Please fill out this field " "For your membership, we need this. Please fill out this field "
"yet." "yet."
msgstr "Diese Angabe wird für eine Mitgliedschaft benötigt, bitte ausfüllen." msgstr "Diese Angabe wird für eine Mitgliedschaft benötigt, bitte ausfüllen."
#: src/membership/forms.py:68 #: membership/forms.py:68
msgid "password" msgid "password"
msgstr "Passwort" msgstr "Passwort"
#: src/membership/forms.py:69 #: membership/forms.py:69
msgid "password (again)" msgid "password (again)"
msgstr "Passwort (wiederholen)" msgstr "Passwort (wiederholen)"
#: src/membership/forms.py:92 #: membership/forms.py:92
msgid "This username is already taken. Please choose another." msgid "This username is already taken. Please choose another."
msgstr "Diesen Benutzername ist schon vergeben. Bitte einen anderen auswählen." msgstr "Diesen Benutzername ist schon vergeben. Bitte einen anderen auswählen."
#: src/membership/forms.py:101 #: membership/forms.py:101
msgid "" msgid ""
"This email address is already in use. Please supply a different " "This email address is already in use. Please supply a different "
"email address." "email address."
msgstr "Die E-Mail Adresse wird schon verwendet. Bitte eine andere angeben." msgstr "Die E-Mail Adresse wird schon verwendet. Bitte eine andere angeben."
#: src/membership/forms.py:110 #: membership/forms.py:110
msgid "The two password fields didn't match." msgid "The two password fields didn't match."
msgstr "Die beiden Passwörter passen nicht." msgstr "Die beiden Passwörter passen nicht."
#: src/membership/models.py:20 #: membership/models.py:20
msgid "Male" msgid "Male"
msgstr "Männlich" msgstr "Männlich"
#: src/membership/models.py:21 #: membership/models.py:21
msgid "Female" msgid "Female"
msgstr "Weiblich" msgstr "Weiblich"
#: src/membership/models.py:90 #: membership/models.py:90
msgid "user" msgid "user"
msgstr "Benutzer" msgstr "Benutzer"
#: src/membership/models.py:92 #: membership/models.py:92
msgid "activation key" msgid "activation key"
msgstr "Aktivierungsschlüssel" msgstr "Aktivierungsschlüssel"
#: src/membership/models.py:96 #: membership/models.py:96
msgid "pending activation" msgid "pending activation"
msgstr "Ausstehende Aktivierung" msgstr "Ausstehende Aktivierung"
#: src/membership/models.py:97 #: membership/models.py:97
msgid "pending activations" msgid "pending activations"
msgstr "Wartende Aktivierungen" msgstr "Wartende Aktivierungen"
#: src/membership/models.py:100 #: membership/models.py:100
#, python-format #, python-format
msgid "user registration for %s" msgid "user registration for %s"
msgstr "Benutzerregistrierung für %s" msgstr "Benutzerregistrierung für %s"
#: src/membership/models.py:147 #: membership/models.py:147
msgid "Gender" msgid "Gender"
msgstr "Geschlecht" msgstr "Geschlecht"
#: src/membership/models.py:166 #: membership/models.py:166
msgid "" msgid ""
"Yes, I confirm that I am in agreement with the statutes and would " "Yes, I confirm that I am in agreement with the statutes and would "
"like to become a member." "like to become a member."
msgstr "Ja, ich bin mit den Statuen einverstanden und möchte Mitglied werden." msgstr "Ja, ich bin mit den Statuen einverstanden und möchte Mitglied werden."
#: src/membership/models.py:170 #: membership/models.py:170
msgid "Birthday Date" msgid "Birthday Date"
msgstr "Geburtstag" msgstr "Geburtstag"
#: src/membership/models.py:174 #: membership/models.py:174
msgid "Telephone" msgid "Telephone"
msgstr "Telefon" msgstr "Telefon"
#: src/membership/models.py:180 #: membership/models.py:180
msgid "Address" msgid "Address"
msgstr "Adresse" msgstr "Adresse"
#: src/membership/models.py:186 #: membership/models.py:186
msgid "Postcode" msgid "Postcode"
msgstr "Postleitzahl" msgstr "Postleitzahl"
#: src/membership/models.py:191 #: membership/models.py:191
msgid "Town/City" msgid "Town/City"
msgstr "Ort" msgstr "Ort"
#: src/membership/models.py:199 #: membership/models.py:199
msgid "Paid until" msgid "Paid until"
msgstr "Bezahlt bis" msgstr "Bezahlt bis"
#: src/membership/models.py:205 #: membership/models.py:205
msgid "Confirmed" msgid "Confirmed"
msgstr "Bestätigt" msgstr "Bestätigt"
#: src/membership/models.py:207 #: membership/models.py:207
msgid "This person has paid the membership fee." msgid "This person has paid the membership fee."
msgstr "Diese Person hat ihre Mitgliedschaft bezahlt" msgstr "Diese Person hat ihre Mitgliedschaft bezahlt"
#: src/membership/models.py:216 #: membership/models.py:216
msgid "Memberships" msgid "Memberships"
msgstr "Mitgliedschaften" msgstr "Mitgliedschaften"
#: src/membership/templates/membership/email/activation_email.txt:2 #: membership/templates/membership/email/activation_email.txt:2
#, python-format #, python-format
msgid "Welcome %(user)s," msgid "Welcome %(user)s,"
msgstr "Herzlich willkommen %(user)s," msgstr "Herzlich willkommen %(user)s,"
#: src/membership/templates/membership/email/activation_email.txt:4 #: membership/templates/membership/email/activation_email.txt:4
#, python-format #, python-format
msgid "" msgid ""
"We received an account request on %(site.domain)s for your email address.\n" "We received an account request on %(site.domain)s for your email address.\n"
@@ -187,7 +185,7 @@ msgstr ""
"Solltest du diesen Account aktivieren wollen, klicke bitte auf den unten " "Solltest du diesen Account aktivieren wollen, klicke bitte auf den unten "
"stehenden Link, oder kopiere diesen in die Adresszeile deines Browsers:" "stehenden Link, oder kopiere diesen in die Adresszeile deines Browsers:"
#: src/membership/templates/membership/email/activation_email.txt:9 #: membership/templates/membership/email/activation_email.txt:9
#, python-format #, python-format
msgid "" msgid ""
"If you do not want to open an account on %(site.domain)s, please ignore this " "If you do not want to open an account on %(site.domain)s, please ignore this "
@@ -198,7 +196,7 @@ msgstr ""
"Mail bitte.\n" "Mail bitte.\n"
"Die Zugangsdaten werden dann in ein paar Tagen automatisch gelöscht." "Die Zugangsdaten werden dann in ein paar Tagen automatisch gelöscht."
#: src/membership/templates/membership/email/activation_email.txt:12 #: membership/templates/membership/email/activation_email.txt:12
#, python-format #, python-format
msgid "" msgid ""
"Best Regards,\n" "Best Regards,\n"
@@ -207,166 +205,166 @@ msgstr ""
"mit den besten Wünschen\n" "mit den besten Wünschen\n"
"Das Team von %(site.domain)s" "Das Team von %(site.domain)s"
#: src/membership/templates/membership/email/password_reset_email.html:2 #: membership/templates/membership/email/password_reset_email.html:2
msgid "You're receiving this e-mail because you requested a password reset" msgid "You're receiving this e-mail because you requested a password reset"
msgstr "" msgstr ""
"Du hast diese E-Mail erhalten, weil jemand die das Passwort zurücksetzen " "Du hast diese E-Mail erhalten, weil jemand die das Passwort zurücksetzen "
"möchte. " "möchte. "
#: src/membership/templates/membership/email/password_reset_email.html:3 #: membership/templates/membership/email/password_reset_email.html:3
#, python-format #, python-format
msgid "for your user account at %(site_name)s" msgid "for your user account at %(site_name)s"
msgstr "Für deinen Benutzerzugang auf %(site_name)s" msgstr "Für deinen Benutzerzugang auf %(site_name)s"
#: src/membership/templates/membership/email/password_reset_email.html:5 #: membership/templates/membership/email/password_reset_email.html:5
msgid "Please go to the following page and choose a new password:" msgid "Please go to the following page and choose a new password:"
msgstr "Bitte gehe auf folgende Seite und wähle ein neues Passwort aus:" msgstr "Bitte gehe auf folgende Seite und wähle ein neues Passwort aus:"
#: src/membership/templates/membership/email/password_reset_email.html:9 #: membership/templates/membership/email/password_reset_email.html:9
msgid "Your username, in case you've forgotten:" msgid "Your username, in case you've forgotten:"
msgstr "Dein Benutzername, für den Fall das du diesen vergessen hast:" msgstr "Dein Benutzername, für den Fall das du diesen vergessen hast:"
#: src/membership/templates/membership/email/password_reset_email.html:11 #: membership/templates/membership/email/password_reset_email.html:11
msgid "Thanks for using our site!" msgid "Thanks for using our site!"
msgstr "Danke das du unsere Seite verwendest!" msgstr "Danke das du unsere Seite verwendest!"
#: src/membership/templates/membership/email/password_reset_email.html:13 #: membership/templates/membership/email/password_reset_email.html:13
#, python-format #, python-format
msgid "The %(site_name)s team" msgid "The %(site_name)s team"
msgstr "Das %(site_name)s Team" msgstr "Das %(site_name)s Team"
#: src/membership/templates/membership/email/password_reset_subject.txt:2 #: membership/templates/membership/email/password_reset_subject.txt:2
#, python-format #, python-format
msgid "Password reset on %(site_name)s" msgid "Password reset on %(site_name)s"
msgstr "Passwort auf %(site_name)s zurücksetzen" msgstr "Passwort auf %(site_name)s zurücksetzen"
#: src/membership/templates/membership/hanchan_table.html:5 #: membership/templates/membership/hanchan_table.html:5
msgid "Start" msgid "Start"
msgstr "Beginn" msgstr "Beginn"
#: src/membership/templates/membership/hanchan_table.html:6 #: membership/templates/membership/hanchan_table.html:6
msgid "Event" msgid "Event"
msgstr "Termin" msgstr "Termin"
#: src/membership/templates/membership/hanchan_table.html:7 #: membership/templates/membership/hanchan_table.html:7
msgid "Players" msgid "Players"
msgstr "Spieler" msgstr "Spieler"
#: src/membership/templates/membership/hanchan_table.html:8 #: membership/templates/membership/hanchan_table.html:8
msgid "Kyu Points" msgid "Kyu Points"
msgstr "Kyū Punkte" msgstr "Kyū Punkte"
#: src/membership/templates/membership/hanchan_table.html:9 #: membership/templates/membership/hanchan_table.html:9
msgid "Dan Points" msgid "Dan Points"
msgstr "Dan Punkte" msgstr "Dan Punkte"
#: src/membership/templates/membership/hanchan_table.html:10 #: membership/templates/membership/hanchan_table.html:10
msgid "Bonus Points" msgid "Bonus Points"
msgstr "Bonus Punkte" msgstr "Bonus Punkte"
#: src/membership/templates/membership/hanchan_table.html:11 #: membership/templates/membership/hanchan_table.html:11
msgid "Comment" msgid "Comment"
msgstr "Anmerkung" msgstr "Anmerkung"
#: src/membership/templates/membership/hanchan_table.html:26 #: membership/templates/membership/hanchan_table.html:26
msgid "This Hanchan does not validate" msgid "This Hanchan does not validate"
msgstr "Diese Hanchan ist ungültig" msgstr "Diese Hanchan ist ungültig"
#: src/membership/templates/membership/membership_detail.html:6 #: membership/templates/membership/membership_detail.html:6
msgid "profile for" msgid "profile for"
msgstr "Profil für" msgstr "Profil für"
#: src/membership/templates/membership/membership_detail.html:10 #: membership/templates/membership/membership_detail.html:10
msgid "Ladder Hanchans" msgid "Ladder Hanchans"
msgstr "Ladder Hanchans" msgstr "Ladder Hanchans"
#: src/membership/templates/membership/membership_detail.html:11 #: membership/templates/membership/membership_detail.html:11
msgid "Kyu Hanchans" msgid "Kyu Hanchans"
msgstr "Kyū Hanchans" msgstr "Kyū Hanchans"
#: src/membership/templates/membership/membership_detail.html:12 #: membership/templates/membership/membership_detail.html:12
msgid "Dan Hanchans" msgid "Dan Hanchans"
msgstr "Dan Hanchans" msgstr "Dan Hanchans"
#: src/membership/templates/membership/membership_detail.html:13 #: membership/templates/membership/membership_detail.html:13
msgid "Invalid Hanchans" msgid "Invalid Hanchans"
msgstr "Ungültige Hanchans" msgstr "Ungültige Hanchans"
#: src/membership/templates/membership/membership_detail.html:14 #: membership/templates/membership/membership_detail.html:14
msgid "Mai-Star Games" msgid "Mai-Star Games"
msgstr "Mai-Star Spiele" msgstr "Mai-Star Spiele"
#: src/membership/templates/membership/membership_detail.html:20 #: membership/templates/membership/membership_detail.html:20
msgid "Profile Image" msgid "Profile Image"
msgstr "Profilbild" msgstr "Profilbild"
#: src/membership/templates/membership/membership_detail.html:28 #: membership/templates/membership/membership_detail.html:28
msgid "Member Since" msgid "Member Since"
msgstr "Mitglied seit" msgstr "Mitglied seit"
#: src/membership/templates/membership/membership_detail.html:29 #: membership/templates/membership/membership_detail.html:29
msgid "Last Login" msgid "Last Login"
msgstr "Letzte Anmeldung" msgstr "Letzte Anmeldung"
#: src/membership/templates/membership/membership_detail.html:38 #: membership/templates/membership/membership_detail.html:38
#: src/membership/templates/membership/membership_detail.html:40 #: membership/templates/membership/membership_detail.html:40
msgid "Points" msgid "Points"
msgstr "Punkte" msgstr "Punkte"
#: src/membership/templates/membership/membership_detail.html:42 #: membership/templates/membership/membership_detail.html:42
msgid "Games Total" msgid "Games Total"
msgstr "Spiele gesamt" msgstr "Spiele gesamt"
#: src/membership/templates/membership/membership_detail.html:43 #: membership/templates/membership/membership_detail.html:43
#: src/membership/templates/membership/membership_detail.html:45 #: membership/templates/membership/membership_detail.html:45
msgid "Won" msgid "Won"
msgstr "Gewonnen" msgstr "Gewonnen"
#: src/membership/templates/membership/membership_detail.html:43 #: membership/templates/membership/membership_detail.html:43
#: src/membership/templates/membership/membership_detail.html:45 #: membership/templates/membership/membership_detail.html:45
msgid "Good" msgid "Good"
msgstr "Gut" msgstr "Gut"
#: src/membership/templates/membership/membership_detail.html:45 #: membership/templates/membership/membership_detail.html:45
msgid "Current Season" msgid "Current Season"
msgstr "Aktuelle Saison" msgstr "Aktuelle Saison"
#: src/membership/templates/membership/membership_detail.html:55 #: membership/templates/membership/membership_detail.html:55
msgid "Edit Profile" msgid "Edit Profile"
msgstr "Profil bearbeiten" msgstr "Profil bearbeiten"
#: src/membership/templates/membership/membership_detail.html:59 #: membership/templates/membership/membership_detail.html:59
#: src/membership/templates/registration/password_change_form.html:23 #: membership/templates/registration/password_change_form.html:23
msgid "Change Password" msgid "Change Password"
msgstr "Passwort ändern" msgstr "Passwort ändern"
#: src/membership/templates/membership/membership_detail.html:63 #: membership/templates/membership/membership_detail.html:63
#: src/membership/templates/membership/membership_detail.html:67 #: membership/templates/membership/membership_detail.html:67
#: src/membership/templates/membership/membership_detail.html:71 #: membership/templates/membership/membership_detail.html:71
#, python-format #, python-format
msgid "Associate with %(name)s" msgid "Associate with %(name)s"
msgstr "Verbinde mit %(name)s" msgstr "Verbinde mit %(name)s"
#: src/membership/templates/membership/membership_form.html:4 #: membership/templates/membership/membership_form.html:4
#: src/membership/templates/membership/membership_form.html:6 #: membership/templates/membership/membership_form.html:6
#: src/membership/templates/membership/membership_form.html:11 #: membership/templates/membership/membership_form.html:11
msgid "Edit Userprofile" msgid "Edit Userprofile"
msgstr "Profil bearbeiten" msgstr "Profil bearbeiten"
#: src/membership/templates/membership/membership_form.html:15 #: membership/templates/membership/membership_form.html:15
msgid "Reset" msgid "Reset"
msgstr "Zurücksetzen" msgstr "Zurücksetzen"
#: src/membership/templates/membership/membership_form.html:16 #: membership/templates/membership/membership_form.html:16
msgid "Save" msgid "Save"
msgstr "Speichern" msgstr "Speichern"
#: src/membership/templates/membership/register_form.html:5 #: membership/templates/membership/register_form.html:4
#: src/membership/templates/membership/register_form.html:7 #: membership/templates/membership/register_form.html:6
msgid "Registration" msgid "Registration"
msgstr "Registrieren" msgstr "Registrieren"
#: src/membership/templates/membership/register_form.html:9 #: membership/templates/membership/register_form.html:8
msgid "" msgid ""
"After you've provided your account data, you'll receive\n" "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 " " an email asking you to verify your email address. You have to click on "
@@ -380,37 +378,52 @@ msgstr ""
"Bitte klicke auf den Link in dieser E-Mail zur Verifizierung, erst dann ist " "Bitte klicke auf den Link in dieser E-Mail zur Verifizierung, erst dann ist "
"die Anmeldung möglich." "die Anmeldung möglich."
#: src/membership/templates/membership/register_form.html:20 #: membership/templates/membership/register_form.html:19
msgid "name" msgid "name"
msgstr "Name" msgstr "Name"
#: src/membership/templates/membership/register_form.html:26 #: membership/templates/membership/register_form.html:25
#: src/membership/templates/registration/login.html:6 #: membership/templates/registration/login.html:41
#: src/membership/templates/registration/login.html:8
#: src/membership/templates/registration/login.html:37
msgid "login" msgid "login"
msgstr "Anmelden" msgstr "Anmelden"
#: src/membership/templates/membership/register_form.html:39 #: membership/templates/membership/register_form.html:38
msgid "reset" msgid "reset"
msgstr "Zurücksetzen" msgstr "Zurücksetzen"
#: src/membership/templates/membership/register_form.html:41 #: membership/templates/membership/register_form.html:40
#: src/membership/templates/registration/login.html:31 #: membership/templates/registration/login.html:35
msgid "register" msgid "register"
msgstr "Registrieren" msgstr "Registrieren"
#: src/membership/templates/membership/register_successful.html:5 #: membership/templates/membership/register_successful.html:5
#: src/membership/templates/membership/register_successful.html:7 #: membership/templates/membership/register_successful.html:7
#: src/membership/templates/membership/register_successful.html:10 #: membership/templates/membership/register_successful.html:10
msgid "Activation sent" msgid "Activation sent"
msgstr "Aktivierung wurde zugesendet" msgstr "Aktivierung wurde zugesendet"
#: src/membership/templates/registration/login.html:15 #: membership/templates/registration/login.html:4
#: membership/templates/registration/login.html:11
#: membership/templates/registration/login.html:53
#: membership/templates/registration/password_reset_complete.html:13
msgid "Login"
msgstr "Anmelden"
#: membership/templates/registration/login.html:17
msgid "Have you already registered?" msgid "Have you already registered?"
msgstr "Bereits registriert?" msgstr "Bereits registriert?"
#: src/membership/templates/registration/login.html:16 #: membership/templates/registration/login.html:18
#, fuzzy
#| 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 "" msgid ""
"\n" "\n"
" <p>As a registered member you can:</p>\n" " <p>As a registered member you can:</p>\n"
@@ -420,6 +433,7 @@ msgid ""
" <li>apply to a membership to our club</li>\n" " <li>apply to a membership to our club</li>\n"
" <li>club-members have access to our ranking-system</li>\n" " <li>club-members have access to our ranking-system</li>\n"
" </ul>\n" " </ul>\n"
" "
msgstr "" msgstr ""
"\n" "\n"
"<p>Als registrierter auf dieser Seite kannst du:</p>\n" "<p>Als registrierter auf dieser Seite kannst du:</p>\n"
@@ -431,13 +445,21 @@ msgstr ""
"li>\n" "li>\n"
"</ul>\n" "</ul>\n"
#: src/membership/templates/registration/login.html:25 #: membership/templates/registration/login.html:27
#, fuzzy
#| 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 "" msgid ""
"\n" "\n"
" <p>You can register here with your Google, or Facebook account.\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 " " If you don't own such an account, or do not want to use it for\n"
" authentication,\n" " authentication,\n"
" you can fill out our registration form.</p>\n" " you can fill out our registration form.</p>\n"
" "
msgstr "" msgstr ""
"\n" "\n"
"<p>Du kannst dich auch über deinen Facebook, Google, oder Twitter Account " "<p>Du kannst dich auch über deinen Facebook, Google, oder Twitter Account "
@@ -445,52 +467,51 @@ msgstr ""
"Wenn du so etwas nicht besitzt, oder nicht verwenden möchtest, \n" "Wenn du so etwas nicht besitzt, oder nicht verwenden möchtest, \n"
"kannst du auch das Registrierungsformular ausfüllen.</p>\n" "kannst du auch das Registrierungsformular ausfüllen.</p>\n"
#: src/membership/templates/registration/login.html:41 #: membership/templates/registration/login.html:45
msgid "Your username and password didn't match. Please try again." #, fuzzy
#| msgid "Your username and password didn't match. Please try again."
msgid ""
"Your username and password didn't match. Please try\n"
" again."
msgstr "" msgstr ""
"Benutzername und Passwort stimmen nicht überein. Bitte noch einmal versuchen." "Benutzername und Passwort stimmen nicht überein. Bitte noch einmal versuchen."
#: src/membership/templates/registration/login.html:44 #: membership/templates/registration/login.html:50
msgid "Forgot your Password?" msgid "Forgot your Password?"
msgstr "Passwort vergessen?" msgstr "Passwort vergessen?"
#: src/membership/templates/registration/login.html:46 #: membership/templates/registration/login.html:60
#: src/membership/templates/registration/password_reset_complete.html:13
msgid "Login"
msgstr "Anmelden"
#: src/membership/templates/registration/login.html:52
msgid "or login with an existing Account" msgid "or login with an existing Account"
msgstr "oder über einen existierenden Account anmelden" msgstr "oder über einen existierenden Account anmelden"
#: src/membership/templates/registration/login.html:53 #: membership/templates/registration/login.html:63
msgid "Login with Facebook" msgid "Login with Facebook"
msgstr "Über Facebook anmelden" msgstr "Über Facebook anmelden"
#: src/membership/templates/registration/login.html:54 #: membership/templates/registration/login.html:66
msgid "Login with Twitter" msgid "Login with Twitter"
msgstr "Über Twitter anmelden" msgstr "Über Twitter anmelden"
#: src/membership/templates/registration/login.html:55 #: membership/templates/registration/login.html:69
msgid "Login with Google" msgid "Login with Google"
msgstr "Über Google Anmelden" msgstr "Über Google Anmelden"
#: src/membership/templates/registration/password_change_done.html:4 #: membership/templates/registration/password_change_done.html:4
#: src/membership/templates/registration/password_change_done.html:7 #: membership/templates/registration/password_change_done.html:7
msgid "Password change successful" msgid "Password change successful"
msgstr "Benutzerprofil erfolgreich geändert." msgstr "Benutzerprofil erfolgreich geändert."
#: src/membership/templates/registration/password_change_done.html:8 #: membership/templates/registration/password_change_done.html:8
msgid "Your password was changed." msgid "Your password was changed."
msgstr "Passwort geändet" msgstr "Passwort geändet"
#: src/membership/templates/registration/password_change_form.html:4 #: membership/templates/registration/password_change_form.html:4
#: src/membership/templates/registration/password_change_form.html:9 #: membership/templates/registration/password_change_form.html:9
#: src/membership/templates/registration/password_change_form.html:16 #: membership/templates/registration/password_change_form.html:16
msgid "Password change" msgid "Password change"
msgstr "Passwort wechseln" msgstr "Passwort wechseln"
#: src/membership/templates/registration/password_change_form.html:10 #: membership/templates/registration/password_change_form.html:10
msgid "" msgid ""
"Please enter your old password, for security's sake, and then enter your new " "Please enter your old password, for security's sake, and then enter your new "
"password twice so we can verify you typed it in correctly." "password twice so we can verify you typed it in correctly."
@@ -498,23 +519,23 @@ msgstr ""
"Zur Sicherheit bitte altes Passwort einmal und das gewünschte neue Passwort " "Zur Sicherheit bitte altes Passwort einmal und das gewünschte neue Passwort "
"zweimal angeben, so können Tippfehler abgefangen werden." "zweimal angeben, so können Tippfehler abgefangen werden."
#: src/membership/templates/registration/password_reset_complete.html:4 #: membership/templates/registration/password_reset_complete.html:4
#: src/membership/templates/registration/password_reset_complete.html:6 #: membership/templates/registration/password_reset_complete.html:6
#: src/membership/templates/registration/password_reset_complete.html:9 #: membership/templates/registration/password_reset_complete.html:9
msgid "Password reset complete" msgid "Password reset complete"
msgstr "Das Rücksetzen des Passwortes ist abgeschlossen." msgstr "Das Rücksetzen des Passwortes ist abgeschlossen."
#: src/membership/templates/registration/password_reset_complete.html:10 #: membership/templates/registration/password_reset_complete.html:10
msgid "Your password has been set. You may go ahead and log in now." msgid "Your password has been set. You may go ahead and log in now."
msgstr "Das Passwort wurde gesetzt, Du kannst dich nun damit anmelden." msgstr "Das Passwort wurde gesetzt, Du kannst dich nun damit anmelden."
#: src/membership/templates/registration/password_reset_confirm.html:4 #: membership/templates/registration/password_reset_confirm.html:4
#: src/membership/templates/registration/password_reset_confirm.html:6 #: membership/templates/registration/password_reset_confirm.html:6
#: src/membership/templates/registration/password_reset_confirm.html:15 #: membership/templates/registration/password_reset_confirm.html:15
msgid "Enter new password" msgid "Enter new password"
msgstr "Neues Passwort eingeben" msgstr "Neues Passwort eingeben"
#: src/membership/templates/registration/password_reset_confirm.html:12 #: membership/templates/registration/password_reset_confirm.html:12
msgid "" msgid ""
"Please enter your new password twice so we can verify you typed it in " "Please enter your new password twice so we can verify you typed it in "
"correctly." "correctly."
@@ -522,15 +543,15 @@ msgstr ""
"Bitte das Passwort zweimal eingeben, um sicher zu stellen das es korrekt " "Bitte das Passwort zweimal eingeben, um sicher zu stellen das es korrekt "
"eingetippt wurde." "eingetippt wurde."
#: src/membership/templates/registration/password_reset_confirm.html:18 #: membership/templates/registration/password_reset_confirm.html:18
msgid "Change my password" msgid "Change my password"
msgstr "Passwort ändern" msgstr "Passwort ändern"
#: src/membership/templates/registration/password_reset_confirm.html:26 #: membership/templates/registration/password_reset_confirm.html:26
msgid "Password reset unsuccessful" msgid "Password reset unsuccessful"
msgstr "Passwort rücksetzen fehlgeschlagen" msgstr "Passwort rücksetzen fehlgeschlagen"
#: src/membership/templates/registration/password_reset_confirm.html:27 #: membership/templates/registration/password_reset_confirm.html:27
msgid "" msgid ""
"The password reset link was invalid, possibly because it has already been " "The password reset link was invalid, possibly because it has already been "
"used. Please request a new password reset." "used. Please request a new password reset."
@@ -538,22 +559,22 @@ msgstr ""
"Der Link für die Rücksetzung des Passwortes war ungültig, vermutlich wurde " "Der Link für die Rücksetzung des Passwortes war ungültig, vermutlich wurde "
"er schon einmal benutzt. Bitte eine neue Rücksetzung beantragen." "er schon einmal benutzt. Bitte eine neue Rücksetzung beantragen."
#: src/membership/templates/registration/password_reset_done.html:4 #: membership/templates/registration/password_reset_done.html:4
#: src/membership/templates/registration/password_reset_done.html:6 #: membership/templates/registration/password_reset_done.html:6
#: src/membership/templates/registration/password_reset_done.html:12 #: membership/templates/registration/password_reset_done.html:12
msgid "Password reset successful" msgid "Password reset successful"
msgstr "Passwort erfolgreich zurückgesetzt." msgstr "Passwort erfolgreich zurückgesetzt."
#: src/membership/templates/registration/password_reset_form.html:4 #: membership/templates/registration/password_reset_form.html:4
#: src/membership/templates/registration/password_reset_form.html:6 #: membership/templates/registration/password_reset_form.html:6
msgid "Password reset" msgid "Password reset"
msgstr "Passwort zurücksetzen" msgstr "Passwort zurücksetzen"
#: src/membership/templates/registration/password_reset_form.html:21 #: membership/templates/registration/password_reset_form.html:21
msgid "Transmit" msgid "Transmit"
msgstr "Übermitteln" msgstr "Übermitteln"
#: src/membership/views.py:58 #: membership/views.py:58
msgid "" msgid ""
"Activation successful. You can now login anytime with you username " "Activation successful. You can now login anytime with you username "
"and password." "and password."
@@ -561,11 +582,11 @@ msgstr ""
"Die Aktivierung war erfolgreich. Du kannst dich ab jetzt jederzeit mit " "Die Aktivierung war erfolgreich. Du kannst dich ab jetzt jederzeit mit "
"deinem Benutzernamen und Passwort anmelden." "deinem Benutzernamen und Passwort anmelden."
#: src/membership/views.py:78 #: membership/views.py:78
msgid "User Profile changed successfully" msgid "User Profile changed successfully"
msgstr "Benutzerprofil erfolgreich geändert." msgstr "Benutzerprofil erfolgreich geändert."
#: src/membership/views.py:92 #: membership/views.py:92
#, python-format #, python-format
msgid "No %(verbose_name)s found matching the query" msgid "No %(verbose_name)s found matching the query"
msgstr "Kein %(verbose_name)s gefunden welche der Anfrage entspricht" msgstr "Kein %(verbose_name)s gefunden welche der Anfrage entspricht"

View File

@@ -1,7 +1,8 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import unicode_literals from __future__ import unicode_literals
from django.db import models, migrations from django.db import migrations
from django.db import models
import django.contrib.auth.models import django.contrib.auth.models

View File

@@ -1,7 +1,8 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import unicode_literals from __future__ import unicode_literals
from django.db import models, migrations from django.db import migrations
from django.db import models
class Migration(migrations.Migration): class Migration(migrations.Migration):

View File

@@ -1,7 +1,8 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import unicode_literals from __future__ import unicode_literals
from django.db import models, migrations from django.db import migrations
from django.db import models
class Migration(migrations.Migration): class Migration(migrations.Migration):

View File

@@ -1,7 +1,8 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import unicode_literals from __future__ import unicode_literals
from django.db import models, migrations from django.db import migrations
from django.db import models
import membership.models import membership.models
import utils import utils
import easy_thumbnails.fields import easy_thumbnails.fields

View File

@@ -14,6 +14,7 @@ class Migration(migrations.Migration):
migrations.AlterField( migrations.AlterField(
model_name='membership', model_name='membership',
name='gender', name='gender',
field=models.CharField(blank=True, max_length=1, null=True, verbose_name='Geschlecht', choices=[(b'm', 'M\xe4nnlich'), (b'f', 'Weiblich')]), field=models.CharField(blank=True, max_length=1, null=True, verbose_name='Geschlecht', choices=[
(b'm', 'M\xe4nnlich'), (b'f', 'Weiblich')]),
), ),
] ]

View File

@@ -65,8 +65,9 @@ class ActivationManager(models.Manager):
creates a PendingActivation instance with an random activation key. creates a PendingActivation instance with an random activation key.
@param user: the user that requests activation. @param user: the user that requests activation.
""" """
salt = str(random.random()) salt = str(user.pk * random.random()) + \
activation_key = hashlib.sha1(salt + user.username).hexdigest() user.registration_date.isoformat()
activation_key = hashlib.sha1(salt.encode('utf-8')).hexdigest()
return self.create(user=user, activation_key=activation_key) return self.create(user=user, activation_key=activation_key)

View File

@@ -11,7 +11,7 @@
<img class="grid_4" src="" alt="" /> <img class="grid_4" src="" alt="" />
<ul class="grid_8"> <ul class="grid_8">
<li><strong>Der Aktivierungs Link ist nicht korrekt.</strong> Bitte kopiere die ganze Zeile aus der E-Mail in die Adressleiste.</li> <li><strong>Der Aktivierungs Link ist nicht korrekt.</strong> Bitte kopiere die ganze Zeile aus der E-Mail in die Adressleiste.</li>
<li><strong>Der Aktivierungs Schlüssel ist abgelaufen.</strong> Dieser ist nur {{ expiration_days }} Tage gültig.In diesem Fall fülle das <a href="{% url 'membership-register' %}">Registrierungs Formular</a> bitte erneut aus.</p></li> <li><strong>Der Aktivierungs Schlüssel ist abgelaufen.</strong> Dieser ist nur {{ expiration_days }} Tage gültig.In diesem Fall fülle das <a href="{% url 'membership-register' %}">Registrierungs Formular</a> bitte erneut aus.</li>
<li><strong>Der Account wurde schon aktiviert.</strong> Dies passiert sehr schnell, veruche zuerst dich mit deinen Daten <a href="{% url 'login' %}">anzumelden</a>. Sollte das nicht funktionieren, kannst du dir ja mal <a href="{% url 'password_reset' %}">neue Zugangsdaten zuschicken</a> lassen.</li> <li><strong>Der Account wurde schon aktiviert.</strong> Dies passiert sehr schnell, veruche zuerst dich mit deinen Daten <a href="{% url 'login' %}">anzumelden</a>. Sollte das nicht funktionieren, kannst du dir ja mal <a href="{% url 'password_reset' %}">neue Zugangsdaten zuschicken</a> lassen.</li>
<li><strong>Ein obskurer Fehler ist aufgetreten.</strong> In diesem Fall, nimm bitte Kontakt mit dem Webadmin auf.</li> <li><strong>Ein obskurer Fehler ist aufgetreten.</strong> In diesem Fall, nimm bitte Kontakt mit dem Webadmin auf.</li>
</ul> </ul>

View File

@@ -2,6 +2,7 @@
{% load i18n fieldset_extras %} {% load i18n fieldset_extras %}
{% block title %}{% trans "Registration"%}{% endblock %} {% block title %}{% trans "Registration"%}{% endblock %}
{% block teaser%} {% block teaser%}
<h1>{% trans "Registration"%}</h1> <h1>{% trans "Registration"%}</h1>
<div id="teaser_text"> <div id="teaser_text">
@@ -42,4 +43,3 @@
</div> </div>
</form> </form>
{% endblock %} {% endblock %}

View File

@@ -25,6 +25,5 @@
</div> </div>
</form> </form>
</fieldset> </fieldset>
</div>
{% endblock %} {% endblock %}

View File

@@ -4,7 +4,8 @@ Created on 03.10.2011
@author: christian @author: christian
""" """
import django.contrib.auth.views as auth_views import django.contrib.auth.views as auth_views
from django.conf.urls import url, include from django.conf.urls import include
from django.conf.urls import url
from . import views from . import views

View File

@@ -12,10 +12,14 @@ from mahjong_ranking.models import KyuDanRanking, SeasonRanking
from utils import mixins from utils import mixins
from . import forms, models from . import forms, models
RECAPTCHA_CSP = { RECAPTCHA_CSP = {
'SCRIPT_SRC': ['https://www.google.com/recaptcha/', 'https://www.gstatic.com/recaptcha/'], 'SCRIPT_SRC': (
'FRAME_SRC': 'https://www.google.com/recaptcha/', 'https://www.google.com/recaptcha/',
'STYLE_SRC': "'unsafe-inline'" 'https://www.gstatic.com/recaptcha/'
),
'CHILD_SRC': ('https://www.google.com/recaptcha/',)
} }
class ActivateRegistration(generic.DetailView): class ActivateRegistration(generic.DetailView):
""" """
Activates the Registration of an User and logs him in Activates the Registration of an User and logs him in

View File

@@ -11,13 +11,14 @@ from .html_cleaner import HtmlCleaner
from .massmailer import MassMailer from .massmailer import MassMailer
CLEANER = HtmlCleaner()
STATUS_REJECTED, STATUS_WAITING, STATUS_PUBLISHED = -1, 0, 1 STATUS_REJECTED, STATUS_WAITING, STATUS_PUBLISHED = -1, 0, 1
STATUS_CHOICES = ( STATUS_CHOICES = (
(STATUS_REJECTED, _('Rejected')), (STATUS_REJECTED, _('Rejected')),
(STATUS_WAITING, _('Waiting...')), (STATUS_WAITING, _('Waiting...')),
(STATUS_PUBLISHED, _('Published')), (STATUS_PUBLISHED, _('Published')),
) )
cleaner = HtmlCleaner()
class OverwriteStorage(FileSystemStorage): class OverwriteStorage(FileSystemStorage):

View File

@@ -1,59 +0,0 @@
"""
Created on 24.11.2011
@author: christian
"""
import datetime
from django import forms
class DateInput(forms.widgets.DateInput):
input_type = 'date'
attrs = {'class': 'dateinput'}
def __init__(self, attrs=None, **kwargs):
forms.widgets.DateInput.__init__(self,
attrs=attrs,
format='%Y-%m-%d',
**kwargs)
class NumberInput(forms.widgets.Input):
input_type = 'number'
class TimeInput(forms.widgets.Select):
def __init__(self, attrs=None, ):
timeset = datetime.datetime(2000, 1, 1, 0, 0)
choices = [('', '-------')]
while timeset < datetime.datetime(2000, 1, 2, 0, 0):
choices.append((
timeset.strftime('%H:%M:%S'),
timeset.strftime('%H:%M')
))
timeset += datetime.timedelta(minutes=30)
forms.widgets.Select.__init__(self, attrs=attrs, choices=choices)
def render(self, name, value, attrs=None, choices=()):
return forms.widgets.Select.render(self, name, value, attrs=attrs,
choices=choices)
class SplitDateTimeWidget(forms.widgets.MultiWidget):
"""
A Widget that splits datetime input into two <input type="text"> boxes.
"""
def __init__(self, attrs=None, date_format='%Y-%m-%d'):
widgets = (
DateInput(attrs=attrs, format=date_format),
TimeInput(attrs=attrs)
)
super(SplitDateTimeWidget, self).__init__(widgets, attrs)
def decompress(self, value):
if value:
return [value.date(), value.time()]
return [None, None]

View File

@@ -1,23 +0,0 @@
#!/usr/bin/python
from . import base
registry = base.LookupRegistry()
def url_autodiscover():
import copy
from django.conf import settings
from django.utils.importlib import import_module
from django.utils.module_loading import module_has_submodule
for app in settings.INSTALLED_APPS:
mod = import_module(app)
# Attempt to import the app's lookups module.
try:
before_import_registry = copy.copy(registry._registry)
import_module('%s.lookups' % app)
except:
registry._registry = before_import_registry
if module_has_submodule(mod, 'lookups'):
raise

View File

@@ -1,139 +0,0 @@
import re
from django.core.urlresolvers import reverse
from django.core.serializers.json import DjangoJSONEncoder
from django.http import HttpResponse
from django.utils.encoding import smart_text, force_text
try:
import json
except ImportError:
from django.utils import simplejson as json
class LookupBase(object):
@classmethod
def name(cls):
app_name = cls.__module__.split('.')[-2].lower()
class_name = cls.__name__.lower()
name = u'%s-%s' % (app_name, class_name)
return name
@classmethod
def url(cls):
return reverse('autocomplete-lookup', args=[cls.name()])
def get_query(self, request, term):
return []
def get_item_label(self, item):
return smart_text(item)
def get_item_id(self, item):
return smart_text(item)
def get_item_value(self, item):
return smart_text(item)
def get_item(self, value):
return value
def create_item(self, value):
raise NotImplemented()
def format_item(self, item):
return {
'id': self.get_item_id(item),
'value': self.get_item_value(item),
'label': self.get_item_label(item)
}
def results(self, request):
term = request.GET.get('term', '')
raw_data = self.get_query(request, term)
data = []
for item in raw_data:
data.append(self.format_item(item))
content = json.dumps(data, cls=DjangoJSONEncoder, ensure_ascii=False)
return HttpResponse(content, content_type='application/json')
class LookupAlreadyRegistered(Exception):
pass
class LookupNotRegistered(Exception):
pass
class LookupInvalid(Exception):
pass
class LookupRegistry(object):
def __init__(self):
self._registry = {}
def validate(self, lookup):
if not issubclass(lookup, LookupBase):
raise LookupInvalid(u'Registered lookups must inherit from the \
LookupBase class')
def register(self, lookup):
self.validate(lookup)
name = force_text(lookup.name())
if name in self._registry:
raise LookupAlreadyRegistered(u'The name %s is already registered'
% name)
self._registry[name] = lookup
def unregister(self, lookup):
self.validate(lookup)
name = force_text(lookup.name())
if name not in self._registry:
raise LookupNotRegistered(u'The name %s is not registered' % name)
del self._registry[name]
def get(self, key):
return self._registry.get(key, None)
class ModelLookup(LookupBase):
model = None
filters = {}
search_field = ''
def get_query(self, request, term):
qs = self.get_queryset()
if term and self.search_field:
qs = qs.filter(**{self.search_field: term})
return qs
def get_queryset(self):
qs = self.model._default_manager.get_query_set()
if self.filters:
qs = qs.filter(**self.filters)
return qs
def get_item_id(self, item):
return item.pk
def get_item(self, value):
item = None
if value:
try:
item = self.get_queryset().filter(pk=value)[0]
except IndexError:
pass
return item
def create_item(self, value):
data = {}
if self.search_field:
field_name = re.sub(r'__\w+$', '', self.search_field)
if field_name:
data = {field_name: value}
return self.model(**data)

View File

@@ -1,250 +0,0 @@
"""
Created on 08.05.2011
@author: christian
"""
import re
from django.conf import settings
from django.core import validators
from django.core.validators import EMPTY_VALUES
from django.forms import utils, Form, ModelForm, ValidationError
import sys
import django.forms.fields
from django.utils.translation import gettext as _
from . import widgets
class Html5Mixin(object):
def widget_attrs(self, widget):
"""
Overwrites the standard Widget Attributes to add some HTML5 Stuff
:param widget: A Widget Object
"""
attrs = super(Html5Mixin, self).widget_attrs(widget)
if self.required and not isinstance(widget, widgets.CheckboxInput):
attrs['required'] = 'required'
if self.help_text:
attrs['title'] = self.help_text
attrs['placeholder'] = self.help_text
if hasattr(self, 'placeholder'):
attrs['placeholder'] = self.placeholder
if self.accesskey:
attrs['accesskey'] = self.accesskey
return attrs
def __init__(self, *args, **kwargs):
self.accesskey = kwargs.pop('accesskey', None)
super(Html5Mixin, self).__init__(*args, **kwargs)
class AutoCompleteSelectField(Html5Mixin, django.forms.Field):
widget = widgets.AutoCompleteSelectWidget
default_error_messages = {
'invalid_choice': _(u'Select a valid choice. That choice is not one \
of the available choices.'),
}
def __init__(self, lookup_class, *args, **kwargs):
self.lookup_class = lookup_class
self.allow_new = kwargs.pop('allow_new', False)
if not kwargs['widget']:
kwargs['widget'] = self.widget(lookup_class,
allow_new=self.allow_new)
super(AutoCompleteSelectField, self).__init__(*args, **kwargs)
def to_python(self, value):
if value in EMPTY_VALUES:
return None
if isinstance(value, list):
# Input comes from an AutoComplete widget. It's two
# components: text and id
if len(value) != 2:
raise django.forms.ValidationError(
self.error_messages['invalid_choice'])
lookup = self.lookup_class()
if value[1] in EMPTY_VALUES:
if not self.allow_new:
if value[0]:
raise django.forms.ValidationError(
self.error_messages['invalid_choice'])
else:
return None
value = lookup.create_item(value[0])
else:
value = lookup.get_item(value[1])
if value is None:
raise django.forms.ValidationError(
self.error_messages['invalid_choice'])
return value
class AutoComboboxSelectField(AutoCompleteSelectField):
widget = widgets.AutoComboboxSelectWidget
class AutoCompleteSelectMultipleField(Html5Mixin, django.forms.Field):
widget = widgets.AutoCompleteSelectMultipleWidget
default_error_messages = {
'invalid_choice': _(u'Select a valid choice. \
That choice is not one of the available choices.'),
}
def __init__(self, lookup_class, *args, **kwargs):
self.lookup_class = lookup_class
kwargs['widget'] = self.widget(lookup_class)
self.attrs['autofocus'] = 'autofocus'
super(AutoCompleteSelectMultipleField, self).__init__(*args, **kwargs)
def to_python(self, value):
if value in EMPTY_VALUES:
return None
lookup = self.lookup_class()
items = []
for v in value:
if v not in EMPTY_VALUES:
item = lookup.get_item(v)
if item is None:
raise django.forms.ValidationError(
self.error_messages['invalid_choice'])
items.append(item)
return items
class AutoComboboxSelectMultipleField(AutoCompleteSelectMultipleField):
widget = widgets.AutoComboboxSelectMultipleWidget
class BooleanField(Html5Mixin, django.forms.BooleanField):
widget = widgets.CheckboxInput
class CharField(Html5Mixin, django.forms.CharField):
pass
class DateField(Html5Mixin, django.forms.fields.DateField):
placeholder = _('yyyy-mm-dd')
widget = widgets.DateInput
class DateTimeField(Html5Mixin, django.forms.fields.DateTimeField):
widget = widgets.DateTimeInput
class EmailField(Html5Mixin, django.forms.fields.EmailField):
widget = widgets.EmailInput
class FileField(Html5Mixin, django.forms.fields.FileField):
widget = widgets.NumberInput
pass
class FloatField(Html5Mixin, django.forms.fields.FloatField):
pass
class HiddenInput(Html5Mixin, django.forms.HiddenInput):
pass
class IntegerField(Html5Mixin, django.forms.fields.IntegerField):
widget = widgets.NumberInput
def widget_attrs(self, widget):
attrs = super(IntegerField, self).widget_attrs(widget)
if isinstance(widget, widgets.NumberInput):
if self.min_value is not None:
attrs['min'] = self.min_value
if self.max_value is not None:
attrs['max'] = self.max_value
return attrs
class ModelChoiceField(Html5Mixin, django.forms.ModelChoiceField):
pass
class PasswordInput(Html5Mixin, django.forms.PasswordInput):
pass
class PhoneField(Html5Mixin, django.forms.CharField):
widget = widgets.PhoneInput
def __init__(self, regex=None, max_length=None, min_length=None,
error_message=None, *args, **kwargs):
super(PhoneField, self).__init__(max_length, min_length,
*args, **kwargs)
self._set_regex(u'^[0-9+-/ ]+$')
def _get_regex(self):
return self._regex
def _set_regex(self, regex=u'^[0-9+-/ ]+$'):
regex = re.compile(regex, re.UNICODE)
self._regex = regex
if hasattr(self, '_regex_validator') \
and self._regex_validator in self.validators:
self.validators.remove(self._regex_validator)
self._regex_validator = validators.RegexValidator(regex=regex)
self.validators.append(self._regex_validator)
regex = property(_get_regex, _set_regex)
class ReCaptchaField(django.forms.fields.CharField):
def __init__(self, *args, **kwargs):
self.widget = widgets.ReCaptchaInput
self.required = True
super(ReCaptchaField, self).__init__(*args, **kwargs)
def get_remote_ip(self):
f = sys._getframe()
while f:
if 'request' in f.f_locals:
request = f.f_locals['request']
if request:
remote_ip = request.META.get('REMOTE_ADDR', None)
forwarded_ip = request.META.get('HTTP_X_FORWARDED_FOR', None)
return forwarded_ip or remote_ip
f = f.f_back
def clean(self, values):
""" test the google recaptcha"""
import json, urllib, urllib2
url = "https://www.google.com/recaptcha/api/siteverify"
challenge_value = values[0]
data = urllib.urlencode({
'secret': settings.RECAPTCHA_PRIVATE_KEY,
'response': values[1],
'remoteip': self.get_remote_ip()
})
req = urllib2.Request(url, data)
response = urllib2.urlopen(req)
result = json.loads(response.read())
# result["success"] will be True on a success
if not result["success"]:
raise ValidationError(
_(u'Only humans are allowed to submit this form.'))
class RegexField(Html5Mixin, django.forms.RegexField):
pass
class SlugField(Html5Mixin, django.forms.SlugField):
pass
class URLField(Html5Mixin, django.forms.fields.URLField):
widget = widgets.URLInput

View File

@@ -1,148 +0,0 @@
"""
Created on 08.05.2011
@author: christian
"""
from django.db import models
from django.db.models import ForeignKey # @UnusedImport
from django.utils import six
from . import forms, widgets
class BooleanField(models.BooleanField):
def formfield(self, **kwargs):
defaults = {'form_class': forms.BooleanField}
defaults.update(kwargs)
return super(BooleanField, self).formfield(**defaults)
class CharField(models.CharField):
def formfield(self, **kwargs):
defaults = {'form_class': forms.CharField}
defaults.update(kwargs)
return super(CharField, self).formfield(**defaults)
class DateField(models.DateField):
def formfield(self, **kwargs):
defaults = {
'form_class': forms.DateField}
defaults.update(kwargs)
return super(DateField, self).formfield(**defaults)
class DateTimeField(models.DateTimeField):
def formfield(self, **kwargs):
defaults = {'form_class': forms.DateTimeField}
defaults.update(kwargs)
return super(DateTimeField, self).formfield(**defaults)
class EmailField(models.EmailField):
def formfield(self, **kwargs):
defaults = {'form_class': forms.EmailField}
defaults.update(kwargs)
return super(EmailField, self).formfield(**defaults)
class FileField(models.FileField):
def formfield(self, **kwargs):
defaults = {'form_class': forms.FileField}
defaults.update(kwargs)
return super(FileField, self).formfield(**defaults)
class ForeignKey(models.ForeignKey):
def formfield(self, **kwargs):
db = kwargs.pop('using', None)
if isinstance(self.rel.to, six.string_types):
raise ValueError("Cannot create form field for %r yet, because "
"its related model %r has not been loaded yet" %
(self.name, self.rel.to))
queryset = self.rel.to._default_manager.using(db)
queryset = queryset.complex_filter(self.rel.limit_choices_to)
defaults = {
'form_class': forms.ModelChoiceField,
'queryset': queryset,
'to_field_name': self.rel.field_name,
}
defaults.update(kwargs)
return super(ForeignKey, self).formfield(**defaults)
class ImageField(models.ImageField):
pass
class IntegerField(models.IntegerField):
def formfield(self, **kwargs):
defaults = {'form_class': forms.IntegerField}
defaults.update(kwargs)
return super(IntegerField, self).formfield(**defaults)
class NullBooleanField(models.NullBooleanField):
def formfield(self, **kwargs):
defaults = {'form_class': forms.BooleanField}
defaults.update(kwargs)
return super(NullBooleanField, self).formfield(**defaults)
class PositiveSmallIntegerField(models.PositiveSmallIntegerField):
def formfield(self, **kwargs):
defaults = {
'form_class': forms.IntegerField,
'min_value': 0,
'max_value': 32767
}
defaults.update(kwargs)
return super(PositiveSmallIntegerField, self).formfield(**defaults)
class PositiveIntegerField(models.IntegerField):
def formfield(self, **kwargs):
defaults = {
'form_class': forms.IntegerField,
'min_value': 0
}
defaults.update(kwargs)
return super(PositiveIntegerField, self).formfield(**defaults)
class SlugField(models.SlugField):
def formfield(self, **kwargs):
defaults = {'form_class': forms.SlugField}
defaults.update(kwargs)
return super(SlugField, self).formfield(**defaults)
class TextField(models.TextField):
def formfield(self, **kwargs):
defaults = {
'form_class': forms.CharField,
'widget': widgets.Textarea
}
defaults.update(kwargs)
return super(TextField, self).formfield(**defaults)
class URLField(models.URLField):
def formfield(self, **kwargs):
defaults = {'form_class': forms.URLField}
defaults.update(kwargs)
return super(URLField, self).formfield(**defaults)

View File

@@ -1,16 +0,0 @@
"""
Created on 05.08.2011
@author: christian
"""
from django.http import Http404
from . import registry
def get_lookup(request, lookup_name):
lookup_cls = registry.get(lookup_name)
if lookup_cls is None:
raise Http404(u'Lookup %s not found' % lookup_name)
lookup = lookup_cls()
return lookup.results(request)

View File

@@ -1,324 +0,0 @@
"""
Created on 08.05.2011
@author: christian
"""
from django.conf import settings
from django.forms import widgets
from django.forms.utils import flatatt
from django.utils import formats
from django.utils.encoding import force_text
from django.utils.html import conditional_escape
from django.utils.http import urlencode
from django.utils.safestring import mark_safe
class AutoCompleteWidget(widgets.TextInput):
def __init__(self, lookup_class, attrs=None, *args, **kwargs):
self.lookup_class = lookup_class
self.allow_new = kwargs.pop('allow_new', False)
self.qs = {}
super(AutoCompleteWidget, self).__init__(*args, **kwargs)
if attrs is not None:
self.attrs = attrs.copy()
else:
self.attrs = {}
def update_query_parameters(self, qs_dict):
self.qs.update(qs_dict)
def build_attrs(self, extra_attrs=None, **kwargs):
attrs = super(AutoCompleteWidget, self).build_attrs(extra_attrs,
**kwargs) # @IgnorePep8
url = self.lookup_class.url()
if self.qs:
url = '%s?%s' % (url, urlencode(self.qs))
attrs[u'data-selectable-url'] = url
attrs[u'data-selectable-type'] = 'text'
attrs[u'data-selectable-allow-new'] = str(self.allow_new).lower()
return attrs
class AutoComboboxWidget(AutoCompleteWidget):
def build_attrs(self, extra_attrs=None, **kwargs):
attrs = super(AutoComboboxWidget, self).build_attrs(extra_attrs,
**kwargs) # @IgnorePep8
attrs[u'data-selectable-type'] = 'combobox'
return attrs
class DateInput(widgets.DateInput):
input_type = 'date'
attrs = {'class': 'dateinput'}
def __init__(self, attrs=None, date_format='%Y-%m-%d'):
super(DateInput, self).__init__(attrs)
if date_format:
self.format = date_format
self.manual_format = True
else:
self.format = formats.get_format('DATE_INPUT_FORMATS')[0]
self.manual_format = False
class DateTimeInput(widgets.MultiWidget):
"""
A Widget that splits datetime input into two <input type="text"> boxes.
"""
def __init__(self, attrs=None, date_format='%Y-%m-%d',
time_format='%H:%M'): # @IgnorePep8
widgets = (
DateInput(attrs=attrs, date_format=date_format),
TimeInput(attrs=attrs, time_format=time_format)
)
super(DateTimeInput, self).__init__(widgets, attrs)
def decompress(self, value):
if value:
return [value.date(), value.time()]
return [None, None]
class CheckboxInput(widgets.CheckboxInput):
input_type = 'checkbox'
is_checkbox = True
def render(self, name, value, attrs=None):
final_attrs = self.build_attrs(attrs, type='checkbox', name=name)
title = force_text(self.attrs.get('title', ''))
try:
result = self.check_test(value)
except: # Silently catch exceptions
result = False
if result:
final_attrs['checked'] = 'checked'
if not (
value is True or value is False or value is None or value == ''): # @IgnorePep8
# Only add the 'value' attribute if a value is non-empty.
final_attrs['value'] = force_text(value)
return mark_safe(u'<input%s /> %s' % (flatatt(final_attrs), title))
class EmailInput(widgets.TextInput):
input_type = 'email'
class NumberInput(widgets.TextInput):
input_type = 'number'
def __init__(self, attrs=None):
widgets.Input.__init__(self, attrs=attrs)
class PhoneInput(widgets.TextInput):
input_type = 'tel'
class RangeInput(widgets.TextInput):
input_type = 'range'
class ReCaptchaInput(widgets.Widget):
"""
Der HTML Code von Googles ReCaptcha als Form Widget
"""
recaptcha_challenge_name = 'g-recaptcha-response'
recaptcha_response_name = 'g-recaptcha-response'
def render(self, name, value, attrs=None):
html_code = u'''
<script src="https://www.google.com/recaptcha/api.js" async defer></script>
<div class="g-recaptcha" data-sitekey="{public_key}" data-size="compact"></div>
'''.format(public_key=settings.RECAPTCHA_PUBLIC_KEY)
return mark_safe(html_code)
def value_from_datadict(self, data, files, name):
return [data.get(self.recaptcha_challenge_name, None),
data.get(self.recaptcha_response_name, None)]
class SelectableMultiWidget(widgets.MultiWidget):
def update_query_parameters(self, qs_dict):
self.widgets[0].update_query_parameters(qs_dict)
class Textarea(widgets.Widget):
def __init__(self, attrs=None):
# The 'rows' and 'cols' attributes are required for HTML correctness.
default_attrs = {'cols': '40', 'rows': '10'}
if attrs:
default_attrs.update(attrs)
super(Textarea, self).__init__(default_attrs)
def render(self, name, value, attrs=None):
value = '' if value is None else value
final_attrs = self.build_attrs(attrs, name=name)
return mark_safe(u'<textarea%s>%s</textarea>' % (flatatt(final_attrs),
conditional_escape(
force_text(
value))))
class TextInput(widgets.TextInput):
pass
class TimeInput(widgets.TimeInput):
input_type = 'time'
def __init__(self, attrs=None, time_format='%H:%M'):
default_attrs = {'maxlength': 5, 'size': 5}
if attrs:
default_attrs.update(attrs)
super(TimeInput, self).__init__(default_attrs)
if time_format:
self.format = time_format
self.manual_format = True
else:
self.format = formats.get_format('TIME_INPUT_FORMATS')[0]
self.manual_format = False
class URLInput(widgets.Input):
input_type = 'url'
class LookupMultipleHiddenInput(widgets.MultipleHiddenInput):
def __init__(self, lookup_class, *args, **kwargs):
self.lookup_class = lookup_class
super(LookupMultipleHiddenInput, self).__init__(*args, **kwargs)
def render(self, name, value, attrs=None, choices=()):
lookup = self.lookup_class()
value = [] if value is None else value
final_attrs = self.build_attrs(attrs, type=self.input_type, name=name)
id_ = final_attrs.get('id', None)
inputs = []
model = getattr(self.lookup_class, 'model', None)
for i, v in enumerate(value):
item = None
if model and isinstance(v, model):
item = v
v = lookup.get_item_id(item)
input_attrs = dict(value=force_text(v), **final_attrs)
if id_:
# An ID attribute was given. Add a numeric index as a suffix
# so that the inputs don't all have the same ID attribute.
input_attrs['id'] = '%s_%s' % (id_, i)
if v:
item = item or lookup.get_item(v)
input_attrs['title'] = lookup.get_item_value(item)
inputs.append(u'<input%s />' % flatatt(input_attrs))
return mark_safe(u'\n'.join(inputs))
def build_attrs(self, extra_attrs=None, **kwargs):
attrs = super(LookupMultipleHiddenInput, self).build_attrs(extra_attrs,
**kwargs) # @IgnorePep8
attrs[u'data-selectable-type'] = 'hidden-multiple'
return attrs
class AutoCompleteSelectMultipleWidget(SelectableMultiWidget):
def __init__(self, lookup_class, *args, **kwargs):
self.lookup_class = lookup_class
widgets = [
AutoCompleteWidget(lookup_class, allow_new=False,
attrs={u'data-selectable-multiple': 'true'}),
LookupMultipleHiddenInput(lookup_class)
]
super(AutoCompleteSelectMultipleWidget, self).__init__(widgets, *args,
**kwargs) # @IgnorePep8
def value_from_datadict(self, data, files, name):
return self.widgets[1].value_from_datadict(data, files, name + '_1')
def render(self, name, value, attrs=None):
if value and not hasattr(value, '__iter__'):
value = [value]
value = [u'', value]
return super(AutoCompleteSelectMultipleWidget, self).render(name, value,
attrs) # @IgnorePep8
class AutoComboboxSelectMultipleWidget(SelectableMultiWidget):
def __init__(self, lookup_class, *args, **kwargs):
self.lookup_class = lookup_class
widgets = [
AutoComboboxWidget(lookup_class, allow_new=False,
attrs={u'data-selectable-multiple': 'true'}),
LookupMultipleHiddenInput(lookup_class)
]
super(AutoComboboxSelectMultipleWidget, self).__init__(widgets, *args,
**kwargs) # @IgnorePep8
def value_from_datadict(self, data, files, name):
return self.widgets[1].value_from_datadict(data, files, name + '_1')
def render(self, name, value, attrs=None):
if value and not hasattr(value, '__iter__'):
value = [value]
value = [u'', value]
return super(AutoComboboxSelectMultipleWidget, self).render(name, value,
attrs) # @IgnorePep8
class AutoCompleteSelectWidget(SelectableMultiWidget):
def __init__(self, lookup_class, *args, **kwargs):
self.lookup_class = lookup_class
self.allow_new = kwargs.pop('allow_new', False)
widget_set = [
AutoCompleteWidget(lookup_class, allow_new=self.allow_new),
widgets.HiddenInput(attrs={u'data-selectable-type': 'hidden'})
]
super(AutoCompleteSelectWidget, self).__init__(widget_set, *args,
**kwargs) # @IgnorePep8
def decompress(self, value):
if value:
lookup = self.lookup_class()
model = getattr(self.lookup_class, 'model', None)
if model and isinstance(value, model):
item = value
value = lookup.get_item_id(item)
else:
item = lookup.get_item(value)
item_value = lookup.get_item_value(item)
return [item_value, value]
return [None, None]
class AutoComboboxSelectWidget(SelectableMultiWidget):
def __init__(self, lookup_class, *args, **kwargs):
self.lookup_class = lookup_class
self.allow_new = kwargs.pop('allow_new', False)
widget_set = [
AutoComboboxWidget(lookup_class, allow_new=self.allow_new),
widgets.HiddenInput(attrs={u'data-selectable-type': 'hidden'})
]
super(AutoComboboxSelectWidget, self).__init__(widget_set, *args,
**kwargs) # @IgnorePep8
def decompress(self, value):
if value:
lookup = self.lookup_class()
model = getattr(self.lookup_class, 'model', None)
if model and isinstance(value, model):
item = value
value = lookup.get_item_id(item)
else:
item = lookup.get_item(value)
item_value = lookup.get_item_value(item)
return [item_value, value]
return [None, None]

View File

@@ -8,6 +8,10 @@ from bs4 import BeautifulSoup
class HtmlCleaner(object): class HtmlCleaner(object):
"""
Tries to clean up HTML code, to reomve all possibilities of an XSS Attack
and unwanted inline Javascript and CSS.
"""
ACCEPTABLE_ELEMENTS = ['a', 'abbr', 'acronym', 'address', 'area', 'b', ACCEPTABLE_ELEMENTS = ['a', 'abbr', 'acronym', 'address', 'area', 'b',
'big', 'blockquote', 'br', 'button', 'caption', 'big', 'blockquote', 'br', 'button', 'caption',
'center', 'cite', 'center', 'cite',
@@ -23,7 +27,7 @@ class HtmlCleaner(object):
'tfoot', 'th', 'tfoot', 'th',
'thead', 'tr', 'tt', 'u', 'ul', 'var'] 'thead', 'tr', 'tt', 'u', 'ul', 'var']
ACCEPTABELE_ATTRIBUTES = [ ACCEPTABLE_ATTRIBUTES = [
'abbr', 'accept', 'accept-charset', 'accesskey', 'abbr', 'accept', 'accept-charset', 'accesskey',
'action', 'align', 'class', 'alt', 'axis', 'action', 'align', 'class', 'alt', 'axis',
'char', 'charoff', 'charset', 'checked', 'cite', 'clear', 'cols', 'char', 'charoff', 'charset', 'checked', 'cite', 'clear', 'cols',
@@ -35,17 +39,28 @@ class HtmlCleaner(object):
'span', 'src', 'start', 'summary', 'tabindex', 'target', 'title', 'span', 'src', 'start', 'summary', 'tabindex', 'target', 'title',
'type', 'usemap', 'valign', 'value', 'vspace', 'width'] 'type', 'usemap', 'valign', 'value', 'vspace', 'width']
counter = 1
tag_removed = False tag_removed = False
def clean_attributes(self, tag): def clean_attributes(self, tag):
"""
reomves all attributes from an element that arenÄt whitelisted.
:param tag: an BeautifulSoup Tag element that should be scrubbed
:return: None
"""
for attr in list(tag.attrs.keys()): for attr in list(tag.attrs.keys()):
if attr not in self.ACCEPTABELE_ATTRIBUTES: if attr not in self.ACCEPTABLE_ATTRIBUTES:
del tag[attr] del tag[attr]
elif tag[attr].count('script:'): elif tag[attr].count('script:'):
del tag[attr] del tag[attr]
def clean_tag(self, tag): def clean_tag(self, tag):
"""
Removes the entire tag with all its content, when its not on the
whitelist. If the tag is acceptable it will be passed to
clean_attributes
:param tag: BeautifulSoup Tag element that should be scrubbed
:return: None
"""
if tag.name not in self.ACCEPTABLE_ELEMENTS: if tag.name not in self.ACCEPTABLE_ELEMENTS:
tag.extract() # remove the bad ones tag.extract() # remove the bad ones
self.tag_removed = True self.tag_removed = True
@@ -55,12 +70,12 @@ class HtmlCleaner(object):
def clean_html(self, fragment=''): def clean_html(self, fragment=''):
""" """
Reparses and cleans the html from XSS Attacks until it stops changing. Reparses and cleans the html from XSS Attacks until it stops changing.
@param fragment: :param str fragment: HTML Text that should be cleaned up
:return str: scrubbed HTML Text
""" """
while True: while True:
soup = BeautifulSoup(fragment, "html.parser") soup = BeautifulSoup(fragment, "html.parser")
self.tag_removed = False self.tag_removed = False
self.counter += 1
for tag in soup.find_all(True): for tag in soup.find_all(True):
self.clean_tag(tag) self.clean_tag(tag)
fragment = str(soup) fragment = str(soup)

File diff suppressed because it is too large Load Diff

View File

@@ -1 +0,0 @@
#!/usr/bin/python

View File

@@ -1 +0,0 @@
#!/usr/bin/python

View File

@@ -1,239 +0,0 @@
"""
Created on 06.06.2011
@author: christian
"""
import fnmatch
from optparse import make_option
import os
import re
from django.conf import settings
from django.contrib.sites.models import Site
from django.core.management.base import BaseCommand
from django.utils.translation import ugettext as _
class Command(BaseCommand):
"""
classdocs
"""
can_import_settings = True
help = _("Reads raw CSS from stdin, and writes compressed CSS to stdout.")
option_list = BaseCommand.option_list + (
make_option('-w', '--wrap',
type='int',
default=None,
metavar='N',
help="Wrap output to approximately N chars per line."),
)
def remove_comments(self, css):
"""Remove all CSS comment blocks."""
iemac = False
preserve = False
comment_start = css.find("/*")
while comment_start >= 0:
# Preserve comments that look like `/*!...*/`.
# Slicing is used to make sure we don"t get an IndexError.
preserve = css[comment_start + 2:comment_start + 3] == "!"
comment_end = css.find("*/", comment_start + 2)
if comment_end < 0:
if not preserve:
css = css[:comment_start]
break
elif comment_end >= (comment_start + 2):
if css[comment_end - 1] == "\\":
# This is an IE Mac-specific comment; leave this one
# and the following one alone.
comment_start = comment_end + 2
iemac = True
elif iemac:
comment_start = comment_end + 2
iemac = False
elif not preserve:
css = css[:comment_start] + css[comment_end + 2:]
else:
comment_start = comment_end + 2
comment_start = css.find("/*", comment_start)
return css
def remove_unnecessary_whitespace(self, css):
"""Remove unnecessary whitespace characters."""
def pseudoclasscolon(css):
"""
Prevents 'p :link' from becoming 'p:link'.
Translates 'p :link' into 'p ___PSEUDOCLASSCOLON___link'; this is
translated back again later.
"""
regex = re.compile(r"(^|\})(([^\{\:])+\:)+([^\{]*\{)")
match = regex.search(css)
while match:
css = ''.join([
css[:match.start()],
match.group().replace(":", "___PSEUDOCLASSCOLON___"),
css[match.end():]])
match = regex.search(css)
return css
css = pseudoclasscolon(css)
# Remove spaces from before things.
css = re.sub(r"\s+([!{};:>+\(\)\],])", r"\1", css)
# If there is a `@charset`,
# then only allow one, and move to the beginning.
css = re.sub(r"^(.*)(@charset \"[^\"]*\";)", r"\2\1", css)
css = re.sub(r"^(\s*@charset [^;]+;\s*)+", r"\1", css)
# Put the space back in for a few cases, such as `@media screen` and
# `(-webkit-min-device-pixel-ratio:0)`.
css = re.sub(r"\band\(", "and (", css)
# Put the colons back.
css = css.replace('___PSEUDOCLASSCOLON___', ':')
# Remove spaces from after things.
css = re.sub(r"([!{}:;>+\(\[,])\s+", r"\1", css)
return css
def remove_unnecessary_semicolons(self, css):
"""Remove unnecessary semicolons."""
return re.sub(r";+\}", "}", css)
def remove_empty_rules(self, css):
"""Remove empty rules."""
return re.sub(r"[^\}\{]+\{\}", "", css)
def normalize_rgb_colors_to_hex(self, css):
"""Convert `rgb(51,102,153)` to `#336699`."""
regex = re.compile(r"rgb\s*\(\s*([0-9,\s]+)\s*\)")
match = regex.search(css)
while match:
colors = match.group(1).split(",")
hexcolor = '#%.2x%.2x%.2x' % map(int, colors)
css = css.replace(match.group(), hexcolor)
match = regex.search(css)
return css
def condense_zero_units(self, css):
"""Replace `0(px, em, %, etc)` with `0`."""
return re.sub(r"([\s:])(0)(px|em|%|in|cm|mm|pc|pt|ex)", r"\1\2", css)
def condense_multidimensional_zeros(self, css):
"""Replace `:0 0 0 0;`, `:0 0 0;` etc. with `:0;`."""
css = css.replace(":0 0 0 0;", ":0;")
css = css.replace(":0 0 0;", ":0;")
css = css.replace(":0 0;", ":0;")
# Revert `background-position:0;` to the valid `background-position:0
# 0;`.
css = css.replace("background-position:0;", "background-position:0 0;")
return css
def condense_floating_points(self, css):
"""Replace `0.6` with `.6` where possible."""
return re.sub(r"(:|\s)0+\.(\d+)", r"\1.\2", css)
def condense_hex_colors(self, css):
"""Shorten colors from #AABBCC to #ABC where possible."""
regex = re.compile(
r"([^\"'=\s])(\s*)#([0-9a-fA-F])([0-9a-fA-F])([0-9a-fA-F])([0-9a-fA-F])([0-9a-fA-F])([0-9a-fA-F])")
match = regex.search(css)
while match:
first = match.group(3) + match.group(5) + match.group(7)
second = match.group(4) + match.group(6) + match.group(8)
if first.lower() == second.lower():
css = css.replace(match.group(),
match.group(1) + match.group(2) + '#' + first)
match = regex.search(css, match.end() - 3)
else:
match = regex.search(css, match.end())
return css
def condense_whitespace(self, css):
"""Condense multiple adjacent whitespace characters into one."""
return re.sub(r"\s+", " ", css)
def condense_semicolons(self, css):
"""Condense multiple adjacent semicolon characters into one."""
return re.sub(r";;+", ";", css)
def wrap_css_lines(self, css, line_length):
"""Wrap the lines of the given CSS to an approximate length."""
lines = []
line_start = 0
for i, char in enumerate(css):
# It's safe to break after `}` characters.
if char == '}' and (i - line_start >= line_length):
lines.append(css[line_start:i + 1])
line_start = i + 1
if line_start < len(css):
lines.append(css[line_start:])
return '\n'.join(lines)
def compress(self, css, wrap=None):
css = self.remove_comments(css)
css = self.condense_whitespace(css)
# A pseudo class for the Box Model Hack
# (see http://tantek.com/CSS/Examples/boxmodelhack.html)
css = css.replace('"\\"}\\""', "___PSEUDOCLASSBMH___")
css = self.remove_unnecessary_whitespace(css)
css = self.remove_unnecessary_semicolons(css)
css = self.condense_zero_units(css)
css = self.condense_multidimensional_zeros(css)
css = self.condense_floating_points(css)
css = self.normalize_rgb_colors_to_hex(css)
css = self.condense_hex_colors(css)
if wrap is not None:
css = self.wrap_css_lines(css, wrap)
css = css.replace("___PSEUDOCLASSBMH___", '"\\"}\\""')
css = self.condense_semicolons(css)
# A Hack for IE compatiblity
# These crappy browser has issues with AND Statments in @MEDIA Blocks
css = css.replace('and(', 'and (')
return css.strip()
def handle(self, *args, **options):
CSS_ROOT = os.path.join(settings.STATICFILES_DIRS[0], 'css')
for site in Site.objects.all():
css_input = os.path.join(CSS_ROOT, site.domain)
css_output = '%s/%s.css' % (CSS_ROOT,
site.domain.replace('.', '_'))
print("Compressing CSS for {}".format(site.name))
print("Input Dir: {}".format(css_input))
print("Output File: {}".format(css_output))
try:
os.makedirs(css_input)
except OSError:
pass
css = ''
"""Read each .css file in the css_input directory,
and append their content"""
for file_name in fnmatch.filter(sorted(os.listdir(css_input)),
'*.css'):
print('Adding: {}'.format(file_name))
with open(os.path.join(css_input, file_name), 'r') as css_file:
css += css_file.read()
with open(css_output, 'w') as css_file:
css_file.write(self.compress(css))

View File

@@ -1,47 +0,0 @@
"""
Created on 06.06.2011
@author: christian
"""
import fnmatch
import os
from django.conf import settings
from django.contrib.sites.models import Site
from django.core.management.base import BaseCommand
from django.utils.translation import ugettext as _
import jsmin
class Command(BaseCommand):
"""
classdocs
"""
can_import_settings = True
help = _("Reads raw CSS from stdin, and writes compressed CSS to stdout.")
def handle(self, *args, **options):
JS_ROOT = os.path.join(settings.STATICFILES_DIRS[0], 'js')
for site in Site.objects.all():
js_input = os.path.join(JS_ROOT, site.domain)
js_output = '%s/%s.js' % (JS_ROOT, site.domain.replace('.', '_'))
print("Compressing JavaScript for {}".format(site.name))
print("Input Dir: {}".format(js_input))
print("Output File: %s".format(js_output))
try:
os.makedirs(js_input)
except OSError:
pass
output = ''
# Read each .js file in the js_input directory, and append their
# content
js_files = fnmatch.filter(sorted(os.listdir(js_input)), '*.js')
for file_name in js_files:
print(" Adding: {}...".format(file_name))
input_file_name = os.path.join(js_input, file_name)
with open(input_file_name, 'r') as input_file:
output += input_file.read()
with open(js_output, 'w') as output_file:
output_file.write(jsmin.jsmin(output))

View File

@@ -1,47 +0,0 @@
"""
Created on 06.06.2011
@author: christian
"""
import os
import fnmatch
from django.conf import settings
from django.contrib.sites.models import Site
from django.core.management.base import BaseCommand
from django.utils.translation import ugettext as _
class Command(BaseCommand):
"""
classdocs
"""
can_import_settings = True
help = _("Compile SCSS rules.")
def handle(self, *args, **options):
CSS_ROOT = os.path.join(settings.STATICFILES_DIRS[0], 'css')
for site in Site.objects.all():
css_input = os.path.join(CSS_ROOT, site.domain)
css_output = '%s/%s.css' % (CSS_ROOT,
site.domain.replace('.', '_'))
print _("Compressing CSS for %s") % site.name
print "Input Dir: %s" % css_input
print "Output File: %s" % css_output
try:
os.makedirs(css_input)
except OSError:
pass
css = ''
"""Read each .css file in the css_input directory,
and append their content"""
for file_name in fnmatch.filter(sorted(os.listdir(css_input)),
'*.css'):
print ' Adding: %s' % file_name
with open(os.path.join(css_input, file_name), 'r') as css_file:
css += css_file.read()
with open(css_output, 'w') as css_file:
css_file.write(self.compress(css))
# file.write(css)

View File

@@ -2,8 +2,7 @@ import logging
from django.core import mail from django.core import mail
from django.contrib.sites.models import Site from django.contrib.sites.models import Site
from django.conf import settings from django.template import loader
from django.template import loader, Context
from django.contrib.auth import get_user_model from django.contrib.auth import get_user_model
@@ -48,7 +47,7 @@ class MassMailer(object):
if isinstance(recipient, self.USER_MODEL): if isinstance(recipient, self.USER_MODEL):
self.recipients.add(recipient) self.recipients.add(recipient)
else: else:
self.log.warn('%s is not a User Object!', recipient) self.log.warning('%s is not a User Object!', recipient)
def add_recipients(self, user_list): def add_recipients(self, user_list):
for user in user_list: for user in user_list:
@@ -56,10 +55,10 @@ class MassMailer(object):
self.log.debug('Adding %s as Recipient' % user) self.log.debug('Adding %s as Recipient' % user)
self.recipients.add(user) self.recipients.add(user)
else: else:
self.log.warn('%s is not a User Object!', user) self.log.warning('%s is not a User Object!', user)
def process_mails(self): def process_mails(self):
mail_context = Context(self.context) mail_context = self.context
mail_context['site'] = Site.objects.get_current() mail_context['site'] = Site.objects.get_current()
self.txt_template = loader.get_template(self.txt_template) self.txt_template = loader.get_template(self.txt_template)
@@ -103,7 +102,7 @@ class MassMailer(object):
try: try:
mail.send() mail.send()
except: except:
self.log.warn("Mail failed for: %s", mail.to) self.log.warning("Mail failed for: %s", mail.to)
else: else:
self.log.info("Mail sent successful to: %s" % mail.to) self.log.info("Mail sent successful to: %s" % mail.to)
self.close_smtp_connection() self.close_smtp_connection()

View File

@@ -5,7 +5,8 @@ import html_cleaner
class HtmlTestCase(unittest.TestCase): class HtmlTestCase(unittest.TestCase):
known_html = ( known_html = (
('<STRONG>Fett!</STRONG>', '<strong>Fett!</strong>'), ('<STRONG>Fett!</STRONG>', '<strong>Fett!</strong>'),
('<a href="link.html" onclick="do_evil()">Bad Link</a>', '<a href="link.html">Bad Link</a>'), ('<a href="link.html" onclick="do_evil()">Bad Link</a>',
'<a href="link.html">Bad Link</a>'),
('''<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"> ('''<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<HTML><HEAD> <HTML><HEAD>
<META http-equiv=Content-Type content="text/html; charset=iso-8859-1"> <META http-equiv=Content-Type content="text/html; charset=iso-8859-1">
@@ -42,5 +43,6 @@ class HtmlTestCase(unittest.TestCase):
for original, tidy in self.known_html: for original, tidy in self.known_html:
self.assertEqual(cleaner.clean_html(original), tidy) self.assertEqual(cleaner.clean_html(original), tidy)
if __name__ == '__main__': if __name__ == '__main__':
unittest.main() unittest.main()