Merge branch 'main' into Docu_Warteliste

pull/82/head
Christian Merten 1 year ago
commit c5c29f89bd
Signed by: christian.merten
GPG Key ID: D953D69721B948B3

@ -0,0 +1,33 @@
DJANGO_ALLOWED_HOST='*'
DJANGO_BASE_URL='localhost:8000'
DJANGO_PROTOCOL='http'
EMAIL_HOST='example.com'
EMAIL_HOST_USER='foo@example.com'
EMAIL_HOST_PASSWORD='password'
EMAIL_SENDING_ADDRESS='foo@example.com'
EMAIL_SENDING_NAME='My association'
DOMAIN='example.com'
DJANGO_DEPLOY=1
DJANGO_DEBUG=1
DJANGO_DATABASE_NAME='kompass'
DJANGO_DATABASE_USER='kompass'
DJANGO_DATABASE_PASSWORD='foobar'
DJANGO_DATABASE_HOST='db'
DJANGO_DATABASE_PORT=3306
MYSQL_ROOT_PASSWORD='secret'
MYSQL_PASSWORD='foobar'
MYSQL_USER='kompass'
MYSQL_DATABASE='kompass'
DJANGO_SETTINGS_MODULE='jdav_web.settings'
DJANGO_STATIC_ROOT='/var/www/jdav_web/assets'
MEMCACHED_URL='cache:11211'
BROKER_URL='redis://redis:6379/0'
SEND_FROM_ASSOCIATION_EMAIL=0

@ -191,3 +191,6 @@ MARKDOWNIFY = {
}
}
}
# allowed characters in names appearing in urls on the website
STARTPAGE_URL_NAME_PATTERN = "[\w\-: *]"

@ -35,7 +35,7 @@ Bitte kontaktiere die Gruppenleitung ({contact_email}) für alle weiteren Abspra
Wenn du nach der Schnupperstunde beschließt der Gruppe beizutreten, benötigen wir noch ein paar
Informationen und deine Anmeldebestätigung von dir. Die lädst du herunter
(siehe %(REGISTRATION_FORM_DOWNLOAD_LINK)s), lässt sie von deinen Eltern ausfüllen, unterschreiben
(siehe %(REGISTRATION_FORM_DOWNLOAD_LINK)s ), lässt sie von deinen Eltern ausfüllen, unterschreiben
und lädst ein Foto davon in unserem Anmeldeformular hoch. Das kannst du alles über folgenden Link erledigen:
{link}

@ -45,9 +45,9 @@ SEND_FROM_ASSOCIATION_EMAIL = os.environ.get('SEND_FROM_ASSOCIATION_EMAIL', '0')
ALLOWANCE_PER_DAY = 22
MAX_NIGHT_COST = 11
CLOUD_LINK = 'https://nc.cloud-jdav-hd.de'
DAV_360_LINK = 'https://dav360.de'
WIKI_LINK = 'https://davbgs.sharepoint.com/sites/S-114-O-JDAV-Jugendreferat'
CLOUD_LINK = os.environ.get('CLOUD_LINK', 'https://startpage.com')
DAV_360_LINK = os.environ.get('DAV_360_LINK', 'https://dav360.de')
WIKI_LINK = os.environ.get('WIKI_LINK', 'https://wikipedia.org')
DOCS_LINK = os.environ.get('DOCS_LINK', 'https://jdav-hd.de/static/docs/')
# Admin setup
@ -65,7 +65,7 @@ MAX_REMINDER_COUNT = 3
TEST_MAIL = "post@flavigny.de"
REGISTRATION_FORM_DOWNLOAD_LINK = 'https://nc.cloud-jdav-hd.de'
REGISTRATION_FORM_DOWNLOAD_LINK = os.environ.get('REGISTRATION_FORM_DOWNLOAD_LINK', 'https://startpage.com')
DOMAIN = os.environ.get('DOMAIN', 'example.com')

@ -0,0 +1,19 @@
# Generated by Django 4.0.1 on 2024-12-03 23:19
import django.core.validators
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('mailer', '0007_emailaddress_internal_only'),
]
operations = [
migrations.AlterField(
model_name='emailaddress',
name='name',
field=models.CharField(max_length=50, unique=True, validators=[django.core.validators.RegexValidator('^[0-9a-zA-Z._-]*$', 'Only alphanumeric characters, ., - and _ are allowed')], verbose_name='name'),
),
]

@ -22,7 +22,8 @@ alphanumeric = RegexValidator(r'^[0-9a-zA-Z._-]*$',
class EmailAddress(models.Model):
"""Represents an email address, that is forwarded to specific members"""
name = models.CharField(_('name'), max_length=50, validators=[alphanumeric])
name = models.CharField(_('name'), max_length=50, validators=[alphanumeric],
unique=True)
to_members = models.ManyToManyField('members.Member',
verbose_name=_('Forward to participants'),
blank=True)
@ -63,6 +64,7 @@ class EmailAddressForm(forms.ModelForm):
exclude = []
def clean(self):
super(EmailAddressForm, self).clean()
group = self.cleaned_data.get('to_groups')
members = self.cleaned_data.get('to_members')
if not group and not members:

@ -15,6 +15,7 @@ from django.template.loader import get_template
from django.urls import path, reverse
from django.http import HttpResponse, HttpResponseRedirect
from wsgiref.util import FileWrapper
from django.utils import timezone
from django import forms
from django.contrib import admin, messages
from django.contrib.admin import DateFieldListFilter
@ -406,7 +407,7 @@ class MemberUnconfirmedAdmin(CommonAdminMixin, admin.ModelAdmin):
'comments',
'legal_guardians',
'dav_badge_no',
'active', 'echoed',
'echoed',
'user',
]
}
@ -439,7 +440,7 @@ class MemberUnconfirmedAdmin(CommonAdminMixin, admin.ModelAdmin):
search_fields = ('prename', 'lastname', 'email')
list_filter = ('group', 'confirmed_mail', 'confirmed_alternative_mail')
readonly_fields = ['confirmed_mail', 'confirmed_alternative_mail',
'good_conduct_certificate_valid']
'good_conduct_certificate_valid', 'echoed']
actions = ['request_mail_confirmation', 'confirm', 'demote_to_waiter_action']
inlines = [EmergencyContactInline]
change_form_template = "members/change_member_unconfirmed.html"
@ -453,6 +454,7 @@ class MemberUnconfirmedAdmin(CommonAdminMixin, admin.ModelAdmin):
field_change_permissions = {
'user': 'members.may_set_auth_user',
'group': 'members.may_change_member_group',
'good_conduct_certificate_presented_date': 'members.may_change_organizationals',
'has_key': 'members.may_change_organizationals',
'has_free_ticket_gym': 'members.may_change_organizationals',
@ -541,17 +543,8 @@ class MemberUnconfirmedAdmin(CommonAdminMixin, admin.ModelAdmin):
def demote_to_waiter(self, request, queryset):
for member in queryset:
waiter = MemberWaitingList(prename=member.prename,
lastname=member.lastname,
email=member.email,
birth_date=member.birth_date,
gender=member.gender,
comments=member.comments,
confirmed_mail=member.confirmed_mail,
confirm_mail_key=member.confirm_mail_key)
waiter.save()
member.delete()
messages.success(request, _("Successfully demoted %(name)s to waiter.") % {'name': waiter.name})
member.demote_to_waiter()
messages.success(request, _("Successfully demoted %(name)s to waiter.") % {'name': member.name})
def response_change(self, request, member):
if "_confirm" in request.POST:
@ -580,14 +573,28 @@ class InvitationToGroupAdmin(admin.TabularInline):
return False
class InvitedToGroupFilter(admin.SimpleListFilter):
title = _('Pending group invitation for group')
parameter_name = 'pending_group_invitation'
def lookups(self, request, model_admin):
return [(g.pk, g.name) for g in Group.objects.all()]
def queryset(self, request, queryset):
pk = self.value()
if not pk:
return queryset
return queryset.filter(invitationtogroup__group__pk=pk, invitationtogroup__rejected=False,
invitationtogroup__date__gt=(timezone.now() - timezone.timedelta(days=30)).date()).distinct()
class MemberWaitingListAdmin(CommonAdminMixin, admin.ModelAdmin):
fields = ['prename', 'lastname', 'email', 'birth_date', 'gender', 'application_text',
'application_date', 'comments',
'sent_reminders']
list_display = ('name', 'birth_date', 'age', 'application_date', 'confirmed_mail',
'waiting_confirmed', 'sent_reminders')
'application_date', 'comments', 'sent_reminders']
list_display = ('name', 'birth_date', 'age', 'gender', 'application_date', 'latest_group_invitation',
'confirmed_mail', 'waiting_confirmed', 'sent_reminders')
search_fields = ('prename', 'lastname', 'email')
list_filter = ('confirmed_mail',)
list_filter = ['confirmed_mail', 'gender', InvitedToGroupFilter]
actions = ['ask_for_registration', 'ask_for_wait_confirmation']
inlines = [InvitationToGroupAdmin]
readonly_fields= ['application_date', 'sent_reminders']
@ -662,6 +669,10 @@ class MemberWaitingListAdmin(CommonAdminMixin, admin.ModelAdmin):
]
return custom_urls + urls
def get_queryset(self, request):
queryset = super().get_queryset(request)
return queryset.prefetch_related('invitationtogroup_set')
def invite_view(self, request, object_id):
waiter = MemberWaitingList.objects.get(pk=object_id)
@ -704,6 +715,10 @@ class RegistrationPasswordInline(admin.TabularInline):
class GroupAdminForm(forms.ModelForm):
name = forms.RegexField(regex=r'^{pattern}+$'.format(pattern=settings.STARTPAGE_URL_NAME_PATTERN),
label=_('name'),
error_messages={'invalid': _('The group name may only consist of letters, numerals, _, -, :, * and spaces.')})
class Meta:
model = Freizeit
exclude = ['add_member']

File diff suppressed because it is too large Load Diff

@ -0,0 +1,18 @@
# Generated by Django 4.0.1 on 2024-12-15 18:24
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('members', '0030_alter_member_options'),
]
operations = [
migrations.AddField(
model_name='member',
name='waitinglist_application_date',
field=models.DateTimeField(blank=True, help_text='If the person registered from the waitinglist, this is their application date.', null=True, verbose_name='waitinglist application date'),
),
]

@ -301,6 +301,9 @@ class Member(Person):
user = models.OneToOneField(User, blank=True, null=True, on_delete=models.SET_NULL,
verbose_name=_('Login data'))
invite_as_user_key = models.CharField(max_length=32, default="")
waitinglist_application_date = models.DateTimeField(verbose_name=_('waitinglist application date'),
null=True, blank=True,
help_text=_('If the person registered from the waitinglist, this is their application date.'))
objects = MemberManager()
@ -452,6 +455,9 @@ class Member(Person):
if self.email == waiter.email:
self.confirmed_mail = waiter.confirmed_mail
self.confirm_mail_key = waiter.confirm_mail_key
# store waitinglist application date in member, this will be used
# if the member is later demoted to waiter again
self.waitinglist_application_date = waiter.application_date
if self.alternative_email:
self.confirmed_alternative_mail = False
self.save()
@ -465,8 +471,7 @@ class Member(Person):
def registration_ready(self):
"""Returns if the member is currently unconfirmed and all email addresses
are confirmed."""
return not self.confirmed and self.confirmed_alternative_mail and self.confirmed_mail and\
all([emc.confirmed_mail for emc in self.emergencycontact_set.all()])
return not self.confirmed and self.confirmed_alternative_mail and self.confirmed_mail
def confirm_mail(self, key):
ret = super().confirm_mail(key)
@ -720,6 +725,23 @@ class Member(Person):
"""Returns a queryset of freizeiten that this member is a youth leader of."""
return Freizeit.objects.filter(jugendleiter__pk=self.pk)[:limit]
def demote_to_waiter(self):
"""Demote this member to a waiter by creating a waiter from the data and removing
this member."""
waiter = MemberWaitingList(prename=self.prename,
lastname=self.lastname,
email=self.email,
birth_date=self.birth_date,
gender=self.gender,
comments=self.comments,
confirmed_mail=self.confirmed_mail,
confirm_mail_key=self.confirm_mail_key)
# if this member was created from the waitinglist, keep the original application date
if self.waitinglist_application_date:
waiter.application_date = self.waitinglist_application_date
waiter.save()
self.delete()
class EmergencyContact(ContactWithPhoneNumber):
"""
@ -828,6 +850,14 @@ class MemberWaitingList(Person):
'delete_obj': has_global_perm('members.delete_global_memberwaitinglist'),
}
def latest_group_invitation(self):
gi = self.invitationtogroup_set.order_by('-pk').first()
if gi:
return "{group}: {status}".format(group=gi.group.name, status=gi.status())
else:
return "-"
latest_group_invitation.short_description = _('Latest group invitation')
@property
def waiting_confirmation_needed(self):
"""Returns if person should be asked to confirm waiting status."""

@ -1,4 +1,7 @@
from django.contrib import admin
from django.conf import settings
from django import forms
from django.utils.translation import gettext_lazy as _
from .models import Post, Image, Section, MemberOnPost
@ -13,14 +16,27 @@ class MemberOnPostInline(admin.TabularInline):
extra = 0
class PostForm(forms.ModelForm):
urlname = forms.RegexField(regex=r'^{pattern}+$'.format(pattern=settings.STARTPAGE_URL_NAME_PATTERN),
label=_('URL'),
error_messages={'invalid': _('The url may only consist of letters, numerals, _, -, :, * and spaces.')})
@admin.register(Post)
class PostAdmin(admin.ModelAdmin):
inlines = [ImageInline, MemberOnPostInline]
list_display = ['title', 'date', 'section', 'absolute_urlname']
list_filter = ['section']
search_fields = ['title']
form = PostForm
class SectionForm(forms.ModelForm):
urlname = forms.RegexField(regex=r'^{pattern}+$'.format(pattern=settings.STARTPAGE_URL_NAME_PATTERN),
label=_('URL'),
error_messages={'invalid': _('The url may only consist of letters, numerals, _, -, :, * and spaces.')})
@admin.register(Section)
class SectionAdmin(admin.ModelAdmin):
list_display = ['title', 'absolute_urlname']
form = SectionForm

@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-12-01 16:23+0100\n"
"POT-Creation-Date: 2024-12-04 00:04+0100\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@ -18,14 +18,21 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: startpage/admin.py:21 startpage/admin.py:36 startpage/models.py:18
#: startpage/models.py:40
msgid "URL"
msgstr "URL"
#: startpage/admin.py:22 startpage/admin.py:37
msgid "The url may only consist of letters, numerals, _, -, :, * and spaces."
msgstr ""
"Die URL darf nur aus Buchstaben, Zahlen, _, -, :, * oder Leerzeichen "
"bestehen."
#: startpage/models.py:17 startpage/models.py:39
msgid "Title"
msgstr "Titel"
#: startpage/models.py:18 startpage/models.py:40
msgid "URL"
msgstr "URL"
#: startpage/models.py:19 startpage/models.py:42
msgid "website text"
msgstr "Webseitentext"

@ -1,3 +1,4 @@
from django.conf import settings
from django.urls import re_path
from . import views
@ -10,7 +11,10 @@ urlpatterns = [
re_path(r'^berichte/?$', views.berichte, name='berichte'),
re_path(r'^gruppen/?$', views.static_view('startpage/gruppen.html'), name='gruppen'),
re_path(r'^gruppen/faq/?$', views.static_view('startpage/gruppen/faq.html'), name='faq'),
re_path(r'^gruppen/(?P<group_name>[\w\-:]+)/?$', views.gruppe_detail, name='gruppe_detail'),
re_path(r'^(?P<section_name>[\w\-:]+)/(?P<post_name>[\w\-:]+)/?$', views.post, name='post'),
re_path(r'^(?P<section_name>[\w\-:]+)/?$', views.section, name='section'),
re_path(r'^gruppen/(?P<group_name>{pattern}+)/?$'.format(pattern=settings.STARTPAGE_URL_NAME_PATTERN),
views.gruppe_detail, name='gruppe_detail'),
re_path(r'^(?P<section_name>{pattern}+)/(?P<post_name>{pattern}+)/?$'.format(pattern=settings.STARTPAGE_URL_NAME_PATTERN),
views.post, name='post'),
re_path(r'^(?P<section_name>{pattern}+)/?$'.format(pattern=settings.STARTPAGE_URL_NAME_PATTERN),
views.section, name='section'),
]

@ -45,20 +45,28 @@ Hier siehst du alle von dir geleiteten Ausfahrten und für dich sichtbare Teilne
<td></td>
</tr>
</table>
{% if perms.members.may_manage_waiting_list %}
{% if perms.members.view_memberunconfirmedproxy %}
<br>
<div class="app-members module current-app">
<h2>Neue Mitglieder</h2>
<p>
Hier werden neue Mitglieder verwaltet. Um ein neues Mitglied anzulegen, muss sich die Person
{% if perms.member.may_manage_waiting_list %}
Um ein neues Mitglied anzulegen, muss sich die Person
<a href="{% url 'members:register_waiting_list' %}">anmelden</a>. Daraufhin landet
sie auf der <a href="{% url 'admin:members_memberwaitinglist_changelist' %}">Warteliste</a>. Eine
Person auf der Warteliste kannst du dann zu einer Schnupperstunde einer gewählten Gruppe einladen.
Diese Einladung enthält einen Registrierungslink zu einem Formular in dem die Person alle ihre
Stammdaten eingbit. Diese Daten landen dann unter
<a href="{% url 'admin:members_memberunconfirmedproxy_changelist' %}">Unbestätigte Registrierungen</a>.
{% else %}
Ob über die Warteliste oder über ein Registrierungspasswort,
liegt eine neue Registrierung für eine von dir geleitete Jugendgruppe vor, kannst du die hier einsehen
und die Daten prüfen. Falls die Daten vollständig sind, bestätige die Registrierung um die Person in deine
Jugendgruppe aufzunehmen.
{% endif %}
</p>
<table>
{% if perms.member.may_manage_waiting_list %}
<tr>
<th scope="row">
<a href="{% url 'admin:members_memberwaitinglist_changelist' %}">Warteliste</a>
@ -66,6 +74,7 @@ Stammdaten eingbit. Diese Daten landen dann unter
<td></td>
<td></td>
</tr>
{% endif %}
<tr>
<th scope="row">
<a href="{% url 'admin:members_memberunconfirmedproxy_changelist' %}">Unbestätigte Registrierungen</a>

Loading…
Cancel
Save