12 Commits

82 changed files with 756 additions and 668 deletions

View File

@@ -1,4 +1,4 @@
ASSESTS=requirements static ASSETS=requirements static
DEV_REQUIREMENTS=requirements/development.txt DEV_REQUIREMENTS=requirements/development.txt
DJANGO_SETTINGS_MODULE="kasu.settings" DJANGO_SETTINGS_MODULE="kasu.settings"
EXCLUDE_FILES=*.pyc EXCLUDE_FILES=*.pyc
@@ -16,7 +16,7 @@ all: cleanup migrate testserver
venv: $(VENV_PATH)/bin/activate venv: $(VENV_PATH)/bin/activate
$(VENV_PATH)/bin/activate: $(VENV_PATH)/bin/activate:
@test -d $(VENV_PATH) || python3 -m venv --clear $(VENV_PATH) @test -d $(VENV_PATH) || python3 -m venv --clear --system-site-packages $(VENV_PATH)
dev-requirements: venv ${DEV_REQUIREMENTS} dev-requirements: venv ${DEV_REQUIREMENTS}
${PYTHON} -m pip install -qU pip ${PYTHON} -m pip install -qU pip
@@ -35,7 +35,7 @@ requirements_remote:
sync_assets: sync_assets:
@echo "Syncing project assets ..." @echo "Syncing project assets ..."
rsync -qr --copy-links --delete ${ASSESTS} ${SSH_LOGIN}:~/ rsync -qr --copy-links --delete ${ASSETS} ${SSH_LOGIN}:~/
sync_src: sync_src:
@echo "Syncing Sourcecode ..." @echo "Syncing Sourcecode ..."
@@ -55,7 +55,7 @@ testserver: venv
messages: venv messages: venv
@echo "aktualisiere Übersetzungen..." @echo "aktualisiere Übersetzungen..."
@(for d in ${SRC_PATH}/*; do \ @(for d in ${SRC_PATH}/*; do \
test -d $$d/locale && cd $$d && ${VENV_PATH}/bin/django-admin.py makemessages -l de;\ test -d $$d/locale && cd $$d && ${VENV_PATH}/bin/django-admin makemessages -l de;\
done) done)
${MANAGE_PY} compilemessages -v0 ${MANAGE_PY} compilemessages -v0

42
pyproject.toml Normal file
View File

@@ -0,0 +1,42 @@
[project]
name = "kasu"
version = "4.230918"
description = "Homepage CMS for Kasu.at"
authors = [
{ name = "Christian Berg", email = "xeniac@xendynastie.at" }
]
requires-python = ">=3.8"
dependencies = ["beautifulsoup4",
"django < 5.0",
"django-appconf",
"django-ckeditor",
"django-contrib-comments",
"django-csp",
"django-compressor",
"django-extra-views",
"django-markdown",
"django-recaptcha",
"easy_thumbnails[svg]",
"icalendar",
"openpyxl",
"markdown",
"pillow",
"psycopg2-binary",
"PyJWT",
"pytz",
"requests",
"requests-oauthlib"
]
[project.optional-dependencies]
dev = [
"django-debug-toolbar",
"django-rosetta",
"sqlparse",
"pylint>=2.0",
"pylint-django"
]
[tool.setuptools.packages.find]
where = ["src"]

View File

@@ -1,5 +1,5 @@
beautifulsoup4 beautifulsoup4
django < 3.0 django < 5.0
django-appconf django-appconf
django-ckeditor django-ckeditor
django-contrib-comments django-contrib-comments
@@ -8,7 +8,7 @@ django-compressor
django-extra-views django-extra-views
django-markdown django-markdown
django-recaptcha django-recaptcha
git+https://github.com/SmileyChris/easy-thumbnails.git easy_thumbnails[svg]
icalendar icalendar
openpyxl openpyxl
markdown markdown

View File

@@ -19,9 +19,9 @@ def content_menus(request):
:param request: a Django request object :param request: a Django request object
:return: a dict with the template variables mentioned above :return: a dict with the template variables mentioned above
""" """
current_page = models.Page() current_page: models.Page = models.Page.objects.get(slug='index')
current_top_page = models.Page() current_top_page: models.Page = models.Page.objects.get(slug='index')
current_path = request.path_info[1:request.path_info.rfind('.')] current_path: str = request.path_info[1:request.path_info.rfind('.')]
# erzeuge das Top-Level Menü # erzeuge das Top-Level Menü
top_level_pages = cache.get('top_level_pages') top_level_pages = cache.get('top_level_pages')

View File

@@ -3,7 +3,7 @@ import django_comments as comments
from django.conf import settings from django.conf import settings
from django.contrib.syndication.views import Feed from django.contrib.syndication.views import Feed
from django.utils.feedgenerator import Rss201rev2Feed from django.utils.feedgenerator import Rss201rev2Feed
from django.utils.translation import ugettext as _ from django.utils.translation import gettext as _
from content.models import Article from content.models import Article
@@ -11,15 +11,15 @@ MAX_ARTICLE_ITEMS = 10 # Maximum count of articles in the news RSS feed.
MAX_COMMENT_ITEMS = 40 # Maximum count of comments in the comments RSS feed. MAX_COMMENT_ITEMS = 40 # Maximum count of comments in the comments RSS feed.
# Start ignoring PyLintBear (R0201) # noinspection PyMethodMayBeStatic
class LatestNews(Feed): class LatestNews(Feed):
""" An Feed with the latest news from this site """ """ An Feed with the latest news from this site """
link = "http://www.kasu.at/"
description = _("Current news from Kasu") description = _("Current news from Kasu")
title = "Kasu - traditonelle asiatische Spielkultur" link = "http://www.kasu.at/"
title = _("Current news from Kasu")
feed_type = Rss201rev2Feed feed_type = Rss201rev2Feed
def items(self): def items(self) -> object:
"""get the newest published news articles for the feed.""" """get the newest published news articles for the feed."""
return Article.objects.published()[:MAX_ARTICLE_ITEMS] return Article.objects.published()[:MAX_ARTICLE_ITEMS]
@@ -78,5 +78,3 @@ class LatestComments(Feed):
'user_name': item.user_name, 'user_name': item.user_name,
'content_object': item.content_object 'content_object': item.content_object
} }
# Stop ignoring

View File

@@ -5,7 +5,7 @@ Created on 04.10.2011
""" """
from django import forms from django import forms
from django.template.defaultfilters import slugify from django.template.defaultfilters import slugify
from django.utils.translation import ugettext as _ from django.utils.translation import gettext as _
from . import models from . import models

View File

@@ -8,7 +8,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: kasu.content\n" "Project-Id-Version: kasu.content\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-12-13 23:38+0100\n" "POT-Creation-Date: 2023-07-27 00:05+0200\n"
"PO-Revision-Date: 2018-01-12 15:25+0105\n" "PO-Revision-Date: 2018-01-12 15:25+0105\n"
"Last-Translator: b'Christian Berg <kasu@xendynastie.at>'\n" "Last-Translator: b'Christian Berg <kasu@xendynastie.at>'\n"
"Language-Team: Deutsch <>\n" "Language-Team: Deutsch <>\n"
@@ -206,25 +206,27 @@ msgstr "Erstellt am"
msgid "share on" msgid "share on"
msgstr "Teile auf" msgstr "Teile auf"
#: templates/content/article_detail.html:48 views.py:156 #: templates/content/article_detail.html:48 views.py:159
msgid "Edit Article" msgid "Edit Article"
msgstr "Artikel bearbeiten" msgstr "Artikel bearbeiten"
#: templates/content/article_form.html:32 templates/content/page_form.html:42 #: templates/content/article_form.html:24
#: templates/content/article_form.html:31 templates/content/page_form.html:42
#: templates/content/page_form.html:49 #: templates/content/page_form.html:49
msgid "German" msgid "German"
msgstr "Deutsch" msgstr "Deutsch"
#: templates/content/article_form.html:33 templates/content/page_form.html:43 #: templates/content/article_form.html:25
#: templates/content/article_form.html:39 templates/content/page_form.html:43
#: templates/content/page_form.html:57 #: templates/content/page_form.html:57
msgid "English" msgid "English"
msgstr "Englisch" msgstr "Englisch"
#: templates/content/article_form.html:59 templates/content/page_form.html:66 #: templates/content/article_form.html:47 templates/content/page_form.html:66
msgid "reset" msgid "reset"
msgstr "Zurücksetzen" msgstr "Zurücksetzen"
#: templates/content/article_form.html:60 templates/content/page_form.html:67 #: templates/content/article_form.html:48 templates/content/page_form.html:67
msgid "save" msgid "save"
msgstr "Speichern" msgstr "Speichern"
@@ -249,16 +251,16 @@ msgstr "HTML spezifisch"
msgid "This Category does not exist." msgid "This Category does not exist."
msgstr "Diese Kategorie existiert nicht." msgstr "Diese Kategorie existiert nicht."
#: views.py:157 #: views.py:160
msgid "Create Article" msgid "Create Article"
msgstr "Artikel erstellen" msgstr "Artikel erstellen"
#: views.py:237 #: views.py:240
#, python-format #, python-format
msgid "No Page found matching the Path %s" msgid "No Page found matching the Path %s"
msgstr "Keine Seite unter dem Pfad %s gefunden" msgstr "Keine Seite unter dem Pfad %s gefunden"
#: views.py:266 #: views.py:269
#, python-format #, python-format
msgid "No PDF Document found matching the Path %s" msgid "No PDF Document found matching the Path %s"
msgstr "Kein PDF Dokument unter dem Pfad %s gefunden." msgstr "Kein PDF Dokument unter dem Pfad %s gefunden."

View File

@@ -0,0 +1,18 @@
# Generated by Django 4.2.2 on 2023-06-11 09:26
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('content', '0008_auto_20190106_1954'),
]
operations = [
migrations.AlterField(
model_name='page',
name='id',
field=models.AutoField(primary_key=True, serialize=False),
),
]

View File

@@ -0,0 +1,19 @@
# Generated by Django 4.2.2 on 2023-06-11 13:41
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('content', '0009_alter_page_id'),
]
operations = [
migrations.AlterField(
model_name='page',
name='parent',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='subpages', to='content.page'),
),
]

View File

@@ -0,0 +1,18 @@
# Generated by Django 4.2.2 on 2023-07-19 18:11
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('content', '0010_alter_page_parent'),
]
operations = [
migrations.AlterField(
model_name='page',
name='id',
field=models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'),
),
]

View File

@@ -8,7 +8,7 @@ from django.template.defaultfilters import slugify
from django.urls import reverse from django.urls import reverse
from django.utils import timezone from django.utils import timezone
from django.utils.safestring import mark_safe from django.utils.safestring import mark_safe
from django.utils.translation import get_language, ugettext as _ from django.utils.translation import get_language, gettext as _
from utils import STATUS_CHOICES, STATUS_WAITING, STATUS_PUBLISHED, CLEANER from utils import STATUS_CHOICES, STATUS_WAITING, STATUS_PUBLISHED, CLEANER
@@ -111,6 +111,7 @@ class Article(models.Model):
"""Returns the headline of this article.""" """Returns the headline of this article."""
return self.headline return self.headline
# noinspection PyUnresolvedReferences
@property @property
def get_image(self): def get_image(self):
"""Return the article image, or the category image if unset.""" """Return the article image, or the category image if unset."""
@@ -169,6 +170,7 @@ class Category(models.Model):
return self.name return self.name
# noinspection PyUnresolvedReferences
class Page(models.Model): class Page(models.Model):
"""A page on this homepage. It can have a "static" HTML page, the URL of a """A page on this homepage. It can have a "static" HTML page, the URL of a
dynamic Django view, or a PDF document. dynamic Django view, or a PDF document.
@@ -216,7 +218,7 @@ class Page(models.Model):
blank=True, blank=True,
null=True, null=True,
related_name='subpages', related_name='subpages',
on_delete=models.SET_NULL on_delete=models.CASCADE
) )
position = models.PositiveSmallIntegerField( position = models.PositiveSmallIntegerField(
blank=True, blank=True,
@@ -317,12 +319,12 @@ class Page(models.Model):
raise ValidationError( raise ValidationError(
_(u'Please upload a PDF-File to this PDF-Page.')) _(u'Please upload a PDF-File to this PDF-Page.'))
def get_absolute_url(self): def get_absolute_url(self) -> str:
"""Return the path with an extension that matches the content type. """Return the path with an extension that matches the content type.
It's useful for an user to match the URL to the contenttype. It's useful for a user to match the URL to the contenttype.
:return: sting with the absolute URL of this page.""" :return: string with the absolute URL of this page."""
return '/' + self.path + CONTENT_TYPE_EXTENSIONS[self.content_type] return '/' + str(self.path) + CONTENT_TYPE_EXTENSIONS[self.content_type]
class Meta(object): class Meta(object):
"""Set default ordering and an unique priamry key to avoid dupes.""" """Set default ordering and an unique priamry key to avoid dupes."""

View File

@@ -4,26 +4,19 @@ Created on 03.10.2011
@author: christian @author: christian
""" """
from django.conf.urls import url from django.urls import path
from .views import ArticleArchiveIndex, ArticleForm, ArticleYearArchive, \ from .views import ArticleArchiveIndex, ArticleForm, ArticleYearArchive, \
ArticleMonthArchive, ArticleDetail ArticleMonthArchive, ArticleDetail
urlpatterns = [ urlpatterns = [
url(r'^$', ArticleArchiveIndex.as_view(), name='article-archive'), path("", ArticleArchiveIndex.as_view(), name='article-archive'),
url(r'^add/$', ArticleForm.as_view(), name='add-article'), path('add/', ArticleForm.as_view(), name='add-article'),
url(r'^edit/(?P<pk>[\d]+)/$', ArticleForm.as_view(), name='edit-article'), path('edit/<int:pk>/', ArticleForm.as_view(), name='edit-article'),
url(r'^(?P<year>[\d]{4})/$', ArticleYearArchive.as_view(), path('<int:year>/', ArticleYearArchive.as_view(), name='article-archive'),
name='article-archive'), path('<int:year>/<int:month>/', ArticleMonthArchive.as_view(), name='article-archive'),
url(r'^(?P<year>[\d]{4})/(?P<month>[\d]+)/$', ArticleMonthArchive.as_view(), path('<int:year>/<int:month>/<slug:slug>/', ArticleDetail.as_view(), name='show-article'),
name='article-archive'), path('<slug:category>/', ArticleArchiveIndex.as_view(), name='article-archive'),
url(r'^(?P<year>[\d]{4})/(?P<month>[\d]+)/(?P<slug>[\-\d\w]+)/$', path('<slug:category>/<int:year>/', ArticleYearArchive.as_view(), name='article-archive'),
ArticleDetail.as_view(), name='show-article'), path('<slug:category>/<int:year>/<int:month>/', ArticleMonthArchive.as_view(), name='article-archive'),
url(r'^(?P<category>[\-\d\w]+)/$', ArticleArchiveIndex.as_view(),
name='article-archive'),
url(r'^(?P<category>[\-\d\w]+)/(?P<year>[\d]{4})/$',
ArticleYearArchive.as_view(), name='article-archive'),
url(r'^(?P<category>[\-\d\w]+)/(?P<year>[\d]{4})/(?P<month>[\d]+)/$',
ArticleMonthArchive.as_view(), name='article-archive'),
] ]

View File

@@ -8,7 +8,7 @@ class ArticleSitemap(GenericSitemap):
min_priority = 0.25 min_priority = 0.25
@staticmethod @staticmethod
def items(): def items(**kwargs):
"""only add published articles to the sitemap""" """only add published articles to the sitemap"""
return Article.objects.published() return Article.objects.published()
@@ -18,6 +18,6 @@ class PageSitemap(GenericSitemap):
min_priority = 0.5 min_priority = 0.5
@staticmethod @staticmethod
def items(): def items(**kwargs):
"""add all pages to the sitemap.""" """add all pages to the sitemap."""
return Page.objects.all() return Page.objects.all()

View File

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

View File

@@ -9,56 +9,44 @@
{% endblock %} {% endblock %}
{% block maincontent %} {% block maincontent %}
{% get_fieldset "category, image" from form as fieldset_common %}
{% get_fieldset "headline_de" from form as fieldset_de %}
{% get_fieldset "headline_en" from form as fieldset_en %}
<form action="" method="post" enctype="multipart/form-data"> <form action="" method="post" enctype="multipart/form-data">
{{ form.media }} {% csrf_token %} {{ form.media }} {% csrf_token %}
<fieldset> <fieldset>
<div> {% with fieldset_common as form %}{% include "form.html" %}{% endwith %}
<label for="id_category" class="field_name {{ form.category.css_classes }}">{{ form.category.label}}</label> </fieldset>
{{ form.category }}
{% if form.category.help_text %}<p class="help_text">{{form.category.help_text}}</p>{% endif %}
{% if form.category.errors %}{{ form.category.errors }}{% endif %}
</div>
<div>
<label for="id_image" class="field_name {{ form.image.css_classes }}">{{ form.image.label}}</label>
{{ form.image }}
{% if form.image.help_text %}<p class="help_text">{{form.image.help_text}}</p>{% endif %}
{% if form.image.errors %}{{ form.image.errors }}{% endif %}
</div>
</fieldset> <ul class="tabs">
<li><a href="#de">{% trans "German" %}</a></li>
<li><a href="#en">{% trans "English" %}</a></li>
</ul>
<ul class="tabs grid_12"> <div class="tab_container">
<li><a href="#de">{% trans "German" %}</a></li> <section id="de" class="tab_content">
<li><a href="#en">{% trans "English" %}</a></li> <fieldset class="grid_12">
</ul> <legend>{% trans "German" %}</legend>
<label for="id_{{ form.headline_de.html_name }}" class="fieldname {{ form.headline_de.css_classes }}"> {% with fieldset_de as form %}{% include "form.html" %}{% endwith %}
{{ form.headline_de.label }}</label> </fieldset>
<p class="grid_10">{{ form.headline_de }}</p> <br>
{{form.content_de}}
<div class="tab_container"> </section>
<div id="de" class="tab_content"> <section id="en" class="tab_content">
<fieldset> <fieldset class="grid_12">
<legend>Deutsch</legend> <legend>{% trans "English" %}</legend>
<label for="id_{{ form.headline_de.html_name }}" class="fieldname {{ form.headline_de.css_classes }}"> {% with fieldset_en as form %}{% include "form.html" %}{% endwith %}
{{ form.headline_de.label }}</label> </fieldset>
<p class="grid_10">{{ form.headline_de }}</p> <br>
</fieldset> {{form.content_en}}
{{ form.content_de }} </section>
</div> </div>
<div id="en" class="tab_content"> <p class="buttonbar">
<h3 class="grid_12">English</h3> <button type="reset"><span class="fa fa-undo"></span> {% trans 'reset' %}</button>
<label for="id_{{ form.headline_en.html_name }}" class="grid_2 {{ form.headline_en.css_classes }}"> <button type="submit"><span class="fa fa-hdd-o"></span> {% trans 'save' %}</button>
{{ form.headline_en.label }}</label> </p>
<p class="grid_10">{{ form.headline_en }}</p>
<p>&nbsp;</p>
{{ form.content_en }}
</div>
</div>
<p class="buttonbar">
<button type="reset"><span class="fa fa-undo"></span> {% trans 'reset' %}</button>
<button type="submit"><span class="fa fa-hdd-o"></span> {% trans 'save' %}</button>
</p>
</form>{% endblock %} </form>{% endblock %}
{% block buttonbar %}{% endblock %} {% block buttonbar %}{% endblock %}

View File

@@ -1,66 +0,0 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>{% block title %}{{page.title}}{% endblock %}</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<style>
@font-face {
font-family: 'Amerika Sans';
src: url('{{ STATIC_ROOT }}/fonts/amerika_sans.ttf');
}
@font-face {
font-family: 'Philosopher';
src: url('{{ STATIC_ROOT }}/fonts/philosopher-regular.ttf');
font-weight: normal;
font-style: normal;
}
h1,h2,h3,h4,h5,h6 {
font-family: 'Amerika Sans', Helvetica;
font-variant: small-caps;
font-weight: bold;
margin: 0.5em 0 0.2em 0;
}
body {
font-family: Philosopher;
font-size: 12pt;
}
#page_header {
text-align: right;
}
@page {
margin-right: 0;
margin-bottom: 0;
margin-top: 35mm;
margin-left: 2cm;
@frame header {
-pdf-frame-content : page_header;
top: 0;
margin: 5mm;
height: 4cm;
}
@frame footer {
-pdf-frame-content: page_footer;
bottom: 0cm;
height: 2cm;
margin: 5mm;
}
}
</style>
</head>
<body>
<h1>{{ page.title }}</h1>
{{ page.content }}
<div id="page_header">
<img src="{{STATIC_ROOT}}/img/logo.png" alt="Kasu">
</div>
<div id="page_footer">
{{ page.title }} Seite:
<pdf:pagenumber />
</div>
</body>
</html>

View File

@@ -5,7 +5,7 @@ from csp.decorators import csp_update
from django.conf import settings from django.conf import settings
from django.contrib.auth.mixins import PermissionRequiredMixin from django.contrib.auth.mixins import PermissionRequiredMixin
from django.http import HttpResponse, Http404 from django.http import HttpResponse, Http404
from django.utils.translation import ugettext as _ from django.utils.translation import gettext as _
from django.views import generic from django.views import generic
from . import models, forms from . import models, forms
@@ -19,7 +19,7 @@ class WYSIWYGEditorMixin(PermissionRequiredMixin):
@csp_update(SCRIPT_SRC="'unsafe-eval'") @csp_update(SCRIPT_SRC="'unsafe-eval'")
def dispatch(self, request, *args, **kwargs): def dispatch(self, request, *args, **kwargs):
"""Add "unsafe-eval" to the Content-Secuirty-Policy HTTP Headers for the """Add "unsafe-eval" to the Content-Security-Policy HTTP Headers for the
WYSIWYG Editor. WYSIWYG Editor.
:param request: the HTTP Request Object :param request: the HTTP Request Object
@@ -40,7 +40,7 @@ class ArticleArchiveMixin(object):
Filter the queryset by category if one has been specified in the URL Filter the queryset by category if one has been specified in the URL
:param queryset: an model.Article.objects Queryset :param queryset: an model.Article.objects Queryset
:return: an model.Article.objects Queryset filterd by category :return: an model.Article.objects Queryset filtered by category
""" """
category_slug = self.kwargs.get('category') category_slug = self.kwargs.get('category')
@@ -90,7 +90,7 @@ class ArticleArchiveIndex(ArticleArchiveMixin, generic.ArchiveIndexView):
class ArticleYearArchive(ArticleArchiveMixin, generic.YearArchiveView): class ArticleYearArchive(ArticleArchiveMixin, generic.YearArchiveView):
""" """
Displays the Articles filterd by a specific year Displays the Articles filtered by a specific year
""" """
queryset = models.Article.objects.filter(status=models.STATUS_PUBLISHED) queryset = models.Article.objects.filter(status=models.STATUS_PUBLISHED)
@@ -113,7 +113,7 @@ class ArticleYearArchive(ArticleArchiveMixin, generic.YearArchiveView):
class ArticleMonthArchive(ArticleArchiveMixin, generic.MonthArchiveView): class ArticleMonthArchive(ArticleArchiveMixin, generic.MonthArchiveView):
""" """
Displays the Articles filterd by a specific month Displays the Articles filtered by a specific month
""" """
queryset = models.Article.objects.filter(status=models.STATUS_PUBLISHED) queryset = models.Article.objects.filter(status=models.STATUS_PUBLISHED)
@@ -139,7 +139,11 @@ class ArticleDetail(generic.DetailView):
Render the news Article, but only if it got published. Render the news Article, but only if it got published.
""" """
queryset = models.Article.objects.filter(status=models.STATUS_PUBLISHED) def get_queryset(self):
queryset = models.Article.objects.filter(status=models.STATUS_PUBLISHED)
queryset = queryset.filter(date_created__year=self.kwargs['year'])
queryset = queryset.filter(date_created__month=self.kwargs['month'])
return queryset
class ArticleForm(WYSIWYGEditorMixin, generic.UpdateView): class ArticleForm(WYSIWYGEditorMixin, generic.UpdateView):
@@ -170,7 +174,7 @@ class ArticleForm(WYSIWYGEditorMixin, generic.UpdateView):
class PageAddForm(WYSIWYGEditorMixin, generic.CreateView): class PageAddForm(WYSIWYGEditorMixin, generic.CreateView):
""" Renders an Form to create a new page for users with conforming """ Renders a Form to create a new page for users with conforming
permissions.""" permissions."""
form_class = forms.PageForm form_class = forms.PageForm
@@ -192,7 +196,7 @@ class PageAddForm(WYSIWYGEditorMixin, generic.CreateView):
class PageEditForm(WYSIWYGEditorMixin, generic.UpdateView): class PageEditForm(WYSIWYGEditorMixin, generic.UpdateView):
"""Renders an Form to edit a page for users with conforming permissions.""" """Renders a Form to edit a page for users with conforming permissions."""
form_class = forms.PageForm form_class = forms.PageForm
model = models.Page model = models.Page
@@ -201,7 +205,7 @@ class PageEditForm(WYSIWYGEditorMixin, generic.UpdateView):
def get_object(self, queryset=None): def get_object(self, queryset=None):
""" Get the path from the URL and fetch the corresponding page. """ Get the path from the URL and fetch the corresponding page.
First get the path wihout fileextentsion leading or trailing slashes, First get the path without file extension leading or trailing slashes,
then search in the database if such a page exists. then search in the database if such a page exists.
:param queryset: Get the single item from this filtered queryset. :param queryset: Get the single item from this filtered queryset.
@@ -270,9 +274,9 @@ class PagePdf(generic.DeleteView):
def render_to_response(self, context, **response_kwargs): def render_to_response(self, context, **response_kwargs):
"""Stream the PDF File to the client and set the right content headers. """Stream the PDF File to the client and set the right content headers.
:param context: useless only for compatility :param context: useless only for compatibility
:param response_kwargs: will be added to the HttpResponse kwargs. :param response_kwargs: will be added to the HttpResponse kwargs.
:return: an HTTPResponse with PDF Content or an Http404 exception :return: an HTTPResponse with PDF Content or a Http404 exception
""" """
try: try:
with open(self.object.pdf_file.path, 'rb') as pdf_file: with open(self.object.pdf_file.path, 'rb') as pdf_file:
@@ -282,8 +286,8 @@ class PagePdf(generic.DeleteView):
**response_kwargs **response_kwargs
) )
return response return response
except: except FileExistsError:
raise Http404('File not Found %s.pdf' % self.kwargs['path']) raise Http404('File %s.pdf not found' % self.kwargs['path'])
class StartPage(generic.TemplateView): class StartPage(generic.TemplateView):
@@ -291,7 +295,7 @@ class StartPage(generic.TemplateView):
template_name = 'index.html' template_name = 'index.html'
def get_context_data(self): def get_context_data(self):
""" Adds recent ariticles and recent comments to the context. """ Adds recent articles and recent comments to the context.
:return: array() with the context data :return: array() with the context data
""" """

View File

@@ -1,6 +1,6 @@
"""Django admin interface for the event app. """Django admin interface for the event app.
It's the best way to add eventseries, or edit/delete events.""" It's the best way to add event-series, or edit/delete events."""
from django.contrib import admin from django.contrib import admin
from django.utils.translation import gettext as _ from django.utils.translation import gettext as _
@@ -8,7 +8,7 @@ from events.models import Event, Photo, Location
class EventInline(admin.TabularInline): class EventInline(admin.TabularInline):
"""To list events of an eventseries below the 'master event'""" """To list events of an event-series below the 'master event'"""
model = Event model = Event
fields = ('name', 'start', 'end') fields = ('name', 'start', 'end')
verbose_name_plural = _('Event Series') verbose_name_plural = _('Event Series')

View File

@@ -1,20 +1,20 @@
""" Content processor to display upcoming events on every page you want. """ """ Content processor to display upcoming events on every page you want. """
from django.core.cache import cache from django.core.cache import cache
from django.http import HttpRequest
from .models import Event from .models import Event
def events_overview(request): # Ignore PyLintBear (W0613) def events_overview(request: HttpRequest) -> dict[str, Event]:
""" """
Adds event information as variables to the template context on every page. Adds event information as variables to the template context on every page.
For speed reasons everything will be cached for an hour. the following For speed reasons everything will be cached for an hour. the following
variables will be added to the template context: variables will be added to the template context:
* current_event: If an event is running at this moment, the correspondi * current_event: If an event is running at this moment, the corresponding event object.
event object.
* next_event: the next event that is upcoming. * next_event: the next event that is upcoming.
* upcoming_events: the next 3 events that are upcoming. * upcoming_events: the next 3 events that are upcoming.
:param request: An Django HTTPRequest object :param request: a Django HTTPRequest object
:return: dict() with the new context variables :return: dict() with the new context variables
""" """
current_event = cache.get('current_event', False) current_event = cache.get('current_event', False)

View File

@@ -1,27 +1,42 @@
"""Django Forms to administrate the event content on the frontend.""" """Django Forms to administrate the event content on the frontend."""
from django import forms from django import forms
from django.contrib.auth import get_user_model from django.contrib.auth import get_user_model
from django.utils.translation import ugettext as _ from django.utils.translation import gettext as _
from . import models from . import models
class ClearableMultipleFileInput(forms.widgets.ClearableFileInput):
allow_multiple_selected = True
accept = "image/jpg"
class MultipleFileField(forms.FileField):
def __init__(self, *args, **kwargs):
kwargs.setdefault("widget", ClearableMultipleFileInput())
super().__init__(*args, **kwargs)
def clean(self, data, initial=None):
single_file_clean = super().clean
if isinstance(data, (list, tuple)):
result = [single_file_clean(d, initial) for d in data]
else:
result = single_file_clean(data, initial)
return result
class PhotoUploadForm(forms.Form): class PhotoUploadForm(forms.Form):
"""Form to upload multiple photos to a single event.""" """Form to upload multiple photos to a single event.
TODO: Check multiple upload
"""
error_css_class = 'error' error_css_class = 'error'
required_css_class = 'required' required_css_class = 'required'
photographer = forms.ModelChoiceField(get_user_model().objects.all(), photographer = forms.ModelChoiceField(get_user_model().objects.all(),
required=True, ) required=True, )
event = forms.ModelChoiceField(models.Event.objects.all(), required=True, ) event = forms.ModelChoiceField(models.Event.objects.all(), required=True, )
upload = forms.FileField( upload = MultipleFileField(
label=_('Images'), label=_('Images'),
required=True, required=True,
widget=forms.widgets.ClearableFileInput(
attrs={
'multiple': 'multiple',
'accept': "image/gif,image/png,image/jpeg"
}
)
) )

View File

@@ -1,18 +1,12 @@
""" urls for the event gallery part of the events app. """ from django.urls import path
from django.conf.urls import url
from . import views from . import views
urlpatterns = [ urlpatterns = [
url(r'^$', views.EventGallery.as_view(), name='event-gallery'), path("", views.EventGallery.as_view(), name='event-gallery'),
url(r'^(?P<event>[\d]+)/$', views.EventPhotoList.as_view(), path('<int:event>/', views.EventPhotoList.as_view(), name='event-photo-list'),
name='event-photo-list'), path('<int:event>/upload/', views.EventPhotoUpload.as_view(), name='event-photo-upload'),
url(r'^(?P<event>[\d]+)/upload/$', views.EventPhotoUpload.as_view(), path('<int:event>/<int:pk>/', views.EventPhoto.as_view(), name='event-photo'),
name='event-photo-upload'), path('delete/<int:pk>/', views.DeleteEventPhoto.as_view(), name='delete-event-photo'),
url(r'^(?P<event>[\d]+)/(?P<pk>[\d]+)/$', views.EventPhoto.as_view(), path('upload/', views.EventPhotoUpload.as_view(), name='event-photo-upload'),
name='event-photo'),
url(r'^delete/(?P<pk>[\d]+)/$', views.DeleteEventPhoto.as_view(),
name='delete-event-photo'),
url(r'^upload/$', views.EventPhotoUpload.as_view(),
name='event-photo-upload'),
] ]

View File

@@ -7,7 +7,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: kasu.events\n" "Project-Id-Version: kasu.events\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-12-13 23:38+0100\n" "POT-Creation-Date: 2023-07-26 18:31+0200\n"
"PO-Revision-Date: 2018-01-12 15:25+0105\n" "PO-Revision-Date: 2018-01-12 15:25+0105\n"
"Last-Translator: b'Christian Berg <kasu@xendynastie.at>'\n" "Last-Translator: b'Christian Berg <kasu@xendynastie.at>'\n"
"Language-Team: Kasu <verein@kasu.at>\n" "Language-Team: Kasu <verein@kasu.at>\n"
@@ -23,19 +23,19 @@ msgstr ""
msgid "Event Series" msgid "Event Series"
msgstr "Veranstaltungsreihen" msgstr "Veranstaltungsreihen"
#: forms.py:17 #: forms.py:38
msgid "Images" msgid "Images"
msgstr "Bilder" msgstr "Bilder"
#: forms.py:46 #: forms.py:61
msgid "start" msgid "start"
msgstr "Beginn" msgstr "Beginn"
#: forms.py:49 #: forms.py:64
msgid "end" msgid "end"
msgstr "Ende" msgstr "Ende"
#: mixins.py:76 #: mixins.py:87
msgid "Event does not exist" msgid "Event does not exist"
msgstr "Veranstaltung gibt es nicht" msgstr "Veranstaltung gibt es nicht"
@@ -242,7 +242,7 @@ msgid "Show on Google Maps"
msgstr "Auf Google Maps zeigen" msgstr "Auf Google Maps zeigen"
#: templates/events/event_detail.html:127 templates/events/event_form.html:9 #: templates/events/event_detail.html:127 templates/events/event_form.html:9
#: views.py:62 #: views.py:63
msgid "Edit Event" msgid "Edit Event"
msgstr "Termin bearbeiten" msgstr "Termin bearbeiten"
@@ -250,7 +250,7 @@ msgstr "Termin bearbeiten"
msgid "Add Dates" msgid "Add Dates"
msgstr "Termine hinzufügen" msgstr "Termine hinzufügen"
#: templates/events/event_form.html:9 templates/events/page.html:9 views.py:64 #: templates/events/event_form.html:9 templates/events/page.html:9 views.py:65
msgid "Add Event" msgid "Add Event"
msgstr "Neuer Termin" msgstr "Neuer Termin"

View File

@@ -3,7 +3,7 @@ from __future__ import unicode_literals
from django.db import migrations, models from django.db import migrations, models
import datetime import datetime
from django.utils.timezone import utc from datetime import timezone
class Migration(migrations.Migration): class Migration(migrations.Migration):
@@ -23,7 +23,7 @@ class Migration(migrations.Migration):
model_name='event', model_name='event',
name='date_modified', name='date_modified',
field=models.DateTimeField(default=datetime.datetime( field=models.DateTimeField(default=datetime.datetime(
2016, 10, 12, 20, 24, 39, 910492, tzinfo=utc), verbose_name='latest updated at', auto_now=True), 2016, 10, 12, 20, 24, 39, 910492, tzinfo=timezone.utc), verbose_name='latest updated at', auto_now=True),
preserve_default=False, preserve_default=False,
), ),
migrations.AddField( migrations.AddField(
@@ -36,7 +36,7 @@ class Migration(migrations.Migration):
model_name='location', model_name='location',
name='date_modified', name='date_modified',
field=models.DateTimeField(default=datetime.datetime( field=models.DateTimeField(default=datetime.datetime(
2016, 10, 12, 20, 24, 44, 566305, tzinfo=utc), verbose_name='latest updated at', auto_now=True), 2016, 10, 12, 20, 24, 44, 566305, tzinfo=timezone.utc), verbose_name='latest updated at', auto_now=True),
preserve_default=False, preserve_default=False,
), ),
migrations.AddField( migrations.AddField(
@@ -49,7 +49,7 @@ class Migration(migrations.Migration):
model_name='photo', model_name='photo',
name='date_modified', name='date_modified',
field=models.DateTimeField(default=datetime.datetime( field=models.DateTimeField(default=datetime.datetime(
2016, 10, 12, 20, 24, 50, 509970, tzinfo=utc), verbose_name='latest updated at', auto_now=True), 2016, 10, 12, 20, 24, 50, 509970, tzinfo=timezone.utc), verbose_name='latest updated at', auto_now=True),
preserve_default=False, preserve_default=False,
), ),
] ]

View File

@@ -1,8 +1,9 @@
"""Mixins for Events.""" """Mixins for Events."""
from django.core.exceptions import ImproperlyConfigured
from django.db.models import Q from django.db.models import Q
from django.http import Http404 from django.http import Http404
from django.shortcuts import get_object_or_404 from django.shortcuts import get_object_or_404
from django.utils.translation import ugettext as _ from django.utils.translation import gettext as _
from . import models from . import models
@@ -32,19 +33,18 @@ class EventDetailMixin(object):
event = None event = None
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
"""Add self.event or the related event of self.object to the template """Add this event or the related event of the given object to the template context.
context.
:return: TemplateContext object""" :return: TemplateContext object"""
context = super(EventDetailMixin, self).get_context_data(**kwargs) context = super(EventDetailMixin, self).get_context_data(**kwargs)
if hasattr(self, 'event'): if getattr(self, 'event'):
context['event'] = self.event context['event'] = self.event
elif hasattr(self, 'object') and isinstance(self.object, models.Event): elif isinstance(getattr(self, 'object'), models.Event):
context['event'] = self.object context['event'] = self.object
elif hasattr(self, 'object') and hasattr(self.object, 'event'): elif getattr(getattr(self, 'object'), 'event'):
context['event'] = self.object.event context['event'] = self.object.event
else: else:
print("No Event in Context!") raise ImproperlyConfigured("No Event in Context!")
return context return context
def get_queryset(self): def get_queryset(self):
@@ -55,11 +55,21 @@ class EventDetailMixin(object):
""" """
if self.model == models.Event: if self.model == models.Event:
self.event = get_object_or_404(models.Event, pk=self.kwargs['pk']) self.event = get_object_or_404(models.Event, pk=self.kwargs['pk'])
queryset = self.model.objects.all() queryset = self.model._default_manager.all()
else: elif self.kwargs.get('event'):
self.event = get_object_or_404(models.Event, self.event = get_object_or_404(
pk=self.kwargs['event']) models.Event,
pk=self.kwargs['event'])
queryset = self.model.objects.filter(event=self.event) queryset = self.model.objects.filter(event=self.event)
elif self.model:
queryset = self.model._default_manager.all()
else:
raise ImproperlyConfigured(
"%(cls)s is missing a QuerySet. Define "
"%(cls)s.model, %(cls)s.queryset, or override "
"%(cls)s.get_queryset()." % {
'cls': self.__class__.__name__
})
return queryset.prefetch_related() return queryset.prefetch_related()

View File

@@ -1,4 +1,4 @@
"""Models to solitary events, events series with an location and photos.""" """Models to solitary events, events series with a location and photos."""
import os import os
from ckeditor.fields import RichTextField from ckeditor.fields import RichTextField
@@ -9,21 +9,22 @@ from django.db.models import Q
from django.template.defaultfilters import slugify from django.template.defaultfilters import slugify
from django.urls import reverse from django.urls import reverse
from django.utils.timezone import now from django.utils.timezone import now
from django.utils.translation import ugettext as _ from django.utils.translation import gettext as _
from easy_thumbnails.fields import ThumbnailerImageField from easy_thumbnails.fields import ThumbnailerImageField
from utils import COUNTRIES, OverwriteStorage from utils import COUNTRIES, OverwriteStorage
from .managers import EventManager from .managers import EventManager
def get_upload_path(instance, filename): def get_upload_path(instance: models.Model, filename: str) -> str:
""" """
Generates the desired file path and filename for an uploaded Image. Generates the desired file path and filename for an uploaded Image.
With this function Django can save the uploaded images to subfolders that With this function Django can save the uploaded images to a subfolder that
also have a meaning for humans. also have a meaning for humans.
@param instance: an Django Object for which the Image has been uploaded. @return: String: path for the image.
@type instance: a instace of an models.Model sub-class. @param instance: a Django Object for which the Image has been uploaded.
@type instance: an instance of a models.Model subclass.
@param filename: The filename of the uploaded image. @param filename: The filename of the uploaded image.
@type filename: String @type filename: String
""" """
@@ -48,7 +49,7 @@ def get_upload_path(instance, filename):
class Event(models.Model): class Event(models.Model):
"""An Event that could be a tournament, a game session, or an convention.""" """An Event that could be a tournament, a game session, or a convention."""
name = models.CharField(_('Name'), max_length=255) name = models.CharField(_('Name'), max_length=255)
description = RichTextField(_("Description"), blank=True) description = RichTextField(_("Description"), blank=True)
location = models.ForeignKey('Location', on_delete=models.PROTECT) location = models.ForeignKey('Location', on_delete=models.PROTECT)
@@ -131,7 +132,7 @@ class Event(models.Model):
'year': self.start.strftime('%Y'), 'year': self.start.strftime('%Y'),
'month': self.start.strftime('%m') 'month': self.start.strftime('%m')
} }
return reverse('eventseries-form', kwargs=kwargs) return reverse('event-series-form', kwargs=kwargs)
def get_edit_url(self): def get_edit_url(self):
kwargs = { kwargs = {
@@ -164,10 +165,10 @@ class Event(models.Model):
self.location = master_event.location self.location = master_event.location
self.url = master_event.url self.url = master_event.url
self.image = self.image or master_event.image self.image = self.image or master_event.image
self.photo_count = self.photo_set.count() self.photo_count = self.photo_set.count() if self.pk else 0
super(Event, self).save(**kwargs) super(Event, self).save(**kwargs)
# Update the Hanchans if necesery: # Update the Hanchans if necessary:
for hanchan in self.hanchan_set.all(): for hanchan in self.hanchan_set.all():
hanchan.save() hanchan.save()
@@ -267,7 +268,7 @@ class Photo(models.Model):
return os.path.basename(self.image.name) return os.path.basename(self.image.name)
def rotate(self, rotate): def rotate(self, rotate):
# TODO: Eine vernüftigte Methode ohne viele Abhängigkeiten finden um # TODO: Eine vernünftige Methode ohne viele Abhängigkeiten finden um
# die Bilder bei Bedarf zu drehen. # die Bilder bei Bedarf zu drehen.
if rotate == 'clockwise': if rotate == 'clockwise':
pass pass

View File

@@ -1,4 +1,3 @@
"""To geneate a Sitemap with all events."""
from kasu.sitemaps import GenericSitemap from kasu.sitemaps import GenericSitemap
from django.utils import timezone from django.utils import timezone
from .models import Event from .models import Event
@@ -6,11 +5,13 @@ from .models import Event
class EventSitemap(GenericSitemap): class EventSitemap(GenericSitemap):
"""sitemap to help indexing all events on this site.""" """sitemap to help indexing all events on this site."""
changefreq = "never" changefreq: str = "never"
protocol = 'https' protocol: str = 'https'
priority_field = 'start' priority_field: str = 'start'
@staticmethod @staticmethod
def items(): def items(**kwargs) -> Event:
"""add all upcoming and archived events to the sitemap.""" """add all upcoming and archived events to the sitemap.
@param **kwargs:
"""
return Event.objects.all() return Event.objects.all()

View File

@@ -58,7 +58,7 @@
<li><span class="fa fa-comments" title="{% trans 'Comments' %}"></span> <a href="{{event.get_absolute_url}}#comments">{{ comment_count }}</a></li> <li><span class="fa fa-comments" title="{% trans 'Comments' %}"></span> <a href="{{event.get_absolute_url}}#comments">{{ comment_count }}</a></li>
<li><span class="fa fa-camera-retro" title="{% trans 'Photos' %}"></span> <a href="{% url 'event-photo-list' event.pk %}">{{ event.photo_count }}</a></li> <li><span class="fa fa-camera-retro" title="{% trans 'Photos' %}"></span> <a href="{% url 'event-photo-list' event.pk %}">{{ event.photo_count }}</a></li>
<li><span class="fa fa-table" title="{% trans 'Hanchans' %}"></span> <a href="{% url 'event-hanchan-list' event.pk %}">{{ event.hanchan_set.count }}</a></li> <li><span class="fa fa-table" title="{% trans 'Hanchans' %}"></span> <a href="{% url 'event-hanchan-list' event.pk %}">{{ event.hanchan_set.count }}</a></li>
<li><span class="fa fa-glass" title="{% trans 'Hanchans' %}"></span> <a href="{% url 'maistar-game-list' event.pk %}">{{ event.maistargame_set.count }}</a></li> <li><span class="fa fa -glass" title="{% trans 'Hanchans' %}"></span> <a href="{% url 'maistar-game-list' event.pk %}">{{ event.maistargame_set.count }}</a></li>
</ul> </ul>
{% if perms.events.change_event %} {% if perms.events.change_event %}
<p class="right"><a href="{{ event.get_edit_url }}" class="button"><span class="fa fa-pencil"></span></a></p> <p class="right"><a href="{{ event.get_edit_url }}" class="button"><span class="fa fa-pencil"></span></a></p>
@@ -66,4 +66,5 @@
</div> </div>
{% endfor %} {% endfor %}
{% endfor %} {% endfor %}
{% block form %}{% endblock %}
{% endblock %} {% endblock %}

View File

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

View File

@@ -5,7 +5,7 @@
<form action="" method="post" class="grid_12"> <form action="" method="post" class="grid_12">
{% csrf_token %} {% csrf_token %}
<header> <header>
<h1 class="grid_12">Dieses Photo wirklich löschen?</h1> <h1 class="grid_12">Dieses Foto wirklich löschen?</h1>
</header> </header>
<p>Sind Sie sicher, dass Sie das Bild &ldquo;{{photo.name}}&rdquo; löschen wollen?</p> <p>Sind Sie sicher, dass Sie das Bild &ldquo;{{photo.name}}&rdquo; löschen wollen?</p>
<img src="{{photo.image|thumbnail_url:'display'}}" alt="{{photo.name}}" title="{{photo.name}}" class="grid_10 push_1"/> <img src="{{photo.image|thumbnail_url:'display'}}" alt="{{photo.name}}" title="{{photo.name}}" class="grid_10 push_1"/>

View File

@@ -59,9 +59,6 @@ if ($('a.next').attr('href')) {
<a class="button" href="http://facebook.com/sharer.php?u=http%3A%2F%2Fwww.kasu.at{{photo.get_absolute_url|urlencode}}" target="_blank" rel="nofollow"> <a class="button" href="http://facebook.com/sharer.php?u=http%3A%2F%2Fwww.kasu.at{{photo.get_absolute_url|urlencode}}" target="_blank" rel="nofollow">
<span class="fa fa-twitter"></span>Facebook <span class="fa fa-twitter"></span>Facebook
</a> </a>
<a class="button" href="https://m.google.com/app/plus/x/?v=compose&amp;content={{photo.headline|urlencode}}+-+http%3A%2F%2Fwww.kasu.at/{{photo.get_absolute_url|urlencode}}" target="_blank" rel="nofollow">
<span class="fa fa-google-plus"></span> Google+
</a>
<a class="button" href="https://twitter.com/share?url=http%3A%2F%2Fwww.kasu.at/{{photo.get_absolute_url|urlencode}}" target='_blank' rel="nofollow"> <a class="button" href="https://twitter.com/share?url=http%3A%2F%2Fwww.kasu.at/{{photo.get_absolute_url|urlencode}}" target='_blank' rel="nofollow">
<span class="fa fa-twitter"></span> Twitter <span class="fa fa-twitter"></span> Twitter
</a> </a>

View File

@@ -1,46 +1,10 @@
{% extends "base.html" %} {% extends "events/event_archive.html" %}
{% load i18n comments thumbnail %} {% load i18n comments thumbnail %}
{% block maincontent %} {% block bottom %}
{% for event in event_list %}
{% get_comment_count for event as comment_count %}
{% ifchanged %}<h3 class="grid_12">{{ event.start|date:'F Y' }}</h3>{% endifchanged %}
<div style="float:left">
<a href="{% url 'event-photo-list' event.pk %}"><img src="{{ event.get_image|thumbnail_url:'thumbnail' }}" alt="" class="thumbnail"/></a>
<div class="grid_4" />
<h4><a href="{% url 'event-photo-list' event.pk %}">{{ event.name }}</a></h4>
<div class="info">
<span class="fa fa-calendar-o" title="{% trans 'Start' %}"></span>
{{ event.start|date }}
{% if event.end %}
{% trans "from" %} {{ event.start|time:'H:i' }} {% trans "to" %} {{ event.end|time:'H:i' }}
{% else %}
{{ event.start|time:'H:i' }}
{% endif %}
</div>
{% if event.description %}<p>{{event.description}}</p>{% endif %}
<div class="info">
<span class="fa fa-map-marker" title="{% trans 'Location' %}"></span>
{{ event.location }}
<span class="fa fa-comments" title="{% trans 'Comments' %}"></span>
<a href="{{event.get_absolute_url}}#comments">{{ comment_count }} {% trans 'Comments' %}</a>
<span class="fa fa-camera-retro" title="{% trans 'Photos' %}"></span>
<a href="{% url 'event-photo-list' event.pk %}">{{ event.photo_count }} {% trans 'Photos' %}</a>
</div>
<p style="text-align:right">
{% if perms.events.add_photo %}
<a href="{% url 'event-photo-list' event.pk %}" class="button">
<span class="fa fa-cloud-upload"></span>
{%trans "Upload" %}</a>
{% endif %}
</p>
</div>
{% endfor %}
<form method="post" enctype="multipart/form-data"> <form method="post" enctype="multipart/form-data">
{% csrf_token %} {% csrf_token %}
<fieldset class="grid_8 push_2"> <fieldset id="bottom_buttonbar" class="grid_12">
<legend>Photos hochladen</legend> <legend>Photos hochladen</legend>
{% include "form.html" %} {% include "form.html" %}
<p class="buttonbar"> <p class="buttonbar">
@@ -52,5 +16,5 @@
</fieldset> </fieldset>
</form> </form>
{% endblock %} {% endblock %}
{% block buttonbar %}
{% endblock %}

View File

@@ -1,20 +1,19 @@
""" """
This file should test the functionality of the events app using the unittest This file should test the functionality of the events app using the unittest
module. These will pass when you run "manage.py test". module. These will pass when you run "manage.py test".
useful tests have to be written yet. sorry!
Usefull tests have to been written yet. sorry!
""" """
from django.test import TestCase from django.test import TestCase
class EventTest(TestCase): class EventTest(TestCase):
""" Here we should test the creation and modifiaction of Events. """ """ Here we should test the creation and modification of Events. """
class LocationTest(TestCase): class LocationTest(TestCase):
""" Here we should test the creation and modifiaction of Locations. """ """ Here we should test the creation and modification of Locations. """
class PhotoTest(TestCase): class PhotoTest(TestCase):
""" Here we should test the creation and modifiaction of Photos. """ """ Here we should test the creation and modification of Photos. """

View File

@@ -1,22 +1,16 @@
"""URLS to access upcoming events and the event archive.""" """URLS to access upcoming events and the event archive."""
from django.conf.urls import url from django.urls import path
from django.views.generic import RedirectView from django.views.generic import RedirectView
from . import views from . import views
urlpatterns = [ urlpatterns = [
url(r'^$', RedirectView.as_view(url='/events/upcoming/', permanent=True)), path("", RedirectView.as_view(url='/events/upcoming/', permanent=True)),
url(r'^(?P<year>[\d]{4})/$', views.EventArchiveYear.as_view(), path('<int:year>/', views.EventArchiveYear.as_view(), name='event-archive'),
name='event-archive'), path('<int:year>/<int:month>/', views.EventArchiveMonth.as_view(), name='event-archive'),
url(r'^(?P<year>[\d]{4})/(?P<month>[\d]+)/$', path('<int:year>/<int:month>/<int:pk>/', views.EventDetail.as_view(), name='event-detail'),
views.EventArchiveMonth.as_view(), path('<int:year>/<int:month>/<int:pk>/add_dates/', views.EventSeriesForm.as_view(), name='event-series-form'),
name='event-archive'), path('<int:year>/<int:month>/<int:pk>/edit/', views.EventForm.as_view(), name='event-form'),
url(r'^(?P<year>[\d]{4})/(?P<month>[\d]+)/(?P<pk>[\d]+)/$', path('add/', views.EventForm.as_view(), name='event-form'),
views.EventDetail.as_view(), name='event-detail'), path('archive/', views.EventArchiveIndex.as_view(), name='event-archive'),
url(r'^(?P<year>[\d]{4})/(?P<month>[\d]+)/(?P<pk>[\d]+)/add_dates/$', path('upcoming/', views.UpcomingEvents.as_view(), name='upcoming-events'),
views.EventSeriesForm.as_view(), name='eventseries-form'),
url(r'^(?P<year>[\d]{4})/(?P<month>[\d]+)/(?P<pk>[\d]+)/edit/$',
views.EventForm.as_view(), name='event-form'),
url(r'^add/$', views.EventForm.as_view(), name='event-form'),
url(r'^archive/$', views.EventArchiveIndex.as_view(), name='event-archive'),
url(r'^upcoming/$', views.UpcomingEvents.as_view(), name='upcoming-events'),
] ]

View File

@@ -7,9 +7,9 @@ from django.urls import reverse
from django.db.models import Q from django.db.models import Q
from django.http import Http404 from django.http import Http404
from django.http import HttpResponse from django.http import HttpResponse
from django.shortcuts import redirect from django.shortcuts import redirect, get_object_or_404
from django.utils import timezone from django.utils import timezone
from django.utils.translation import ugettext as _ from django.utils.translation import gettext as _
from django.views import generic from django.views import generic
from extra_views import InlineFormSetView from extra_views import InlineFormSetView
from icalendar import Calendar, Event from icalendar import Calendar, Event
@@ -50,19 +50,15 @@ class EventDetail(mixins.EventDetailMixin, generic.DetailView):
class EventForm(PermissionRequiredMixin, mixins.EventDetailMixin, class EventForm(PermissionRequiredMixin, mixins.EventDetailMixin,
generic.UpdateView): generic.UpdateView):
"""Frontend formular to add or edit a Event.""" """Frontend formular to add or edit an Event."""
form_class = forms.EventForm form_class = forms.EventForm
template_name = 'events/event_form.html' template_name = 'events/event_form.html'
permission_required = 'events.add_event' permission_required = 'events.add_event'
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
"""Dynamicle set the title to Add or Edit Event, depanding if an """set the title to add or edit Event, depending on the fact if an event ID was given."""
event ID was given, or not."""
context = super(EventForm, self).get_context_data(**kwargs) context = super(EventForm, self).get_context_data(**kwargs)
if self.kwargs.get('pk'): context['title'] = _("Edit Event") if self.kwargs.get('pk') else _("Add Event")
context['title'] = _("Edit Event")
else:
context['title'] = _("Add Event")
return context return context
def get_object(self, queryset=None): def get_object(self, queryset=None):
@@ -74,7 +70,7 @@ class EventForm(PermissionRequiredMixin, mixins.EventDetailMixin,
class EventGallery(generic.ListView): class EventGallery(generic.ListView):
"""Display a overview of all event photo albums.""" """Display an overview of all event photo albums."""
template_name = 'events/photo_gallery.html' template_name = 'events/photo_gallery.html'
paginate_by = 24 paginate_by = 24
@@ -89,7 +85,7 @@ class EventGallery(generic.ListView):
class EventListIcal(generic.View): class EventListIcal(generic.View):
"""Generates an returns an iCal File with all upcoming events.""" """Generates and returns an iCal File with all upcoming events."""
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
"""Add all upcoming events to an .ics file and send it.""" """Add all upcoming events to an .ics file and send it."""
@@ -132,7 +128,7 @@ class EventPhoto(mixins.EventPhotoMixin, generic.UpdateView):
class EventPhotoList(mixins.EventPhotoMixin, generic.ListView): class EventPhotoList(mixins.EventPhotoMixin, generic.ListView):
"""List all Photos of the event or event series in an album.""" """List all Photos of the event or event series."""
context_object_name = 'photo_list' context_object_name = 'photo_list'
event = None event = None
paginate_by = 36 paginate_by = 36
@@ -188,7 +184,7 @@ class EventSeriesForm(mixins.EventDetailMixin, PermissionRequiredMixin,
template_name = 'events/eventseries_form.html' template_name = 'events/eventseries_form.html'
def get_object(self, queryset=None): def get_object(self, queryset=None):
self.event = models.Event.objects.get(pk=self.kwargs['pk']) self.event = get_object_or_404(models.Event, pk=self.kwargs['pk'])
if self.event.event_series: if self.event.event_series:
self.event = self.event.event_series self.event = self.event.event_series
return self.event return self.event

View File

@@ -7,7 +7,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: kasu.utils\n" "Project-Id-Version: kasu.utils\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-12-13 23:38+0100\n" "POT-Creation-Date: 2023-07-26 18:31+0200\n"
"PO-Revision-Date: 2018-12-30 11:14+0105\n" "PO-Revision-Date: 2018-12-30 11:14+0105\n"
"Last-Translator: b' <kasu@xendynastie.at>'\n" "Last-Translator: b' <kasu@xendynastie.at>'\n"
"Language-Team: Kasu <verein@kasu.at>\n" "Language-Team: Kasu <verein@kasu.at>\n"
@@ -19,11 +19,11 @@ msgstr ""
"X-Generator: Poedit 1.8.9\n" "X-Generator: Poedit 1.8.9\n"
"X-Translated-Using: django-rosetta 0.9.0\n" "X-Translated-Using: django-rosetta 0.9.0\n"
#: settings.py:140 #: settings.py:144
msgid "German" msgid "German"
msgstr "Deutsch" msgstr "Deutsch"
#: settings.py:140 #: settings.py:144
msgid "English" msgid "English"
msgstr "Englisch" msgstr "Englisch"
@@ -192,8 +192,8 @@ msgid ""
" From <a href=\"%(user_link)s\">%(author)s</a> in\n" " From <a href=\"%(user_link)s\">%(author)s</a> in\n"
" <a href=\"%(comment_link)s\">&ldquo;%(object)s&rdquo;</a>\n" " <a href=\"%(comment_link)s\">&ldquo;%(object)s&rdquo;</a>\n"
" since\n" " since\n"
" <time datetime=\"%(submit_date|date:'Y-m-d\\TH:i:sO')s\">" " <time "
"%(since)s</time>\n" "datetime=\"%(submit_date|date:'Y-m-d\\TH:i:sO')s\">%(since)s</time>\n"
" " " "
msgstr "" msgstr ""
"\n" "\n"
@@ -215,11 +215,11 @@ msgstr "Besuche uns auf"
msgid "Add Article" msgid "Add Article"
msgstr "Artikel hinzufügen" msgstr "Artikel hinzufügen"
#: templates/paginator.html:8 #: templates/paginator.html:7
msgid "Previous" msgid "Previous"
msgstr "Vorherige" msgstr "Vorherige"
#: templates/paginator.html:20 #: templates/paginator.html:18
msgid "Next" msgid "Next"
msgstr "Nächste" msgstr "Nächste"

View File

@@ -133,6 +133,10 @@ LOGIN_URL = '/membership/login/'
LOGIN_ERROR_URL = '/membership/login/error/' LOGIN_ERROR_URL = '/membership/login/error/'
LOGIN_REDIRECT_URL = '/users/' LOGIN_REDIRECT_URL = '/users/'
# Set the primarykey handing to old django style
DEFAULT_AUTO_FIELD = 'django.db.models.AutoField'
# Localization # Localization
USE_I18N = True USE_I18N = True
USE_L10N = True USE_L10N = True

View File

@@ -37,7 +37,7 @@
{% block extra_head %}{% endblock %} {% block extra_head %}{% endblock %}
<script src="{{ STATIC_URL }}js/piwik.js"></script> <script src="{{ STATIC_URL }}js/piwik.js"></script>
</head> </head>
<body id="body" {% block itemscope %}{% endblock %}> <body id="body" itemscope>
<header id="siteheader"> <header id="siteheader">
<div id="sitelogo"><a href="/index.html">Kasu - traditionelle asiatische Spielkultur</a></div> <div id="sitelogo"><a href="/index.html">Kasu - traditionelle asiatische Spielkultur</a></div>
<nav id="mainnav"> <nav id="mainnav">
@@ -46,12 +46,12 @@
<ul class="main_menu"> <ul class="main_menu">
{% for item in top_menu_items %} {% for item in top_menu_items %}
<li><a href="{{item.get_absolute_url}}" title="{{ item.title }}" <li><a href="{{item.get_absolute_url}}" title="{{ item.title }}"
class="{%if item.active %}active{% endif %}">{{item.menu_name}}</a> class="{% if item.active %}active{% endif %}">{{item.menu_name}}</a>
{% if item.subpages.all %} {% if item.subpages.all %}
<ul class="main_dropdown"> <ul class="main_dropdown">
{% for subpage in item.subpages.all %}<li><a {% for subpage in item.subpages.all %}<li><a
href="{{subpage.get_absolute_url}}" href="{{subpage.get_absolute_url}}"
{% ifequal subpage current_page %}class="active"{% endifequal %}>{{subpage.menu_name}}</a></li> >{{subpage.menu_name}}</a></li>
{% endfor %} {% endfor %}
</ul> </ul>
{% endif %} {% endif %}
@@ -116,9 +116,9 @@
{% block navigation %}{% if current_top_page.subpages.count %} {% block navigation %}{% if current_top_page.subpages.count %}
<ul id="navigation"> <ul id="navigation">
<li><a href="{{current_top_page.get_absolute_url}}" <li><a href="{{current_top_page.get_absolute_url}}"
class="{% ifequal current_page current_top_page %}{% endifequal %}">{{current_top_page.menu_name}}</a></li> class="{% if current_page == current_top_page %}{% endif %}">{{current_top_page.menu_name}}</a></li>
{% for subpage in current_top_page.subpages.all %} {% for subpage in current_top_page.subpages.all %}
<li><a href="{{subpage.get_absolute_url}}" class="{% ifequal subpage current_page %}active{% endifequal %}">{{subpage.menu_name}}</a> <li><a href="{{subpage.get_absolute_url}}" class="{% if subpage == current_page %}active{% endif %}">{{subpage.menu_name}}</a>
</li> </li>
{% endfor %} {% endfor %}
{% block additional_nav_elements %}{% endblock %} {% block additional_nav_elements %}{% endblock %}
@@ -142,20 +142,21 @@
{% endblock %} {% endblock %}
{% block comments %}{% endblock %} {% block comments %}{% endblock %}
<br class="clear"/> <br class="clear"/>
<p id="bottom_buttonbar" class="buttonbar"> {% block bottom %}
<div id="bottom_buttonbar" class="buttonbar">
{% block buttonbar %} {% block buttonbar %}
{% if current_page and perms.content.add_page %} {% if current_page and perms.content.add_page %}
<a href="{% url 'add-page' current_page.path %}" class="button"><span class="fa fa-plus"></span> <a href="{% url 'add-page' current_page.path %}" class="button"><span class="fa fa-plus"></span>
{% trans "Add Subpage" %}</a> {% trans "Add Subpage" %}</a>
{% endif %} {% endif %}
{% if current_page and perms.content.change_page %} {% if current_page and perms.content.change_page %}
<a href="{% url 'edit-page' current_page.path %}" class="button"><span class="fa fa-pencil"></span> <a href="{% url 'edit-page' current_page.path %}" class="button"><span class="fa fa-pencil"></span>
{% trans "Edit Page" %}</a> {% trans "Edit Page" %}</a>
{% endif %} {% endif %}
{% block additional_buttonbar %}{% endblock %} {% block additional_buttonbar %}{% endblock %}
{% endblock %} {% endblock %}
</p> </div>
{% endblock %}
</main> </main>
<footer id="footer"> <footer id="footer">
<p><strong>Herausgeber:</strong> Verein Kasu - traditionelle asiatische Spielkultur (<a <p><strong>Herausgeber:</strong> Verein Kasu - traditionelle asiatische Spielkultur (<a
@@ -169,8 +170,8 @@
<select name="language" id="language"> <select name="language" id="language">
{% get_language_info_list for LANGUAGES as languages %} {% get_language_info_list for LANGUAGES as languages %}
{% for language in languages %} {% for language in languages %}
<option value="{{language.code}}" {% ifequal language.code LANGUAGE_CODE %} <option value="{{language.code}}"
selected="selected" {% endifequal %}>{{ language.name_local }} ({{ language.code }}) selected="selected">{{ language.name_local }} ({{ language.code }})
</option> </option>
{% endfor %} {% endfor %}
</select> </select>

View File

@@ -16,7 +16,7 @@
</div> </div>
<header class="comment_header"> <header class="comment_header">
<a href="{{ user.get_profile.get_absolute_url }}" class="user">{{comment.user}}</a> <a href="{{ user.get_profile.get_absolute_url }}" class="user">{{comment.user}}</a>
<div class="submit_date"><time time="{% now 'c' %}">{% now 'DATETIME_FORMAT' %}</time></div> <div class="submit_date"><time>{% now 'DATETIME_FORMAT' %}</time></div>
</header> </header>
<div class="comment_text">{{comment}}</div> <div class="comment_text">{{comment}}</div>
</article> </article>

View File

@@ -1 +1,2 @@
<input type="{{ type|default:'text' }}" name="{{ widget.name }}"{% if widget.value != None %} value="{{ widget.value|stringformat:'s' }}"{% endif %}{% include "django/forms/widgets/attrs.html" %} /> <input type="{{ type|default:'text' }}" name="{{ widget.name }}"
value="{{ widget.value|stringformat:'s' }}" "django/forms/widgets/attrs.html" %} />

View File

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

View File

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

View File

@@ -1,6 +1,6 @@
""" the main URL config that imports many URL configs from the applications. """ """ the main URL config that imports many URL configs from the applications. """
from django.conf import settings from django.conf import settings
from django.conf.urls import include, url from django.urls import include, path
from django.conf.urls.static import static from django.conf.urls.static import static
from django.contrib import admin from django.contrib import admin
from django.contrib.sitemaps.views import sitemap from django.contrib.sitemaps.views import sitemap
@@ -15,11 +15,12 @@ from maistar_ranking.sitemaps import *
from membership.views import MembershipDetail from membership.views import MembershipDetail
admin.autodiscover() admin.autodiscover()
# register_converter('path')
sitemaps = { sitemaps = {
'event_rankings': EventRankingSitemap, 'event_rankings': EventRankingSitemap,
'event_hanchans': EventHanchanSitemap, 'event_hanchans': EventHanchanSitemap,
'mahjong_seasons': MajongSeasonSitemap, 'mahjong_seasons': MahjongSeasonSitemap,
'maistar_games': MaistarGamesSitemap, 'maistar_games': MaistarGamesSitemap,
'articles': ArticleSitemap, 'articles': ArticleSitemap,
'events': EventSitemap, 'events': EventSitemap,
@@ -27,41 +28,33 @@ sitemaps = {
} }
urlpatterns = [ # Ignore PyLintBear (C0103) urlpatterns = [ # Ignore PyLintBear (C0103)
url(r'^$', views.StartPage.as_view()), path("", views.StartPage.as_view(), name="index"),
url(r'^404/$', TemplateView.as_view(template_name='404.html')), path('index.html', views.StartPage.as_view()),
url(r'^add_page/(?P<path>[\+\.\-\d\w\/]*)$', path('404/', TemplateView.as_view(template_name='404.html'), name="404"),
views.PageAddForm.as_view(), name='add-page'), path('admin/doc/', include('django.contrib.admindocs.urls'), name="django-docs"),
url(r'^admin/doc/', include('django.contrib.admindocs.urls')), path('admin/', admin.site.urls, name="django-admin"),
url(r'^admin/', admin.site.urls), path('ckeditor/', include('ckeditor_uploader.urls')),
url(r'^ckeditor/', include('ckeditor_uploader.urls')), path('comments/', include('django_comments.urls')),
url(r'^comments/', include('django_comments.urls')), path('events/', include('events.urls')),
url(r'^edit_page/(?P<path>[\+\.\-\d\w\/]*)$', path('events.ics', EventListIcal.as_view(), name='events-ical'),
views.PageEditForm.as_view(), name='edit-page'), path('feeds/latest/', feeds.LatestNews(), name='feed-latest-news'),
url(r'^events/', include('events.urls')), path('feeds/comments/', feeds.LatestComments(), name='feed-latest-comments'),
url(r'^events.ics$', EventListIcal.as_view(), name='events-ical'), path('gallery/', include('events.gallery_urls')),
url(r'^feeds/latest/$', feeds.LatestNews(), name='feed-latest-news'), path('google25dabc1a49a9ef03.html', TemplateView.as_view(template_name='google25dabc1a49a9ef03.html')),
url(r'^feeds/comments/$', feeds.LatestComments(), path('i18n/', include('django.conf.urls.i18n'), name='start-page'),
name='feed-latest-comments'), path('manifest.json', TemplateView.as_view(template_name='manifest.json')),
url(r'^gallery/', include('events.gallery_urls')), path('membership/', include('membership.urls')),
url(r'^google25dabc1a49a9ef03.html$', TemplateView.as_view( path('news/', include('content.news_urls')),
template_name='google25dabc1a49a9ef03.html')), path('ranking/', include('mahjong_ranking.urls')),
url(r'^i18n/', include('django.conf.urls.i18n'), name='start-page'), path('ranking/', include('maistar_ranking.urls')),
url(r'^index.html$', views.StartPage.as_view()), path('sitemap.xml', sitemap, {'sitemaps': sitemaps}, name='django.contrib.sitemaps.views.sitemap'),
url(r'^manifest.json$', path('robots.txt', TemplateView.as_view(template_name='robots.txt')),
TemplateView.as_view(template_name='manifest.json')), path('users/', MembershipDetail.as_view(), name='membership-details'),
url(r'^membership/', include('membership.urls')), path('users/<slug:username>/', MembershipDetail.as_view(), name='membership-details'),
url(r'^news/', include('content.news_urls')), path('add_page/<path:path>', views.PageAddForm.as_view(), name='add-page'),
url(r'^ranking/', include('mahjong_ranking.urls')), path('edit_page/<path:path>', views.PageEditForm.as_view(), name='edit-page'),
url(r'^ranking/', include('maistar_ranking.urls')), path('<path:path>.html', views.PageHtml.as_view(), name='view-page'),
url(r'^sitemap\.xml$', sitemap, { path('<path:path>.pdf', views.PagePdf.as_view()),
'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$',
views.PageHtml.as_view(), name='view-page'),
url(r'^(?P<path>[\-\d\w\/]+)\.pdf$', views.PagePdf.as_view()),
] ]
if settings.DEBUG: if settings.DEBUG:
@@ -70,8 +63,8 @@ if settings.DEBUG:
urlpatterns += static(settings.STATIC_URL, urlpatterns += static(settings.STATIC_URL,
document_root=settings.STATIC_ROOT) document_root=settings.STATIC_ROOT)
if 'rosetta' in settings.INSTALLED_APPS: if 'rosetta' in settings.INSTALLED_APPS:
urlpatterns += [url(r'^rosetta/', include('rosetta.urls'))] urlpatterns += [path("rosetta/", include('rosetta.urls'))]
if 'debug_toolbar' in settings.INSTALLED_APPS: if 'debug_toolbar' in settings.INSTALLED_APPS:
import debug_toolbar import debug_toolbar
urlpatterns += [url(r'^__debug__/', include(debug_toolbar.urls)), ] urlpatterns += [path('__debug__/', include(debug_toolbar.urls)), ]

View File

@@ -1,6 +1,7 @@
""" """
Helper to generate XLSX Spreadsheets in an uniform way. Helper to generate XLSX Spreadsheets in an uniform way.
""" """
import datetime
from datetime import date from datetime import date
import openpyxl import openpyxl
@@ -68,6 +69,8 @@ def getattr_recursive(obj, attr_string):
for attr in attr_list: for attr in attr_list:
return_value = getattr(obj, attr) return_value = getattr(obj, attr)
obj = return_value obj = return_value
if isinstance(return_value, datetime.datetime):
return_value = return_value.replace(tzinfo=None)
return return_value return return_value

View File

@@ -1,7 +1,7 @@
""" Adds management of the mahong ranking system to the admin interface. """ """ Adds management of the mahjong ranking system to the admin interface. """
from django.contrib import admin from django.contrib import admin
from django.utils.translation import ugettext as _ from django.utils.translation import gettext as _
from . import models, set_dirty from . import models, set_dirty
@@ -36,14 +36,14 @@ def confirm(modeladmin, request, queryset): # Ignore PyLintBear (W0613)
confirm.short_description = _("Confirm") confirm.short_description = _("Confirm")
def unconfirm(modeladmin, request, queryset): # Ignore PyLintBear (W0613) def reject(modeladmin, request, queryset): # Ignore PyLintBear (W0613)
"""An admin action to quickly set selected hanchans to unconfirmed. """ """An admin action to quickly set selected hanchans to unconfirmed. """
for hanchan in queryset: for hanchan in queryset:
hanchan.confirmed = False hanchan.confirmed = False
hanchan.save() hanchan.save()
unconfirm.short_description = _('Set unconfirmed') reject.short_description = _('Reject')
class EventRankingAdmin(admin.ModelAdmin): class EventRankingAdmin(admin.ModelAdmin):
@@ -57,7 +57,7 @@ class EventRankingAdmin(admin.ModelAdmin):
class HanchanAdmin(admin.ModelAdmin): class HanchanAdmin(admin.ModelAdmin):
""" To administrate the stored Hanchans. """ """ To administrate the stored Hanchans. """
actions = [recalculate, confirm, unconfirm] actions = [recalculate, confirm, reject]
date_hierarchy = 'start' date_hierarchy = 'start'
list_filter = ('season', 'event', 'confirmed') list_filter = ('season', 'event', 'confirmed')
search_fields = ('player_names',) search_fields = ('player_names',)

View File

@@ -7,7 +7,7 @@ Created on 04.10.2011
""" """
from django import forms from django import forms
from django.contrib.auth import get_user_model from django.contrib.auth import get_user_model
from django.utils.translation import ugettext as _ from django.utils.translation import gettext as _
from events.models import Event from events.models import Event
from . import models from . import models
@@ -64,7 +64,7 @@ class HanchanAdminForm(HanchanForm):
""" Extends the HanchanForm for users with admin privileges. """ Extends the HanchanForm for users with admin privileges.
They are allowed to confirm/unconfirm Hanchans, this could be userful if They are allowed to confirm/unconfirm Hanchans, this could be userful if
one games smells fishy and needs the opinion of an referee.""" one games smells fishy and needs the opinion of a referee."""
class Meta(object): class Meta(object):
""" Extend the formfields to add the confirmed checkbox. """ """ Extend the formfields to add the confirmed checkbox. """

View File

@@ -7,7 +7,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: kasu.mahjong_ranking\n" "Project-Id-Version: kasu.mahjong_ranking\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-12-13 23:38+0100\n" "POT-Creation-Date: 2023-07-27 00:05+0200\n"
"PO-Revision-Date: 2018-05-08 00:20+0105\n" "PO-Revision-Date: 2018-05-08 00:20+0105\n"
"Last-Translator: b'Christian Berg <kasu@xendynastie.at>'\n" "Last-Translator: b'Christian Berg <kasu@xendynastie.at>'\n"
"Language-Team: Kasu <verein@kasu.at>\n" "Language-Team: Kasu <verein@kasu.at>\n"
@@ -19,15 +19,15 @@ msgstr ""
"X-Generator: Poedit 1.8.9\n" "X-Generator: Poedit 1.8.9\n"
"X-Translated-Using: django-rosetta 0.8.1\n" "X-Translated-Using: django-rosetta 0.8.1\n"
#: admin.py:24 #: admin.py:26
msgid "Recalculate" msgid "Recalculate"
msgstr "Neuberechnen" msgstr "Neuberechnen"
#: admin.py:34 #: admin.py:36
msgid "Confirm" msgid "Confirm"
msgstr "Bestätigen" msgstr "Bestätigen"
#: admin.py:44 #: admin.py:46
msgid "Set unconfirmed" msgid "Set unconfirmed"
msgstr "Als unbestätigt markieren" msgstr "Als unbestätigt markieren"
@@ -35,7 +35,7 @@ msgstr "Als unbestätigt markieren"
msgid "start" msgid "start"
msgstr "Beginn" msgstr "Beginn"
#: models.py:91 templates/mahjong_ranking/player_dan_score.html:14 #: models.py:93 templates/mahjong_ranking/player_dan_score.html:14
#: templates/mahjong_ranking/player_invalid_score.html:13 #: templates/mahjong_ranking/player_invalid_score.html:13
#: templates/mahjong_ranking/player_kyu_score.html:15 #: templates/mahjong_ranking/player_kyu_score.html:15
#: templates/mahjong_ranking/player_ladder_score.html:15 #: templates/mahjong_ranking/player_ladder_score.html:15
@@ -43,16 +43,16 @@ msgstr "Beginn"
msgid "Start" msgid "Start"
msgstr "Beginn" msgstr "Beginn"
#: models.py:92 #: models.py:94
msgid "This is crucial to get the right Hanchans that scores" msgid "This is crucial to get the right Hanchans that scores"
msgstr "Wichtig damit die richtigen Hanchans in die Wertung kommen." msgstr "Wichtig damit die richtigen Hanchans in die Wertung kommen."
#: models.py:99 #: models.py:101
msgid "Player 1" msgid "Player 1"
msgstr "Spieler 1" msgstr "Spieler 1"
#: models.py:100 models.py:102 models.py:119 models.py:121 models.py:138 #: models.py:102 models.py:104 models.py:121 models.py:123 models.py:140
#: models.py:140 models.py:157 models.py:159 #: models.py:142 models.py:159 models.py:161
#: templates/mahjong_ranking/eventhanchan_list.html:19 #: templates/mahjong_ranking/eventhanchan_list.html:19
#: templates/mahjong_ranking/eventranking_list.html:21 #: templates/mahjong_ranking/eventranking_list.html:21
#: templates/mahjong_ranking/hanchan_confirm_delete.html:16 #: templates/mahjong_ranking/hanchan_confirm_delete.html:16
@@ -62,76 +62,76 @@ msgstr "Spieler 1"
msgid "Score" msgid "Score"
msgstr "Punkte" msgstr "Punkte"
#: models.py:112 models.py:131 models.py:150 models.py:169 models.py:171 #: models.py:114 models.py:133 models.py:152 models.py:171 models.py:173
#: templates/mahjong_ranking/hanchan_form.html:20 #: templates/mahjong_ranking/hanchan_form.html:20
#: templates/mahjong_ranking/player_dan_score.html:18 #: templates/mahjong_ranking/player_dan_score.html:18
#: templates/mahjong_ranking/player_invalid_score.html:17 #: templates/mahjong_ranking/player_invalid_score.html:17
msgid "Comment" msgid "Comment"
msgstr "Kommentar" msgstr "Kommentar"
#: models.py:118 #: models.py:120
msgid "Player 2" msgid "Player 2"
msgstr "Spieler 2" msgstr "Spieler 2"
#: models.py:137 #: models.py:139
msgid "Player 3" msgid "Player 3"
msgstr "Spieler 3" msgstr "Spieler 3"
#: models.py:156 #: models.py:158
msgid "Player 4" msgid "Player 4"
msgstr "Spieler 4" msgstr "Spieler 4"
#: models.py:173 #: models.py:175
msgid "Has been Confirmed" msgid "Has been Confirmed"
msgstr "Wurde bestätigt" msgstr "Wurde bestätigt"
#: models.py:174 #: models.py:176
msgid "Only valid and confirmed Hanchans will be counted in the rating." msgid "Only valid and confirmed Hanchans will be counted in the rating."
msgstr "Nur gültige und bestätigte Hanchans kommen in die Wertung." msgstr "Nur gültige und bestätigte Hanchans kommen in die Wertung."
#: models.py:179 models.py:607 templates/mahjong_ranking/ladder_redbox.html:29 #: models.py:181 models.py:620 templates/mahjong_ranking/ladder_redbox.html:29
#: templates/mahjong_ranking/player_ladder_score.html:63 #: templates/mahjong_ranking/player_ladder_score.html:63
msgid "Season" msgid "Season"
msgstr "Saison" msgstr "Saison"
#: models.py:184 #: models.py:186
msgid "Hanchan" msgid "Hanchan"
msgstr "Hanchan" msgstr "Hanchan"
#: models.py:185 templates/mahjong_ranking/eventranking_list.html:17 #: models.py:187 templates/mahjong_ranking/eventranking_list.html:17
msgid "Hanchans" msgid "Hanchans"
msgstr "Hanchans" msgstr "Hanchans"
#: models.py:188 #: models.py:190
msgid "Hanchan from {0:%Y-%m-%d} at {0:%H:%M} with {1}" msgid "Hanchan from {0:%Y-%m-%d} at {0:%H:%M} with {1}"
msgstr "Hanchan vom {0:%Y-%m-%d} um {0:%H:%M} mit {1}" msgstr "Hanchan vom {0:%Y-%m-%d} um {0:%H:%M} mit {1}"
#: models.py:215 #: models.py:217
#, python-format #, python-format
msgid "%s can't attend the same game multiple times" msgid "%s can't attend the same game multiple times"
msgstr "%s kann an einem Spiel nicht mehrfach teilnehmen." msgstr "%s kann an einem Spiel nicht mehrfach teilnehmen."
#: models.py:223 #: models.py:225
msgid "Games in the future may not be added, Dr. Brown" msgid "Games in the future may not be added, Dr. Brown"
msgstr "Spiele aus der Zukunft dürfen noch nicht erfasst werden. Dr. Brown." msgstr "Spiele aus der Zukunft dürfen noch nicht erfasst werden. Dr. Brown."
#: models.py:225 #: models.py:227
msgid "Only games during the event are allowed" msgid "Only games during the event are allowed"
msgstr "Nur Spiele während der Veranstaltung zählen." msgstr "Nur Spiele während der Veranstaltung zählen."
#: models.py:228 #: models.py:230
msgid "Gamescore is lower then 100.000 Pt." msgid "Gamescore is lower then 100.000 Pt."
msgstr "Spielstand ist weniger als 100.000 Punkte" msgstr "Spielstand ist weniger als 100.000 Punkte"
#: models.py:230 #: models.py:232
msgid "Gamescore is over 100.000 Pt." msgid "Gamescore is over 100.000 Pt."
msgstr "Spielstand ist über 100.000 Punkte." msgstr "Spielstand ist über 100.000 Punkte."
#: models.py:362 #: models.py:368
msgid "Kyū/Dan Ranking" msgid "Kyū/Dan Ranking"
msgstr "Kyū/Dan Wertung" msgstr "Kyū/Dan Wertung"
#: models.py:363 #: models.py:369
msgid "Kyū/Dan Rankings" msgid "Kyū/Dan Rankings"
msgstr "Kyū/Dan Wertungen" msgstr "Kyū/Dan Wertungen"
@@ -386,7 +386,7 @@ msgstr "%s wurde erfolgreich aktualisiert."
msgid "%s has been added successfully. You can now add a new one." msgid "%s has been added successfully. You can now add a new one."
msgstr "%s wurde erfolgreich hinzugefügt. Du kannst eine neue eintragen." msgstr "%s wurde erfolgreich hinzugefügt. Du kannst eine neue eintragen."
#: views.py:219 #: views.py:218
msgid "No user found matching the name {}" msgid "No user found matching the name {}"
msgstr "Kein Benutzer mit dem Namen %s gefunden" msgstr "Kein Benutzer mit dem Namen %s gefunden"

View File

@@ -1,4 +1,4 @@
"""Export Mahjong Rankings as excel files.""" """Export Mahjong Rankings as Excel files."""
import os import os
from datetime import date, time, datetime from datetime import date, time, datetime

View File

@@ -0,0 +1,56 @@
"""
Recalculates all Kyu/Dan Rankings until the given date a writes them to the legacy fields.
"""
from django.core.management.base import BaseCommand
from datetime import datetime, date, time
from mahjong_ranking import models
from django.utils.dateparse import parse_date
from django.utils import timezone
LEGACY_ATTRIBUTES = (
"dan",
"dan_points",
"max_dan_points",
"kyu",
"kyu_points",
"hanchan_count",
"good_hanchans",
"won_hanchans"
)
class Command(BaseCommand):
""" Recalculates all Kyu/Dan Rankings until the given date a writes them to the legacy fields. """
help = "Recalculates all Kyu/Dan Rankings until the given date a writes them to the legacy fields."
def add_arguments(self, parser):
parser.add_argument('-s', '--since', nargs='?', type=parse_date,
metavar='YYYY-MM-DD',
help='Use all Hanchans since the given date.')
parser.add_argument('-u', '--until', nargs='?', type=parse_date,
metavar='YYYY-MM-DD',
help='Only use Hanchans until the given date.')
def handle(self, *args, **options):
since = options.get('since', None)
until = options.get('until', None)
if isinstance(since, date):
since = datetime.combine(since, time(0, 0, 0))
since = timezone.make_aware(since)
if isinstance(until, date):
until = datetime.combine(until, time(23, 59, 59))
until = timezone.make_aware(until)
models.KyuDanRanking.objects.update(since=since, until=until, force_recalc=True)
# write the updated values to the legacy fields
for ranking in models.KyuDanRanking.objects.all():
print(ranking)
for attribute in LEGACY_ATTRIBUTES:
setattr(ranking, f"legacy_{attribute}", getattr(ranking, attribute))
value = getattr(ranking, attribute)
legacy_value = getattr(ranking, f"legacy_{attribute}")
print(f"{attribute}: {value}, legacy_{attribute}: {legacy_value}")
ranking.legacy_date = until.date()
print(f"legacy_date: {ranking.legacy_date}")
ranking.save()

View File

@@ -11,7 +11,7 @@ from django.utils import timezone
class Command(BaseCommand): class Command(BaseCommand):
""" Recalculate all Kyu/Dan Rankings """ """ reset every dan player to 1st dan with 0 points. """
help = "reset every dan player to 1st dan with 0 points." help = "reset every dan player to 1st dan with 0 points."

View File

@@ -74,11 +74,12 @@ class HanchanManager(models.Manager):
[hanchan.get_playerdata(user) for hanchan in queryset] [hanchan.get_playerdata(user) for hanchan in queryset]
return queryset return queryset
def season_hanchans(self, user=None, season=None, until=None): def season_hanchans(self, user: object = None, season: int = None, until: date = None):
"""Return all Hanchans that belong to a given or the current season. """Return all Hanchans that belong to a given or the current season.
:param user: Only return Hanchans where this user participated. :param user: Only return Hanchans where this user participated.
:param season: the year of the wanted season, current year if None. :param season: the year of the wanted season, current year if None.
:param until: only return hanchans played until the given date.
:return: QuerySet Object :return: QuerySet Object
""" """
try: try:
@@ -92,6 +93,7 @@ class HanchanManager(models.Manager):
:param user: Return Hanchans where this user participated. :param user: Return Hanchans where this user participated.
:param since: only return Hanchans played since the given datetime :param since: only return Hanchans played since the given datetime
:param until: only return hanchans played until the given date.
:param filter_args: To add specific arguments to the Django filter. :param filter_args: To add specific arguments to the Django filter.
:return: a QuerySet Object :return: a QuerySet Object
""" """
@@ -199,10 +201,8 @@ class SeasonRankingManager(models.Manager):
class KyuDanRankingManager(models.Manager): class KyuDanRankingManager(models.Manager):
def json_data(self): def json_data(self):
""" Get all Rankings for a given Season and return them as a list of """ Get all Rankings for a given Season and return them as a list of
dict objects, suitable for JSON exports and other processings. dict objects, suitable for JSON exports and other processing.
:return: a list() of dict() objects suitable for JSON export.
:param season: Season that should be exported, current season if empty
:return: a list() of dict() objects suiteable for JSON export.
""" """
json_data = list() json_data = list()
values = self.all() values = self.all()
@@ -237,8 +237,9 @@ class KyuDanRankingManager(models.Manager):
def update(self, since=None, until=None, force_recalc=False): def update(self, since=None, until=None, force_recalc=False):
old_attr = {'dan': None, 'dan_points': None, old_attr = {'dan': None, 'dan_points': None,
'kyu': None, 'kyu_points': None, 'won_hanchans': None, 'kyu': None, 'kyu_points': None,
'good_hanchans': None, 'hanchan_count': None} 'won_hanchans': None, 'good_hanchans': None,
'hanchan_count': None}
for ranking in self.all(): for ranking in self.all():
old_attr = {attr: getattr(ranking, attr) for attr in old_attr = {attr: getattr(ranking, attr) for attr in
old_attr.keys()} old_attr.keys()}

View File

@@ -0,0 +1,22 @@
# Generated by Django 4.2.2 on 2023-07-19 18:11
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('mahjong_ranking', '0006_auto_20171214_1318'),
]
operations = [
migrations.AlterModelOptions(
name='kyudanranking',
options={'ordering': (models.OrderBy(models.F('dan'), descending=True, nulls_last=True), '-dan_points', '-kyu_points', '-won_hanchans', '-good_hanchans', '-last_hanchan_date'), 'verbose_name': 'Kyū/Dan Wertung', 'verbose_name_plural': 'Kyū/Dan Wertungen'},
),
migrations.AddField(
model_name='kyudanranking',
name='legacy_max_dan_points',
field=models.PositiveIntegerField(default=0),
),
]

View File

@@ -12,7 +12,8 @@ from django.core.exceptions import ValidationError
from django.db import models from django.db import models
from django.urls import reverse from django.urls import reverse
from django.utils import timezone from django.utils import timezone
from django.utils.translation import ugettext as _ from django.utils.translation import gettext as _
from events.models import Event from events.models import Event
from . import DAN_RANKS_DICT, LOGGER, set_dirty from . import DAN_RANKS_DICT, LOGGER, set_dirty
@@ -47,11 +48,11 @@ class EventRanking(models.Model):
def recalculate(self): def recalculate(self):
""" """
Berechnet die durschnittliche Platzierung und Punkte, u.v.m. neu. Berechnet die durchschnittliche Platzierung und Punkte, u.v.m. neu.
Diese Daten werden benötigt um die Platzierung zu erstellen. Sie Diese Daten werden benötigt, um die Platzierung zu erstellen. Sie
können zwar sehr leicht errechnet werden, es macht trotzdem Sinn können zwar sehr leicht errechnet werden, es macht trotzdem Sinn
sie zwischen zu speichern. sie zwischenzuspeichern.
""" """
LOGGER.info( LOGGER.info(
u'Recalculate EventRanking for Player %s in %s', u'Recalculate EventRanking for Player %s in %s',
@@ -290,9 +291,8 @@ class Hanchan(models.Model):
self.bonus_points = getattr(self, '%s_bonus_points' % player) self.bonus_points = getattr(self, '%s_bonus_points' % player)
self.player_comment = getattr(self, '%s_comment' % player) self.player_comment = getattr(self, '%s_comment' % player)
def update_playerdata(self, user, **kwargs): def update_player_data(self, user, **kwargs):
"""i small workaround to access score, placement of a specific user """to access scores and placement of a specific user from templates"""
prominent from a in the user templates"""
for player in ('player1', 'player2', 'player3', 'player4'): for player in ('player1', 'player2', 'player3', 'player4'):
if getattr(self, player) == user: if getattr(self, player) == user:
setattr(self, '%s_input_score' % player, self.input_score) setattr(self, '%s_input_score' % player, self.input_score)
@@ -349,6 +349,7 @@ class KyuDanRanking(models.Model):
legacy_date = models.DateField(blank=True, null=True) legacy_date = models.DateField(blank=True, null=True)
legacy_dan = models.PositiveSmallIntegerField(blank=True, null=True) legacy_dan = models.PositiveSmallIntegerField(blank=True, null=True)
legacy_dan_points = models.PositiveIntegerField(blank=True, null=True) legacy_dan_points = models.PositiveIntegerField(blank=True, null=True)
legacy_max_dan_points = models.PositiveIntegerField(default=0)
legacy_kyu = models.PositiveSmallIntegerField(blank=True, null=True) legacy_kyu = models.PositiveSmallIntegerField(blank=True, null=True)
legacy_kyu_points = models.PositiveIntegerField(blank=True, null=True) legacy_kyu_points = models.PositiveIntegerField(blank=True, null=True)
legacy_hanchan_count = models.PositiveIntegerField(blank=True, null=True) legacy_hanchan_count = models.PositiveIntegerField(blank=True, null=True)
@@ -359,7 +360,10 @@ class KyuDanRanking(models.Model):
objects = managers.KyuDanRankingManager() objects = managers.KyuDanRankingManager()
class Meta(object): class Meta(object):
ordering = ('-dan_points', 'dan', '-kyu_points') ordering = (models.F("dan").desc(nulls_last=True),
'-dan_points', '-kyu_points',
'-won_hanchans', '-good_hanchans',
'-last_hanchan_date')
verbose_name = _(u'Kyū/Dan Ranking') verbose_name = _(u'Kyū/Dan Ranking')
verbose_name_plural = _(u'Kyū/Dan Rankings') verbose_name_plural = _(u'Kyū/Dan Rankings')
@@ -418,7 +422,7 @@ class KyuDanRanking(models.Model):
def append_tournament_bonuspoints(self, hanchan): def append_tournament_bonuspoints(self, hanchan):
""" """
Prüft ob es die letzte Hanchan in einem Turnier war. Wenn ja werden Prüft, ob es die letzte Hanchan in einem Turnier war. Wenn ja, werden
bei Bedarf Bonuspunkte vergeben, falls der Spieler das Turnier bei Bedarf Bonuspunkte vergeben, falls der Spieler das Turnier
gewonnen hat. gewonnen hat.
:param hanchan: Ein Player Objekt :param hanchan: Ein Player Objekt
@@ -474,7 +478,7 @@ class KyuDanRanking(models.Model):
# Setze alles auf die legacy Werte und berechne alles von neuem. # Setze alles auf die legacy Werte und berechne alles von neuem.
self.dan = self.legacy_dan self.dan = self.legacy_dan
self.dan_points = self.legacy_dan_points or 0 self.dan_points = self.legacy_dan_points or 0
self.max_dan_points = self.dan_points self.max_dan_points = self.legacy_max_dan_points or 0
self.kyu = self.legacy_kyu self.kyu = self.legacy_kyu
self.kyu_points = self.legacy_kyu_points or 0 self.kyu_points = self.legacy_kyu_points or 0
self.hanchan_count = self.legacy_hanchan_count or 0 self.hanchan_count = self.legacy_hanchan_count or 0
@@ -491,19 +495,24 @@ class KyuDanRanking(models.Model):
since = timezone.make_aware( since = timezone.make_aware(
datetime.combine(self.legacy_date, time(0, 0, 0)) datetime.combine(self.legacy_date, time(0, 0, 0))
) )
LOGGER.info(
"recalculating Kyu/Dan points for %(user)s since %(since)s...",
{'user': self.user, 'since': str(since)}
)
if since: if since:
valid_hanchans = valid_hanchans.filter(start__gt=since) valid_hanchans = valid_hanchans.filter(start__gt=since)
else:
since = valid_hanchans.aggregate(since=models.Min("start"))["since"]
if until: if until:
valid_hanchans = valid_hanchans.filter(start__lte=until) valid_hanchans = valid_hanchans.filter(start__lte=until)
else:
until = valid_hanchans.aggregate(until=models.Max("start"))["until"]
if valid_hanchans.count() > 0:
LOGGER.info(f"recalculating Kyu/Dan points for {self.user} ({since:%Y-%m-%d} - {until:%Y-%m-%d})...")
else:
LOGGER.info(f"No new valid Hanchans for {self.user}...")
for hanchan in valid_hanchans: for hanchan in valid_hanchans:
self.hanchan_count += 1 self.hanchan_count += 1
LOGGER.info(f"{self.user} Hanchan no. {self.hanchan_count} from {hanchan.start}")
hanchan.get_playerdata(self.user) hanchan.get_playerdata(self.user)
if since and hanchan.start < since: if since and hanchan.start < since:
LOGGER.debug(hanchan, "<", since, "no recalc") LOGGER.info(hanchan, "<", since, "no recalc")
self.dan_points += hanchan.dan_points or 0 self.dan_points += hanchan.dan_points or 0
self.kyu_points += hanchan.kyu_points or 0 self.kyu_points += hanchan.kyu_points or 0
self.update_rank() self.update_rank()
@@ -515,7 +524,7 @@ class KyuDanRanking(models.Model):
self.append_tournament_bonuspoints(hanchan) self.append_tournament_bonuspoints(hanchan)
self.update_rank() self.update_rank()
self.append_3_in_a_row_bonuspoints(hanchan) self.append_3_in_a_row_bonuspoints(hanchan)
hanchan.update_playerdata(self.user) hanchan.update_player_data(self.user)
hanchan.save(recalculate=False) hanchan.save(recalculate=False)
self.won_hanchans += 1 if hanchan.placement == 1 else 0 self.won_hanchans += 1 if hanchan.placement == 1 else 0
self.good_hanchans += 1 if hanchan.placement == 2 else 0 self.good_hanchans += 1 if hanchan.placement == 2 else 0
@@ -526,7 +535,7 @@ class KyuDanRanking(models.Model):
""" """
Berechne die Kyu bzw. Dan Punkte für eine Hanchan neu. Berechne die Kyu bzw. Dan Punkte für eine Hanchan neu.
:type hanchan: Hanchan :type hanchan: Hanchan
:param hanchan: Das Player Objekt das neuberechnet werden soll. :param hanchan: das Player-Objekt, welches neu berechnet werden soll.
""" """
hanchan.kyu_points = None hanchan.kyu_points = None
hanchan.dan_points = None hanchan.dan_points = None

View File

@@ -11,38 +11,42 @@ from .models import SeasonRanking
class EventRankingSitemap(GenericSitemap): class EventRankingSitemap(GenericSitemap):
@staticmethod @staticmethod
def items(): def items(**kwargs):
"""add all upcoming and archived events to the sitemap.""" """add all upcoming and archived events to the sitemap.
@param **kwargs:
"""
return Event.objects.all().exclude(eventranking=None) return Event.objects.all().exclude(eventranking=None)
@staticmethod @staticmethod
def location(event): def location(event, **kwargs):
return reverse('event-ranking', kwargs={'event': event.id}) return reverse('event-ranking', kwargs={'event': event.id})
class EventHanchanSitemap(GenericSitemap): class EventHanchanSitemap(GenericSitemap):
@staticmethod @staticmethod
def items(): def items(**kwargs):
"""add all upcoming and archived events to the sitemap.""" """add all upcoming and archived events to the sitemap.
@param **kwargs:
"""
return Event.objects.all().exclude(eventranking=None) return Event.objects.all().exclude(eventranking=None)
@staticmethod @staticmethod
def location(event): def location(event, **kwargs):
return reverse('event-hanchan-list', kwargs={'event': event.id}) return reverse('event-hanchan-list', kwargs={'event': event.id})
class MajongSeasonSitemap(Sitemap): class MahjongSeasonSitemap(Sitemap):
priority = 0.5 priority = 0.5
@staticmethod @staticmethod
def items(): def items(**kwargs):
seasons = SeasonRanking.objects.all().distinct('season').order_by( seasons = SeasonRanking.objects.all().distinct('season').order_by(
'season') 'season')
seasons = seasons.values_list('season', flat=True) seasons = seasons.values_list('season', flat=True)
return seasons return seasons
@staticmethod @staticmethod
def location(season): def location(season, **kwargs):
return reverse('mahjong-ladder', kwargs={'season': season}) return reverse('mahjong-ladder', kwargs={'season': season})
@staticmethod @staticmethod

View File

@@ -18,7 +18,7 @@
<fieldset class="hanchan"> <fieldset class="hanchan">
{% for hidden in form.hidden_fields %} {{ hidden }} {% endfor %} {% for hidden in form.hidden_fields %} {{ hidden }} {% endfor %}
<p> <p>
<label for="id_{{ form.start.html_name }}_0" class="field_name {{ form.start.css_classes }}">{{ form.start.label }}:</label> <label for="{{ form.start.if_for_label }}" class="field_name {{ form.start.css_classes }}">{{ form.start.label }}:</label>
{{ form.start }} {{ form.start }}
{{ form.start.errors }} {{ form.start.errors }}
</p> </p>

View File

@@ -7,7 +7,7 @@
<h3 class="grid_12" id="{{ hanchan.pk }}">{{hanchan.start|time:'H:i'}}: {{ hanchan.player_names }}</h3> <h3 class="grid_12" id="{{ hanchan.pk }}">{{hanchan.start|time:'H:i'}}: {{ hanchan.player_names }}</h3>
{% for player in hanchan.player_list %} {% for player in hanchan.player_list %}
<a class="grid_1" href="{% url 'player-ladder-score' player.user %}"><img <a class="grid_1" href="{% url 'player-ladder-score' player.user %}"><img
src="{% thumbnail user.avatar|default:'unknown_profile.jpg' 'avatar' %}" src="{% thumbnail player.user.avatar|default:'unknown_profile.jpg' 'avatar' %}"
width="70" height="70" width="70" height="70"
class="avatar" alt="{{ player.user }}" title="{{ player.user }}"/></a> class="avatar" alt="{{ player.user }}" title="{{ player.user }}"/></a>
<div class="grid_2"> <div class="grid_2">
@@ -33,7 +33,7 @@
<legend>{% trans "Delete Hanchan" %}</legend> <legend>{% trans "Delete Hanchan" %}</legend>
{% include 'form.html' %} {% include 'form.html' %}
<label class="field_name fa fa-exclamation-triangle fa-5x fa-pull-left" ></label> <label class="field_name fa fa-exclamation-triangle fa-5x fa-pull-left" ></label>
<p>Bist du sicher dass du diese Hanchan und alle Ergebnisse und Wertungen welche sich hierauf beziehen löschen möchtest?</p> <p>Bist du sicher, dass du diese Hanchan und alle Ergebnisse und Wertungen, welche sich hierauf beziehen löschen möchtest?</p>
<br class="clear"> <br class="clear">
<p class="buttonbar"> <p class="buttonbar">
<button type="button" onclick="window.history.back()"><span class="fa fa-close"></span> {% trans 'Cancel' %}</button> <button type="button" onclick="window.history.back()"><span class="fa fa-close"></span> {% trans 'Cancel' %}</button>

View File

@@ -31,7 +31,7 @@
{% for season_link in season_list%} {% for season_link in season_list%}
<option <option
value="{% url 'mahjong-ladder' season_link %}" value="{% url 'mahjong-ladder' season_link %}"
{% ifequal season season_link %} selected="selected"{% endifequal %}>{{ season_link }} selected="selected">{{ season_link }}
</option> </option>
{% endfor %} {% endfor %}
</select> </select>

View File

@@ -52,7 +52,7 @@
</table> </table>
{% if kyu_dan_ranking.legacy_date %} {% if kyu_dan_ranking.legacy_date %}
<p><strong>Frühere Dan Punkte vom {{ kyu_dan_ranking.legacy_date|date }}:</strong> {{kyu_dan_ranking.legacy_dan_points }}</p> <p><strong>Frühere Dan Punkte vom {{kyu_dan_ranking.legacy_date|date}}:</strong> {{kyu_dan_ranking.legacy_dan_points}}</p>
{% endif %} {% endif %}
{% endblock %} {% endblock %}

View File

@@ -64,8 +64,8 @@
<select id="season" name="season" size="1"> <select id="season" name="season" size="1">
{% for season_link in season_list%} {% for season_link in season_list%}
<option <option
{% ifequal season season_link %} selected="selected"{% endifequal %} selected="selected"
value="{{ season_link }}">{{ season_link }}</option> value="{{ season_link }}">{{ season_link }}</option>
{% endfor %} {% endfor %}
</select> </select>
<button type="submit">{% trans 'Go' %}</button> <button type="submit">{% trans 'Go' %}</button>
@@ -75,5 +75,5 @@
{% endblock %} {% endblock %}
{% block buttonbar %} {% block buttonbar %}
<a href="?download=xlsx" class="button"><span class="fa fa-table"></span> Download</a> <a href="?download=xlsx{% if season %}&season={{ season }}{% endif %}" class="button"><span class="fa fa-table"></span> Download</a>
{% endblock %} {% endblock %}

View File

@@ -50,7 +50,7 @@
{% empty %} {% empty %}
<tr> <tr>
<td colspan="8">Leider hat es noch niemand in das Ranking geschafft. <td colspan="8">Leider hat es noch niemand in das Ranking geschafft.
Ein Spieler wird erst ins Ranking genommen wenn er 5 Hanchans absolviert hat. Spieler werden erst ins Ranking genommen, wenn sie 5 Hanchans absolviert haben.
</td> </td>
</tr> </tr>
{% endfor %} {% endfor %}

View File

@@ -1,38 +1,24 @@
""" URLS to display the Riichi Mahjong Rankings and the Ladder system.""" """ URLS to display the Riichi Mahjong Rankings and the Ladder system."""
from django.conf.urls import url from django.urls import path
from django.views.generic import RedirectView from django.views.generic import RedirectView
from . import views from . import views
urlpatterns = [ # Ignore PyLintBear (C0103)
url(r'^$', urlpatterns = [
RedirectView.as_view(url='/ranking/mahjong-ladder/', permanent=True)), path("", RedirectView.as_view(url='/ranking/mahjong-ladder/', permanent=True)),
url(r'^event/(?P<event>[\d]+)/add-hanchan/$', path('event/<int:event>/add-hanchan/', views.HanchanForm.as_view(), name="add-hanchan-form"),
views.HanchanForm.as_view(), name="add-hanchan-form"), path('event/<int:event>/edit/', views.EventHanchanForm.as_view(), name="event-hanchan-form"),
url(r'^event/(?P<event>[\d]+)/edit/$', path('event/<int:event>/mahjong/', views.EventHanchanList.as_view(), name="event-hanchan-list"),
views.EventHanchanForm.as_view(), name="event-hanchan-form"), path('event/<int:event>/mahjong-ranking/', views.EventRankingList.as_view(), name="event-ranking"),
url(r'^event/(?P<event>[\d]+)/mahjong/$', path('hanchan/<int:hanchan>/edit/', views.HanchanForm.as_view(), name="edit-hanchan"),
views.EventHanchanList.as_view(), name="event-hanchan-list"), path('hanchan/<int:hanchan>/delete/', views.DeleteHanchan.as_view(), name="delete-hanchan"),
url(r'^event/(?P<event>[\d]+)/mahjong-ranking/$', path('mahjong-ladder/', views.SeasonRankingList.as_view(), name="mahjong-ladder"),
views.EventRankingList.as_view(), name="event-ranking"), path('mahjong-ladder/<int:season>/', views.SeasonRankingList.as_view(), name="mahjong-ladder"),
url(r'^hanchan/(?P<hanchan>[\d]+)/edit/$', path('player/<slug:username>/dan/', views.PlayerDanScore.as_view(), name="player-dan-score"),
views.HanchanForm.as_view(), name="edit-hanchan"), path('player/<slug:username>/invalid/', views.PlayerInvalidScore.as_view(), name="player-invalid-score"),
url(r'^hanchan/(?P<hanchan>[\d]+)/delete/$', path('player/<slug:username>/kyu/', views.PlayerKyuScore.as_view(), name="player-kyu-score"),
views.DeleteHanchan.as_view(), name="delete-hanchan"), path('player/<slug:username>/ladder/', views.PlayerLadderScore.as_view(), name="player-ladder-score"),
url(r'^mahjong-ladder/$', views.SeasonRankingList.as_view(), path('mahjong/', views.KyuDanRankingList.as_view(), name="kyudanranking-list"),
name="mahjong-ladder"), path('mahjong/<str:order_by>/', views.KyuDanRankingList.as_view(), name="kyudanranking-list"),
url(r'^mahjong-ladder/(?P<season>[\d]+)/$',
views.SeasonRankingList.as_view(), name="mahjong-ladder"),
url(r'^player/(?P<username>[\-\.\d\w]+)/dan/$',
views.PlayerDanScore.as_view(), name="player-dan-score"),
url(r'^player/(?P<username>[\-\.\d\w]+)/invalid/$',
views.PlayerInvalidScore.as_view(), name="player-invalid-score"),
url(r'^player/(?P<username>[\-\.\d\w]+)/kyu/$',
views.PlayerKyuScore.as_view(), name="player-kyu-score"),
url(r'^player/(?P<username>[\-\.\d\w]+)/ladder/$',
views.PlayerLadderScore.as_view(), name="player-ladder-score"),
url(r'^mahjong/$', views.KyuDanRankingList.as_view(),
name="kyudanranking-list"),
url(r'^mahjong/(?P<order_by>[\+\-][a-z_]+)/$',
views.KyuDanRankingList.as_view(), name="kyudanranking-list"),
] ]

View File

@@ -8,7 +8,7 @@ from django.contrib.auth.mixins import LoginRequiredMixin, \
PermissionRequiredMixin PermissionRequiredMixin
from django.contrib.messages.views import SuccessMessageMixin from django.contrib.messages.views import SuccessMessageMixin
from django.urls import reverse from django.urls import reverse
from django.utils.translation import ugettext as _ from django.utils.translation import gettext as _
from django.views import generic from django.views import generic
from events.mixins import EventDetailMixin from events.mixins import EventDetailMixin
@@ -45,7 +45,7 @@ def get_kyu_dan_ranking(user=None):
class DeleteHanchan(EventDetailMixin, PermissionRequiredMixin, class DeleteHanchan(EventDetailMixin, PermissionRequiredMixin,
generic.DeleteView): generic.DeleteView):
"""Deletes a Hanchan if confimration has been answerd with 'yes'.""" """Deletes a Hanchan if confirmation has been answered with 'yes'."""
form_class = forms.HanchanForm form_class = forms.HanchanForm
model = models.Hanchan model = models.Hanchan
permission_required = 'mahjong_ranking.delete_hanchan' permission_required = 'mahjong_ranking.delete_hanchan'
@@ -53,7 +53,7 @@ class DeleteHanchan(EventDetailMixin, PermissionRequiredMixin,
def get_success_url(self): def get_success_url(self):
""" """
Return to the HachanList of the event form the deleted hanchan. Return to the HanchanList of the event form the deleted hanchan.
:return: URL of the EventHanchanList for the event :return: URL of the EventHanchanList for the event
""" """
return reverse('event-hanchan-list', return reverse('event-hanchan-list',
@@ -71,7 +71,7 @@ class HanchanForm(SuccessMessageMixin, EventDetailMixin,
def get_form_class(self): def get_form_class(self):
""" """
Users with hanchan edit persmission can also un-/confirm hanchans. Users with hanchan edit permission can also un-/confirm a Hanchan.
:return: forms.HanchanForm, or forms.HanchanAdminForm :return: forms.HanchanForm, or forms.HanchanAdminForm
""" """
return forms.HanchanAdminForm if self.request.user.has_perm( return forms.HanchanAdminForm if self.request.user.has_perm(
@@ -123,27 +123,27 @@ class HanchanForm(SuccessMessageMixin, EventDetailMixin,
class EventHanchanForm(EventDetailMixin, PermissionRequiredMixin, class EventHanchanForm(EventDetailMixin, PermissionRequiredMixin,
generic.TemplateView): generic.TemplateView):
"""Display a Formset to add and Edit Hanchans of the specific Event.""" """Display a Formset to add and Edit Hanchans of the specific Event."""
formset: forms.HanchanFormset
permission_required = 'mahjong_ranking.add_hanchan' permission_required = 'mahjong_ranking.add_hanchan'
template_name = 'mahjong_ranking/eventhanchan_form.html' template_name = 'mahjong_ranking/eventhanchan_form.html'
model = models.Hanchan model = models.Hanchan
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
self.event = models.Event.objects.get(pk=self.kwargs['event']) self.event = models.Event.objects.get(pk=self.kwargs['event'])
self.formset = forms.HanchanFormset(
instance=self.event,
initial=[{'start': self.event.start}]
)
context = super(EventHanchanForm, self).get_context_data() context = super(EventHanchanForm, self).get_context_data()
context['formset'] = self.formset context['formset'] = self.formset
return context return context
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
self.get_queryset() self.get_queryset()
self.formset = forms.HanchanFormset(
instance=self.event,
initial=[{'start': self.event.start}]
)
context = self.get_context_data(**kwargs) context = self.get_context_data(**kwargs)
return self.render_to_response(context) return self.render_to_response(context)
def post(self, request, *args, **kwargs): def post(self, request, *args, **kwargs):
print("ICH WURDE GEPOSTET!!!!")
self.get_queryset() self.get_queryset()
self.formset = forms.HanchanFormset( self.formset = forms.HanchanFormset(
self.request.POST, self.request.POST,
@@ -172,9 +172,9 @@ class EventRankingList(EventDetailMixin, generic.ListView):
class KyuDanRankingList(MahjongMixin, generic.ListView): class KyuDanRankingList(MahjongMixin, generic.ListView):
"""List all Players with an Kyu or Dan score. """ """List all Players with a Kyu or Dan score. """
order_by = None order_by = None
paginate_by = 25 paginate_by = 100
def dispatch(self, request, *args, **kwargs): def dispatch(self, request, *args, **kwargs):
"""Set the order_by settings, revert to default_order if necessary.""" """Set the order_by settings, revert to default_order if necessary."""
@@ -192,7 +192,7 @@ class KyuDanRankingList(MahjongMixin, generic.ListView):
class SeasonRankingList(MahjongMixin, generic.ListView): class SeasonRankingList(MahjongMixin, generic.ListView):
model = models.SeasonRanking model = models.SeasonRanking
paginate_by = 25 paginate_by = 100
season = None season = None
def get_queryset(self): def get_queryset(self):
@@ -205,18 +205,23 @@ class SeasonRankingList(MahjongMixin, generic.ListView):
class PlayerScore(LoginRequiredMixin, generic.ListView): class PlayerScore(LoginRequiredMixin, generic.ListView):
paginate_by = 25 paginate_by: int = 100
user = auth.get_user_model() user = auth.get_user_model()
kyu_dan_ranking = None
season = None
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
user_model = auth.get_user_model() user_model = auth.get_user_model()
try: try:
self.user = user_model.objects.get( self.user = user_model.objects.get(
username=self.kwargs.get('username')) username=self.kwargs.get('username'))
self.kyu_dan_ranking = get_kyu_dan_ranking(user=self.user)
self.season = int(self.request.GET.get('season', date.today().year))
except user_model.DoesNotExist: except user_model.DoesNotExist:
raise django.http.Http404( raise django.http.Http404(
_("No user found matching the name {}").format( _("No user found matching the name {}").format(
self.kwargs.get('username'))) self.kwargs.get('username')))
if request.GET.get('download') == 'xlsx': if request.GET.get('download') == 'xlsx':
return self.get_xlsx(request, *args, **kwargs) return self.get_xlsx(request, *args, **kwargs)
return super(PlayerScore, self).get(request, *args, **kwargs) return super(PlayerScore, self).get(request, *args, **kwargs)
@@ -228,7 +233,7 @@ class PlayerScore(LoginRequiredMixin, generic.ListView):
context['kyu_dan_ranking'] = models.KyuDanRanking.objects.get( context['kyu_dan_ranking'] = models.KyuDanRanking.objects.get(
user=self.user) user=self.user)
except models.KyuDanRanking.DoesNotExist: except models.KyuDanRanking.DoesNotExist:
context['ranking'] = None context['kyu_dan_ranking'] = None
try: try:
context['ladder_ranking'] = models.SeasonRanking.objects.get( context['ladder_ranking'] = models.SeasonRanking.objects.get(
user=self.user, user=self.user,
@@ -238,27 +243,31 @@ class PlayerScore(LoginRequiredMixin, generic.ListView):
return context return context
def get_xlsx(self, request, *args, **kwargs): def get_xlsx(self, request, *args, **kwargs):
self.object_list = self.get_queryset()
response = django.http.HttpResponse( response = django.http.HttpResponse(
content_type='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet') content_type='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet')
response['Content-Disposition'] = 'attachment; ' \ response['Content-Disposition'] = f'attachment; filename="{self.xlsx_filename}"'
'filename="{xlsx_filename}"'.format( xlsx_workbook = xlsx.Workbook()
xlsx_filename=self.xlsx_filename) xlsx_workbook.generate_sheet(
xlxs_workbook = xlsx.Workbook()
xlxs_workbook.generate_sheet(
title=self.xlsx_filename.split('.')[0], title=self.xlsx_filename.split('.')[0],
columns_settings=self.xlsx_columns, columns_settings=self.xlsx_columns,
object_list=self.object_list object_list=self.get_queryset()
) )
xlxs_workbook.save(response) xlsx_workbook.save(response)
return response return response
@property
def xlsx_columns(self):
return {}
@property
def xlsx_filename(self):
return ""
class PlayerDanScore(PlayerScore): class PlayerDanScore(PlayerScore):
template_name = 'mahjong_ranking/player_dan_score.html' template_name = 'mahjong_ranking/player_dan_score.html'
def get_queryset(self): def get_queryset(self):
self.kyu_dan_ranking = get_kyu_dan_ranking(user=self.user)
return models.Hanchan.objects.dan_hanchans( return models.Hanchan.objects.dan_hanchans(
user=self.user, user=self.user,
since=self.kyu_dan_ranking.legacy_date) since=self.kyu_dan_ranking.legacy_date)
@@ -298,17 +307,20 @@ class PlayerDanScore(PlayerScore):
@property @property
def xlsx_filename(self): def xlsx_filename(self):
return "{username}_dan_score.xlsx".format(username=self.user.username) return f"{self.user.username}_dan_score.xlsx"
class PlayerInvalidScore(PlayerScore): class PlayerInvalidScore(PlayerScore):
template_name = 'mahjong_ranking/player_invalid_score.html' template_name = 'mahjong_ranking/player_invalid_score.html'
def get_queryset(self): def get_queryset(self):
self.xlsx_filename = "{username}_invalid_score.xlsx".format(
username=self.user.username)
return models.Hanchan.objects.unconfirmed(user=self.user) return models.Hanchan.objects.unconfirmed(user=self.user)
@property
def xlsx_filename(self):
return f"{self.user.username}_invalid_score.xlsx"
class PlayerKyuScore(PlayerScore): class PlayerKyuScore(PlayerScore):
template_name = 'mahjong_ranking/player_kyu_score.html' template_name = 'mahjong_ranking/player_kyu_score.html'
@@ -354,7 +366,7 @@ class PlayerKyuScore(PlayerScore):
@property @property
def xlsx_filename(self): def xlsx_filename(self):
return "{username}_kyu_score.xlsx".format(username=self.user.username) return f"{self.user.username}_kyu_score.xlsx"
class PlayerLadderScore(PlayerScore): class PlayerLadderScore(PlayerScore):
@@ -370,7 +382,6 @@ class PlayerLadderScore(PlayerScore):
return context return context
def get_queryset(self, **kwargs): def get_queryset(self, **kwargs):
self.season = int(self.request.GET.get('season', date.today().year))
hanchan_list = models.Hanchan.objects.season_hanchans( hanchan_list = models.Hanchan.objects.season_hanchans(
user=self.user, user=self.user,
season=self.season season=self.season
@@ -408,7 +419,4 @@ class PlayerLadderScore(PlayerScore):
@property @property
def xlsx_filename(self): def xlsx_filename(self):
return "{username}_ladder_score_{season}.xlsx".format( return f"{self.user.username}_ladder_{self.season}_score.xlsx"
username=self.user.username,
season=self.season
)

View File

@@ -1,6 +1,6 @@
""" Admin Interface to manipulate the maistar ranking """ """ Admin Interface to manipulate the maistar ranking """
from django.contrib import admin from django.contrib import admin
from django.utils.translation import ugettext as _ from django.utils.translation import gettext as _
from . import forms, models from . import forms, models

View File

@@ -1,7 +1,7 @@
"""Django Forms to add and edit Mai-Star games.""" """Django Forms to add and edit Mai-Star games."""
from django import forms from django import forms
from django.utils.translation import ugettext as _ from django.utils.translation import gettext as _
from . import models from . import models

View File

@@ -7,7 +7,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: kasu.mahjong_ranking\n" "Project-Id-Version: kasu.mahjong_ranking\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-12-13 23:38+0100\n" "POT-Creation-Date: 2023-07-26 18:31+0200\n"
"PO-Revision-Date: 2016-09-28 00:24+0200\n" "PO-Revision-Date: 2016-09-28 00:24+0200\n"
"Last-Translator: Christian Berg <xeniac.at@gmail.com>\n" "Last-Translator: Christian Berg <xeniac.at@gmail.com>\n"
"Language-Team: Kasu <verein@kasu.at>\n" "Language-Team: Kasu <verein@kasu.at>\n"
@@ -69,7 +69,7 @@ msgstr "Wurde bestätigt"
msgid "the game only counts whe it has been confirmed" msgid "the game only counts whe it has been confirmed"
msgstr "das Spiel zählt nur wenn es bestätigt wurde" msgstr "das Spiel zählt nur wenn es bestätigt wurde"
#: models.py:70 models.py:153 templates/maistar_ranking/player_game_list.html:6 #: models.py:70 models.py:154 templates/maistar_ranking/player_game_list.html:6
#: templates/maistar_ranking/ranking_list.html:4 #: templates/maistar_ranking/ranking_list.html:4
#: templates/maistar_ranking/ranking_list.html:72 #: templates/maistar_ranking/ranking_list.html:72
msgid "Season" msgid "Season"
@@ -79,6 +79,30 @@ msgstr "Saison"
msgid "Mai-Star Game with {0} from {1:%Y-%m-%d}" msgid "Mai-Star Game with {0} from {1:%Y-%m-%d}"
msgstr "Mai-Star Spiel mit {0} vom {1:%Y-%m-%d}" msgstr "Mai-Star Spiel mit {0} vom {1:%Y-%m-%d}"
#: templates/maistar_ranking/game_confirm_delete.html:4
#: templates/maistar_ranking/game_confirm_delete.html:9
msgid "Delete game"
msgstr "Spiel löschen"
#: templates/maistar_ranking/game_confirm_delete.html:12
#: templates/maistar_ranking/game_list.html:14
msgid "Place"
msgstr "Platz"
#: templates/maistar_ranking/game_confirm_delete.html:18
#: templates/maistar_ranking/game_list.html:19
#: templates/maistar_ranking/player_game_list.html:36
msgid "Points"
msgstr "Punkte"
#: templates/maistar_ranking/game_confirm_delete.html:23
msgid "Cancel"
msgstr "Abbrechen"
#: templates/maistar_ranking/game_confirm_delete.html:25
msgid "Delete"
msgstr "Löschen"
#: templates/maistar_ranking/game_form.html:5 #: templates/maistar_ranking/game_form.html:5
#: templates/maistar_ranking/game_form.html:16 #: templates/maistar_ranking/game_form.html:16
#: templates/maistar_ranking/game_list.html:27 #: templates/maistar_ranking/game_list.html:27
@@ -113,15 +137,6 @@ msgstr "Gespielte Mai-Star Spiele"
msgid "Game" msgid "Game"
msgstr "Spiel" msgstr "Spiel"
#: templates/maistar_ranking/game_list.html:14
msgid "Place"
msgstr "Platz"
#: templates/maistar_ranking/game_list.html:19
#: templates/maistar_ranking/player_game_list.html:36
msgid "Points"
msgstr "Punkte"
#: templates/maistar_ranking/game_list.html:24 #: templates/maistar_ranking/game_list.html:24
#: templates/maistar_ranking/player_game_list.html:41 #: templates/maistar_ranking/player_game_list.html:41
msgid "Delete Game" msgid "Delete Game"
@@ -135,19 +150,6 @@ msgstr "Für diese Veranstaltung wurden noch keine Mai-Star Spiele erfasst."
msgid "Edit Event" msgid "Edit Event"
msgstr "Veranstaltung bearbeiten" msgstr "Veranstaltung bearbeiten"
#: templates/maistar_ranking/hanchan_confirm_delete.html:4
#: templates/maistar_ranking/hanchan_confirm_delete.html:10
msgid "Delete game"
msgstr "Spiel löschen"
#: templates/maistar_ranking/hanchan_confirm_delete.html:13
msgid "Cancel"
msgstr "Abbrechen"
#: templates/maistar_ranking/hanchan_confirm_delete.html:14
msgid "Delete"
msgstr "Löschen"
#: templates/maistar_ranking/page.html:5 #: templates/maistar_ranking/page.html:5
msgid "Archive" msgid "Archive"
msgstr "Archiv" msgstr "Archiv"

View File

@@ -6,7 +6,7 @@ from django.db import models
from django.db.models.signals import post_delete, post_save from django.db.models.signals import post_delete, post_save
from django.dispatch import receiver from django.dispatch import receiver
from django.urls import reverse from django.urls import reverse
from django.utils.translation import ugettext as _ from django.utils.translation import gettext as _
from events.models import Event from events.models import Event
from . import settings, managers from . import settings, managers

View File

@@ -0,0 +1,30 @@
{% extends "base.html" %}
{% load i18n humanize thumbnail %}
{% block meta_title %}{% trans 'Delete game' %}{% endblock %}
{% block maincontent %}
<form method="post">
{% csrf_token %}
<h2 class="grid_12">{% trans "Delete game" %}</h2>
{% for player in game.player_list %}
<div class="grid_2 center">
<h4>{{ player.placement|ordinal }} {% trans 'Place' %}</h4>
<a class="avatar player"
href="{% url 'maistar-player-games' username=player.user.username season=game.season %}"><img
src="{% thumbnail player.user.avatar|default:'unknown_profile.jpg' 'avatar' %}"
width="70" height="70" alt=""/></a>
<p><a href="{% url 'maistar-player-games' username=player.user.username season=game.season %}">{{player.user.username}}</a><br/>
{{player.score}} {% trans 'Points' %}</p>
</div>
{% endfor %}
{% include 'form.html' %}
<p class="buttonbar grid_12">
<button type="button" onclick="window.history.back()"><span class="fa fa-close"></span>{% trans 'Cancel' %}
</button>
<button type="submit"><span class="fa fa-trash"></span>{% trans 'Delete' %}</button>
</p>
</form>
{% endblock %}
{% block buttonbar %}{% endblock %}

View File

@@ -1,20 +0,0 @@
{% extends "base.html" %}
{% load i18n comments %}
{% block meta_title %}{% trans 'Delete game' %}{% endblock %}
{% block content %}
<form method="post">
{% csrf_token %}
<fieldset>
<legend>{% trans "Delete game" %}</legend>
{% include 'form.html' %}
<p class="buttonbar">
<button type="button" onclick="window.history.back()"><span class="fa fa-close"></span>{% trans 'Cancel' %}</button>
<button type="submit"><span class="fa fa-trash"></span>{% trans 'Delete' %}</button>
</p>
</fieldset>
</form>
{% endblock %}
{% block buttonbar %}{% endblock %}

View File

@@ -4,6 +4,6 @@
{% block additional_nav_elements %} {% block additional_nav_elements %}
<a href="{% url 'season_ranking-archive' %}" class="{% if is_archive %}active{% endif %}">{% trans 'Archive' %}</a> <a href="{% url 'season_ranking-archive' %}" class="{% if is_archive %}active{% endif %}">{% trans 'Archive' %}</a>
{% if perms.events.add_event %} {% if perms.events.add_event %}
<a href="{% url 'event-form' %}" class="{% ifequal request.path '/events/add/' %}active{% endifequal %}">{% trans 'Add Event' %}</a> <a href="{% url 'event-form' %}" class="{% if request.path == '/events/add/' %}active{% endif %}">{% trans 'Add Event' %}</a>
{% endif %} {% endif %}
{% endblock %} {% endblock %}

View File

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

View File

@@ -1,24 +1,16 @@
""" Definition of the URL paths to access the views of the Maistar ranking. """ """ Definition of the URL paths to access the views of the Maistar ranking. """
from django.conf.urls import url from django.urls import path
from . import views from . import views
urlpatterns = [ urlpatterns = [
url('^$', views.ListRankings.as_view()), path("", views.ListRankings.as_view()),
url(r'^event/(?P<event>[\d]+)/maistar/$', path('event/<int:event>/maistar/', views.ListGames.as_view(), name="maistar-game-list"),
views.ListGames.as_view(), name="maistar-game-list"), path('event/<int:event>/maistar/add/', views.GameForm.as_view(), name="maistar-add-game"),
url(r'^event/(?P<event>[\d]+)/maistar/add/$', path('maistar/', views.ListRankings.as_view(), name="maistar-ranking"),
views.GameForm.as_view(), name="maistar-add-game"), path('maistar/<int:season>/', views.ListRankings.as_view(), name="maistar-ranking"),
url(r'^maistar/(?P<game>[\d]+)/edit/$', path('maistar/<int:game>/edit/', views.GameForm.as_view(), name="maistar-edit-game"),
views.GameForm.as_view(), name="maistar-edit-game"), path('maistar/<int:game>/delete/', views.DeleteGame.as_view(), name="maistar-delete-game"),
url(r'^maistar/(?P<game>[\d]+)/delete/$', path('player/<slug:username>/maistar/', views.ListPlayerGames.as_view(), name="maistar-player-games"),
views.DeleteGame.as_view(), name="maistar-delete-game"), path('player/<slug:username>/maistar/<int:season>/', views.ListPlayerGames.as_view(), name="maistar-player-games"),
url(r'^maistar/$',
views.ListRankings.as_view(), name="maistar-ranking"),
url(r'^maistar/(?P<season>[\d]+)/$',
views.ListRankings.as_view(), name="maistar-ranking"),
url(r'^player/(?P<username>[\-\.\d\w]+)/maistar/$',
views.ListPlayerGames.as_view(), name="maistar-player-games"),
url(r'^player/(?P<username>[\-\.\d\w]+)/maistar/(?P<season>[\d]+)/$',
views.ListPlayerGames.as_view(), name="maistar-player-games"),
] ]

View File

@@ -31,11 +31,11 @@ class GameForm(EventDetailMixin, PermissionRequiredMixin, generic.UpdateView):
class DeleteGame(EventDetailMixin, PermissionRequiredMixin, generic.DeleteView): class DeleteGame(EventDetailMixin, PermissionRequiredMixin, generic.DeleteView):
""" """
Fragt zuerst nach, ob die Hanchan wirklich gelöscht werden soll. Fragt zuerst nach, ob die Hanchan wirklich gelöscht werden soll.
Wir die Frage mit "Ja" beantwortet, wird die die Hanchan gelöscht. Wir die Frage mit "Ja" beantwortet, wird die Hanchan gelöscht.
""" """
model = models.Game model = models.Game
permission_required = 'maistar_ranking.delete_game' permission_required = 'maistar_ranking.delete_game'
pk_url_kwarg = 'hanchan' pk_url_kwarg = 'game'
def get_success_url(self): def get_success_url(self):
return reverse( return reverse(
@@ -46,11 +46,12 @@ class DeleteGame(EventDetailMixin, PermissionRequiredMixin, generic.DeleteView):
class UpdateGame(EventDetailMixin, PermissionRequiredMixin, generic.UpdateView): class UpdateGame(EventDetailMixin, PermissionRequiredMixin, generic.UpdateView):
""" """
Ein Formular um eine neues Spiel anzulegen, bzw. eine bestehendes zu Ein Formular um ein neues Spiel anzulegen, bzw. eine bestehendes zu
bearbeiten. bearbeiten.
""" """
model = models.Game model = models.Game
permission_required = 'maistar_ranking.update_game' permission_required = 'maistar_ranking.update_game'
pk_url_kwarg = 'game'
def get_object(self, queryset=None): # @UnusedVariable def get_object(self, queryset=None): # @UnusedVariable
game = models.Game.objects.get(id=self.kwargs['game']) game = models.Game.objects.get(id=self.kwargs['game'])

View File

@@ -4,7 +4,7 @@ from django.contrib import admin, messages
from django.contrib.auth.admin import UserAdmin, GroupAdmin from django.contrib.auth.admin import UserAdmin, GroupAdmin
from django.contrib.auth.models import Group from django.contrib.auth.models import Group
from django.core.exceptions import PermissionDenied from django.core.exceptions import PermissionDenied
from django.utils.translation import ugettext as _ from django.utils.translation import gettext as _
from easy_thumbnails import fields, widgets from easy_thumbnails import fields, widgets
from membership.models import Membership, ActivationRequest from membership.models import Membership, ActivationRequest

View File

@@ -9,7 +9,7 @@ from django import forms
from django.conf import settings from django.conf import settings
from django.contrib import auth from django.contrib import auth
from django.contrib.sites.models import Site from django.contrib.sites.models import Site
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import gettext_lazy as _
from utils.massmailer import MassMailer from utils.massmailer import MassMailer
from . import models from . import models
from content.models import Page from content.models import Page

View File

@@ -7,7 +7,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: kasu.membership\n" "Project-Id-Version: kasu.membership\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-12-13 23:38+0100\n" "POT-Creation-Date: 2023-06-09 22:00+0200\n"
"PO-Revision-Date: 2018-05-08 00:19+0105\n" "PO-Revision-Date: 2018-05-08 00:19+0105\n"
"Last-Translator: b'Christian Berg <kasu@xendynastie.at>'\n" "Last-Translator: b'Christian Berg <kasu@xendynastie.at>'\n"
"Language-Team: Kasu <verein@kasu.at>\n" "Language-Team: Kasu <verein@kasu.at>\n"
@@ -35,38 +35,38 @@ msgstr "Ausgewählte Benutzer freischalten"
msgid "Cleanup selected Activation Requests" msgid "Cleanup selected Activation Requests"
msgstr "Ausgewählte Aktivierungsanfragen bereinigen" msgstr "Ausgewählte Aktivierungsanfragen bereinigen"
#: admin.py:62 #: admin.py:65
#, python-format #, python-format
msgid "Can't remove personal data from active member %s." msgid "Can't remove personal data from active member %s."
msgstr "" msgstr ""
"Persönliche Daten von aktiven Mitglied %s können nicht entfernt werden." "Persönliche Daten von aktiven Mitglied %s können nicht entfernt werden."
#: admin.py:64 #: admin.py:68
#, python-format #, python-format
msgid "Cleared %d personal data profiles." msgid "Cleared %d personal data profiles."
msgstr "Persönliche Daten in %d Profilen entfernt." msgstr "Persönliche Daten in %d Profilen entfernt."
#: admin.py:66 #: admin.py:71
msgid "Clear personal Data" msgid "Clear personal Data"
msgstr "Persönliche Daten bereinigen" msgstr "Persönliche Daten bereinigen"
#: admin.py:75 #: admin.py:80
msgid "Group" msgid "Group"
msgstr "Gruppe" msgstr "Gruppe"
#: admin.py:76 #: admin.py:81
msgid "Groups" msgid "Groups"
msgstr "Gruppen" msgstr "Gruppen"
#: admin.py:102 forms.py:73 models.py:163 models.py:218 #: admin.py:107 forms.py:73 models.py:163 models.py:218
msgid "Membership" msgid "Membership"
msgstr "Mitgliedschaft" msgstr "Mitgliedschaft"
#: admin.py:107 #: admin.py:112
msgid "Permissions" msgid "Permissions"
msgstr "Berechtigung" msgstr "Berechtigung"
#: admin.py:109 #: admin.py:114
msgid "Important dates" msgid "Important dates"
msgstr "Wichtige Daten" msgstr "Wichtige Daten"

View File

@@ -0,0 +1,18 @@
# Generated by Django 4.2.2 on 2023-06-11 09:18
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('membership', '0008_auto_20190106_1954'),
]
operations = [
migrations.AlterField(
model_name='membership',
name='first_name',
field=models.CharField(blank=True, max_length=150, verbose_name='first name'),
),
]

View File

@@ -10,7 +10,7 @@ from django.contrib.auth.models import AbstractUser
from django.db import models from django.db import models
from django.urls import reverse from django.urls import reverse
from django.utils import timezone from django.utils import timezone
from django.utils.translation import ugettext as _ from django.utils.translation import gettext as _
from easy_thumbnails.fields import ThumbnailerImageField from easy_thumbnails.fields import ThumbnailerImageField
from utils import OverwriteStorage from utils import OverwriteStorage

View File

@@ -50,10 +50,10 @@
<li><strong>{% trans "Member Since" %}:</strong> {{membership.date_joined}}</li> <li><strong>{% trans "Member Since" %}:</strong> {{membership.date_joined}}</li>
<li><strong>{% trans "Last Login" %}:</strong> {{membership.last_login}}</li> <li><strong>{% trans "Last Login" %}:</strong> {{membership.last_login}}</li>
</ul> </ul>
{% ifequal membership user %} {% if membership == user %}
<a href="{% url 'membership-edit' membership.username %}" class="button"> <span class="fa fa-pencil"></span> {% trans "Edit Profile" %} </a> <a href="{% url 'membership-edit' membership.username %}" class="button"> <span class="fa fa-pencil"></span> {% trans "Edit Profile" %} </a>
<a href="{% url 'password_change' %}" class="button"> <span class="fa fa-key"></span> {% trans 'Change Password' %}</a> <a href="{% url 'password_change' %}" class="button"> <span class="fa fa-key"></span> {% trans 'Change Password' %}</a>
{% endifequal %} {% endif %}
</div> </div>
{% endif %} {% endif %}

View File

@@ -4,38 +4,25 @@ Created on 03.10.2011
@author: christian @author: christian
""" """
import django.contrib.auth.views as auth_views import django.contrib.auth.views as auth_views
from django.conf.urls import url from django.urls import path
from . import views from . import views
urlpatterns = [ urlpatterns = [
url(r'^$', views.MembershipDetail.as_view()), path("", views.MembershipDetail.as_view()),
url(r'^activate/(?P<activation_key>[\d\w]+)/$', path('activate/<str:activation_key>', views.ActivateRegistration.as_view(),
views.ActivateRegistration.as_view(), name='membership-activate-registration'),
name='membership-activate-registration'), path('activation_sent/', views.ActivationSent.as_view(), name="membership-registration-complete"),
url(r'^activation_sent/$', path('login/', auth_views.LoginView.as_view(), name='login'),
views.ActivationSent.as_view(), path('logout/', auth_views.LogoutView.as_view(), name='logout'),
name="membership-registration-complete"), path('password_change/', auth_views.PasswordChangeView.as_view(), name='password_change'),
url(r'^login/$', auth_views.LoginView.as_view(), name='login'), path('password_change/done/', auth_views.PasswordChangeDoneView.as_view(), name='password_change_done'),
url(r'^logout/$', auth_views.LogoutView.as_view(), name='logout'), path('password_reset/', auth_views.PasswordResetView.as_view(), name='password_reset'),
url(r'^password_change/$', auth_views.PasswordChangeView.as_view(), path('password_reset/done/', auth_views.PasswordResetDoneView.as_view(), name='password_reset_done'),
name='password_change'), path('register/', views.RegisterForm.as_view(), name="membership-register"),
url(r'^password_change/done/$', auth_views.PasswordChangeDoneView.as_view(), path('reset/<uuid:uidb64>/<str:token>/', auth_views.PasswordResetConfirmView.as_view(),
name='password_change_done'), name='password_reset_confirm'),
url(r'^password_reset/$', auth_views.PasswordResetView.as_view(), path('reset/done/', auth_views.PasswordResetCompleteView.as_view(), name='password_reset_complete'),
name='password_reset'), path('<slug:username>/', views.MembershipDetail.as_view(), name='membership-details'),
url(r'^password_reset/done/$', auth_views.PasswordResetDoneView.as_view(), path('<slug:username>/edit/', views.EditMembership.as_view(), name="membership-edit")
name='password_reset_done'),
url(r'^register/$', views.RegisterForm.as_view(),
name="membership-register"),
url(
r'^reset/(?P<uidb64>[0-9A-Za-z_\-]+)/(?P<token>[0-9A-Za-z]{1,13}-[0-9A-Za-z]{1,20})/$',
auth_views.PasswordResetConfirmView.as_view(),
name='password_reset_confirm'),
url(r'^reset/done/$', auth_views.PasswordResetCompleteView.as_view(),
name='password_reset_complete'),
url(r'^(?P<username>[\-\.\d\w]+)/$',
views.MembershipDetail.as_view(), name='membership-details'),
url(r'^(?P<username>[\-\.\d\w]+)/edit/$',
views.EditMembership.as_view(), name="membership-edit")
] ]

View File

@@ -10,7 +10,7 @@ from django.contrib.auth.mixins import LoginRequiredMixin
from django.urls import reverse from django.urls import reverse
from django.http import Http404 from django.http import Http404
from django.utils.decorators import method_decorator from django.utils.decorators import method_decorator
from django.utils.translation import ugettext as _ from django.utils.translation import gettext as _
from django.views import generic from django.views import generic
from mahjong_ranking.models import KyuDanRanking, SeasonRanking from mahjong_ranking.models import KyuDanRanking, SeasonRanking

View File

@@ -4,7 +4,7 @@ Created on 28.09.2011
@author: christian @author: christian
""" """
from django.core.files.storage import FileSystemStorage from django.core.files.storage import FileSystemStorage
from django.utils.translation import ugettext as _ from django.utils.translation import gettext as _
from .countries import COUNTRIES from .countries import COUNTRIES
from .html_cleaner import HtmlCleaner from .html_cleaner import HtmlCleaner

View File

@@ -1,5 +1,5 @@
"""A list of all approved countries on planet earth, i18n enabled.""" """A list of all approved countries on planet earth, i18n enabled."""
from django.utils.translation import ugettext as _ from django.utils.translation import gettext as _
COUNTRIES = ( COUNTRIES = (
('GB', _('United Kingdom')), ('GB', _('United Kingdom')),

View File

@@ -7,7 +7,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: kasu.utils\n" "Project-Id-Version: kasu.utils\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-12-13 23:38+0100\n" "POT-Creation-Date: 2023-06-09 22:00+0200\n"
"PO-Revision-Date: 2016-09-28 00:24+0200\n" "PO-Revision-Date: 2016-09-28 00:24+0200\n"
"Last-Translator: Christian Berg <xeniac@posteo.at>\n" "Last-Translator: Christian Berg <xeniac@posteo.at>\n"
"Language-Team: Kasu <verein@kasu.at>\n" "Language-Team: Kasu <verein@kasu.at>\n"