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

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

View File

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

View File

@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: kasu.content\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"
"Last-Translator: Christian Berg <xeniac@posteo.at>\n"
"Language-Team: Deutsch <>\n"
@@ -20,235 +20,265 @@ msgstr ""
"X-Translated-Using: django-rosetta 0.7.2\n"
"X-Generator: Poedit 1.8.9\n"
#: src/content/feeds.py:14
#: content/feeds.py:14
msgid "Current news from Kasu"
msgstr "Aktuelle Nachrichten von Kasu"
#: src/content/feeds.py:42
#: content/feeds.py:42
msgid "Latest comments on kasu.at"
msgstr "Neueste Kommentare auf Kasu.at "
#: src/content/feeds.py:43
#: content/feeds.py:43
msgid "Kasu - latest comments"
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."
msgstr "Bitte eine PDF Datei für diese PDF Seite hochladen."
#: src/content/models.py:51
#: content/models.py:51
msgid "Headline"
msgstr "Schlagzeile"
#: src/content/models.py:53
#: content/models.py:53
msgid "Content"
msgstr "Inhalt"
#: src/content/models.py:55 src/content/models.py:234
#: src/content/templates/content/article_detail.html:25
#: content/models.py:55 content/models.py:279
#: content/templates/content/article_detail.html:25
msgid "Category"
msgstr "Kategorie"
#: src/content/models.py:56 src/content/models.py:228
#: content/models.py:56 content/models.py:273
msgid "Image"
msgstr "Bild"
#: src/content/models.py:58 src/content/models.py:230
#: content/models.py:58 content/models.py:275
msgid "Slug"
msgstr "Slug"
#: src/content/models.py:60
#: src/content/templates/content/article_detail.html:23
#: content/models.py:60 content/templates/content/article_detail.html:23
msgid "Author"
msgstr "Autor"
#: src/content/models.py:61
#: content/models.py:61
msgid "Status"
msgstr "Status"
#: src/content/models.py:63
#: content/models.py:63
msgid "Created"
msgstr "Erstellt"
#: src/content/models.py:64
#: content/models.py:64
msgid "Modified"
msgstr "Bearbeitet"
#: src/content/models.py:68
#: content/models.py:68
msgid "Article"
msgstr "Artikel"
#: src/content/models.py:69
#: content/models.py:69
msgid "Articles"
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"
msgstr "Ein kurzer Name für den Menüeintrag"
#: src/content/models.py:136 src/content/models.py:139
msgid "This title appears in the HTML header"
msgstr "Der Titel erscheint im HTML Header"
#: content/models.py:137 content/models.py:142
msgid "The page title as you'd like it to be seen by the public"
msgstr ""
#: src/content/models.py:140
#: content/models.py:144
msgid "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"
msgstr "Pfad"
#: src/content/models.py:147
#: content/models.py:165
msgid "Position"
msgstr "Position"
#: src/content/models.py:149
#: content/models.py:170
msgid "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"
msgstr "Kommentare möglich"
#: src/content/models.py:156
#: content/models.py:186
msgid "Template"
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"
msgstr "Seite"
#: src/content/models.py:220
#: content/models.py:265
msgid "Pages"
msgstr "Seiten"
#: src/content/models.py:224 src/content/models.py:225
#: content/models.py:269 content/models.py:270
msgid "Name"
msgstr "Name"
#: src/content/models.py:226 src/content/models.py:227
#: content/models.py:271 content/models.py:272
msgid "Description"
msgstr "Beschreibung"
#: src/content/models.py:235
#: content/models.py:280
msgid "Categories"
msgstr "Kategorien"
#: src/content/templates/content/article_archive.html:5
#: src/content/templates/content/article_archive.html:20
#: content/templates/content/article_archive.html:5
#: content/templates/content/article_archive.html:20
msgid "Article Archive"
msgstr "Nachrichtenarchiv"
#: src/content/templates/content/article_archive.html:35
#: src/content/templates/content/article_archive_month.html:5
#: src/content/templates/content/article_archive_year.html:7
#: content/templates/content/article_archive.html:35
#: content/templates/content/article_archive_month.html:5
#: content/templates/content/article_archive_year.html:7
msgid "Archive"
msgstr "Archiv"
#: src/content/templates/content/article_archive.html:52
#: content/templates/content/article_archive.html:52
msgid "All Categories"
msgstr "Alle Kategorien"
#: src/content/templates/content/article_archive.html:67
#: content/templates/content/article_archive.html:67
msgid "created on"
msgstr "erstellt am"
#: src/content/templates/content/article_archive.html:68
#: content/templates/content/article_archive.html:68
msgid "by"
msgstr "von"
#: src/content/templates/content/article_archive.html:69
#: content/templates/content/article_archive.html:69
msgid "comments"
msgstr "Kommentare"
#: src/content/templates/content/article_archive.html:73
#: content/templates/content/article_archive.html:73
msgid "Read More"
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."
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"
msgstr "neuer Artikel "
#: src/content/templates/content/article_archive_month.html:7
#: content/templates/content/article_archive_month.html:7
msgid "back"
msgstr "Zurück"
#: src/content/templates/content/article_detail.html:24
#: content/templates/content/article_detail.html:24
msgid "Created on"
msgstr "Erstellt am"
#: src/content/templates/content/article_detail.html:36
#: content/templates/content/article_detail.html:36
msgid "share on"
msgstr "Teile auf"
#: src/content/templates/content/article_detail.html:51
#: src/content/templates/content/article_form.html:17 src/content/views.py:88
#: content/templates/content/article_detail.html:51
#: content/templates/content/article_form.html:24 content/views.py:88
msgid "Edit Article"
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"
msgstr "Artikel erstellen"
#: src/content/templates/content/article_form.html:22
#: src/content/templates/content/page_form.html:39
#: src/content/templates/content/page_form.html:46
#: content/templates/content/article_form.html:29
#: content/templates/content/page_form.html:49
#: content/templates/content/page_form.html:56
msgid "German"
msgstr "Deutsch"
#: src/content/templates/content/article_form.html:23
#: src/content/templates/content/page_form.html:40
#: src/content/templates/content/page_form.html:54
#: content/templates/content/article_form.html:30
#: content/templates/content/page_form.html:50
#: content/templates/content/page_form.html:64
msgid "English"
msgstr "Englisch"
#: src/content/templates/content/article_form.html:42
#: src/content/templates/content/page_form.html:63
#: content/templates/content/article_form.html:49
#: content/templates/content/page_form.html:73
msgid "reset"
msgstr "Zurücksetzen"
#: src/content/templates/content/article_form.html:43
#: src/content/templates/content/page_form.html:64
#: content/templates/content/article_form.html:50
#: content/templates/content/page_form.html:74
msgid "save"
msgstr "Speichern"
#: src/content/templates/content/page_form.html:5
#: src/content/templates/content/page_form.html:32
#: content/templates/content/page_form.html:5
#: content/templates/content/page_form.html:42
msgid "Edit Page"
msgstr "Seite bearbeiten"
#: src/content/templates/content/page_form.html:5
#: src/content/templates/content/page_form.html:18
#: src/content/templates/content/page_form.html:32
#: content/templates/content/page_form.html:5
#: content/templates/content/page_form.html:28
#: content/templates/content/page_form.html:42
msgid "Add Page"
msgstr "Seite hinzufügen"
#: src/content/templates/content/page_form.html:17
#: content/templates/content/page_form.html:27
msgid "Edit"
msgstr "Bearbeiten"
#: src/content/templates/content/page_form.html:34
#: content/templates/content/page_form.html:44
msgid "HTML Specific"
msgstr "HTML spezifisch"
#: src/content/views.py:23
#: content/views.py:23
msgid "This Category does not exist."
msgstr "Diese Kategorie existiert nicht."
#: src/content/views.py:157
#: content/views.py:157
#, python-format
msgid "No Page found matching the Path %s"
msgstr "Keine Seite unter dem Pfad %s gefunden"
#: src/content/views.py:172
#: content/views.py:172
#, python-format
msgid "No PDF Document found matching the Path %s"
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+"
#~ msgstr "Auf Google+ teilen"

View File

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

View File

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

View File

@@ -15,46 +15,55 @@ class Migration(migrations.Migration):
migrations.AddField(
model_name='page',
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(
model_name='page',
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(
model_name='article',
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(
model_name='page',
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(
model_name='page',
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(
model_name='page',
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(
model_name='page',
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(
model_name='page',
name='title_de',
field=models.CharField(help_text="The page title as you'd like it to be seen by the public", max_length=255, verbose_name=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(
model_name='page',
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(
model_name='page',
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(
model_name='page',
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(
model_name='page',
name='description_de',
field=models.TextField(verbose_name='search description', blank=True),
field=models.TextField(
verbose_name='search description', blank=True),
),
migrations.AddField(
model_name='page',
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 -*-
from os import path
from ckeditor.fields import RichTextField
from django.conf import settings
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.translation import get_language, ugettext as _
from utils import STATUS_CHOICES, STATUS_WAITING, STATUS_PUBLISHED, \
cleaner
from utils import STATUS_CHOICES, STATUS_WAITING, STATUS_PUBLISHED, CLEANER
CONTENT_CHOICES = (
(0, u'Django View'),
@@ -44,7 +41,10 @@ class ArticleManager(models.Manager):
'author', 'category')
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):
@@ -74,8 +74,8 @@ class Article(models.Model):
self.date_created = timezone.now()
if not self.slug:
self.slug = slugify(self.headline_de)[:50]
self.content_de = cleaner.clean_html(self.content_de)
self.content_en = cleaner.clean_html(self.content_en)
self.content_de = CLEANER.clean_html(self.content_de)
self.content_en = CLEANER.clean_html(self.content_en)
def __str__(self):
return self.headline
@@ -120,6 +120,7 @@ class Page(models.Model):
Ist keine englische Übersetzung vorhanden, wird die deutsche Version
angeboten.
"""
menu_name_de = models.CharField(
max_length=255,
verbose_name='Menü Name',
@@ -143,7 +144,10 @@ class Page(models.Model):
slug = models.SlugField(
verbose_name=_('slug'),
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(
max_length=255,
@@ -169,8 +173,10 @@ class Page(models.Model):
default=STATUS_WAITING,
verbose_name=_('status')
)
description_de = models.TextField(verbose_name=_('search description'), blank=True)
description_en = models.TextField(verbose_name=_('search description'), blank=True)
description_de = models.TextField(
verbose_name=_('search description'), blank=True)
description_en = models.TextField(
verbose_name=_('search description'), blank=True)
content_type = models.IntegerField(
choices=CONTENT_CHOICES,
verbose_name=_('content type'))
@@ -213,26 +219,34 @@ class Page(models.Model):
@property
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
def menu_name(self):
return getattr(self,
"menu_name_%s" % get_language()) or self.menu_name_de
lang_attr = "menu_name_%s" % get_language()
return getattr(self, lang_attr, self.menu_name_de)
@property
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
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):
"""
:return:
"""
if self.parent is None:
self.path = self.slug
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.pdf_de:
@@ -242,8 +256,8 @@ class Page(models.Model):
else:
self.content_type = 0
if self.content_type == 1:
self.content_de = cleaner.clean_html(self.content_de)
self.content_en = cleaner.clean_html(self.content_en)
self.content_de = CLEANER.clean_html(self.content_de)
self.content_en = CLEANER.clean_html(self.content_en)
elif self.content_type == 2 and not self.pdf_de.name:
raise ValidationError(
_(u'Please upload a PDF-File to this PDF-Page.'))

View File

@@ -1,6 +1,7 @@
from django.contrib.sitemaps import Sitemap
from .models import Article, Page
class ArticleSitemap(Sitemap):
changefreq = "never"
priority = 0.6
@@ -17,8 +18,7 @@ class PageSitemap(Sitemap):
priority = 0.4
def items(self):
return Page.objects.all() #filter(status__gt=0)
return Page.objects.all() # filter(status__gt=0)
def lastmod(self, page):
return page.date_modified

View File

@@ -49,10 +49,10 @@
{% block navigation %}
<ul id="navigation">
<li><a href="{{current_top_page.get_absolute_url}}" {% if not active_category %}class="active"{% endif %}>{% trans 'All Categories' %}</a></li>
<li><a href="{{current_top_page.get_absolute_url}}" {% if not active_category %} class="active"{% endif %}>{% trans 'All Categories' %}</a></li>
{% for category in categories %}
<li><a href="{% url 'article-archive' category=category.slug %}"
{% ifequal category.slug active_category.slug %}class="active"{% endifequal %}>{{ category.name }}</a></li>
{% ifequal category.slug active_category.slug %} class="active"{% endifequal %}>{{ category.name }}</a></li>
{% endfor %}
</ul>
{% endblock %}

View File

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

View File

@@ -50,7 +50,7 @@ window.onload = function () {
<li><a href="#en">{% trans "English" %}</a></li>
</ul>
<div class="tab_container"">
<div class="tab_container">
<section id="de" class="tab_content">
<fieldset class="grid_12">
<legend>{% trans "German" %}</legend>
@@ -67,7 +67,7 @@ window.onload = function () {
<br>
{{form.content_en}}
</section>
</div>
</div>
<p></p>
<p class="buttonbar grid_12">
<button type="reset"><span class="fa fa-undo"></span> {% trans 'reset' %}</button>

View File

@@ -60,7 +60,7 @@ body {
</div>
<div id="page_footer">
{{ page.title }} Seite:
<pdf:pagenumber>
<pdf:pagenumber />
</div>
</body>
</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 os
from csp.decorators import csp_update
from django.conf import settings
from django.http import HttpResponse, Http404
from django.utils.decorators import method_decorator
from django.utils.translation import ugettext as _
from django.views import generic
from . import models, forms
from events.models import Photo
from utils.mixins import PermissionRequiredMixin
from . import models, forms
class ArticleArchiveMixin(object):
"""
Mixin View to add common context data to Views of the news article archive.
"""
def get_category(self, queryset):
try:
self.category = models.Category.objects.get(
slug=self.kwargs['category'])
return queryset.filter(category=self.category)
except models.Category.DoesNotExist:
raise Http404(_("This Category does not exist."))
except KeyError:
"""
Filter the queryset by category if one has been specified in the URL
:param queryset: an model.Article.objects Queryset
:return: an model.Article.objects Queryset filterd by category
"""
category_slug = self.kwargs.get('category')
if not category_slug:
self.category = None
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):
"""
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['categories'] = models.Category.objects.all()
context['active_category'] = self.category
@@ -33,6 +50,10 @@ class ArticleArchiveMixin(object):
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)
date_field = 'date_created'
paginate_by = 5
@@ -40,12 +61,21 @@ class ArticleArchiveIndex(ArticleArchiveMixin, generic.ArchiveIndexView):
allow_empty = True
def get_queryset(self):
queryset = generic.ArchiveIndexView.get_queryset(self)
queryset = self.get_category(queryset)
return queryset
"""
Filter the Queryset by category.
:return: models.Article.objects queryset
"""
return self.get_category(
super(ArticleArchiveIndex, self).get_queryset()
)
class ArticleYearArchive(ArticleArchiveMixin, generic.YearArchiveView):
"""
Displays the Articles filterd by a specific year
"""
queryset = models.Article.objects.filter(status=models.STATUS_PUBLISHED)
date_field = 'date_created'
paginate_by = 5
@@ -54,12 +84,21 @@ class ArticleYearArchive(ArticleArchiveMixin, generic.YearArchiveView):
allow_empty = True
def get_queryset(self):
queryset = generic.YearArchiveView.get_queryset(self)
queryset = self.get_category(queryset)
return queryset
"""
Filter the Queryset by category.
:return: models.Article.objects queryset
"""
return self.get_category(
super(ArticleYearArchive, self).get_queryset()
)
class ArticleMonthArchive(ArticleArchiveMixin, generic.MonthArchiveView):
"""
Displays the Articles filterd by a specific month
"""
queryset = models.Article.objects.filter(status=models.STATUS_PUBLISHED)
date_field = 'date_created'
month_format = '%m'
@@ -68,16 +107,28 @@ class ArticleMonthArchive(ArticleArchiveMixin, generic.MonthArchiveView):
allow_empty = True
def get_queryset(self):
queryset = generic.MonthArchiveView.get_queryset(self)
queryset = self.get_category(queryset)
return queryset
"""
Filter the Queryset by category.
:return: models.Article.objects queryset
"""
return self.get_category(
super(ArticleMonthArchive, self).get_queryset()
)
class ArticleDetail(generic.DetailView):
"""
Render the news Article, but only if it got published.
"""
queryset = models.Article.objects.filter(status=models.STATUS_PUBLISHED)
class ArticleForm(PermissionRequiredMixin, generic.UpdateView):
"""
View to add or edit an Article
"""
model = models.Article
form_class = forms.ArticleForm
permission_required = 'content.change_article'
@@ -97,28 +148,11 @@ class ArticleForm(PermissionRequiredMixin, generic.UpdateView):
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):
"""
Renders an Form to create a new page for users with conforming permissions.
"""
form_class = forms.PageForm
template_name = 'content/page_form.html'
permission_required = 'content.add_page'
@@ -132,8 +166,16 @@ class PageAddForm(PermissionRequiredMixin, generic.CreateView):
parent = models.Page.objects.get(path=path)
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):
"""
Renders an Form to edit a page for users with conforming permissions.
"""
form_class = forms.PageForm
permission_required = 'content.change_page'
@@ -145,6 +187,10 @@ class PageEditForm(PermissionRequiredMixin, generic.UpdateView):
path = path[:-1]
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):
@@ -184,23 +230,6 @@ class PagePdf(generic.DeleteView):
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):
template_name = 'index.html'

View File

@@ -13,10 +13,10 @@ def upcoming_events(request):
next_event = cache.get('next_event', False)
upcoming_events = cache.get('upcoming_events', False)
if current_event == False:
if not current_event:
current_event = Event.objects.current_event()
cache.set('current_event', current_event, 360)
if next_event == False:
if not next_event:
next_event = Event.objects.next_event()
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 . import models
from utils.html5.widgets import DateTimeInput
user_query = get_user_model().objects.all()
@@ -47,16 +46,17 @@ class EventForm(forms.ModelForm):
start = forms.DateTimeField(
label=_('start'), required=True,
widget=DateTimeInput() # widget=SplitDateTimeWidget()
widget=forms.SplitHiddenDateTimeWidget()
)
end = forms.DateTimeField(
label=_('end'), required=False,
widget=DateTimeInput() # widget=SplitDateTimeWidget()
widget=forms.SplitDateTimeWidget()
)
class Meta(object):
model = models.Event
exclude = ('event_count', 'event_series', )
EventSeriesFormset = forms.inlineformset_factory(
models.Event, models.Event, fields=('start', 'end'), form=EventForm)

View File

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

View File

@@ -13,12 +13,14 @@ class Migration(migrations.Migration):
operations = [
migrations.AlterModelOptions(
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(
model_name='event',
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(
model_name='photo',
@@ -28,11 +30,13 @@ class Migration(migrations.Migration):
migrations.AlterField(
model_name='photo',
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(
model_name='photo',
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(
model_name='event',
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(
model_name='event',
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,
),
migrations.AddField(
model_name='location',
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(
model_name='location',
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,
),
migrations.AddField(
model_name='photo',
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(
model_name='photo',
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,
),
]

View File

@@ -346,7 +346,6 @@ class Photo(models.Model):
)[0]
except IndexError:
return None
return self.get_next_by_created_date(event=self.event)
@property
def previous_photo(self):
@@ -366,4 +365,3 @@ class Photo(models.Model):
)[0]
except IndexError:
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.utils import timezone
from .models import Event, Photo
from .models import Event
from .models import Photo
class EventSitemap(Sitemap):
changefreq = "never"
priority = 0.6
protocol = 'https'
def items(self):
@@ -13,9 +13,8 @@ class EventSitemap(Sitemap):
def priority(self, event):
delta = timezone.now() - event.start
delta = abs(delta.days / 300.0 )
delta = abs(delta.days / 300.0)
return max(1 - delta, 0.1)
def lastmod(self, event):
return event.date_modified

View File

@@ -12,7 +12,6 @@
<p class="warning">
<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.
</strong>
</p>
{% endif %}
<p class="buttonbar">

View File

@@ -35,7 +35,7 @@
{%trans "Upload" %}</a>
{% endif %}
</p>
</div></div>
</div>
{% endfor %}
<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 import get_user_model
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.shortcuts import redirect
from django.utils import timezone

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,9 @@
# -*- coding: utf-8 -*-
from os import path
gettext = lambda s: s
def gettext(s): return s
ADMINS = (('Max Mustermann', 'email@example.com'),)
ALLOWED_HOSTS = ['.kasu.at']
@@ -39,6 +41,7 @@ PREREQ_APPS = [
'django.contrib.sitemaps',
'django.contrib.staticfiles',
'django_comments',
'captcha',
'ckeditor',
'ckeditor_uploader',
'easy_thumbnails',
@@ -95,7 +98,7 @@ TEMPLATES = [
'django.contrib.messages.context_processors.messages',
'django.contrib.messages.context_processors.messages',
'events.context_processors.upcoming_events',
'social_django.context_processors.backends',
'social_django.context_processors.backends',
'social_django.context_processors.login_redirect'
],
'loaders': [
@@ -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_AGE = 15768000 # Session dauer: 4 Wochen
SESSION_COOKIE_SECURE = True
@@ -169,21 +162,21 @@ CKEDITOR_CONFIGS = {'default': {
'width': '100%',
'extraPlugins': 'divarea',
'toolbarGroups': """[
{ name: 'clipboard', groups: [ 'clipboard', 'undo' ] },
{ name: 'editing', groups: [ 'find', 'selection', 'spellchecker' ] },
{ name: 'clipboard', groups: [ 'clipboard', 'undo' ] },
{ name: 'editing', groups: [ 'find', 'selection', 'spellchecker' ] },
{ name: 'links' },
{ name: 'insert' },
{ name: 'forms' },
{ name: 'tools' },
{ name: 'document', groups: [ 'mode', 'document', 'doctools' ] },
{ name: 'document', groups: [ 'mode', 'document', 'doctools' ] },
{ name: 'others' },
'/',
{ name: 'basicstyles', groups: [ 'basicstyles', 'cleanup' ] },
{ name: 'paragraph', groups: [ 'list', 'indent', 'blocks', 'align', 'bidi' ] },
{ name: 'paragraph', groups: [ 'list', 'indent', 'blocks', 'align', 'bidi' ] },
{ name: 'styles' },
{ name: 'colors' },
{ name: 'about' }
];"""
];"""
}
}
@@ -191,11 +184,9 @@ CKEDITOR_CONFIGS = {'default': {
FACEBOOK_APP_ID = ''
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)
NOCAPTCHA = True
RECAPTCHA_USE_SSL = True
RECAPTCHA_PUBLIC_KEY = ''
RECAPTCHA_PRIVATE_KEY = ''
RECAPTCHA_THEME = 'red'

View File

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

View File

@@ -35,21 +35,7 @@
<meta property="og:image" content="http://www.kasu.at/static/img/logo.png"/>
{% endblock %}
{% block extra_head %}{% endblock %}
<!-- Piwik -->
<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 -->
<script src="{{ STATIC_URL }}js/piwik.js"></script>
</head>
<body id="body" {% block itemscope %}{% endblock %}>
<header id="siteheader">
@@ -63,7 +49,10 @@
class="{%if item.active %}active{% endif %}">{{item.menu_name}}</a>
{% if item.subpages.all %}
<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>
{% endif %}
</li>

View File

@@ -2,7 +2,8 @@
{% for hidden in form.hidden_fields %}{{ hidden }}{% endfor %}
{% for field in form.visible_fields %}
<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 }}
{% if field.help_text and not field.field.widget.input_type %}
{{field.help_text}}

View File

@@ -1,15 +1,22 @@
{% load i18n %}
<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 %}>
<span class="fa fa-arrow-left"></span>{% trans "Previous" %}
<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" %}
</a>
{% for page in paginator.page_range %}
<a {% ifequal page_obj.number page %}class="active"{% else %}href="?page={{page}}"{% endifequal %}>{{page}}</a>
{% endfor %}
<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>
<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>
</a>
</nav>
</nav>

View File

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

View File

@@ -23,19 +23,21 @@ sitemaps = {
urlpatterns = [
url(r'^$', StartPage.as_view()),
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/', include(admin.site.urls)),
url(r'^ckeditor/', include('ckeditor_uploader.urls')),
url(r'^comments/', include('django_comments.urls')),
url(r'^content/', include('content.urls')),
url(r'^edit_page/(?P<path>[\+\.\-\d\w\/]+)/$', PageEditForm.as_view(), name='edit-page'),
url(r'^edit_page/(?P<path>[\+\.\-\d\w\/]+)/$',
PageEditForm.as_view(), name='edit-page'),
url(r'^events/', include('events.urls')),
url(r'^events.ics$', EventListIcal.as_view(), name='events-ical'),
url(r'^feeds/latest/$', LatestNews(), name='feed-latest-news'),
url(r'^feeds/comments/$', LatestComments(), name='feed-latest-comments'),
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'^index.html$', StartPage.as_view()),
url(r'^manifest.json$', TemplateView.as_view(template_name='manifest.json')),
@@ -43,23 +45,28 @@ urlpatterns = [
url(r'^news/', include('content.news_urls')),
url(r'^ranking/', include('mahjong_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'^users/$', MembershipDetail.as_view(), name='membership-details'),
url(r'^users/(?P<username>[\-\.\d\w]+)/$', MembershipDetail.as_view(), name='membership-details'),
url(r'^(?P<path>[\-\d\w\/]+)\.html$', PageHtml.as_view(), name='view-page'),
url(r'^users/(?P<username>[\-\.\d\w]+)/$',
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('', include('social_django.urls', namespace='social'))
]
if settings.DEBUG:
from django.conf.urls.static import static
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
urlpatterns += static(settings.MEDIA_URL,
document_root=settings.MEDIA_ROOT)
urlpatterns += static(settings.STATIC_URL,
document_root=settings.STATIC_ROOT)
if 'rosetta' in settings.INSTALLED_APPS:
urlpatterns += url(r'^rosetta/', include('rosetta.urls'))
if 'debug_toolbar' in settings.INSTALLED_APPS:
import debug_toolbar
urlpatterns.append(url(r'^__debug__/', include(debug_toolbar.urls)))
urlpatterns.append(url(r'^__debug__/', include(debug_toolbar.urls)))

View File

@@ -326,7 +326,7 @@ class MassMailer(object):
if isinstance(recipient, USER_MODEL):
self.recipients.add(recipient)
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):
for user in user_list:
@@ -334,7 +334,7 @@ class MassMailer(object):
self.log.debug('Adding %s as Recipient' % user)
self.recipients.add(user)
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):
mail_context = Context(self.context)
@@ -381,7 +381,7 @@ class MassMailer(object):
try:
mail.send()
except:
self.log.warn("Mail failed for: %s", mail.to)
self.log.warning("Mail failed for: %s", mail.to)
else:
self.log.info("Mail sent successful to: %s" % mail.to)
self.close_smtp_connection()

View File

@@ -2,15 +2,16 @@
import os
import sys
src_path = '/var/www/virtual/kasu.at/'
virtpy_path = '/var/www/virtual/kasu.at/virtenv/lib/python2.6/site-packages'
src_path = '/home/kasu/src'
virtpy_path = '/home/kasu/virtualenv/lib/python3.4/site-packages'
if virtpy_path not in sys.path:
sys.path.insert(1, virtpy_path)
if src_path not in sys.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):
key_to_add = None
queue_name = None
if season and user:
key_to_add = (season, user)
queue_name = 'ladder_ranking_queue'

View File

@@ -26,6 +26,8 @@ def recalculate(modeladmin, request, queryset):
for ladder_ranking in queryset:
set_dirty(user=ladder_ranking.user_id,
season=ladder_ranking.season)
recalculate.short_description = _("Recalculate")
@@ -33,6 +35,8 @@ def confirm(modeladmin, request, queryset):
for hanchan in queryset:
hanchan.confirmed = True
hanchan.save()
confirm.short_description = _("Confirm")
@@ -40,6 +44,8 @@ def unconfirm(modeladmin, request, queryset):
for hanchan in queryset:
hanchan.confirmed = False
hanchan.save()
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
"""
from django.contrib.auth import get_user_model
import django.forms
from django.utils import timezone
from django import forms
from django.utils.translation import ugettext as _
from utils.html5 import forms
from . import models
USER_MODEL = get_user_model()
@@ -19,7 +17,7 @@ USER_MODEL = get_user_model()
class HanchanForm(forms.ModelForm):
error_css_class = 'error'
required_css_class = 'required'
start = forms.DateTimeField(label=_('start'), required=True)
start = forms.SplitDateTimeField(label=_('start'), required=True)
class Meta(object):
model = models.Hanchan
@@ -29,8 +27,9 @@ class HanchanForm(forms.ModelForm):
'player3', 'player3_input_score',
'player4', 'player4_input_score',
'comment')
widgets = {'event': forms.HiddenInput(),
'comment': forms.widgets.Textarea(attrs={'rows': 4, 'cols': 40})
widgets = {
'event': forms.HiddenInput(),
'comment': forms.widgets.Textarea(attrs={'rows': 4, 'cols': 40})
}
def __init__(self, *args, **kwargs):
@@ -45,7 +44,6 @@ class HanchanForm(forms.ModelForm):
self.fields[player].queryset = player_queryset
class HanchanAdminForm(HanchanForm):
class Meta(object):
@@ -53,12 +51,12 @@ class HanchanAdminForm(HanchanForm):
fields = HanchanForm.Meta.fields + ('confirmed',)
class SeasonSelectForm(django.forms.Form):
season = django.forms.ChoiceField(label='', choices=('a', 'b', 'c'))
class SeasonSelectForm(forms.Form):
season = forms.ChoiceField(label='', choices=('a', 'b', 'c'))
def __init__(self, user, *args, **kwargs):
super(SeasonSelectForm, self).__init__(args, kwargs)
season_list = models.LadderRanking.objects.filter(user=user)
season_list = season_list.select_related('user')
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 ""
"Project-Id-Version: kasu.mahjong_ranking\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"
"Last-Translator: Christian Berg <xeniac.at@gmail.com>\n"
"Language-Team: Kasu <verein@kasu.at>\n"
@@ -19,376 +19,376 @@ msgstr ""
"X-Generator: Poedit 1.8.9\n"
"X-Translated-Using: django-rosetta 0.7.6\n"
#: src/mahjong_ranking/admin.py:29
#: mahjong_ranking/admin.py:29
msgid "Recalculate"
msgstr "Neuberechnen"
#: src/mahjong_ranking/admin.py:36
#: mahjong_ranking/admin.py:36
msgid "Confirm"
msgstr "Bestätigen"
#: src/mahjong_ranking/admin.py:43
#: mahjong_ranking/admin.py:43
msgid "Set unconfirmed"
msgstr "Als unbestätigt markieren"
#: src/mahjong_ranking/forms.py:22
#: mahjong_ranking/forms.py:22
msgid "start"
msgstr "Beginn"
#: src/mahjong_ranking/models.py:91
#: src/mahjong_ranking/templates/mahjong_ranking/player_dan_score.html:14
#: src/mahjong_ranking/templates/mahjong_ranking/player_invalid_score.html:13
#: src/mahjong_ranking/templates/mahjong_ranking/player_kyu_score.html:15
#: src/mahjong_ranking/templates/mahjong_ranking/player_ladder_score.html:15
#: src/mahjong_ranking/templates/mahjong_ranking/seasonranking_list.html:10
#: mahjong_ranking/models.py:90
#: mahjong_ranking/templates/mahjong_ranking/player_dan_score.html:14
#: mahjong_ranking/templates/mahjong_ranking/player_invalid_score.html:13
#: mahjong_ranking/templates/mahjong_ranking/player_kyu_score.html:15
#: mahjong_ranking/templates/mahjong_ranking/player_ladder_score.html:15
#: mahjong_ranking/templates/mahjong_ranking/seasonranking_list.html:10
msgid "Start"
msgstr "Beginn"
#: src/mahjong_ranking/models.py:92
#: mahjong_ranking/models.py:91
msgid "This is crucial to get the right Hanchans that scores"
msgstr "Wichtig damit die richtigen Hanchans in die Wertung kommen."
#: src/mahjong_ranking/models.py:97
#: mahjong_ranking/models.py:98
msgid "Player 1"
msgstr "Spieler 1"
#: src/mahjong_ranking/models.py:98 src/mahjong_ranking/models.py:100
#: src/mahjong_ranking/models.py:115 src/mahjong_ranking/models.py:117
#: src/mahjong_ranking/models.py:132 src/mahjong_ranking/models.py:134
#: src/mahjong_ranking/models.py:149 src/mahjong_ranking/models.py:151
#: src/mahjong_ranking/templates/mahjong_ranking/eventhanchan_list.html:19
#: src/mahjong_ranking/templates/mahjong_ranking/eventranking_list.html:21
#: src/mahjong_ranking/templates/mahjong_ranking/hanchan_confirm_delete.html:16
#: src/mahjong_ranking/templates/mahjong_ranking/hanchan_form.html:43
#: src/mahjong_ranking/templates/mahjong_ranking/kyudanranking_list.html:31
#: src/mahjong_ranking/templates/mahjong_ranking/seasonranking_list.html:32
#: mahjong_ranking/models.py:99 mahjong_ranking/models.py:101
#: mahjong_ranking/models.py:118 mahjong_ranking/models.py:120
#: mahjong_ranking/models.py:137 mahjong_ranking/models.py:139
#: mahjong_ranking/models.py:156 mahjong_ranking/models.py:158
#: mahjong_ranking/templates/mahjong_ranking/eventhanchan_list.html:19
#: mahjong_ranking/templates/mahjong_ranking/eventranking_list.html:21
#: mahjong_ranking/templates/mahjong_ranking/hanchan_confirm_delete.html:16
#: mahjong_ranking/templates/mahjong_ranking/hanchan_form.html:19
#: mahjong_ranking/templates/mahjong_ranking/kyudanranking_list.html:35
#: mahjong_ranking/templates/mahjong_ranking/seasonranking_list.html:32
msgid "Score"
msgstr "Punkte"
#: src/mahjong_ranking/models.py:110 src/mahjong_ranking/models.py:127
#: src/mahjong_ranking/models.py:144 src/mahjong_ranking/models.py:161
#: src/mahjong_ranking/models.py:163
#: src/mahjong_ranking/templates/mahjong_ranking/hanchan_form.html:44
#: src/mahjong_ranking/templates/mahjong_ranking/player_dan_score.html:18
#: src/mahjong_ranking/templates/mahjong_ranking/player_invalid_score.html:17
#: mahjong_ranking/models.py:111 mahjong_ranking/models.py:130
#: mahjong_ranking/models.py:149 mahjong_ranking/models.py:168
#: mahjong_ranking/models.py:170
#: mahjong_ranking/templates/mahjong_ranking/hanchan_form.html:20
#: mahjong_ranking/templates/mahjong_ranking/player_dan_score.html:18
#: mahjong_ranking/templates/mahjong_ranking/player_invalid_score.html:17
msgid "Comment"
msgstr "Kommentar"
#: src/mahjong_ranking/models.py:114
#: mahjong_ranking/models.py:117
msgid "Player 2"
msgstr "Spieler 2"
#: src/mahjong_ranking/models.py:131
#: mahjong_ranking/models.py:136
msgid "Player 3"
msgstr "Spieler 3"
#: src/mahjong_ranking/models.py:148
#: mahjong_ranking/models.py:155
msgid "Player 4"
msgstr "Spieler 4"
#: src/mahjong_ranking/models.py:164
#: mahjong_ranking/models.py:171
msgid "Has been Confirmed"
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."
msgstr "Nur gültige und bestätigte Hanchans kommen in die Wertung."
#: src/mahjong_ranking/models.py:171 src/mahjong_ranking/models.py:547
#: src/mahjong_ranking/templates/mahjong_ranking/ladder_redbox.html:29
#: src/mahjong_ranking/templates/mahjong_ranking/player_ladder_score.html:63
#: mahjong_ranking/models.py:178 mahjong_ranking/models.py:565
#: mahjong_ranking/templates/mahjong_ranking/ladder_redbox.html:29
#: mahjong_ranking/templates/mahjong_ranking/player_ladder_score.html:63
msgid "Season"
msgstr "Saison"
#: src/mahjong_ranking/models.py:176
#: mahjong_ranking/models.py:183
msgid "Hanchan"
msgstr "Hanchan"
#: src/mahjong_ranking/models.py:177
#: src/mahjong_ranking/templates/mahjong_ranking/eventranking_list.html:17
#: mahjong_ranking/models.py:184
#: mahjong_ranking/templates/mahjong_ranking/eventranking_list.html:17
msgid "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}"
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
msgid "%s can't attend the same game multiple times"
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"
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"
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."
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."
msgstr "Spielstand ist über 100.000 Punkte."
#: src/mahjong_ranking/models.py:344
#: mahjong_ranking/models.py:353
msgid "Kyū/Dan Ranking"
msgstr "Kyū/Dan Wertung"
#: src/mahjong_ranking/models.py:345
#: mahjong_ranking/models.py:354
msgid "Kyū/Dan Rankings"
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"
msgstr "Gespielte Hanchans"
#: src/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/eventhanchan_list.html:18
#: mahjong_ranking/templates/mahjong_ranking/hanchan_confirm_delete.html:15
msgid "Place"
msgstr "Platz"
#: src/mahjong_ranking/templates/mahjong_ranking/eventhanchan_list.html:21
#: src/mahjong_ranking/templates/mahjong_ranking/hanchan_confirm_delete.html:18
#: src/mahjong_ranking/templates/mahjong_ranking/player_dan_score.html:17
#: mahjong_ranking/templates/mahjong_ranking/eventhanchan_list.html:21
#: mahjong_ranking/templates/mahjong_ranking/hanchan_confirm_delete.html:18
#: mahjong_ranking/templates/mahjong_ranking/player_dan_score.html:17
msgid "Dan Points"
msgstr "Dan Punkte"
#: src/mahjong_ranking/templates/mahjong_ranking/eventhanchan_list.html:23
#: src/mahjong_ranking/templates/mahjong_ranking/hanchan_confirm_delete.html:20
#: src/mahjong_ranking/templates/mahjong_ranking/player_invalid_score.html:16
#: src/mahjong_ranking/templates/mahjong_ranking/player_kyu_score.html:18
#: mahjong_ranking/templates/mahjong_ranking/eventhanchan_list.html:23
#: mahjong_ranking/templates/mahjong_ranking/hanchan_confirm_delete.html:20
#: mahjong_ranking/templates/mahjong_ranking/player_invalid_score.html:16
#: mahjong_ranking/templates/mahjong_ranking/player_kyu_score.html:18
msgid "Kyu Points"
msgstr "Kyu Punkte"
#: src/mahjong_ranking/templates/mahjong_ranking/eventhanchan_list.html:37
#: src/mahjong_ranking/templates/mahjong_ranking/hanchan_confirm_delete.html:4
#: src/mahjong_ranking/templates/mahjong_ranking/hanchan_confirm_delete.html:33
#: src/mahjong_ranking/templates/mahjong_ranking/player_dan_score.html:44
#: src/mahjong_ranking/templates/mahjong_ranking/player_invalid_score.html:33
#: src/mahjong_ranking/templates/mahjong_ranking/player_kyu_score.html:41
#: src/mahjong_ranking/templates/mahjong_ranking/player_ladder_score.html:52
#: mahjong_ranking/templates/mahjong_ranking/eventhanchan_list.html:37
#: mahjong_ranking/templates/mahjong_ranking/hanchan_confirm_delete.html:4
#: mahjong_ranking/templates/mahjong_ranking/hanchan_confirm_delete.html:33
#: mahjong_ranking/templates/mahjong_ranking/player_dan_score.html:44
#: mahjong_ranking/templates/mahjong_ranking/player_invalid_score.html:33
#: mahjong_ranking/templates/mahjong_ranking/player_kyu_score.html:41
#: mahjong_ranking/templates/mahjong_ranking/player_ladder_score.html:52
msgid "Delete Hanchan"
msgstr "Hanchan löschen"
#: src/mahjong_ranking/templates/mahjong_ranking/eventhanchan_list.html:43
#: src/mahjong_ranking/templates/mahjong_ranking/hanchan_form.html:4
#: src/mahjong_ranking/templates/mahjong_ranking/hanchan_form.html:38
#: src/mahjong_ranking/templates/mahjong_ranking/player_dan_score.html:47
#: src/mahjong_ranking/templates/mahjong_ranking/player_invalid_score.html:36
#: src/mahjong_ranking/templates/mahjong_ranking/player_kyu_score.html:44
#: src/mahjong_ranking/templates/mahjong_ranking/player_ladder_score.html:55
#: mahjong_ranking/templates/mahjong_ranking/eventhanchan_list.html:43
#: mahjong_ranking/templates/mahjong_ranking/hanchan_form.html:4
#: mahjong_ranking/templates/mahjong_ranking/hanchan_form.html:14
#: mahjong_ranking/templates/mahjong_ranking/player_dan_score.html:47
#: mahjong_ranking/templates/mahjong_ranking/player_invalid_score.html:36
#: mahjong_ranking/templates/mahjong_ranking/player_kyu_score.html:44
#: mahjong_ranking/templates/mahjong_ranking/player_ladder_score.html:55
msgid "Edit Hanchan"
msgstr "Hanchan bearbeiten"
#: src/mahjong_ranking/templates/mahjong_ranking/eventhanchan_list.html:48
#: mahjong_ranking/templates/mahjong_ranking/eventhanchan_list.html:48
msgid "No Hanchan has been added to this event yet."
msgstr "Für diese Veranstaltung wurde noch keine Hanchan eingetragen."
#: src/mahjong_ranking/templates/mahjong_ranking/eventhanchan_list.html:54
#: src/mahjong_ranking/templates/mahjong_ranking/eventranking_list.html:51
#: mahjong_ranking/templates/mahjong_ranking/eventhanchan_list.html:54
#: mahjong_ranking/templates/mahjong_ranking/eventranking_list.html:51
msgid "Edit Event"
msgstr "Veranstaltung bearbeiten"
#: src/mahjong_ranking/templates/mahjong_ranking/eventhanchan_list.html:55
#: src/mahjong_ranking/templates/mahjong_ranking/eventranking_list.html:52
#: src/mahjong_ranking/templates/mahjong_ranking/hanchan_form.html:4
#: src/mahjong_ranking/templates/mahjong_ranking/hanchan_form.html:38
#: mahjong_ranking/templates/mahjong_ranking/eventhanchan_list.html:55
#: mahjong_ranking/templates/mahjong_ranking/eventranking_list.html:52
#: mahjong_ranking/templates/mahjong_ranking/hanchan_form.html:4
#: mahjong_ranking/templates/mahjong_ranking/hanchan_form.html:14
msgid "Add Hanchan"
msgstr "Hanchan hinzufügen"
#: src/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:4
#: mahjong_ranking/templates/mahjong_ranking/eventranking_list.html:5
msgid "Tournament Ranking"
msgstr "Turnierwertung"
#: src/mahjong_ranking/templates/mahjong_ranking/eventranking_list.html:12
#: src/mahjong_ranking/templates/mahjong_ranking/kyudanranking_list.html:26
#: src/mahjong_ranking/templates/mahjong_ranking/seasonranking_list.html:23
#: mahjong_ranking/templates/mahjong_ranking/eventranking_list.html:12
#: mahjong_ranking/templates/mahjong_ranking/kyudanranking_list.html:30
#: mahjong_ranking/templates/mahjong_ranking/seasonranking_list.html:23
msgid "Rank"
msgstr "Rang"
#: src/mahjong_ranking/templates/mahjong_ranking/eventranking_list.html:13
#: src/mahjong_ranking/templates/mahjong_ranking/kyudanranking_list.html:13
#: src/mahjong_ranking/templates/mahjong_ranking/seasonranking_list.html:24
#: mahjong_ranking/templates/mahjong_ranking/eventranking_list.html:13
#: mahjong_ranking/templates/mahjong_ranking/kyudanranking_list.html:17
#: mahjong_ranking/templates/mahjong_ranking/seasonranking_list.html:24
msgid "Avatar"
msgstr "Avatar"
#: src/mahjong_ranking/templates/mahjong_ranking/eventranking_list.html:14
#: src/mahjong_ranking/templates/mahjong_ranking/kyudanranking_list.html:16
#: src/mahjong_ranking/templates/mahjong_ranking/seasonranking_list.html:25
#: mahjong_ranking/templates/mahjong_ranking/eventranking_list.html:14
#: mahjong_ranking/templates/mahjong_ranking/kyudanranking_list.html:20
#: mahjong_ranking/templates/mahjong_ranking/seasonranking_list.html:25
msgid "Nickname"
msgstr "Spitzname"
#: src/mahjong_ranking/templates/mahjong_ranking/eventranking_list.html:15
#: src/mahjong_ranking/templates/mahjong_ranking/seasonranking_list.html:26
#: mahjong_ranking/templates/mahjong_ranking/eventranking_list.html:15
#: mahjong_ranking/templates/mahjong_ranking/seasonranking_list.html:26
msgid "Name"
msgstr "Name"
#: src/mahjong_ranking/templates/mahjong_ranking/eventranking_list.html:16
#: src/mahjong_ranking/templates/mahjong_ranking/seasonranking_list.html:27
#: mahjong_ranking/templates/mahjong_ranking/eventranking_list.html:16
#: mahjong_ranking/templates/mahjong_ranking/seasonranking_list.html:27
msgid "Average"
msgstr "Durchschnitt"
#: src/mahjong_ranking/templates/mahjong_ranking/eventranking_list.html:20
#: src/mahjong_ranking/templates/mahjong_ranking/player_dan_score.html:15
#: src/mahjong_ranking/templates/mahjong_ranking/player_invalid_score.html:15
#: src/mahjong_ranking/templates/mahjong_ranking/player_kyu_score.html:16
#: src/mahjong_ranking/templates/mahjong_ranking/player_ladder_score.html:16
#: src/mahjong_ranking/templates/mahjong_ranking/seasonranking_list.html:31
#: mahjong_ranking/templates/mahjong_ranking/eventranking_list.html:20
#: mahjong_ranking/templates/mahjong_ranking/player_dan_score.html:15
#: mahjong_ranking/templates/mahjong_ranking/player_invalid_score.html:15
#: mahjong_ranking/templates/mahjong_ranking/player_kyu_score.html:16
#: mahjong_ranking/templates/mahjong_ranking/player_ladder_score.html:16
#: mahjong_ranking/templates/mahjong_ranking/seasonranking_list.html:31
msgid "Placement"
msgstr "Platzierung"
#: src/mahjong_ranking/templates/mahjong_ranking/eventranking_list.html:22
#: src/mahjong_ranking/templates/mahjong_ranking/seasonranking_list.html:33
#: mahjong_ranking/templates/mahjong_ranking/eventranking_list.html:22
#: mahjong_ranking/templates/mahjong_ranking/seasonranking_list.html:33
msgid "count"
msgstr "Anzahl"
#: src/mahjong_ranking/templates/mahjong_ranking/eventranking_list.html:23
#: src/mahjong_ranking/templates/mahjong_ranking/seasonranking_list.html:34
#: mahjong_ranking/templates/mahjong_ranking/eventranking_list.html:23
#: mahjong_ranking/templates/mahjong_ranking/seasonranking_list.html:34
msgid "good"
msgstr "gut"
#: src/mahjong_ranking/templates/mahjong_ranking/eventranking_list.html:24
#: src/mahjong_ranking/templates/mahjong_ranking/seasonranking_list.html:35
#: mahjong_ranking/templates/mahjong_ranking/eventranking_list.html:24
#: mahjong_ranking/templates/mahjong_ranking/seasonranking_list.html:35
msgid "won"
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"
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"
msgstr "Löschen"
#: src/mahjong_ranking/templates/mahjong_ranking/hanchan_form.html:42
#: mahjong_ranking/templates/mahjong_ranking/hanchan_form.html:18
msgid "Player"
msgstr "Spieler"
#: src/mahjong_ranking/templates/mahjong_ranking/hanchan_form.html:82
#: mahjong_ranking/templates/mahjong_ranking/hanchan_form.html:58
msgid "Total"
msgstr "Total"
#: src/mahjong_ranking/templates/mahjong_ranking/hanchan_form.html:94
#: mahjong_ranking/templates/mahjong_ranking/hanchan_form.html:71
msgid "back"
msgstr "Zurück"
#: src/mahjong_ranking/templates/mahjong_ranking/hanchan_form.html:95
#: mahjong_ranking/templates/mahjong_ranking/hanchan_form.html:72
msgid "save"
msgstr "Speichern"
#: src/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:4
#: mahjong_ranking/templates/mahjong_ranking/kyudanranking_list.html:9
msgid "Player List"
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"
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"
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"
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"
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"
msgstr "Ladder Archiv"
#: src/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:4
#: mahjong_ranking/templates/mahjong_ranking/player_dan_score.html:5
msgid "Dan Score for"
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"
msgstr "Hanchans welche zur Dan Wertung zählen"
#: src/mahjong_ranking/templates/mahjong_ranking/player_dan_score.html:12
#: src/mahjong_ranking/templates/mahjong_ranking/player_kyu_score.html:13
#: src/mahjong_ranking/templates/mahjong_ranking/player_ladder_score.html:13
#: mahjong_ranking/templates/mahjong_ranking/player_dan_score.html:12
#: mahjong_ranking/templates/mahjong_ranking/player_kyu_score.html:13
#: mahjong_ranking/templates/mahjong_ranking/player_ladder_score.html:13
msgid "Date"
msgstr "Datum"
#: src/mahjong_ranking/templates/mahjong_ranking/player_dan_score.html:13
#: src/mahjong_ranking/templates/mahjong_ranking/player_invalid_score.html:12
#: src/mahjong_ranking/templates/mahjong_ranking/player_kyu_score.html:14
#: src/mahjong_ranking/templates/mahjong_ranking/player_ladder_score.html:14
#: mahjong_ranking/templates/mahjong_ranking/player_dan_score.html:13
#: mahjong_ranking/templates/mahjong_ranking/player_invalid_score.html:12
#: mahjong_ranking/templates/mahjong_ranking/player_kyu_score.html:14
#: mahjong_ranking/templates/mahjong_ranking/player_ladder_score.html:14
msgid "Event"
msgstr "Veranstaltung"
#: src/mahjong_ranking/templates/mahjong_ranking/player_dan_score.html:16
#: src/mahjong_ranking/templates/mahjong_ranking/player_invalid_score.html:14
#: src/mahjong_ranking/templates/mahjong_ranking/player_kyu_score.html:17
#: src/mahjong_ranking/templates/mahjong_ranking/player_ladder_score.html:17
#: mahjong_ranking/templates/mahjong_ranking/player_dan_score.html:16
#: mahjong_ranking/templates/mahjong_ranking/player_invalid_score.html:14
#: mahjong_ranking/templates/mahjong_ranking/player_kyu_score.html:17
#: mahjong_ranking/templates/mahjong_ranking/player_ladder_score.html:17
msgid "Players"
msgstr "Spieler"
#: src/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:4
#: mahjong_ranking/templates/mahjong_ranking/player_invalid_score.html:6
msgid "Unconfirmed Hanchans from"
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"
msgstr "Ungültige Hanchans mit"
#: src/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:4
#: mahjong_ranking/templates/mahjong_ranking/player_kyu_score.html:6
msgid "Kyu Score for"
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"
msgstr "Hanchans welche zur Kyu Wertung zählen"
#: src/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:4
#: mahjong_ranking/templates/mahjong_ranking/player_ladder_score.html:5
msgid "Ladder Score for"
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"
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"
msgstr "Los"
#: src/mahjong_ranking/templates/mahjong_ranking/seasonranking_list.html:11
#: mahjong_ranking/templates/mahjong_ranking/seasonranking_list.html:11
msgid "End"
msgstr "Ende"
#: src/mahjong_ranking/templates/mahjong_ranking/seasonranking_list.html:12
#: mahjong_ranking/templates/mahjong_ranking/seasonranking_list.html:12
msgid "Participants"
msgstr "Teilnehmer"
#: src/mahjong_ranking/views.py:97
#: mahjong_ranking/views.py:97
#, python-format
msgid "%s has been updated successfully."
msgstr "%s wurde erfolgreich aktualisiert."
#: src/mahjong_ranking/views.py:100
#: mahjong_ranking/views.py:100
#, python-format
msgid "%s has been added successfully. You can now add a new one."
msgstr "%s wurde erfolgreich hinzugefügt. Du kannst eine neue eintragen."
#: 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"
msgstr "Veranstaltung existiert nicht"
#: src/mahjong_ranking/views.py:199
#: mahjong_ranking/views.py:199
msgid "No user found matching the name {}"
msgstr "Kein Benutzer mit dem Namen %s gefunden"

View File

@@ -10,31 +10,32 @@ from mahjong_ranking.models import SeasonRanking
from openpyxl import Workbook
def geneate_seasonexcel(json_data):
wb = Workbook()
worksheet = wb.active
worksheet.append([
'Rang', 'Spitzname',
'⌀ Platz', '⌀ Punkte',
'Hanchans', 'Gut', 'Gewonnen'
])
json_data = json_data.values()
json_data = sorted(json_data, key=itemgetter('placement'))
for row in json_data:
worksheet.append([
row['placement'], row['username'],
row['avg_placement'], row['avg_score'],
row['hanchan_count'],
row['good_hanchans'], row['won_hanchans']
])
wb.save("sample.xlsx")
class Command(BaseCommand):
help = "Exports the SeasonRankings"
def geneate_seasonexcel(self, json_data):
wb = Workbook()
worksheet = wb.active
worksheet.append([
'Rang', 'Spitzname',
'⌀ Platz', '⌀ Punkte',
'Hanchans', 'Gut', 'Gewonnen'
])
json_data = json_data.values()
json_data = sorted(json_data, key=itemgetter('placement'))
for row in json_data:
worksheet.append([
row['placement'], row['username'],
row['avg_placement'], row['avg_score'],
row['hanchan_count'],
row['good_hanchans'], row['won_hanchans']
])
wb.save("sample.xlsx")
def handle(self, *args, **options):
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):
for ranking in models.KyuDanRanking.objects.all():
ranking.recalculate()

View File

@@ -42,14 +42,22 @@ class HanchanManager(models.Manager):
season = season or date.today().year
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(
models.Q(player1=user) | models.Q(player2=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:
queryset = queryset.filter(**kwargs)
queryset = queryset.select_related().order_by('-start')
for hanchan in queryset:
hanchan.get_playerdata(user)
return queryset

View File

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

View File

@@ -19,21 +19,25 @@ class Migration(migrations.Migration):
migrations.AlterField(
model_name='hanchan',
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(
model_name='hanchan',
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(
model_name='hanchan',
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(
model_name='hanchan',
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(
model_name='hanchan',
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(
model_name='hanchan',
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(
model_name='hanchan',
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(
model_name='hanchan',
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.
from __future__ import division
from datetime import date
from django.conf import settings
from django.core.exceptions import ValidationError
@@ -371,7 +370,8 @@ class KyuDanRanking(models.Model):
self.wins_in_a_row = 0
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_points = DAN_RANKS_DICT[new_dan_rank] + 1
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("current dan_points: %d", self.dan_points)
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)
hanchan.dan_points += bonus_points
@@ -404,7 +405,8 @@ class KyuDanRanking(models.Model):
).order_by('-start')
last_hanchan_this_event = hanchans_this_event[0]
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:
event_ranking = EventRanking.objects.get(
user=self.user,
@@ -455,14 +457,13 @@ class KyuDanRanking(models.Model):
valid_hanchans = valid_hanchans.order_by('start')
if self.legacy_date:
valid_hanchans = valid_hanchans.filter(start__gt=self.legacy_date)
""" TODO: Hanchan Punkte nur neu berechnen wenn sie vor hachan_start
lag. Es müssen aber alle durch die Schleife rennen, damit die Punkte
""" TODO: Hanchan Punkte nur neu berechnen wenn sie vor hachan_start
lag. Es müssen aber alle durch die Schleife rennen, damit die Punkte
richtig gezählt werden."""
if hanchan_start:
valid_hanchans = valid_hanchans.filter(start__gte=hanchan_start)
self.hanchan_count += valid_hanchans.count()
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.bonus_points = 0
hanchan.player_comment = u""
@@ -476,6 +477,16 @@ class KyuDanRanking(models.Model):
self.good_hanchans += 1 if hanchan.placement == 2 else 0
hanchan.update_playerdata(self.user)
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)
def update_hanchan_points(self, hanchan):
@@ -543,7 +554,7 @@ class KyuDanRanking(models.Model):
if self.dan_points > min_points:
self.dan = dan_rank
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
elif self.kyu_points < 1:
self.kyu_points = 0
@@ -608,7 +619,7 @@ def update_ranking(sender, instance, **kwargs):
for user in (instance.player1, instance.player2, instance.player3, instance.player4):
logger.debug(
"marking %(user)s's kyu/dan for recalculation since %(start)s",
{'user':user, 'start':str(instance.start.date())}
{'user': user, 'start': str(instance.start.date())}
)
set_dirty(user=user.id, hanchan_date=instance.start.date())
if instance.season:
@@ -622,5 +633,6 @@ def update_ranking(sender, instance, **kwargs):
logger.debug("marking event %s for recalculation.", instance.event)
set_dirty(event=instance.event_id, user=user.id)
models.signals.pre_delete.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>
<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%}
<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 %}
</select>
</form>

View File

@@ -63,7 +63,9 @@
<label for="season">{% trans 'Season' %}:</label>
<select id="season" name="season" size="1">
{% 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 %}
</select>
<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.
"""
import random
from django.test import TestCase
from mahjong_ranking.models import Hanchan, KyuDanRanking
class SimpleTest(TestCase):
class KyuDanTest(TestCase):
"""
Unittest to check if the hanchan calculation works against the rulebook.
"""
equal_attrs = ('dan', 'dan_points', 'kyu', 'kyu_points', 'good_hanchans',
'won_hanchans', 'hanchan_count')
def test_basic_addition(self):
fixtures = [
'test_membership.json',
'test_events.json',
'test_hanchans.json',
'test_event_rankings.json',
'test_kyu_dan_rankings.json'
]
def recalc(self):
"""
Tests that 1 + 1 always equals 2.
Test if a Kyu/Dan Ranking recalculation gives the same result as stored.
:return:
"""
self.assertEqual(1 + 1, 2)
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 ""
"Project-Id-Version: kasu.mahjong_ranking\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"
"Last-Translator: Christian Berg <xeniac.at@gmail.com>\n"
"Language-Team: Kasu <verein@kasu.at>\n"
@@ -19,208 +19,208 @@ msgstr ""
"X-Generator: Poedit 1.8.9\n"
"X-Translated-Using: django-rosetta 0.7.2\n"
#: src/maistar_ranking/admin.py:24
#: maistar_ranking/admin.py:24
msgid "Recalculate"
msgstr "Neuberechnen"
#: src/maistar_ranking/forms.py:33
#: maistar_ranking/forms.py:33
#, python-format
msgid "%s may only participate once."
msgstr "%s darf nur einmal teilnehmen."
#: src/maistar_ranking/models.py:20
#: maistar_ranking/models.py:20
msgid "Comment"
msgstr "Kommentar"
#: src/maistar_ranking/models.py:22
#: maistar_ranking/models.py:22
msgid "Player 1"
msgstr "Spieler 1"
#: src/maistar_ranking/models.py:24 src/maistar_ranking/models.py:30
#: src/maistar_ranking/models.py:36 src/maistar_ranking/models.py:42
#: src/maistar_ranking/models.py:48 src/maistar_ranking/models.py:54
#: src/maistar_ranking/templates/maistar_ranking/ranking_list.html:19
#: maistar_ranking/models.py:24 maistar_ranking/models.py:30
#: maistar_ranking/models.py:36 maistar_ranking/models.py:42
#: maistar_ranking/models.py:48 maistar_ranking/models.py:54
#: maistar_ranking/templates/maistar_ranking/ranking_list.html:19
msgid "Score"
msgstr "Punkte"
#: src/maistar_ranking/models.py:28
#: maistar_ranking/models.py:28
msgid "Player 2"
msgstr "Spieler 2"
#: src/maistar_ranking/models.py:34
#: maistar_ranking/models.py:34
msgid "Player 3"
msgstr "Spieler 3"
#: src/maistar_ranking/models.py:40
#: maistar_ranking/models.py:40
msgid "Player 4"
msgstr "Spieler 4"
#: src/maistar_ranking/models.py:46
#: maistar_ranking/models.py:46
msgid "Player 5"
msgstr "Spieler 5"
#: src/maistar_ranking/models.py:52
#: maistar_ranking/models.py:52
msgid "Player 6"
msgstr "Spieler 6"
#: src/maistar_ranking/models.py:58
#: maistar_ranking/models.py:58
msgid "Has been confirmed"
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"
msgstr "das Spiel zählt nur wenn es bestätigt wurde"
#: src/maistar_ranking/models.py:63 src/maistar_ranking/models.py:148
#: src/maistar_ranking/templates/maistar_ranking/player_game_list.html:6
#: src/maistar_ranking/templates/maistar_ranking/ranking_list.html:4
#: src/maistar_ranking/templates/maistar_ranking/ranking_list.html:72
#: maistar_ranking/models.py:63 maistar_ranking/models.py:148
#: maistar_ranking/templates/maistar_ranking/player_game_list.html:6
#: maistar_ranking/templates/maistar_ranking/ranking_list.html:4
#: maistar_ranking/templates/maistar_ranking/ranking_list.html:72
msgid "Season"
msgstr "Saison"
#: src/maistar_ranking/models.py:69
#: maistar_ranking/models.py:69
msgid "Mai-Star Game with {0} from {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
#: src/maistar_ranking/templates/maistar_ranking/game_form.html:16
#: src/maistar_ranking/templates/maistar_ranking/game_list.html:27
#: src/maistar_ranking/templates/maistar_ranking/player_game_list.html:45
#: maistar_ranking/templates/maistar_ranking/game_form.html:5
#: maistar_ranking/templates/maistar_ranking/game_form.html:16
#: maistar_ranking/templates/maistar_ranking/game_list.html:27
#: maistar_ranking/templates/maistar_ranking/player_game_list.html:45
msgid "Edit Game"
msgstr "Spiel bearbeiten"
#: src/maistar_ranking/templates/maistar_ranking/game_form.html:5
#: src/maistar_ranking/templates/maistar_ranking/game_form.html:16
#: src/maistar_ranking/templates/maistar_ranking/game_list.html:41
#: maistar_ranking/templates/maistar_ranking/game_form.html:5
#: maistar_ranking/templates/maistar_ranking/game_form.html:16
#: maistar_ranking/templates/maistar_ranking/game_list.html:41
msgid "Add Game"
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"
msgstr "Zurück"
#: src/maistar_ranking/templates/maistar_ranking/game_form.html:77
#: maistar_ranking/templates/maistar_ranking/game_form.html:77
msgid "Save"
msgstr "Speichern"
#: src/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/game_list.html:4
#: maistar_ranking/templates/maistar_ranking/player_game_list.html:6
msgid "Mai-Star Games"
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"
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"
msgstr "Spiel"
#: src/maistar_ranking/templates/maistar_ranking/game_list.html:14
#: maistar_ranking/templates/maistar_ranking/game_list.html:14
msgid "Place"
msgstr "Platz"
#: src/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/game_list.html:19
#: maistar_ranking/templates/maistar_ranking/player_game_list.html:37
msgid "Points"
msgstr "Punkte"
#: src/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/game_list.html:24
#: maistar_ranking/templates/maistar_ranking/player_game_list.html:42
msgid "Delete Game"
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."
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"
msgstr "Veranstaltung bearbeiten"
#: src/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:4
#: maistar_ranking/templates/maistar_ranking/hanchan_confirm_delete.html:10
msgid "Delete game"
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"
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"
msgstr "Löschen"
#: src/maistar_ranking/templates/maistar_ranking/page.html:5
#: maistar_ranking/templates/maistar_ranking/page.html:5
msgid "Archive"
msgstr "Archiv"
#: src/maistar_ranking/templates/maistar_ranking/page.html:7
#: maistar_ranking/templates/maistar_ranking/page.html:7
msgid "Add Event"
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"
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"
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"
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"
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"
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"
msgstr "Mai-Star Platzierung"
#: src/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:10
#: maistar_ranking/templates/maistar_ranking/ranking_list.html:18
msgid "Placement"
msgstr "Platzierung"
#: src/maistar_ranking/templates/maistar_ranking/ranking_list.html:11
#: maistar_ranking/templates/maistar_ranking/ranking_list.html:11
msgid "Avatar"
msgstr "Avatar"
#: src/maistar_ranking/templates/maistar_ranking/ranking_list.html:12
#: maistar_ranking/templates/maistar_ranking/ranking_list.html:12
msgid "Nickname"
msgstr "Spitzname"
#: src/maistar_ranking/templates/maistar_ranking/ranking_list.html:13
#: maistar_ranking/templates/maistar_ranking/ranking_list.html:13
msgid "Name"
msgstr "Name"
#: src/maistar_ranking/templates/maistar_ranking/ranking_list.html:14
#: maistar_ranking/templates/maistar_ranking/ranking_list.html:14
msgid "Average"
msgstr "Durchschnitt"
#: src/maistar_ranking/templates/maistar_ranking/ranking_list.html:15
#: maistar_ranking/templates/maistar_ranking/ranking_list.html:15
msgid "Games"
msgstr "Spiele"
#: src/maistar_ranking/templates/maistar_ranking/ranking_list.html:20
#: maistar_ranking/templates/maistar_ranking/ranking_list.html:20
msgid "count"
msgstr "Anzahl"
#: src/maistar_ranking/templates/maistar_ranking/ranking_list.html:21
#: maistar_ranking/templates/maistar_ranking/ranking_list.html:21
msgid "good"
msgstr "Gut"
#: src/maistar_ranking/templates/maistar_ranking/ranking_list.html:22
#: maistar_ranking/templates/maistar_ranking/ranking_list.html:22
msgid "won"
msgstr "Gewonnen"
#: src/maistar_ranking/templates/maistar_ranking/ranking_list.html:43
#: maistar_ranking/templates/maistar_ranking/ranking_list.html:43
msgid ""
"Unfortunately, nobody has it been done in the ranking.\n"
" A player must have 6 games done, to be added to the ranking."
@@ -229,15 +229,15 @@ msgstr ""
"als 6 Spiele innerhalb einer Saison absolviert haben, werden für das "
"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"
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"
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"
msgstr "Archiv"

View File

@@ -30,7 +30,6 @@
<tr>
<td class="center">{{ game.event.start|date:'SHORT_DATE_FORMAT' }}</td>
<td><a href="{{ game.get_absolute_url }}">{{ game.event.name }}</a></td>
</td>
{% for player in game.player_list %}
<td class="center">
<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>
<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%}
<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 %}
</select>
</form>

View File

@@ -6,7 +6,7 @@ Created on 03.10.2011
"""
from django.conf.urls import url
from django.views.generic import RedirectView
from .views import DeleteGame, ListGames, ListPlayerGames, \
from .views import DeleteGame, ListGames, ListPlayerGames, \
ListRankings, GameForm

View File

@@ -11,7 +11,8 @@ from django.views import generic
from events.models import Event
from events.views import EventDetailMixin
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 . import forms, models

View File

@@ -43,7 +43,7 @@ class ProxyGroup(Group):
class MembershipAdmin(UserAdmin):
# admin_thumbnail = AdminThumbnail(image_field='thumbnail')
list_filter = ('is_active','membership', 'confirmed')
list_filter = ('is_active', 'membership', 'confirmed')
list_display = (
'avatar',
'username',

View File

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

View File

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

View File

@@ -1,7 +1,8 @@
# -*- coding: utf-8 -*-
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

View File

@@ -1,7 +1,8 @@
# -*- coding: utf-8 -*-
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):

View File

@@ -1,7 +1,8 @@
# -*- coding: utf-8 -*-
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):

View File

@@ -1,7 +1,8 @@
# -*- coding: utf-8 -*-
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 utils
import easy_thumbnails.fields

View File

@@ -14,6 +14,7 @@ class Migration(migrations.Migration):
migrations.AlterField(
model_name='membership',
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.
@param user: the user that requests activation.
"""
salt = str(random.random())
activation_key = hashlib.sha1(salt + user.username).hexdigest()
salt = str(user.pk * random.random()) + \
user.registration_date.isoformat()
activation_key = hashlib.sha1(salt.encode('utf-8')).hexdigest()
return self.create(user=user, activation_key=activation_key)

View File

@@ -11,7 +11,7 @@
<img class="grid_4" src="" alt="" />
<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 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>Ein obskurer Fehler ist aufgetreten.</strong> In diesem Fall, nimm bitte Kontakt mit dem Webadmin auf.</li>
</ul>

View File

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

View File

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

View File

@@ -4,7 +4,8 @@ Created on 03.10.2011
@author: christian
"""
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

View File

@@ -12,10 +12,14 @@ from mahjong_ranking.models import KyuDanRanking, SeasonRanking
from utils import mixins
from . import forms, models
RECAPTCHA_CSP = {
'SCRIPT_SRC': ['https://www.google.com/recaptcha/', 'https://www.gstatic.com/recaptcha/'],
'FRAME_SRC': 'https://www.google.com/recaptcha/',
'STYLE_SRC': "'unsafe-inline'"
'SCRIPT_SRC': (
'https://www.google.com/recaptcha/',
'https://www.gstatic.com/recaptcha/'
),
'CHILD_SRC': ('https://www.google.com/recaptcha/',)
}
class ActivateRegistration(generic.DetailView):
"""
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
CLEANER = HtmlCleaner()
STATUS_REJECTED, STATUS_WAITING, STATUS_PUBLISHED = -1, 0, 1
STATUS_CHOICES = (
(STATUS_REJECTED, _('Rejected')),
(STATUS_WAITING, _('Waiting...')),
(STATUS_PUBLISHED, _('Published')),
)
cleaner = HtmlCleaner()
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

@@ -4,10 +4,14 @@ Created on 19.10.2011
@author: christian
"""
from bs4 import BeautifulSoup
#TODO: Nach BeatutifulSoup 4 convertieren
# TODO: Nach BeatutifulSoup 4 convertieren
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',
'big', 'blockquote', 'br', 'button', 'caption',
'center', 'cite',
@@ -23,7 +27,7 @@ class HtmlCleaner(object):
'tfoot', 'th',
'thead', 'tr', 'tt', 'u', 'ul', 'var']
ACCEPTABELE_ATTRIBUTES = [
ACCEPTABLE_ATTRIBUTES = [
'abbr', 'accept', 'accept-charset', 'accesskey',
'action', 'align', 'class', 'alt', 'axis',
'char', 'charoff', 'charset', 'checked', 'cite', 'clear', 'cols',
@@ -35,17 +39,28 @@ class HtmlCleaner(object):
'span', 'src', 'start', 'summary', 'tabindex', 'target', 'title',
'type', 'usemap', 'valign', 'value', 'vspace', 'width']
counter = 1
tag_removed = False
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()):
if attr not in self.ACCEPTABELE_ATTRIBUTES:
if attr not in self.ACCEPTABLE_ATTRIBUTES:
del tag[attr]
elif tag[attr].count('script:'):
del tag[attr]
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:
tag.extract() # remove the bad ones
self.tag_removed = True
@@ -55,12 +70,12 @@ class HtmlCleaner(object):
def clean_html(self, fragment=''):
"""
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:
soup = BeautifulSoup(fragment, "html.parser")
self.tag_removed = False
self.counter += 1
for tag in soup.find_all(True):
self.clean_tag(tag)
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.contrib.sites.models import Site
from django.conf import settings
from django.template import loader, Context
from django.template import loader
from django.contrib.auth import get_user_model
@@ -48,7 +47,7 @@ class MassMailer(object):
if isinstance(recipient, self.USER_MODEL):
self.recipients.add(recipient)
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):
for user in user_list:
@@ -56,10 +55,10 @@ class MassMailer(object):
self.log.debug('Adding %s as Recipient' % user)
self.recipients.add(user)
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):
mail_context = Context(self.context)
mail_context = self.context
mail_context['site'] = Site.objects.get_current()
self.txt_template = loader.get_template(self.txt_template)
@@ -103,7 +102,7 @@ class MassMailer(object):
try:
mail.send()
except:
self.log.warn("Mail failed for: %s", mail.to)
self.log.warning("Mail failed for: %s", mail.to)
else:
self.log.info("Mail sent successful to: %s" % mail.to)
self.close_smtp_connection()

View File

@@ -5,7 +5,8 @@ import html_cleaner
class HtmlTestCase(unittest.TestCase):
known_html = (
('<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">
<HTML><HEAD>
<META http-equiv=Content-Type content="text/html; charset=iso-8859-1">
@@ -32,8 +33,8 @@ class HtmlTestCase(unittest.TestCase):
href="http://www.xxxxxxxxxxxx.com">http://www.xxxxxxxxxxxx.com</A><BR>(xxx)
xxx-xxxx</FONT></DIV></BODY></HTML>
''',
'<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">'
)
'<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">'
)
)
def test_html_cleaner(self):
@@ -42,5 +43,6 @@ class HtmlTestCase(unittest.TestCase):
for original, tidy in self.known_html:
self.assertEqual(cleaner.clean_html(original), tidy)
if __name__ == '__main__':
unittest.main()