chore(*): reformat using ruff (#19)

mk-personal-profile
Christian Merten 2 weeks ago committed by GitHub
parent 91575eb280
commit 589527e1ac
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -1,14 +1,12 @@
import copy import copy
from django.contrib.auth import get_permission_codename
from django.contrib import messages
from django.contrib.auth import get_permission_codename
from django.core.exceptions import PermissionDenied from django.core.exceptions import PermissionDenied
from django.contrib import admin, messages
from django.utils.translation import gettext_lazy as _
from django.http import HttpResponse, HttpResponseRedirect
from django.urls import path, reverse
from django.db import models from django.db import models
from django.contrib.admin import helpers, widgets from django.http import HttpResponseRedirect
import rules.contrib.admin from django.urls import reverse
from django.utils.translation import gettext_lazy as _
from rules.permissions import perm_exists from rules.permissions import perm_exists
@ -16,19 +14,36 @@ def decorate_admin_view(model, perm=None):
""" """
Decorator for wrapping admin views. Decorator for wrapping admin views.
""" """
def decorator(fun): def decorator(fun):
def aux(self, request, object_id): def aux(self, request, object_id):
try: try:
obj = model.objects.get(pk=object_id) obj = model.objects.get(pk=object_id)
except model.DoesNotExist: except model.DoesNotExist:
messages.error(request, _('%(modelname)s not found.') % {'modelname': self.opts.verbose_name}) messages.error(
return HttpResponseRedirect(reverse('admin:%s_%s_changelist' % (self.opts.app_label, self.opts.model_name))) request, _("%(modelname)s not found.") % {"modelname": self.opts.verbose_name}
permitted = self.has_change_permission(request, obj) if not perm else request.user.has_perm(perm) )
return HttpResponseRedirect(
reverse(
"admin:{}_{}_changelist".format(self.opts.app_label, self.opts.model_name)
)
)
permitted = (
self.has_change_permission(request, obj)
if not perm
else request.user.has_perm(perm)
)
if not permitted: if not permitted:
messages.error(request, _('Insufficient permissions.')) messages.error(request, _("Insufficient permissions."))
return HttpResponseRedirect(reverse('admin:%s_%s_changelist' % (self.opts.app_label, self.opts.model_name))) return HttpResponseRedirect(
reverse(
"admin:{}_{}_changelist".format(self.opts.app_label, self.opts.model_name)
)
)
return fun(self, request, obj) return fun(self, request, obj)
return aux return aux
return decorator return decorator
@ -37,7 +52,7 @@ class FieldPermissionsAdminMixin:
field_view_permissions = {} field_view_permissions = {}
def may_view_field(self, field_desc, request, obj=None): def may_view_field(self, field_desc, request, obj=None):
if not type(field_desc) is tuple: if type(field_desc) is not tuple:
field_desc = (field_desc,) field_desc = (field_desc,)
for fd in field_desc: for fd in field_desc:
if fd not in self.field_view_permissions: if fd not in self.field_view_permissions:
@ -47,37 +62,41 @@ class FieldPermissionsAdminMixin:
return True return True
def get_fieldsets(self, request, obj=None): def get_fieldsets(self, request, obj=None):
fieldsets = super(FieldPermissionsAdminMixin, self).get_fieldsets(request, obj) fieldsets = super().get_fieldsets(request, obj)
d = [] d = []
for title, attrs in fieldsets: for title, attrs in fieldsets:
allowed = [f for f in attrs['fields'] if self.may_view_field(f, request, obj)] allowed = [f for f in attrs["fields"] if self.may_view_field(f, request, obj)]
if len(allowed) == 0: if len(allowed) == 0:
continue continue
d.append((title, dict(attrs, **{'fields': allowed}))) d.append((title, dict(attrs, **{"fields": allowed})))
return d return d
def get_fields(self, request, obj=None): def get_fields(self, request, obj=None):
fields = super(FieldPermissionsAdminMixin, self).get_fields(request, obj) fields = super().get_fields(request, obj)
return [fd for fd in fields if self.may_view_field(fd, request, obj)] return [fd for fd in fields if self.may_view_field(fd, request, obj)]
def get_readonly_fields(self, request, obj=None): def get_readonly_fields(self, request, obj=None):
readonly_fields = super(FieldPermissionsAdminMixin, self).get_readonly_fields(request, obj) readonly_fields = super().get_readonly_fields(request, obj)
return list(readonly_fields) +\ return list(readonly_fields) + [
[fd for fd, perm in self.field_change_permissions.items() if not request.user.has_perm(perm)] fd
for fd, perm in self.field_change_permissions.items()
if not request.user.has_perm(perm)
]
class ChangeViewAdminMixin: class ChangeViewAdminMixin:
def change_view(self, request, object_id, form_url="", extra_context=None): def change_view(self, request, object_id, form_url="", extra_context=None):
try: try:
return super(ChangeViewAdminMixin, self).change_view(request, object_id, return super().change_view(
form_url=form_url, request, object_id, form_url=form_url, extra_context=extra_context
extra_context=extra_context) )
except PermissionDenied: except PermissionDenied:
opts = self.opts opts = self.opts
obj = self.model.objects.get(pk=object_id) obj = self.model.objects.get(pk=object_id)
messages.error(request, messages.error(request, _("You are not allowed to view %(name)s.") % {"name": str(obj)})
_("You are not allowed to view %(name)s.") % {'name': str(obj)}) return HttpResponseRedirect(
return HttpResponseRedirect(reverse('admin:%s_%s_changelist' % (opts.app_label, opts.model_name))) reverse("admin:{}_{}_changelist".format(opts.app_label, opts.model_name))
)
class FilteredQuerysetAdminMixin: class FilteredQuerysetAdminMixin:
@ -91,28 +110,34 @@ class FilteredQuerysetAdminMixin:
if ordering: if ordering:
qs = qs.order_by(*ordering) qs = qs.order_by(*ordering)
queryset = qs queryset = qs
list_global_perm = '%s.list_global_%s' % (self.opts.app_label, self.opts.model_name) list_global_perm = "{}.list_global_{}".format(self.opts.app_label, self.opts.model_name)
if request.user.has_perm(list_global_perm): if request.user.has_perm(list_global_perm):
view_global_perm = '%s.view_global_%s' % (self.opts.app_label, self.opts.model_name) view_global_perm = "{}.view_global_{}".format(self.opts.app_label, self.opts.model_name)
if request.user.has_perm(view_global_perm): if request.user.has_perm(view_global_perm):
return queryset return queryset
if hasattr(request.user, 'member'): if hasattr(request.user, "member"):
return request.user.member.annotate_view_permission(queryset, model=self.model) return request.user.member.annotate_view_permission(queryset, model=self.model)
return queryset.annotate(_viewable=models.Value(False)) return queryset.annotate(_viewable=models.Value(False))
if not hasattr(request.user, 'member'): if not hasattr(request.user, "member"):
return self.model.objects.none() return self.model.objects.none()
return request.user.member.filter_queryset_by_permissions(queryset, annotate=True, model=self.model) return request.user.member.filter_queryset_by_permissions(
queryset, annotate=True, model=self.model
)
# class ObjectPermissionsInlineModelAdminMixin(rules.contrib.admin.ObjectPermissionsInlineModelAdminMixin):
#class ObjectPermissionsInlineModelAdminMixin(rules.contrib.admin.ObjectPermissionsInlineModelAdminMixin):
class CommonAdminMixin(FieldPermissionsAdminMixin, ChangeViewAdminMixin, FilteredQuerysetAdminMixin): class CommonAdminMixin(
FieldPermissionsAdminMixin, ChangeViewAdminMixin, FilteredQuerysetAdminMixin
):
def has_add_permission(self, request, obj=None): def has_add_permission(self, request, obj=None):
assert obj is None assert obj is None
opts = self.opts opts = self.opts
codename = get_permission_codename("add_global", opts) codename = get_permission_codename("add_global", opts)
perm = "%s.%s" % (opts.app_label, codename) perm = "{}.{}".format(opts.app_label, codename)
return request.user.has_perm(perm, obj) return request.user.has_perm(perm, obj)
def has_view_permission(self, request, obj=None): def has_view_permission(self, request, obj=None):
@ -121,7 +146,7 @@ class CommonAdminMixin(FieldPermissionsAdminMixin, ChangeViewAdminMixin, Filtere
codename = get_permission_codename("view", opts) codename = get_permission_codename("view", opts)
else: else:
codename = get_permission_codename("view_obj", opts) codename = get_permission_codename("view_obj", opts)
perm = "%s.%s" % (opts.app_label, codename) perm = "{}.{}".format(opts.app_label, codename)
if perm_exists(perm): if perm_exists(perm):
return request.user.has_perm(perm, obj) return request.user.has_perm(perm, obj)
else: else:
@ -133,7 +158,7 @@ class CommonAdminMixin(FieldPermissionsAdminMixin, ChangeViewAdminMixin, Filtere
codename = get_permission_codename("view", opts) codename = get_permission_codename("view", opts)
else: else:
codename = get_permission_codename("change_obj", opts) codename = get_permission_codename("change_obj", opts)
return request.user.has_perm("%s.%s" % (opts.app_label, codename), obj) return request.user.has_perm("{}.{}".format(opts.app_label, codename), obj)
def has_delete_permission(self, request, obj=None): def has_delete_permission(self, request, obj=None):
opts = self.opts opts = self.opts
@ -141,7 +166,7 @@ class CommonAdminMixin(FieldPermissionsAdminMixin, ChangeViewAdminMixin, Filtere
codename = get_permission_codename("delete_global", opts) codename = get_permission_codename("delete_global", opts)
else: else:
codename = get_permission_codename("delete_obj", opts) codename = get_permission_codename("delete_obj", opts)
return request.user.has_perm("%s.%s" % (opts.app_label, codename), obj) return request.user.has_perm("{}.{}".format(opts.app_label, codename), obj)
def formfield_for_dbfield(self, db_field, request, **kwargs): def formfield_for_dbfield(self, db_field, request, **kwargs):
""" """
@ -176,7 +201,7 @@ class CommonAdminMixin(FieldPermissionsAdminMixin, ChangeViewAdminMixin, Filtere
# extra HTML -- the "add other" interface -- to the end of the # extra HTML -- the "add other" interface -- to the end of the
# rendered output. formfield can be None if it came from a # rendered output. formfield can be None if it came from a
# OneToOneField with parent_link=True or a M2M intermediary. # OneToOneField with parent_link=True or a M2M intermediary.
#if formfield and db_field.name not in self.raw_id_fields: # if formfield and db_field.name not in self.raw_id_fields:
# formfield.widget = widgets.RelatedFieldWidgetWrapper( # formfield.widget = widgets.RelatedFieldWidgetWrapper(
# formfield.widget, # formfield.widget,
# db_field.remote_field, # db_field.remote_field,
@ -198,13 +223,13 @@ class CommonAdminMixin(FieldPermissionsAdminMixin, ChangeViewAdminMixin, Filtere
class CommonAdminInlineMixin(CommonAdminMixin): class CommonAdminInlineMixin(CommonAdminMixin):
def has_add_permission(self, request, obj): def has_add_permission(self, request, obj):
#assert obj is not None # assert obj is not None
if obj is None: if obj is None:
return True return True
if obj.pk is None: if obj.pk is None:
return True return True
codename = get_permission_codename("add_obj", self.opts) codename = get_permission_codename("add_obj", self.opts)
return request.user.has_perm('%s.%s' % (self.opts.app_label, codename), obj) return request.user.has_perm("{}.{}".format(self.opts.app_label, codename), obj)
def has_view_permission(self, request, obj=None): # pragma: no cover def has_view_permission(self, request, obj=None): # pragma: no cover
if obj is None: if obj is None:
@ -216,7 +241,7 @@ class CommonAdminInlineMixin(CommonAdminMixin):
codename = get_permission_codename("view", opts) codename = get_permission_codename("view", opts)
else: else:
codename = get_permission_codename("view_obj", opts) codename = get_permission_codename("view_obj", opts)
perm = "%s.%s" % (opts.app_label, codename) perm = "{}.{}".format(opts.app_label, codename)
if perm_exists(perm): if perm_exists(perm):
return request.user.has_perm(perm, obj) return request.user.has_perm(perm, obj)
else: else:
@ -234,7 +259,7 @@ class CommonAdminInlineMixin(CommonAdminMixin):
opts = field.rel.to._meta opts = field.rel.to._meta
break break
codename = get_permission_codename("change_obj", opts) codename = get_permission_codename("change_obj", opts)
return request.user.has_perm("%s.%s" % (opts.app_label, codename), obj) return request.user.has_perm("{}.{}".format(opts.app_label, codename), obj)
def has_delete_permission(self, request, obj=None): # pragma: no cover def has_delete_permission(self, request, obj=None): # pragma: no cover
if obj is None: if obj is None:

@ -2,5 +2,5 @@ from django.apps import AppConfig
class ContribConfig(AppConfig): class ContribConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField' default_auto_field = "django.db.models.BigAutoField"
name = 'contrib' name = "contrib"

@ -1,9 +1,9 @@
import os import os
from wsgiref.util import FileWrapper
from django import template
from django.conf import settings from django.conf import settings
from django.http import HttpResponse from django.http import HttpResponse
from django import template
from django.template.loader import get_template
from wsgiref.util import FileWrapper
def find_template(template_name): def find_template(template_name):
@ -27,12 +27,15 @@ def serve_media(filename, content_type):
""" """
Serve the media file with the given `filename` as an HTTP response. Serve the media file with the given `filename` as an HTTP response.
""" """
with open(media_path(filename), 'rb') as f: with open(media_path(filename), "rb") as f:
response = HttpResponse(FileWrapper(f)) response = HttpResponse(FileWrapper(f))
response['Content-Type'] = content_type response["Content-Type"] = content_type
# download other files than pdf, show pdfs in the browser # download other files than pdf, show pdfs in the browser
response['Content-Disposition'] = 'filename='+filename if content_type == 'application/pdf' else 'attachment; filename='+filename response["Content-Disposition"] = (
"filename=" + filename
if content_type == "application/pdf"
else "attachment; filename=" + filename
)
return response return response

@ -1,10 +1,17 @@
from django.db import models from django.db import models
from rules.contrib.models import RulesModelBase, RulesModelMixin from rules.contrib.models import RulesModelBase
from rules.contrib.models import RulesModelMixin
# Create your models here. # Create your models here.
class CommonModel(models.Model, RulesModelMixin, metaclass=RulesModelBase): class CommonModel(models.Model, RulesModelMixin, metaclass=RulesModelBase):
class Meta: class Meta:
abstract = True abstract = True
default_permissions = ( default_permissions = (
'add_global', 'change_global', 'view_global', 'delete_global', 'list_global', 'view', "add_global",
"change_global",
"view_global",
"delete_global",
"list_global",
"view",
) )

@ -1,12 +1,12 @@
from django.contrib.auth import get_permission_codename
import rules.contrib.admin import rules.contrib.admin
import rules
def memberize_user(func): def memberize_user(func):
def inner(user, other): def inner(user, other):
if not hasattr(user, 'member'): if not hasattr(user, "member"):
return False return False
return func(user.member, other) return func(user.member, other)
return inner return inner

@ -1,28 +1,38 @@
from datetime import datetime, timedelta from datetime import timedelta
from decimal import Decimal from unittest.mock import Mock
from django.test import TestCase, RequestFactory from unittest.mock import patch
from django.contrib.auth import get_user_model
from contrib.admin import CommonAdminMixin
from contrib.models import CommonModel
from contrib.rules import has_global_perm
from django.contrib import admin from django.contrib import admin
from django.db import models from django.contrib.auth import get_user_model
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django.core.files.uploadedfile import SimpleUploadedFile from django.db import models
from django.test import RequestFactory
from django.test import TestCase
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from unittest.mock import Mock, patch from rules.contrib.models import RulesModelBase
from rules.contrib.models import RulesModelMixin, RulesModelBase from rules.contrib.models import RulesModelMixin
from contrib.models import CommonModel from utils import file_size_validator
from contrib.rules import has_global_perm from utils import mondays_until_nth
from contrib.admin import CommonAdminMixin from utils import RestrictedFileField
from utils import file_size_validator, RestrictedFileField, cvt_to_decimal, get_member, normalize_name, normalize_filename, coming_midnight, mondays_until_nth
User = get_user_model() User = get_user_model()
class CommonModelTestCase(TestCase): class CommonModelTestCase(TestCase):
def test_common_model_abstract_base(self): def test_common_model_abstract_base(self):
"""Test that CommonModel provides the correct meta attributes""" """Test that CommonModel provides the correct meta attributes"""
meta = CommonModel._meta meta = CommonModel._meta
self.assertTrue(meta.abstract) self.assertTrue(meta.abstract)
expected_permissions = ( expected_permissions = (
'add_global', 'change_global', 'view_global', 'delete_global', 'list_global', 'view', "add_global",
"change_global",
"view_global",
"delete_global",
"list_global",
"view",
) )
self.assertEqual(meta.default_permissions, expected_permissions) self.assertEqual(meta.default_permissions, expected_permissions)
@ -38,15 +48,13 @@ class CommonModelTestCase(TestCase):
class GlobalPermissionRulesTestCase(TestCase): class GlobalPermissionRulesTestCase(TestCase):
def setUp(self): def setUp(self):
self.user = User.objects.create_user( self.user = User.objects.create_user(
username='testuser', username="testuser", email="test@example.com", password="testpass123"
email='test@example.com',
password='testpass123'
) )
def test_has_global_perm_predicate_creation(self): def test_has_global_perm_predicate_creation(self):
"""Test that has_global_perm creates a predicate function""" """Test that has_global_perm creates a predicate function"""
# has_global_perm is a decorator factory, not a direct predicate # has_global_perm is a decorator factory, not a direct predicate
predicate = has_global_perm('auth.add_user') predicate = has_global_perm("auth.add_user")
self.assertTrue(callable(predicate)) self.assertTrue(callable(predicate))
def test_has_global_perm_with_superuser(self): def test_has_global_perm_with_superuser(self):
@ -54,33 +62,32 @@ class GlobalPermissionRulesTestCase(TestCase):
self.user.is_superuser = True self.user.is_superuser = True
self.user.save() self.user.save()
predicate = has_global_perm('auth.add_user') predicate = has_global_perm("auth.add_user")
result = predicate(self.user, None) result = predicate(self.user, None)
self.assertTrue(result) self.assertTrue(result)
def test_has_global_perm_with_regular_user(self): def test_has_global_perm_with_regular_user(self):
"""Test that regular users don't automatically have global permissions""" """Test that regular users don't automatically have global permissions"""
predicate = has_global_perm('auth.add_user') predicate = has_global_perm("auth.add_user")
result = predicate(self.user, None) result = predicate(self.user, None)
self.assertFalse(result) self.assertFalse(result)
class CommonAdminMixinTestCase(TestCase): class CommonAdminMixinTestCase(TestCase):
def setUp(self): def setUp(self):
self.user = User.objects.create_user(username='testuser', password='testpass') self.user = User.objects.create_user(username="testuser", password="testpass")
def test_formfield_for_dbfield_with_formfield_overrides(self): def test_formfield_for_dbfield_with_formfield_overrides(self):
"""Test formfield_for_dbfield when db_field class is in formfield_overrides""" """Test formfield_for_dbfield when db_field class is in formfield_overrides"""
# Create a test admin instance that inherits from Django's ModelAdmin # Create a test admin instance that inherits from Django's ModelAdmin
class TestAdmin(CommonAdminMixin, admin.ModelAdmin): class TestAdmin(CommonAdminMixin, admin.ModelAdmin):
formfield_overrides = { formfield_overrides = {models.ForeignKey: {"widget": Mock()}}
models.ForeignKey: {'widget': Mock()}
}
# Create a mock model to use with the admin # Create a mock model to use with the admin
class TestModel: class TestModel:
_meta = Mock() _meta = Mock()
_meta.app_label = 'test' _meta.app_label = "test"
admin_instance = TestAdmin(TestModel, admin.site) admin_instance = TestAdmin(TestModel, admin.site)
@ -88,11 +95,11 @@ class CommonAdminMixinTestCase(TestCase):
db_field = models.ForeignKey(User, on_delete=models.CASCADE) db_field = models.ForeignKey(User, on_delete=models.CASCADE)
# Create a test request # Create a test request
request = RequestFactory().get('/') request = RequestFactory().get("/")
request.user = self.user request.user = self.user
# Call the method to test formfield_overrides usage # Call the method to test formfield_overrides usage
result = admin_instance.formfield_for_dbfield(db_field, request, help_text='Test help text') result = admin_instance.formfield_for_dbfield(db_field, request, help_text="Test help text")
# Verify that the formfield_overrides were used # Verify that the formfield_overrides were used
self.assertIsNotNone(result) self.assertIsNotNone(result)
@ -101,9 +108,7 @@ class CommonAdminMixinTestCase(TestCase):
class UtilsTestCase(TestCase): class UtilsTestCase(TestCase):
def setUp(self): def setUp(self):
self.user = User.objects.create_user( self.user = User.objects.create_user(
username='testuser', username="testuser", email="test@example.com", password="testpass123"
email='test@example.com',
password='testpass123'
) )
def test_file_size_validator_exceeds_limit(self): def test_file_size_validator_exceeds_limit(self):
@ -118,12 +123,14 @@ class UtilsTestCase(TestCase):
validator(mock_file) validator(mock_file)
# Check for the translated error message # Check for the translated error message
expected_message = str(_('Please keep filesize under {} MiB. Current filesize: {:10.2f} MiB.').format(1, 2.00)) expected_message = str(
_("Please keep filesize under {} MiB. Current filesize: {:10.2f} MiB.").format(1, 2.00)
)
self.assertIn(expected_message, str(cm.exception)) self.assertIn(expected_message, str(cm.exception))
def test_restricted_file_field_content_type_not_supported(self): def test_restricted_file_field_content_type_not_supported(self):
"""Test RestrictedFileField when content type is not supported""" """Test RestrictedFileField when content type is not supported"""
field = RestrictedFileField(content_types=['image/jpeg']) field = RestrictedFileField(content_types=["image/jpeg"])
# Create mock data with unsupported content type # Create mock data with unsupported content type
mock_data = Mock() mock_data = Mock()
@ -131,12 +138,12 @@ class UtilsTestCase(TestCase):
mock_data.file.content_type = "text/plain" mock_data.file.content_type = "text/plain"
# Mock the super().clean() to return our mock data # Mock the super().clean() to return our mock data
with patch.object(models.FileField, 'clean', return_value=mock_data): with patch.object(models.FileField, "clean", return_value=mock_data):
with self.assertRaises(ValidationError) as cm: with self.assertRaises(ValidationError) as cm:
field.clean("dummy") field.clean("dummy")
# Check for the translated error message # Check for the translated error message
expected_message = str(_('Filetype not supported.')) expected_message = str(_("Filetype not supported."))
self.assertIn(expected_message, str(cm.exception)) self.assertIn(expected_message, str(cm.exception))
def test_restricted_file_field_size_exceeds_limit(self): def test_restricted_file_field_size_exceeds_limit(self):
@ -150,12 +157,14 @@ class UtilsTestCase(TestCase):
mock_data.file._size = 2 # 2 bytes, exceeds limit mock_data.file._size = 2 # 2 bytes, exceeds limit
# Mock the super().clean() to return our mock data # Mock the super().clean() to return our mock data
with patch.object(models.FileField, 'clean', return_value=mock_data): with patch.object(models.FileField, "clean", return_value=mock_data):
with self.assertRaises(ValidationError) as cm: with self.assertRaises(ValidationError) as cm:
field.clean("dummy") field.clean("dummy")
# Check for the translated error message # Check for the translated error message
expected_message = str(_('Please keep filesize under {}. Current filesize: {}').format(1, 2)) expected_message = str(
_("Please keep filesize under {}. Current filesize: {}").format(1, 2)
)
self.assertIn(expected_message, str(cm.exception)) self.assertIn(expected_message, str(cm.exception))
def test_mondays_until_nth(self): def test_mondays_until_nth(self):

@ -1,10 +1,16 @@
from django.utils.translation import gettext_lazy as _
from django.contrib import admin from django.contrib import admin
from django.contrib.auth.admin import UserAdmin as BaseUserAdmin, GroupAdmin as BaseAuthGroupAdmin from django.contrib.auth.admin import GroupAdmin as BaseAuthGroupAdmin
from django.contrib.auth.models import User as BaseUser, Group as BaseAuthGroup from django.contrib.auth.admin import UserAdmin as BaseUserAdmin
from .models import AuthGroup, LoginDatum, RegistrationPassword from django.contrib.auth.models import Group as BaseAuthGroup
from django.contrib.auth.models import User as BaseUser
from django.utils.translation import gettext_lazy as _
from members.models import Member from members.models import Member
from .models import AuthGroup
from .models import LoginDatum
from .models import RegistrationPassword
# Register your models here. # Register your models here.
class AuthGroupAdmin(BaseAuthGroupAdmin): class AuthGroupAdmin(BaseAuthGroupAdmin):
pass pass
@ -17,8 +23,8 @@ class UserInline(admin.StackedInline):
class LoginDatumAdmin(BaseUserAdmin): class LoginDatumAdmin(BaseUserAdmin):
list_display = ('username', 'is_superuser') list_display = ("username", "is_superuser")
#inlines = [UserInline] # inlines = [UserInline]
fieldsets = ( fieldsets = (
(None, {"fields": ("username", "password")}), (None, {"fields": ("username", "password")}),
( (
@ -45,6 +51,7 @@ class LoginDatumAdmin(BaseUserAdmin):
), ),
) )
admin.site.unregister(BaseUser) admin.site.unregister(BaseUser)
admin.site.unregister(BaseAuthGroup) admin.site.unregister(BaseAuthGroup)
admin.site.register(LoginDatum, LoginDatumAdmin) admin.site.register(LoginDatum, LoginDatumAdmin)

@ -3,6 +3,6 @@ from django.utils.translation import gettext_lazy as _
class LoginDataConfig(AppConfig): class LoginDataConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField' default_auto_field = "django.db.models.BigAutoField"
name = 'logindata' name = "logindata"
verbose_name = _('Authentication') verbose_name = _("Authentication")

@ -1,39 +1,41 @@
from django.utils.translation import gettext_lazy as _ from django.contrib.auth.models import Group as BaseAuthGroup
from django.contrib.auth.models import User as BaseUser
from django.db import models from django.db import models
from django.contrib.auth.admin import UserAdmin as BaseUserAdmin, GroupAdmin as BaseAuthGroupAdmin from django.utils.translation import gettext_lazy as _
from django.contrib.auth.models import User as BaseUser, Group as BaseAuthGroup
class AuthGroup(BaseAuthGroup): class AuthGroup(BaseAuthGroup):
class Meta: class Meta:
proxy = True proxy = True
verbose_name = _('Permission group') verbose_name = _("Permission group")
verbose_name_plural = _('Permission groups') verbose_name_plural = _("Permission groups")
class LoginDatum(BaseUser): class LoginDatum(BaseUser):
class Meta: class Meta:
proxy = True proxy = True
verbose_name = _('Login Datum') verbose_name = _("Login Datum")
verbose_name_plural = _('Login Data') verbose_name_plural = _("Login Data")
class RegistrationPassword(models.Model): class RegistrationPassword(models.Model):
""" """
A password that can be used to register after inviting a member. A password that can be used to register after inviting a member.
""" """
password = models.CharField(max_length=100, verbose_name=_('Password'))
password = models.CharField(max_length=100, verbose_name=_("Password"))
def __str__(self): def __str__(self):
return self.password return self.password
class Meta: class Meta:
verbose_name = _('Active registration password') verbose_name = _("Active registration password")
verbose_name_plural = _('Active registration passwords') verbose_name_plural = _("Active registration passwords")
def initial_user_setup(user, member): def initial_user_setup(user, member):
try: try:
standard_group = AuthGroup.objects.get(name='Standard') standard_group = AuthGroup.objects.get(name="Standard")
except AuthGroup.DoesNotExist: except AuthGroup.DoesNotExist:
return False return False
@ -41,6 +43,6 @@ def initial_user_setup(user, member):
user.save() user.save()
user.groups.add(standard_group) user.groups.add(standard_group)
member.user = user member.user = user
member.invite_as_user_key = '' member.invite_as_user_key = ""
member.save() member.save()
return True return True

@ -7,7 +7,7 @@ class CustomOAuth2Validator(OAuth2Validator):
def get_additional_claims(self, request): def get_additional_claims(self, request):
if request.user.member: if request.user.member:
context = {'email': request.user.member.email} context = {"email": request.user.member.email}
else: else:
context = {} context = {}
return dict(context, preferred_username=request.user.username) return dict(context, preferred_username=request.user.username)

@ -4,5 +4,5 @@ from . import views
app_name = "logindata" app_name = "logindata"
urlpatterns = [ urlpatterns = [
re_path(r'^register', views.register , name='register'), re_path(r"^register", views.register, name="register"),
] ]

@ -1,44 +1,46 @@
from django import forms from django.contrib.auth.forms import UserCreationForm
from django.shortcuts import render
from django.http import HttpResponseRedirect from django.http import HttpResponseRedirect
from django.utils.translation import gettext_lazy as _ from django.shortcuts import render
from django.urls import reverse from django.urls import reverse
from django.contrib.auth.forms import UserCreationForm from django.utils.translation import gettext_lazy as _
from members.models import Member from members.models import Member
from .models import initial_user_setup, RegistrationPassword
from .models import initial_user_setup
from .models import RegistrationPassword
def render_register_password(request, key, member, error_message=''): def render_register_password(request, key, member, error_message=""):
return render(request, 'logindata/register_password.html', return render(
context={'key': key, request,
'member': member, "logindata/register_password.html",
'error_message': error_message}) context={"key": key, "member": member, "error_message": error_message},
)
def render_register_failed(request): def render_register_failed(request):
return render(request, 'logindata/register_failed.html') return render(request, "logindata/register_failed.html")
def render_register_form(request, key, password, member, form): def render_register_form(request, key, password, member, form):
return render(request, 'logindata/register_form.html', return render(
context={'key': key, request,
'password': password, "logindata/register_form.html",
'member': member, context={"key": key, "password": password, "member": member, "form": form},
'form': form}) )
def render_register_success(request): def render_register_success(request):
return render(request, 'logindata/register_success.html') return render(request, "logindata/register_success.html")
# Create your views here. # Create your views here.
def register(request): def register(request):
if request.method == 'GET' and 'key' not in request.GET: if request.method == "GET" and "key" not in request.GET:
return HttpResponseRedirect(reverse('startpage:index')) return HttpResponseRedirect(reverse("startpage:index"))
if request.method == 'POST' and 'key' not in request.POST: if request.method == "POST" and "key" not in request.POST:
return HttpResponseRedirect(reverse('startpage:index')) return HttpResponseRedirect(reverse("startpage:index"))
key = request.GET['key'] if request.method == 'GET' else request.POST['key'] key = request.GET["key"] if request.method == "GET" else request.POST["key"]
if not key: if not key:
return render_register_failed(request) return render_register_failed(request)
try: try:
@ -46,17 +48,19 @@ def register(request):
except (Member.DoesNotExist, Member.MultipleObjectsReturned): except (Member.DoesNotExist, Member.MultipleObjectsReturned):
return render_register_failed(request) return render_register_failed(request)
if request.method == 'GET': if request.method == "GET":
return render_register_password(request, request.GET['key'], member) return render_register_password(request, request.GET["key"], member)
if 'password' not in request.POST: if "password" not in request.POST:
return render_register_failed(request) return render_register_failed(request)
password = request.POST['password'] password = request.POST["password"]
# check if the entered password is one of the active registration passwords # check if the entered password is one of the active registration passwords
if RegistrationPassword.objects.filter(password=password).count() == 0: if RegistrationPassword.objects.filter(password=password).count() == 0:
return render_register_password(request, key, member, error_message=_('You entered a wrong password.')) return render_register_password(
request, key, member, error_message=_("You entered a wrong password.")
)
if "save" in request.POST: if "save" in request.POST:
form = UserCreationForm(request.POST) form = UserCreationForm(request.POST)
@ -70,6 +74,6 @@ def register(request):
else: else:
return render_register_failed(request) return render_register_failed(request)
else: else:
prefill = {'username': member.suggested_username()} prefill = {"username": member.suggested_username()}
form = UserCreationForm(initial=prefill) form = UserCreationForm(initial=prefill)
return render_register_form(request, key, password, member, form) return render_register_form(request, key, password, member, form)

@ -1,26 +1,23 @@
import os import xlsxwriter
from contrib.media import ensure_media_dir
from contrib.media import media_path
from contrib.media import serve_media
from django.contrib import admin from django.contrib import admin
from wsgiref.util import FileWrapper
from django.http import HttpResponse
from django.conf import settings
from .models import Termin
from contrib.media import media_path, serve_media, ensure_media_dir
import xlsxwriter from .models import Termin
class TerminAdmin(admin.ModelAdmin): class TerminAdmin(admin.ModelAdmin):
list_display = ('title','start_date', 'end_date', 'group', 'category', 'responsible') list_display = ("title", "start_date", "end_date", "group", "category", "responsible")
list_filter = ('group',) list_filter = ("group",)
ordering = ('start_date','end_date') ordering = ("start_date", "end_date")
actions = ['make_overview'] actions = ["make_overview"]
def make_overview(self, request, queryset): def make_overview(self, request, queryset):
ensure_media_dir() ensure_media_dir()
filename = 'termine.xlsx' filename = "termine.xlsx"
workbook = xlsxwriter.Workbook(media_path(filename)) workbook = xlsxwriter.Workbook(media_path(filename))
bold = workbook.add_format({'bold': True}) bold = workbook.add_format({"bold": True})
worksheet = workbook.add_worksheet() worksheet = workbook.add_worksheet()
worksheet.write(0, 0, "Titel", bold) worksheet.write(0, 0, "Titel", bold)
worksheet.write(0, 1, "Untertitel", bold) worksheet.write(0, 1, "Untertitel", bold)
@ -44,30 +41,32 @@ class TerminAdmin(admin.ModelAdmin):
worksheet.write(0, 19, "Telefonnummer", bold) worksheet.write(0, 19, "Telefonnummer", bold)
worksheet.write(0, 20, "Emailadresse", bold) worksheet.write(0, 20, "Emailadresse", bold)
for row, termin in enumerate(queryset): for row, termin in enumerate(queryset):
worksheet.write(row+2, 0, termin.title) worksheet.write(row + 2, 0, termin.title)
worksheet.write(row+2, 1, termin.subtitle) worksheet.write(row + 2, 1, termin.subtitle)
worksheet.write(row+2, 2, termin.start_date.strftime('%d.%m.%Y')) worksheet.write(row + 2, 2, termin.start_date.strftime("%d.%m.%Y"))
worksheet.write(row+2, 3, termin.end_date.strftime('%d.%m.%Y')) worksheet.write(row + 2, 3, termin.end_date.strftime("%d.%m.%Y"))
worksheet.write(row+2, 4, termin.group) worksheet.write(row + 2, 4, termin.group)
worksheet.write(row+2, 5, termin.category) worksheet.write(row + 2, 5, termin.category)
worksheet.write(row+2, 6, termin.technik) worksheet.write(row + 2, 6, termin.technik)
worksheet.write(row+2, 7, termin.condition) worksheet.write(row + 2, 7, termin.condition)
worksheet.write(row+2, 8, termin.saison) worksheet.write(row + 2, 8, termin.saison)
worksheet.write(row+2, 9, termin.eventart) worksheet.write(row + 2, 9, termin.eventart)
worksheet.write(row+2, 10, termin.klassifizierung) worksheet.write(row + 2, 10, termin.klassifizierung)
worksheet.write(row+2, 11, termin.anforderung_hoehe) worksheet.write(row + 2, 11, termin.anforderung_hoehe)
worksheet.write(row+2, 12, termin.anforderung_strecke) worksheet.write(row + 2, 12, termin.anforderung_strecke)
worksheet.write(row+2, 13, termin.anforderung_dauer) worksheet.write(row + 2, 13, termin.anforderung_dauer)
worksheet.write(row+2, 14, termin.voraussetzungen) worksheet.write(row + 2, 14, termin.voraussetzungen)
worksheet.write(row+2, 15, termin.description) worksheet.write(row + 2, 15, termin.description)
worksheet.write(row+2, 16, termin.equipment) worksheet.write(row + 2, 16, termin.equipment)
worksheet.write(row+2, 17, termin.max_participants) worksheet.write(row + 2, 17, termin.max_participants)
worksheet.write(row+2, 18, termin.responsible) worksheet.write(row + 2, 18, termin.responsible)
worksheet.write(row+2, 19, termin.phone) worksheet.write(row + 2, 19, termin.phone)
worksheet.write(row+2, 20, termin.email) worksheet.write(row + 2, 20, termin.email)
workbook.close() workbook.close()
return serve_media(filename, 'application/xlsx') return serve_media(filename, "application/xlsx")
make_overview.short_description = "Termine in Excel Liste überführen" make_overview.short_description = "Termine in Excel Liste überführen"
# Register your models here. # Register your models here.
admin.site.register(Termin, TerminAdmin) admin.site.register(Termin, TerminAdmin)

@ -2,4 +2,4 @@ from django.apps import AppConfig
class LudwigsburgalpinConfig(AppConfig): class LudwigsburgalpinConfig(AppConfig):
name = 'ludwigsburgalpin' name = "ludwigsburgalpin"

@ -1,121 +1,128 @@
from django.db import models
from django.core.validators import MinValueValidator from django.core.validators import MinValueValidator
from django.db import models
GRUPPE = [ GRUPPE = [
('ASG', 'Alpinsportgruppe'), ("ASG", "Alpinsportgruppe"),
('OGB', 'Ortsgruppe Bietigheim'), ("OGB", "Ortsgruppe Bietigheim"),
('OGV', 'Ortsgruppe Vaihingen'), ("OGV", "Ortsgruppe Vaihingen"),
('JUG', 'Jugend'), ("JUG", "Jugend"),
('FAM', 'Familie'), ("FAM", "Familie"),
('Ü30', 'Ü30'), ("Ü30", "Ü30"),
('MTB', 'Mountainbike'), ("MTB", "Mountainbike"),
('RA', 'RegioAktiv'), ("RA", "RegioAktiv"),
('SEK', 'Sektion'), ("SEK", "Sektion"),
] ]
KATEGORIE = [ KATEGORIE = [
('WAN', 'Wandern'), ("WAN", "Wandern"),
('BW', 'Bergwandern'), ("BW", "Bergwandern"),
('KST', 'Klettersteig'), ("KST", "Klettersteig"),
('KL', 'Klettern'), ("KL", "Klettern"),
('SKI', 'Piste, Loipe'), ("SKI", "Piste, Loipe"),
('SCH', 'Schneeschuhgehen'), ("SCH", "Schneeschuhgehen"),
('ST', 'Skitour'), ("ST", "Skitour"),
('STH', 'Skihochtour'), ("STH", "Skihochtour"),
('HT', 'Hochtour'), ("HT", "Hochtour"),
('MTB', 'Montainbike'), ("MTB", "Montainbike"),
('AUS', 'Ausbildung'), ("AUS", "Ausbildung"),
('SON', 'Sonstiges z.B. Treffen') ("SON", "Sonstiges z.B. Treffen"),
] ]
KONDITION = [ KONDITION = [
('gering', 'gering'), ("gering", "gering"),
('mittel', 'mittel'), ("mittel", "mittel"),
('groß', 'groß'), ("groß", "groß"),
('sehr groß', 'sehr groß'), ("sehr groß", "sehr groß"),
] ]
TECHNIK = [ TECHNIK = [
('leicht', 'leicht'), ("leicht", "leicht"),
('mittel', 'mittel'), ("mittel", "mittel"),
('schwer', 'schwer'), ("schwer", "schwer"),
('sehr schwer', 'sehr schwer'), ("sehr schwer", "sehr schwer"),
] ]
SAISON = [ SAISON = [
('ganzjährig','ganzjährig'), ("ganzjährig", "ganzjährig"),
('Indoor', 'Indoor'), ("Indoor", "Indoor"),
('Sommer', 'Sommer'), ("Sommer", "Sommer"),
('Winter', 'Winter'), ("Winter", "Winter"),
] ]
EVENTART = [ EVENTART = [
('Einzeltermin', 'Einzeltermin',), (
('Mehrtagesevent', 'Mehrtagesevent',), "Einzeltermin",
('Regelmäßiges Event/Training', 'Regelmäßiges Event/Training',), "Einzeltermin",
('Tagesevent', 'Tagesevent',), ),
('Wochenendevent', 'Wochenendevent',), (
"Mehrtagesevent",
"Mehrtagesevent",
),
(
"Regelmäßiges Event/Training",
"Regelmäßiges Event/Training",
),
(
"Tagesevent",
"Tagesevent",
),
(
"Wochenendevent",
"Wochenendevent",
),
] ]
KLASSIFIZIERUNG = [ KLASSIFIZIERUNG = [
('Gemeinschaftstour', 'Gemeinschaftstour'), ("Gemeinschaftstour", "Gemeinschaftstour"),
('Ausbildung', 'Ausbildung'), ("Ausbildung", "Ausbildung"),
] ]
# Create your models here. # Create your models here.
class Termin(models.Model): class Termin(models.Model):
title = models.CharField('Titel', max_length=100) title = models.CharField("Titel", max_length=100)
subtitle = models.CharField('Untertitel', max_length=100, blank=True) subtitle = models.CharField("Untertitel", max_length=100, blank=True)
start_date = models.DateField('Von') start_date = models.DateField("Von")
end_date = models.DateField('Bis') end_date = models.DateField("Bis")
group = models.CharField('Gruppe', group = models.CharField("Gruppe", choices=GRUPPE, max_length=100)
choices=GRUPPE, responsible = models.CharField("Organisator", max_length=100, blank=False)
max_length=100) phone = models.CharField(max_length=20, verbose_name="Telefonnumer", blank=True)
responsible = models.CharField('Organisator', max_length=100, blank=False) email = models.EmailField(max_length=100, verbose_name="Email", blank=False)
phone = models.CharField(max_length=20, verbose_name='Telefonnumer', blank=True) category = models.CharField(
email = models.EmailField(max_length=100, verbose_name='Email', blank=False) "Kategorie", blank=False, choices=KATEGORIE, max_length=100, default="SON"
category = models.CharField('Kategorie', blank=False, choices=KATEGORIE, max_length=100, )
default='SON') condition = models.CharField(
condition = models.CharField('Kondition', blank=False, choices=KONDITION, max_length=100, "Kondition", blank=False, choices=KONDITION, max_length=100, default="mittel"
default='mittel') )
technik = models.CharField('Technik', blank=False, choices=TECHNIK, max_length=100, technik = models.CharField(
default='mittel') "Technik", blank=False, choices=TECHNIK, max_length=100, default="mittel"
saison = models.CharField('Saison', blank=False, choices=SAISON, max_length=100, )
default='ganzjährig') saison = models.CharField(
eventart = models.CharField('Eventart', blank=False, choices=EVENTART, max_length=100, "Saison", blank=False, choices=SAISON, max_length=100, default="ganzjährig"
default='Einzeltermin') )
klassifizierung = models.CharField('Klassifizierung', blank=False, choices=KLASSIFIZIERUNG, eventart = models.CharField(
max_length=100, "Eventart", blank=False, choices=EVENTART, max_length=100, default="Einzeltermin"
default='Gemeinschaftstour') )
equipment = models.TextField('Ausrüstung', klassifizierung = models.CharField(
blank=True) "Klassifizierung",
voraussetzungen = models.TextField('Voraussetzungen', blank=False,
blank=True) choices=KLASSIFIZIERUNG,
description = models.TextField('Beschreibung', max_length=100,
blank=True) default="Gemeinschaftstour",
max_participants = models.IntegerField('Max. Teilnehmerzahl', )
blank=False, equipment = models.TextField("Ausrüstung", blank=True)
validators=[ voraussetzungen = models.TextField("Voraussetzungen", blank=True)
MinValueValidator(1) description = models.TextField("Beschreibung", blank=True)
], max_participants = models.IntegerField(
default=10) "Max. Teilnehmerzahl", blank=False, validators=[MinValueValidator(1)], default=10
anforderung_hoehe = models.IntegerField('Höhenmeter in Meter', )
blank=True, anforderung_hoehe = models.IntegerField(
validators=[ "Höhenmeter in Meter", blank=True, validators=[MinValueValidator(0)], default=0
MinValueValidator(0) )
], anforderung_strecke = models.IntegerField(
default=0) "Strecke in Kilometer", blank=True, validators=[MinValueValidator(0)], default=0
anforderung_strecke = models.IntegerField('Strecke in Kilometer', )
blank=True, anforderung_dauer = models.IntegerField(
validators=[ "Etappendauer in Stunden", blank=True, validators=[MinValueValidator(0)], default=0
MinValueValidator(0) )
],
default=0)
anforderung_dauer = models.IntegerField('Etappendauer in Stunden',
blank=True,
validators=[
MinValueValidator(0)
],
default=0)
def __str__(self): def __str__(self):
return "{} {}".format(self.title, str(self.group)) return "{} {}".format(self.title, str(self.group))
class Meta: class Meta:
verbose_name = 'Termin' verbose_name = "Termin"
verbose_name_plural = 'Termine' verbose_name_plural = "Termine"

@ -1,13 +1,21 @@
from http import HTTPStatus from http import HTTPStatus
from django.test import TestCase, RequestFactory from django.conf import settings
from django.utils import timezone
from django.contrib.admin.sites import AdminSite from django.contrib.admin.sites import AdminSite
from django.test import RequestFactory
from django.test import TestCase
from django.urls import reverse from django.urls import reverse
from django.conf import settings from django.utils import timezone
from .models import Termin, GRUPPE, KATEGORIE, KONDITION, TECHNIK, SAISON,\
EVENTART, KLASSIFIZIERUNG
from .admin import TerminAdmin from .admin import TerminAdmin
from .models import EVENTART
from .models import GRUPPE
from .models import KATEGORIE
from .models import KLASSIFIZIERUNG
from .models import KONDITION
from .models import SAISON
from .models import TECHNIK
from .models import Termin
class BasicTerminTestCase(TestCase): class BasicTerminTestCase(TestCase):
@ -15,68 +23,80 @@ class BasicTerminTestCase(TestCase):
def setUp(self): def setUp(self):
for i in range(BasicTerminTestCase.TERMIN_NO): for i in range(BasicTerminTestCase.TERMIN_NO):
Termin.objects.create(title='Foo {}'.format(i), Termin.objects.create(
start_date=timezone.now().date(), title="Foo {}".format(i),
end_date=timezone.now().date(), start_date=timezone.now().date(),
group=GRUPPE[0][0], end_date=timezone.now().date(),
email=settings.TEST_MAIL, group=GRUPPE[0][0],
category=KATEGORIE[0][0], email=settings.TEST_MAIL,
technik=TECHNIK[0][0], category=KATEGORIE[0][0],
max_participants=42, technik=TECHNIK[0][0],
anforderung_hoehe=10) max_participants=42,
anforderung_hoehe=10,
)
class TerminAdminTestCase(BasicTerminTestCase): class TerminAdminTestCase(BasicTerminTestCase):
def test_str(self): def test_str(self):
t = Termin.objects.all()[0] t = Termin.objects.all()[0]
self.assertEqual(str(t), '{} {}'.format(t.title, str(t.group))) self.assertEqual(str(t), "{} {}".format(t.title, str(t.group)))
def test_make_overview(self): def test_make_overview(self):
factory = RequestFactory() factory = RequestFactory()
admin = TerminAdmin(Termin, AdminSite()) admin = TerminAdmin(Termin, AdminSite())
url = reverse('admin:ludwigsburgalpin_termin_changelist') url = reverse("admin:ludwigsburgalpin_termin_changelist")
request = factory.get(url) request = factory.get(url)
response = admin.make_overview(request, Termin.objects.all()) response = admin.make_overview(request, Termin.objects.all())
self.assertEqual(response['Content-Type'], 'application/xlsx', self.assertEqual(
'The content-type of the generated overview should be an .xlsx file.') response["Content-Type"],
"application/xlsx",
"The content-type of the generated overview should be an .xlsx file.",
)
class ViewTestCase(BasicTerminTestCase): class ViewTestCase(BasicTerminTestCase):
def test_get_index(self): def test_get_index(self):
url = reverse('ludwigsburgalpin:index') url = reverse("ludwigsburgalpin:index")
response = self.client.get(url) response = self.client.get(url)
self.assertEqual(response.status_code, HTTPStatus.OK) self.assertEqual(response.status_code, HTTPStatus.OK)
def test_submit_termin(self): def test_submit_termin(self):
url = reverse('ludwigsburgalpin:index') url = reverse("ludwigsburgalpin:index")
response = self.client.post(url, data={ response = self.client.post(
'title': 'My Title', url,
'subtitle': 'My Subtitle', data={
'start_date': '2024-01-01', "title": "My Title",
'end_date': '2024-02-01', "subtitle": "My Subtitle",
'group': GRUPPE[0][0], "start_date": "2024-01-01",
'category': KATEGORIE[0][0], "end_date": "2024-02-01",
'condition': KONDITION[0][0], "group": GRUPPE[0][0],
'technik': TECHNIK[0][0], "category": KATEGORIE[0][0],
'saison': SAISON[0][0], "condition": KONDITION[0][0],
'eventart': EVENTART[0][0], "technik": TECHNIK[0][0],
'klassifizierung': KLASSIFIZIERUNG[0][0], "saison": SAISON[0][0],
'anforderung_hoehe': 10, "eventart": EVENTART[0][0],
'anforderung_strecke': 10, "klassifizierung": KLASSIFIZIERUNG[0][0],
'anforderung_dauer': 10, "anforderung_hoehe": 10,
'max_participants': 100, "anforderung_strecke": 10,
}) "anforderung_dauer": 10,
t = Termin.objects.get(title='My Title') "max_participants": 100,
},
)
t = Termin.objects.get(title="My Title")
self.assertEqual(t.group, GRUPPE[0][0]) self.assertEqual(t.group, GRUPPE[0][0])
self.assertEqual(response.status_code, HTTPStatus.OK) self.assertEqual(response.status_code, HTTPStatus.OK)
self.assertContains(response, "Termin erfolgreich eingereicht", html=True) self.assertContains(response, "Termin erfolgreich eingereicht", html=True)
def test_submit_termin_invalid(self): def test_submit_termin_invalid(self):
url = reverse('ludwigsburgalpin:index') url = reverse("ludwigsburgalpin:index")
# many required fields are missing # many required fields are missing
response = self.client.post(url, data={ response = self.client.post(
'title': 'My Title', url,
}) data={
"title": "My Title",
},
)
self.assertEqual(response.status_code, HTTPStatus.OK) self.assertEqual(response.status_code, HTTPStatus.OK)
self.assertContains(response, "Dieses Feld ist zwingend erforderlich.", html=True) self.assertContains(response, "Dieses Feld ist zwingend erforderlich.", html=True)

@ -4,6 +4,6 @@ from . import views
app_name = "ludwigsburgalpin" app_name = "ludwigsburgalpin"
urlpatterns = [ urlpatterns = [
re_path(r'^$', views.index, name='index') re_path(r"^$", views.index, name="index")
# re_path(r'^subscribe', views.subscribe, name='subscribe'), # re_path(r'^subscribe', views.subscribe, name='subscribe'),
] ]

@ -1,100 +1,89 @@
from django.shortcuts import render
from django import forms from django import forms
from django.http import HttpResponseRedirect
from django.contrib.admin import widgets
from django.core.validators import MinValueValidator from django.core.validators import MinValueValidator
from .models import Termin, GRUPPE, KATEGORIE, KONDITION, TECHNIK, SAISON, EVENTART, KLASSIFIZIERUNG from django.shortcuts import render
datepicker = forms.TextInput(attrs={'class': 'datepicker'}) from .models import EVENTART
from .models import GRUPPE
from .models import KATEGORIE
from .models import KLASSIFIZIERUNG
from .models import KONDITION
from .models import SAISON
from .models import TECHNIK
from .models import Termin
datepicker = forms.TextInput(attrs={"class": "datepicker"})
class TerminForm(forms.Form):
class TerminForm(forms.Form):
title = forms.CharField(label='Titel') title = forms.CharField(label="Titel")
subtitle = forms.CharField(label='Untertitel') subtitle = forms.CharField(label="Untertitel")
start_date = forms.DateField(label='Von', start_date = forms.DateField(label="Von", widget=datepicker)
widget=datepicker) end_date = forms.DateField(label="Bis", widget=datepicker)
end_date = forms.DateField(label='Bis', group = forms.ChoiceField(label="Gruppe", required=True, choices=GRUPPE)
widget=datepicker) category = forms.ChoiceField(label="Kategorie", required=True, choices=KATEGORIE)
group = forms.ChoiceField(label='Gruppe', condition = forms.ChoiceField(label="Kondition", required=True, choices=KONDITION)
required=True, technik = forms.ChoiceField(label="Technik", required=True, choices=TECHNIK)
choices=GRUPPE) saison = forms.ChoiceField(label="Saison", required=True, choices=SAISON)
category = forms.ChoiceField(label='Kategorie', required=True, choices=KATEGORIE) eventart = forms.ChoiceField(label="Eventart", required=True, choices=EVENTART)
condition = forms.ChoiceField(label='Kondition', required=True, choices=KONDITION) klassifizierung = forms.ChoiceField(
technik = forms.ChoiceField(label='Technik', required=True, choices=TECHNIK) label="Klassifizierung", required=True, choices=KLASSIFIZIERUNG
saison = forms.ChoiceField(label='Saison', required=True, choices=SAISON) )
eventart = forms.ChoiceField(label='Eventart', required=True, choices=EVENTART) anforderung_hoehe = forms.IntegerField(
klassifizierung = forms.ChoiceField(label='Klassifizierung', required=True, choices=KLASSIFIZIERUNG) label="Höhenmeter in Metern", required=True, validators=[MinValueValidator(0)]
anforderung_hoehe = forms.IntegerField(label='Höhenmeter in Metern', )
required=True, anforderung_strecke = forms.IntegerField(
validators=[ label="Strecke in Kilometern", required=True, validators=[MinValueValidator(0)]
MinValueValidator(0) )
]) anforderung_dauer = forms.IntegerField(
anforderung_strecke = forms.IntegerField(label='Strecke in Kilometern', label="Etappendauer in Stunden", required=True, validators=[MinValueValidator(0)]
required=True, )
validators=[ description = forms.CharField(label="Beschreibung", widget=forms.Textarea, required=False)
MinValueValidator(0) equipment = forms.CharField(label="Ausrüstung", widget=forms.Textarea, required=False)
]) voraussetzungen = forms.CharField(
anforderung_dauer = forms.IntegerField(label='Etappendauer in Stunden', label="Voraussetzungen", widget=forms.Textarea, required=False
required=True, )
validators=[ max_participants = forms.IntegerField(
MinValueValidator(0) label="Max. Teilnehmerzahl", required=True, validators=[MinValueValidator(1)]
]) )
description = forms.CharField(label='Beschreibung', responsible = forms.CharField(label="Organisator", max_length=100, required=False)
widget=forms.Textarea, phone = forms.CharField(max_length=20, label="Telefonnumer", required=False)
required=False) email = forms.EmailField(max_length=100, label="Email", required=False)
equipment = forms.CharField(label='Ausrüstung',
widget=forms.Textarea,
required=False)
voraussetzungen = forms.CharField(label='Voraussetzungen',
widget=forms.Textarea,
required=False)
max_participants = forms.IntegerField(label='Max. Teilnehmerzahl',
required=True,
validators=[
MinValueValidator(1)
])
responsible = forms.CharField(label='Organisator', max_length=100,
required=False)
phone = forms.CharField(max_length=20, label='Telefonnumer',
required=False)
email = forms.EmailField(max_length=100, label='Email',
required=False)
# Create your views here. # Create your views here.
def index(request, *args): def index(request, *args):
if request.method == 'POST': if request.method == "POST":
form = TerminForm(request.POST) form = TerminForm(request.POST)
if form.is_valid(): if form.is_valid():
termin = Termin(title=form.cleaned_data["title"], termin = Termin(
subtitle=form.cleaned_data["subtitle"], title=form.cleaned_data["title"],
start_date=form.cleaned_data["start_date"], subtitle=form.cleaned_data["subtitle"],
end_date=form.cleaned_data["end_date"], start_date=form.cleaned_data["start_date"],
group=form.cleaned_data["group"], end_date=form.cleaned_data["end_date"],
responsible=form.cleaned_data["responsible"], group=form.cleaned_data["group"],
phone=form.cleaned_data["phone"], responsible=form.cleaned_data["responsible"],
email=form.cleaned_data["email"], phone=form.cleaned_data["phone"],
category=form.cleaned_data["category"], email=form.cleaned_data["email"],
condition=form.cleaned_data["condition"], category=form.cleaned_data["category"],
technik=form.cleaned_data["technik"], condition=form.cleaned_data["condition"],
saison=form.cleaned_data["saison"], technik=form.cleaned_data["technik"],
eventart=form.cleaned_data["eventart"], saison=form.cleaned_data["saison"],
klassifizierung=form.cleaned_data["klassifizierung"], eventart=form.cleaned_data["eventart"],
equipment=form.cleaned_data["equipment"], klassifizierung=form.cleaned_data["klassifizierung"],
voraussetzungen=form.cleaned_data["voraussetzungen"], equipment=form.cleaned_data["equipment"],
max_participants=form.cleaned_data["max_participants"], voraussetzungen=form.cleaned_data["voraussetzungen"],
anforderung_hoehe=form.cleaned_data["anforderung_hoehe"], max_participants=form.cleaned_data["max_participants"],
anforderung_strecke=form.cleaned_data["anforderung_strecke"], anforderung_hoehe=form.cleaned_data["anforderung_hoehe"],
anforderung_dauer=form.cleaned_data["anforderung_dauer"], anforderung_strecke=form.cleaned_data["anforderung_strecke"],
description=form.cleaned_data["description"]) anforderung_dauer=form.cleaned_data["anforderung_dauer"],
description=form.cleaned_data["description"],
)
termin.save() termin.save()
return published(request) return published(request)
else: else:
form = TerminForm() form = TerminForm()
return render(request, 'ludwigsburgalpin/termine.html', {'form': form.as_table()}) return render(request, "ludwigsburgalpin/termine.html", {"form": form.as_table()})
def published(request): def published(request):
return render(request, 'ludwigsburgalpin/published.html') return render(request, "ludwigsburgalpin/published.html")

@ -1,11 +1,15 @@
from datetime import datetime, timedelta import logging
import unicodedata
from datetime import datetime
from datetime import timedelta
from decimal import Decimal
from decimal import ROUND_HALF_DOWN
from django.core.exceptions import ValidationError
from django.db import models from django.db import models
from django.utils import timezone from django.utils import timezone
from django.core.exceptions import ValidationError
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from decimal import Decimal, ROUND_HALF_DOWN
import unicodedata
import logging
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -14,18 +18,20 @@ def file_size_validator(max_upload_size):
Returns a function checking if the supplied file has file size less or equal Returns a function checking if the supplied file has file size less or equal
than `max_upload_size` in MB. than `max_upload_size` in MB.
""" """
def check_file_size(value): def check_file_size(value):
limit = max_upload_size * 1024 * 1024 limit = max_upload_size * 1024 * 1024
if value.size > limit: if value.size > limit:
raise ValidationError(_('Please keep filesize under {} MiB. ' raise ValidationError(
'Current filesize: ' _("Please keep filesize under {} MiB. Current filesize: {:10.2f} MiB.").format(
'{:10.2f} MiB.').format(max_upload_size, max_upload_size, value.size / 1024 / 1024
value.size / 1024 / 1024)) )
)
return check_file_size return check_file_size
class RestrictedFileField(models.FileField): class RestrictedFileField(models.FileField):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
if "max_upload_size" in kwargs: if "max_upload_size" in kwargs:
self.max_upload_size = kwargs.pop("max_upload_size") self.max_upload_size = kwargs.pop("max_upload_size")
@ -36,32 +42,33 @@ class RestrictedFileField(models.FileField):
else: else:
self.content_types = None self.content_types = None
super(RestrictedFileField, self).__init__(*args, **kwargs) super().__init__(*args, **kwargs)
self.validators = [file_size_validator(self.max_upload_size)] self.validators = [file_size_validator(self.max_upload_size)]
def clean(self, *args, **kwargs): def clean(self, *args, **kwargs):
data = super(RestrictedFileField, self).clean(*args, **kwargs) data = super().clean(*args, **kwargs)
f = data.file f = data.file
try: try:
content_type = f.content_type content_type = f.content_type
if self.content_types is not None and content_type not in self.content_types: if self.content_types is not None and content_type not in self.content_types:
raise ValidationError(_('Filetype not supported.')) raise ValidationError(_("Filetype not supported."))
if self.max_upload_size is not None and f._size > self.max_upload_size: if self.max_upload_size is not None and f._size > self.max_upload_size:
raise ValidationError(_('Please keep filesize under {}. ' raise ValidationError(
'Current filesize: ' _("Please keep filesize under {}. Current filesize: {}").format(
'{}').format(self.max_upload_size, self.max_upload_size, f._size
f._size)) )
)
except AttributeError as e: except AttributeError as e:
logger.warning(e) logger.warning(e)
return data return data
def cvt_to_decimal(f): def cvt_to_decimal(f):
return Decimal(f).quantize(Decimal('.01'), rounding=ROUND_HALF_DOWN) return Decimal(f).quantize(Decimal(".01"), rounding=ROUND_HALF_DOWN)
def get_member(request): def get_member(request):
if not hasattr(request.user, 'member'): if not hasattr(request.user, "member"):
return None return None
else: else:
return request.user.member return request.user.member
@ -69,10 +76,10 @@ def get_member(request):
def normalize_name(raw, nospaces=True, noumlaut=True): def normalize_name(raw, nospaces=True, noumlaut=True):
if noumlaut: if noumlaut:
raw = raw.replace('ö', 'oe').replace('ä', 'ae').replace('ü', 'ue') raw = raw.replace("ö", "oe").replace("ä", "ae").replace("ü", "ue")
if nospaces: if nospaces:
raw = raw.replace(' ', '_') raw = raw.replace(" ", "_")
return unicodedata.normalize('NFKD', raw).encode('ascii', 'ignore').decode('ascii') return unicodedata.normalize("NFKD", raw).encode("ascii", "ignore").decode("ascii")
def normalize_filename(filename, append_date=True, date=None): def normalize_filename(filename, append_date=True, date=None):
@ -80,20 +87,27 @@ def normalize_filename(filename, append_date=True, date=None):
date = datetime.today() date = datetime.today()
if date: if date:
filename = filename + "_" + date.strftime("%d_%m_%Y") filename = filename + "_" + date.strftime("%d_%m_%Y")
filename = filename.replace(' ', '_').replace('&', '').replace('/', '_') filename = filename.replace(" ", "_").replace("&", "").replace("/", "_")
# drop umlauts, accents etc. # drop umlauts, accents etc.
return unicodedata.normalize('NFKD', filename).encode('ASCII', 'ignore').decode() return unicodedata.normalize("NFKD", filename).encode("ASCII", "ignore").decode()
def coming_midnight(): def coming_midnight():
base = timezone.now() + timezone.timedelta(days=1) base = timezone.now() + timezone.timedelta(days=1)
return timezone.datetime(year=base.year, month=base.month, day=base.day, return timezone.datetime(
hour=0, minute=0, second=0, microsecond=0, year=base.year,
tzinfo=base.tzinfo) month=base.month,
day=base.day,
hour=0,
minute=0,
second=0,
microsecond=0,
tzinfo=base.tzinfo,
)
def mondays_until_nth(n): def mondays_until_nth(n):
""" Returns a list of dates for the next n Mondays, starting from the next Monday. """Returns a list of dates for the next n Mondays, starting from the next Monday.
This functions aids in the generation of weekly schedules or reports.""" This functions aids in the generation of weekly schedules or reports."""
today = datetime.today() today = datetime.today()
next_monday = today + timedelta(days=(7 - today.weekday()) % 7 or 7) next_monday = today + timedelta(days=(7 - today.weekday()) % 7 or 7)

Loading…
Cancel
Save