members: emergency contacts, replace parent email with alternative email
gitea/kompass/pipeline/head There was a failure building this commit Details

individual-sender-address
Christian Merten 1 year ago
parent 1f857e4fa3
commit cdab970bfc
Signed by: christian.merten
GPG Key ID: D953D69721B948B3

@ -140,11 +140,11 @@ class Message(CommonModel):
recipients_without_reminder = [m for m in filtered if m.registered] recipients_without_reminder = [m for m in filtered if m.registered]
emails_rem = [member.email for member in recipients_with_reminder] emails_rem = [member.email for member in recipients_with_reminder]
emails_rem.extend([member.email_parents for member in recipients_with_reminder emails_rem.extend([member.alternative_email for member in recipients_with_reminder
if member.email_parents and member.cc_email_parents]) if member.alternative_email])
emails_no_rem = [member.email for member in recipients_without_reminder] emails_no_rem = [member.email for member in recipients_without_reminder]
emails_no_rem.extend([member.email_parents for member in recipients_without_reminder emails_no_rem.extend([member.alternative_email for member in recipients_without_reminder
if member.email_parents and member.cc_email_parents]) if member.alternative_email])
# remove any underscores from subject to prevent Arne from using # remove any underscores from subject to prevent Arne from using
# terrible looking underscores in subjects # terrible looking underscores in subjects
self.subject = self.subject.replace('_', ' ') self.subject = self.subject.replace('_', ' ')

@ -35,7 +35,7 @@ import nested_admin
from .models import (Member, Group, Freizeit, MemberNoteList, NewMemberOnList, Klettertreff, from .models import (Member, Group, Freizeit, MemberNoteList, NewMemberOnList, Klettertreff,
MemberWaitingList, LJPProposal, Intervention, PermissionMember, MemberWaitingList, LJPProposal, Intervention, PermissionMember,
PermissionGroup, MemberTraining, TrainingCategory, PermissionGroup, MemberTraining, TrainingCategory,
KlettertreffAttendee, ActivityCategory, KlettertreffAttendee, ActivityCategory, EmergencyContact,
annotate_activity_score, RegistrationPassword, MemberUnconfirmedProxy) annotate_activity_score, RegistrationPassword, MemberUnconfirmedProxy)
from finance.models import Statement, BillOnExcursionProxy from finance.models import Statement, BillOnExcursionProxy
from mailer.mailutils import send as send_mail, get_echo_link from mailer.mailutils import send as send_mail, get_echo_link
@ -102,6 +102,15 @@ class TrainingOnMemberInline(CommonAdminInlineMixin, admin.TabularInline):
extra = 0 extra = 0
class EmergencyContactInline(CommonAdminInlineMixin, admin.TabularInline):
model = EmergencyContact
formfield_overrides = {
TextField: {'widget': Textarea(attrs={'rows': 1, 'cols': 40})}
}
fields = ['prename', 'lastname', 'email', 'phone_number']
extra = 0
class TrainingCategoryAdmin(admin.ModelAdmin): class TrainingCategoryAdmin(admin.ModelAdmin):
list_display = ('name', 'permission_needed') list_display = ('name', 'permission_needed')
ordering = ('name', ) ordering = ('name', )
@ -109,7 +118,7 @@ class TrainingCategoryAdmin(admin.ModelAdmin):
class RegistrationFilter(admin.SimpleListFilter): class RegistrationFilter(admin.SimpleListFilter):
title = _('Registration complete') title = _('Registration complete')
parameter_name = 'registered' parameter_name = 'registration_complete'
default_value = ('All', None) default_value = ('All', None)
def lookups(self, request, model_admin): def lookups(self, request, model_admin):
@ -121,14 +130,14 @@ class RegistrationFilter(admin.SimpleListFilter):
def queryset(self, request, queryset): def queryset(self, request, queryset):
if self.value() == 'True': if self.value() == 'True':
return queryset.filter(registered=True) return queryset.filter(registration_complete=True)
elif self.value() == 'False': elif self.value() == 'False':
return queryset.filter(registered=False) return queryset.filter(registration_complete=False)
elif self.value() is None: elif self.value() is None:
if self.default_value[1] is None: if self.default_value[1] is None:
return queryset return queryset
else: else:
return queryset.filter(registered=self.default_value[1]) return queryset.filter(registration_complete=self.default_value[1])
elif self.value() == 'All': elif self.value() == 'All':
return queryset return queryset
@ -149,27 +158,25 @@ class RegistrationFilter(admin.SimpleListFilter):
# Register your models here. # Register your models here.
class MemberAdmin(CommonAdminMixin, admin.ModelAdmin): class MemberAdmin(CommonAdminMixin, admin.ModelAdmin):
fields = ['prename', 'lastname', 'email', fields = ['prename', 'lastname', 'email',
('email_parents', 'cc_email_parents'), 'alternative_email',
'street', 'plz', 'town', 'address_extra', 'country', 'nationality', 'street', 'plz', 'town', 'address_extra', 'country',
'phone_number_private', 'phone_number_mobile', 'phone_number', 'birth_date', 'gender',
'phone_number_parents', 'birth_date', 'gender', 'civil_status',
'dav_badge_no', 'dav_badge_no',
'group', 'group',
'swimming_badge', 'climbing_badge', 'rock_experience', 'allergies', 'swimming_badge', 'climbing_badge', 'alpine_experience', 'allergies',
'medication', 'tetanus_vaccination', 'photos_may_be_taken', 'legal_guardians', 'medication', 'tetanus_vaccination', 'photos_may_be_taken', 'legal_guardians',
('good_conduct_certificate_presented_date', 'good_conduct_certificate_presentation_needed'), ('good_conduct_certificate_presented_date', 'good_conduct_certificate_presentation_needed'),
'iban', 'has_key', 'has_free_ticket_gym', 'gets_newsletter', 'registered', 'registration_form', 'iban', 'has_key', 'has_free_ticket_gym', 'gets_newsletter',
'image', 'registration_form', 'image',
'active', 'echoed', 'active', 'echoed',
('join_date', 'leave_date'), ('join_date', 'leave_date'),
'comments', 'technical_comments', 'comments', 'user']
'user']
list_display = ('name_text_or_link', 'birth_date', 'age', 'get_group', 'gets_newsletter', list_display = ('name_text_or_link', 'birth_date', 'age', 'get_group', 'gets_newsletter',
'registered', 'active', 'echoed', 'comments', 'activity_score') 'registration_complete', 'active', 'echoed', 'comments', 'activity_score')
search_fields = ('prename', 'lastname', 'email') search_fields = ('prename', 'lastname', 'email')
list_filter = ('group', 'gets_newsletter', RegistrationFilter, 'active') list_filter = ('group', 'gets_newsletter', RegistrationFilter, 'active')
list_display_links = None list_display_links = None
inlines = [TrainingOnMemberInline, PermissionOnMemberInline] inlines = [EmergencyContactInline, TrainingOnMemberInline, PermissionOnMemberInline]
#formfield_overrides = { #formfield_overrides = {
# ManyToManyField: {'widget': forms.CheckboxSelectMultiple}, # ManyToManyField: {'widget': forms.CheckboxSelectMultiple},
# ForeignKey: {'widget': apply_select2(forms.Select)} # ForeignKey: {'widget': apply_select2(forms.Select)}
@ -213,11 +220,8 @@ class MemberAdmin(CommonAdminMixin, admin.ModelAdmin):
for member in queryset: for member in queryset:
if not member.gets_newsletter: if not member.gets_newsletter:
continue continue
send_mail(_("Echo required"), member.send_mail(_("Echo required"),
settings.ECHO_TEXT.format(name=member.prename, link=get_echo_link(member)), settings.ECHO_TEXT.format(name=member.prename, link=get_echo_link(member)))
settings.DEFAULT_SENDING_MAIL,
[member.email, member.email_parents] if member.email_parents and member.cc_email_parents
else member.email)
messages.success(request, _("Successfully requested echo from selected members.")) messages.success(request, _("Successfully requested echo from selected members."))
request_echo.short_description = _('Request echo from selected members') request_echo.short_description = _('Request echo from selected members')
@ -251,12 +255,12 @@ class MemberAdmin(CommonAdminMixin, admin.ModelAdmin):
class MemberUnconfirmedAdmin(admin.ModelAdmin): class MemberUnconfirmedAdmin(admin.ModelAdmin):
fields = ['prename', 'lastname', 'email', 'email_parents', 'cc_email_parents', 'street', 'plz', fields = ['prename', 'lastname', 'email', 'alternative_email', 'street', 'plz',
'town', 'phone_number_mobile', 'phone_number_private','phone_number_parents', 'birth_date', 'group', 'town', 'phone_number', 'birth_date', 'gender', 'group',
'registered', 'registration_form', 'active', 'comments'] 'registration_form', 'comments']
list_display = ('name', 'birth_date', 'age', 'get_group', 'confirmed_mail', 'confirmed_mail_parents') list_display = ('name', 'birth_date', 'age', 'get_group', 'confirmed_mail', 'confirmed_alternative_mail')
search_fields = ('prename', 'lastname', 'email') search_fields = ('prename', 'lastname', 'email')
list_filter = ('group', 'confirmed_mail', 'confirmed_mail_parents') list_filter = ('group', 'confirmed_mail', 'confirmed_alternative_mail')
actions = ['request_mail_confirmation', 'confirm', 'demote_to_waiter'] actions = ['request_mail_confirmation', 'confirm', 'demote_to_waiter']
change_form_template = "members/change_member_unconfirmed.html" change_form_template = "members/change_member_unconfirmed.html"
@ -306,14 +310,10 @@ class MemberUnconfirmedAdmin(admin.ModelAdmin):
waiter = MemberWaitingList(prename=member.prename, waiter = MemberWaitingList(prename=member.prename,
lastname=member.lastname, lastname=member.lastname,
email=member.email, email=member.email,
email_parents=member.email_parents,
cc_email_parents=member.cc_email_parents,
birth_date=member.birth_date, birth_date=member.birth_date,
comments=member.comments, comments=member.comments,
confirmed_mail=member.confirmed_mail, confirmed_mail=member.confirmed_mail,
confirmed_mail_parents=member.confirmed_mail_parents, confirm_mail_key=member.confirm_mail_key)
confirm_mail_key=member.confirm_mail_key,
confirm_mail_parents_key=member.confirm_mail_parents_key)
waiter.save() waiter.save()
member.delete() member.delete()
messages.success(request, _("Successfully demoted %(name)s to waiter.") % {'name': waiter.name}) messages.success(request, _("Successfully demoted %(name)s to waiter.") % {'name': waiter.name})
@ -336,13 +336,14 @@ class WaiterInviteForm(forms.Form):
class MemberWaitingListAdmin(CommonAdminMixin, admin.ModelAdmin): class MemberWaitingListAdmin(CommonAdminMixin, admin.ModelAdmin):
fields = ['prename', 'lastname', 'email', 'email_parents', 'birth_date', 'application_text', 'application_date', 'comments', 'invited_for_group'] fields = ['prename', 'lastname', 'email', 'birth_date', 'gender', 'application_text',
list_display = ('name', 'birth_date', 'age', 'application_date', 'confirmed_mail', 'confirmed_mail_parents', 'application_date', 'comments', 'invited_for_group']
list_display = ('name', 'birth_date', 'age', 'application_date', 'confirmed_mail',
'waiting_confirmed') 'waiting_confirmed')
search_fields = ('prename', 'lastname', 'email') search_fields = ('prename', 'lastname', 'email')
list_filter = ('confirmed_mail', 'confirmed_mail_parents') list_filter = ('confirmed_mail',)
actions = ['ask_for_registration', 'ask_for_wait_confirmation'] actions = ['ask_for_registration', 'ask_for_wait_confirmation']
readonly_fields= ('invited_for_group',) readonly_fields= ('invited_for_group', 'application_date')
def has_add_permission(self, request, obj=None): def has_add_permission(self, request, obj=None):
return False return False

@ -0,0 +1,156 @@
# Generated by Django 4.0.1 on 2024-10-13 19:02
from django.db import migrations, models
import django.db.models.deletion
import django.utils.timezone
import rules.contrib.models
class Migration(migrations.Migration):
dependencies = [
('members', '0013_memberwaitinglist_add_application_text_and_date'),
]
operations = [
migrations.RemoveField(
model_name='member',
name='civil_status',
),
migrations.RemoveField(
model_name='member',
name='nationality',
),
migrations.RemoveField(
model_name='member',
name='registered',
),
migrations.RemoveField(
model_name='member',
name='rock_experience',
),
migrations.RemoveField(
model_name='member',
name='technical_comments',
),
migrations.AddField(
model_name='member',
name='alpine_experience',
field=models.TextField(blank=True, default='', verbose_name='Alpine experience'),
),
migrations.RemoveField(
model_name='member',
name='gender',
),
migrations.AddField(
model_name='member',
name='gender',
field=models.IntegerField(choices=[(0, 'Männlich'), (1, 'Weiblich'), (2, 'Divers')], default=2, verbose_name='Gender'),
),
migrations.RemoveField(
model_name='member',
name='swimming_badge',
),
migrations.AddField(
model_name='member',
name='swimming_badge',
field=models.BooleanField(default=False, verbose_name='Knows how to swim'),
),
migrations.RenameField(
model_name='member',
old_name='confirm_mail_parents_key',
new_name='confirm_alternative_mail_key',
),
migrations.RemoveField(
model_name='member',
name='cc_email_parents',
),
migrations.RemoveField(
model_name='member',
name='confirmed_mail_parents',
),
migrations.RemoveField(
model_name='member',
name='email_parents',
),
migrations.RemoveField(
model_name='member',
name='phone_number_mobile',
),
migrations.RemoveField(
model_name='member',
name='phone_number_parents',
),
migrations.RemoveField(
model_name='member',
name='phone_number_private',
),
migrations.RemoveField(
model_name='memberwaitinglist',
name='cc_email_parents',
),
migrations.RemoveField(
model_name='memberwaitinglist',
name='confirm_mail_parents_key',
),
migrations.RemoveField(
model_name='memberwaitinglist',
name='confirmed_mail_parents',
),
migrations.RemoveField(
model_name='memberwaitinglist',
name='email_parents',
),
migrations.AddField(
model_name='member',
name='alternative_email',
field=models.EmailField(blank=True, default=None, max_length=100),
),
migrations.AddField(
model_name='member',
name='confirmed_alternative_mail',
field=models.BooleanField(default=True, verbose_name='Alternative email confirmed'),
),
migrations.AddField(
model_name='member',
name='phone_number',
field=models.CharField(default='', max_length=100, verbose_name='phone number'),
preserve_default=False,
),
migrations.CreateModel(
name='EmergencyContact',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('member', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='members.member', verbose_name='Member')),
('confirm_mail_key', models.CharField(default='', max_length=32)),
('confirmed_mail', models.BooleanField(default=True, verbose_name='Email confirmed')),
('email', models.EmailField(default='', max_length=100)),
('lastname', models.CharField(default='', max_length=20, verbose_name='last name')),
('phone_number', models.CharField(default='', max_length=100, verbose_name='phone number')),
('prename', models.CharField(default='', max_length=20, verbose_name='prename')),
],
options={
'verbose_name': 'Emergency contact',
'verbose_name_plural': 'Emergency contacts',
'abstract': False,
'default_permissions': ('add_global', 'change_global', 'view_global', 'delete_global', 'list_global', 'view'),
},
bases=(models.Model, rules.contrib.models.RulesModelMixin),
),
migrations.AlterField(
model_name='memberwaitinglist',
name='application_text',
field=models.TextField(blank=True, default='', verbose_name='Do you want to tell us something else?'),
),
migrations.AddField(
model_name='memberwaitinglist',
name='gender',
field=models.IntegerField(choices=[(0, 'Männlich'), (1, 'Weiblich'), (2, 'Divers')], default=2, verbose_name='Gender'),
),
migrations.AlterField(
model_name='memberwaitinglist',
name='application_date',
field=models.DateTimeField(auto_now=True, default=django.utils.timezone.now, verbose_name='application date'),
preserve_default=False,
),
]

@ -0,0 +1,28 @@
# Generated by Django 4.0.1 on 2024-10-13 19:04
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('members', '0014_remove_fields_alternative_email'),
]
operations = [
migrations.AlterField(
model_name='emergencycontact',
name='lastname',
field=models.CharField(max_length=20, verbose_name='last name'),
),
migrations.AlterField(
model_name='emergencycontact',
name='phone_number',
field=models.CharField(max_length=100, verbose_name='phone number'),
),
migrations.AlterField(
model_name='emergencycontact',
name='prename',
field=models.CharField(max_length=20, verbose_name='prename'),
),
]

@ -30,9 +30,9 @@ def generate_random_key():
return uuid.uuid4().hex return uuid.uuid4().hex
GEMEINSCHAFTS_TOUR = MUSKELKRAFT_ANREISE = 0 GEMEINSCHAFTS_TOUR = MUSKELKRAFT_ANREISE = MALE = 0
FUEHRUNGS_TOUR = OEFFENTLICHE_ANREISE = 1 FUEHRUNGS_TOUR = OEFFENTLICHE_ANREISE = FEMALE = 1
AUSBILDUNGS_TOUR = FAHRGEMEINSCHAFT_ANREISE = 2 AUSBILDUNGS_TOUR = FAHRGEMEINSCHAFT_ANREISE = DIVERSE = 2
class ActivityCategory(models.Model): class ActivityCategory(models.Model):
@ -77,25 +77,16 @@ class MemberManager(models.Manager):
return super().get_queryset().filter(confirmed=True) return super().get_queryset().filter(confirmed=True)
class Person(CommonModel): class Contact(CommonModel):
""" """
Represents an abstract person. Not necessarily a member of any group. Represents an abstract person with only absolutely necessary contact information.
""" """
prename = models.CharField(max_length=20, verbose_name=_('prename')) prename = models.CharField(max_length=20, verbose_name=_('prename'))
lastname = models.CharField(max_length=20, verbose_name=_('last name')) lastname = models.CharField(max_length=20, verbose_name=_('last name'))
email = models.EmailField(max_length=100, default="")
email_parents = models.EmailField(max_length=100, default="", blank=True,
verbose_name=_("Parents' Email"))
cc_email_parents = models.BooleanField(default=True, verbose_name=_('Also send mails to parents'))
birth_date = models.DateField(_('birth date'), null=True, blank=True) # to determine the age
comments = models.TextField(_('comments'), default='', blank=True)
email = models.EmailField(max_length=100, default="")
confirmed_mail = models.BooleanField(default=True, verbose_name=_('Email confirmed')) confirmed_mail = models.BooleanField(default=True, verbose_name=_('Email confirmed'))
confirmed_mail_parents = models.BooleanField(default=True, verbose_name=_('Parents email confirmed'))
confirm_mail_key = models.CharField(max_length=32, default="") confirm_mail_key = models.CharField(max_length=32, default="")
confirm_mail_parents_key = models.CharField(max_length=32, default="")
class Meta(CommonModel.Meta): class Meta(CommonModel.Meta):
abstract = True abstract = True
@ -109,6 +100,85 @@ class Person(CommonModel):
"""Returning whole name (prename + lastname)""" """Returning whole name (prename + lastname)"""
return "{0} {1}".format(self.prename, self.lastname) return "{0} {1}".format(self.prename, self.lastname)
@property
def email_fields(self):
"""Returns all tuples of emails and confirmation data related to this contact.
By default, this is only the principal email field, but extending classes can add
more email fields and then override this method."""
return [('email', 'confirmed_mail', 'confirm_mail_key')]
def request_mail_confirmation(self, rerequest=True):
"""Request mail confirmation for every mail field. If `rerequest` is false, then only
confirmation is requested for currently unconfirmed emails.
Returns true if any mail confirmation was requested, false otherwise."""
requested_confirmation = False
for email_fd, confirmed_email_fd, confirm_mail_key_fd in self.email_fields:
if getattr(self, confirmed_email_fd) and not rerequest:
continue
requested_confirmation = True
setattr(self, confirmed_email_fd, False)
confirm_mail_key = uuid.uuid4().hex
setattr(self, confirm_mail_key_fd, confirm_mail_key)
send_mail(_('Email confirmation needed'),
settings.CONFIRM_MAIL_TEXT.format(name=self.prename,
link=get_mail_confirmation_link(confirm_mail_key),
whattoconfirm='deiner Emailadresse'),
settings.DEFAULT_SENDING_MAIL,
getattr(self, email_fd))
self.save()
return requested_confirmation
def confirm_mail(self, key):
for email_fd, confirmed_email_fd, confirm_mail_key_fd in self.email_fields:
if getattr(self, confirm_mail_key_fd) == key:
setattr(self, confirmed_email_fd, True)
setattr(self, confirm_mail_key_fd, "")
self.save()
return getattr(self, email_fd)
return None
def send_mail(self, subject, content):
send_mail(subject, content, settings.DEFAULT_SENDING_MAIL,
[getattr(self, email_fd) for email_fd, _, _ in self.email_fields])
def confirm_mail_by_key(key):
matching_unconfirmed = MemberUnconfirmedProxy.objects.filter(confirm_mail_key=key) \
| MemberUnconfirmedProxy.objects.filter(confirm_alternative_mail_key=key)
matching_waiter = MemberWaitingList.objects.filter(confirm_mail_key=key)
if len(matching_unconfirmed) + len(matching_waiter) != 1:
return None
person = matching_unconfirmed[0] if len(matching_unconfirmed) == 1 else matching_waiter[0]
return person, person.confirm_mail(key)
class ContactWithPhoneNumber(Contact):
"""
A contact with a phone number.
"""
phone_number = models.CharField(max_length=100, verbose_name=_('phone number'))
class Meta(CommonModel.Meta):
abstract = True
class Person(Contact):
"""
Represents an abstract person. Not necessarily a member of any group.
"""
birth_date = models.DateField(_('birth date'), null=True, blank=True) # to determine the age
gender_choices = ((MALE, 'Männlich'),
(FEMALE, 'Weiblich'),
(DIVERSE, 'Divers'))
gender = models.IntegerField(choices=gender_choices,
default=DIVERSE,
verbose_name=_('Gender'))
comments = models.TextField(_('comments'), default='', blank=True)
class Meta(CommonModel.Meta):
abstract = True
@property @property
def age(self): def age(self):
"""Age of member""" """Age of member"""
@ -120,53 +190,18 @@ class Person(CommonModel):
return "---" return "---"
return self.birth_date.strftime("%d.%m.%Y") return self.birth_date.strftime("%d.%m.%Y")
def request_mail_confirmation(self):
self.confirmed_mail = False
self.confirm_mail_key = uuid.uuid4().hex
send_mail(_('Email confirmation needed'),
settings.CONFIRM_MAIL_TEXT.format(name=self.prename,
link=get_mail_confirmation_link(self.confirm_mail_key),
whattoconfirm='deiner Emailadresse'),
settings.DEFAULT_SENDING_MAIL,
self.email)
if self.email_parents:
self.confirmed_mail_parents = False
self.confirm_mail_parents_key = uuid.uuid4().hex
send_mail(_('Email confirmation needed'),
settings.CONFIRM_MAIL_TEXT.format(name=self.prename,
link=get_mail_confirmation_link(self.confirm_mail_parents_key),
whattoconfirm='der Emailadresse deiner Eltern'),
settings.DEFAULT_SENDING_MAIL,
self.email_parents)
else:
self.confirmed_mail_parents = True
self.save()
def confirm_mail(self, key):
parents = False
email = None
if self.confirm_mail_key == key:
self.confirm_mail_key, self.confirmed_mail = "", True
email, parents = self.email, False
elif self.confirm_mail_parents_key == key:
self.confirm_mail_parents_key, self.confirmed_mail_parents = "", True
email, parents = self.email_parents, True
self.save()
return (email, parents)
def send_mail(self, subject, content):
send_mail(subject,
content,
settings.DEFAULT_SENDING_MAIL,
[self.email, self.email_parents] if self.email_parents and self.cc_email_parents
else self.email)
class Member(Person): class Member(Person):
""" """
Represents a member of the association Represents a member of the association
Might be a member of different groups: e.g. J1, J2, Jugendleiter, etc. Might be a member of different groups: e.g. J1, J2, Jugendleiter, etc.
""" """
alternative_email = models.EmailField(max_length=100, default=None, blank=True)
confirmed_alternative_mail = models.BooleanField(default=True,
verbose_name=_('Alternative email confirmed'))
confirm_alternative_mail_key = models.CharField(max_length=32, default="")
phone_number = models.CharField(max_length=100, verbose_name=_('phone number'))
street = models.CharField(max_length=30, verbose_name=_('street and house number'), default='', blank=True) street = models.CharField(max_length=30, verbose_name=_('street and house number'), default='', blank=True)
plz = models.CharField(max_length=10, verbose_name=_('Postcode'), plz = models.CharField(max_length=10, verbose_name=_('Postcode'),
default='', blank=True) default='', blank=True)
@ -176,39 +211,30 @@ class Member(Person):
good_conduct_certificate_presentation_needed = models.BooleanField(_('Good conduct certificate presentation needed'), default=False) good_conduct_certificate_presentation_needed = models.BooleanField(_('Good conduct certificate presentation needed'), default=False)
good_conduct_certificate_presented_date = models.DateField(_('Good conduct certificate presented on'), default=None, blank=True, null=True) good_conduct_certificate_presented_date = models.DateField(_('Good conduct certificate presented on'), default=None, blank=True, null=True)
gender = models.CharField(max_length=30, verbose_name=_('Gender'), default='', blank=True)
nationality = models.CharField(max_length=30, verbose_name=_('Nationality'), default='', blank=True)
join_date = models.DateField(_('Joined on'), default=None, blank=True, null=True) join_date = models.DateField(_('Joined on'), default=None, blank=True, null=True)
leave_date = models.DateField(_('Left on'), default=None, blank=True, null=True) leave_date = models.DateField(_('Left on'), default=None, blank=True, null=True)
civil_status = models.CharField(_('Civil status'), max_length=30, default='', blank=True)
has_key = models.BooleanField(_('Has key'), default=False) has_key = models.BooleanField(_('Has key'), default=False)
has_free_ticket_gym = models.BooleanField(_('Has a free ticket for the climbing gym'), default=False) has_free_ticket_gym = models.BooleanField(_('Has a free ticket for the climbing gym'), default=False)
dav_badge_no = models.CharField(max_length=20, verbose_name=_('DAV badge number'), default='', blank=True) dav_badge_no = models.CharField(max_length=20, verbose_name=_('DAV badge number'), default='', blank=True)
swimming_badge = models.CharField(max_length=20, verbose_name=_('Swimming badge'), default='', blank=True) swimming_badge = models.BooleanField(verbose_name=_('Knows how to swim'), default=False)
climbing_badge = models.CharField(max_length=100, verbose_name=_('Climbing badge'), default='', blank=True) climbing_badge = models.CharField(max_length=100, verbose_name=_('Climbing badge'), default='', blank=True)
rock_experience = models.CharField(max_length=50, verbose_name=_('Rock experience'), default='', blank=True) alpine_experience = models.TextField(verbose_name=_('Alpine experience'), default='', blank=True)
allergies = models.CharField(max_length=100, verbose_name=_('Allergies'), default='', blank=True) allergies = models.CharField(max_length=100, verbose_name=_('Allergies'), default='', blank=True)
medication = models.CharField(max_length=100, verbose_name=_('Medication'), default='', blank=True) medication = models.CharField(max_length=100, verbose_name=_('Medication'), default='', blank=True)
tetanus_vaccination = models.CharField(max_length=50, verbose_name=_('Tetanus vaccination'), default='', blank=True) tetanus_vaccination = models.CharField(max_length=50, verbose_name=_('Tetanus vaccination'), default='', blank=True)
photos_may_be_taken = models.BooleanField(verbose_name=_('Photos may be taken'), default=False) photos_may_be_taken = models.BooleanField(verbose_name=_('Photos may be taken'), default=False)
legal_guardians = models.CharField(max_length=100, verbose_name=_('Legal guardians'), default='', blank=True) legal_guardians = models.CharField(max_length=100, verbose_name=_('Legal guardians'), default='', blank=True)
phone_number_private = models.CharField(max_length=100, verbose_name=_('phone number private'), default='', blank=True)
phone_number_mobile = models.CharField(max_length=100, verbose_name=_('phone number mobile'), default='', blank=True)
phone_number_parents = models.CharField(max_length=200, verbose_name=_('parents phone number'), default='', blank=True)
group = models.ManyToManyField(Group, verbose_name=_('group')) group = models.ManyToManyField(Group, verbose_name=_('group'))
iban = models.CharField(max_length=30, blank=True, verbose_name='IBAN') iban = models.CharField(max_length=30, blank=True, verbose_name='IBAN')
technical_comments = models.TextField(verbose_name=_('Technical comments'), default='', blank=True)
gets_newsletter = models.BooleanField(_('receives newsletter'), gets_newsletter = models.BooleanField(_('receives newsletter'),
default=True) default=True)
unsubscribe_key = models.CharField(max_length=32, default="") unsubscribe_key = models.CharField(max_length=32, default="")
unsubscribe_expire = models.DateTimeField(default=timezone.now) unsubscribe_expire = models.DateTimeField(default=timezone.now)
created = models.DateField(auto_now=True, verbose_name=_('created')) created = models.DateField(auto_now=True, verbose_name=_('created'))
registered = models.BooleanField(default=False, verbose_name=_('Registration complete'))
active = models.BooleanField(default=True, verbose_name=_('Active')) active = models.BooleanField(default=True, verbose_name=_('Active'))
#not_waiting = models.BooleanField(default=True, verbose_name=_('Not waiting'))
registration_form = RestrictedFileField(verbose_name=_('registration form'), registration_form = RestrictedFileField(verbose_name=_('registration form'),
upload_to='registration_forms', upload_to='registration_forms',
blank=True, blank=True,
@ -232,6 +258,11 @@ class Member(Person):
objects = MemberManager() objects = MemberManager()
@property
def email_fields(self):
return [('email', 'confirmed_mail', 'confirm_mail_key'),
('alternative_email', 'confirmed_alternative_mail', 'confirm_alternative_mail_key')]
@property @property
def place(self): def place(self):
"""Returning the whole place (plz + town)""" """Returning the whole place (plz + town)"""
@ -259,7 +290,7 @@ class Member(Person):
return self.echo_key return self.echo_key
def confirm(self): def confirm(self):
if not self.confirmed_mail or not self.confirmed_mail_parents: if not self.confirmed_mail or not self.confirmed_alternative_mail:
return False return False
self.confirmed = True self.confirmed = True
self.save() self.save()
@ -281,23 +312,16 @@ class Member(Person):
@property @property
def contact_phone_number(self): def contact_phone_number(self):
"""Returning, if available phone number of parents, else member's phone number""" """Synonym for phone number field."""
if self.phone_number_parents: if self.phone_number:
return str(self.phone_number_parents) return str(self.phone_number)
elif self.phone_number_mobile:
return str(self.phone_number_mobile)
elif self.phone_number_private:
return str(self.phone_number_private)
else: else:
return "---" return "---"
@property @property
def contact_email(self): def contact_email(self):
"""Returning, if available email of parents, else member's email""" """A synonym for the email field."""
if self.email_parents: return self.email
return self.email_parents
else:
return self.email
@property @property
def association_email(self): def association_email(self):
@ -305,6 +329,13 @@ class Member(Person):
raw = "{0}.{1}@{2}".format(self.prename.lower(), self.lastname.lower(), settings.DOMAIN) raw = "{0}.{1}@{2}".format(self.prename.lower(), self.lastname.lower(), settings.DOMAIN)
return raw.replace('ö', 'oe').replace('ä', 'ae').replace('ü', 'ue') return raw.replace('ö', 'oe').replace('ä', 'ae').replace('ü', 'ue')
def registration_complete(self):
"""Check if all necessary fields are set."""
# TODO: implement a proper predicate here
return True
registration_complete.boolean = True
registration_complete.short_description = _('Registration complete')
def get_group(self): def get_group(self):
"""Returns a string of groups in which the member is.""" """Returns a string of groups in which the member is."""
groupstring = ''.join(g.name + ',\n' for g in self.group.all()) groupstring = ''.join(g.name + ',\n' for g in self.group.all())
@ -342,9 +373,29 @@ class Member(Person):
# get activity overview # get activity overview
return Freizeit.objects.filter(membersonlist__member=self) return Freizeit.objects.filter(membersonlist__member=self)
def create_from_registration(self, waiter, group):
"""Given a member, a corresponding waiting-list object and a group, this completes
the registration and requests email confirmations if necessary.
Returns if any mail confirmation requests have been sent out."""
self.group.add(group)
self.confirmed = False
if waiter:
if self.email == waiter.email:
self.confirmed_mail = waiter.confirmed_mail
self.confirm_mail_key = waiter.confirm_mail_key
if self.alternative_email:
self.confirmed_alternative_mail = False
self.save()
if self.confirmed_alternative_mail and self.confirmed_mail and not self.confirmed:
self.notify_jugendleiters_about_confirmed_mail()
if waiter:
waiter.delete()
return self.request_mail_confirmation(rerequest=False)
def confirm_mail(self, key): def confirm_mail(self, key):
ret = super().confirm_mail(key) ret = super().confirm_mail(key)
if self.confirmed_mail_parents and self.confirmed_mail and not self.confirmed: if self.confirmed_alternative_mail and self.confirmed_mail and not self.confirmed:
self.notify_jugendleiters_about_confirmed_mail() self.notify_jugendleiters_about_confirmed_mail()
return ret return ret
@ -556,6 +607,26 @@ class Member(Person):
return False return False
class EmergencyContact(ContactWithPhoneNumber):
"""
Emergency contact of a member
"""
member = models.ForeignKey(Member, verbose_name=_('Member'), on_delete=models.CASCADE)
def __str__(self):
return str(self.member)
class Meta(CommonModel.Meta):
verbose_name = _('Emergency contact')
verbose_name_plural = _('Emergency contacts')
rules_permissions = {
'add_obj': may_change | has_global_perm('members.change_global_member'),
'view_obj': may_view | has_global_perm('members.view_global_member'),
'change_obj': may_change | has_global_perm('members.change_global_member'),
'delete_obj': may_delete | has_global_perm('members.delete_global_member'),
}
class MemberUnconfirmedManager(models.Manager): class MemberUnconfirmedManager(models.Manager):
def get_queryset(self): def get_queryset(self):
return super().get_queryset().filter(confirmed=False) return super().get_queryset().filter(confirmed=False)
@ -584,8 +655,8 @@ class MemberWaitingList(Person):
WAITING_CONFIRMATION_EXPIRED = 1 WAITING_CONFIRMATION_EXPIRED = 1
WAITING_CONFIRMED = 2 WAITING_CONFIRMED = 2
application_text = models.TextField(_('application text'), default='', blank=True) application_text = models.TextField(_('Do you want to tell us something else?'), default='', blank=True)
application_date = models.DateTimeField(verbose_name=_('application date'), null=True, blank=True) application_date = models.DateTimeField(verbose_name=_('application date'), auto_now=True)
last_wait_confirmation = models.DateField(auto_now=True, verbose_name=_('Last wait confirmation')) last_wait_confirmation = models.DateField(auto_now=True, verbose_name=_('Last wait confirmation'))
wait_confirmation_key = models.CharField(max_length=32, default="") wait_confirmation_key = models.CharField(max_length=32, default="")
@ -670,12 +741,9 @@ class MemberWaitingList(Person):
return self.registration_key == key and timezone.now() < self.registration_expire return self.registration_key == key and timezone.now() < self.registration_expire
def invite_to_group(self): def invite_to_group(self):
send_mail(_("Good news"), self.send_mail(_("Invitation to trial group meeting"),
settings.INVITE_TEXT.format(name=self.prename, settings.INVITE_TEXT.format(name=self.prename,
link=get_registration_link(self)), link=get_registration_link(self)))
settings.DEFAULT_SENDING_MAIL,
[self.email, self.email_parents] if self.email_parents and self.cc_email_parents
else self.email)
class NewMemberOnList(CommonModel): class NewMemberOnList(CommonModel):
@ -1302,12 +1370,10 @@ CLUBDESK_TO_KOMPASS = {
'Land': 'country', 'Land': 'country',
'Nationalität': 'nationality', 'Nationalität': 'nationality',
'E-Mail': 'email', 'E-Mail': 'email',
'E-Mail Alternativ': 'email_parents', 'E-Mail Alternativ': 'alternative_email',
'Status': ('active', parse_status), 'Status': ('active', parse_status),
'Eintritt': ('join_date', parse_date), 'Eintritt': ('join_date', parse_date),
'Austritt': ('leave_date', parse_date), 'Austritt': ('leave_date', parse_date),
'Zivilstand': 'civil_status',
'Geschlecht': 'gender',
'Geburtsdatum': ('birth_date', parse_date), 'Geburtsdatum': ('birth_date', parse_date),
'Geburtstag': ('birth_date', parse_date), 'Geburtstag': ('birth_date', parse_date),
'Bemerkungen': 'comments', 'Bemerkungen': 'comments',
@ -1323,12 +1389,11 @@ CLUBDESK_TO_KOMPASS = {
'DAV Ausweis Nr.': 'dav_badge_no', 'DAV Ausweis Nr.': 'dav_badge_no',
'Schwimmabzeichen': 'swimming_badge', 'Schwimmabzeichen': 'swimming_badge',
'Kletterschein': 'climbing_badge', 'Kletterschein': 'climbing_badge',
'Felserfahrung': 'rock_experience', 'Felserfahrung': 'alpine_experience',
'Allergien': 'allergies', 'Allergien': 'allergies',
'Medikamente': 'medication', 'Medikamente': 'medication',
'Tetanusimpfung': 'tetanus_vaccination', 'Tetanusimpfung': 'tetanus_vaccination',
'Fotoerlaubnis': ('photos_may_be_taken', parse_boolean), 'Fotoerlaubnis': ('photos_may_be_taken', parse_boolean),
'Kommentar': 'technical_comments',
'Erziehungsberechtigte': 'legal_guardians', 'Erziehungsberechtigte': 'legal_guardians',
'Mobil Eltern': 'phone_number_parents', 'Mobil Eltern': 'phone_number_parents',
'Sonstiges': 'application_text', 'Sonstiges': 'application_text',

@ -35,22 +35,4 @@
<p><input type="submit" value="{% trans "submit" %}"/></p> <p><input type="submit" value="{% trans "submit" %}"/></p>
</form> </form>
<script>
var checkBox = document.querySelector('input[id="id_cc_email_parents"]');
var textInput = document.querySelector('input[id="id_email_parents"]');
function toggleRequired() {
if (checkBox.checked ) {
textInput.setAttribute('required','required');
}
else {
textInput.removeAttribute('required');
}
}
checkBox.addEventListener('change',toggleRequired,false);
</script>
{% endblock %} {% endblock %}

@ -39,22 +39,4 @@ Die Anmeldung für die Gruppen <a href="{% url 'startpage:gruppe_detail' group_n
<p><input type="submit" value="{% trans "submit" %}"/></p> <p><input type="submit" value="{% trans "submit" %}"/></p>
</form> </form>
<script>
var checkBox = document.querySelector('input[id="id_cc_email_parents"]');
var textInput = document.querySelector('input[id="id_email_parents"]');
function toggleRequired() {
if (checkBox.checked ) {
textInput.setAttribute('required','required');
}
else {
textInput.removeAttribute('required');
}
}
checkBox.addEventListener('change',toggleRequired,false);
</script>
{% endblock %} {% endblock %}

@ -2,7 +2,8 @@ from startpage.views import render
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from django.http import HttpResponseRedirect from django.http import HttpResponseRedirect
from django.forms import ModelForm, TextInput, DateInput from django.forms import ModelForm, TextInput, DateInput
from members.models import Member, RegistrationPassword, MemberUnconfirmedProxy, MemberWaitingList, Group from members.models import Member, RegistrationPassword, MemberUnconfirmedProxy, MemberWaitingList, Group,\
confirm_mail_by_key
from django.urls import reverse from django.urls import reverse
from django.utils import timezone from django.utils import timezone
from django.conf import settings from django.conf import settings
@ -12,7 +13,7 @@ class MemberForm(ModelForm):
class Meta: class Meta:
model = Member model = Member
fields = ['prename', 'lastname', 'street', 'plz', 'town', 'address_extra', 'country', fields = ['prename', 'lastname', 'street', 'plz', 'town', 'address_extra', 'country',
'phone_number_private', 'phone_number_mobile', 'phone_number_parents', 'birth_date'] 'phone_number', 'birth_date']
widgets = { widgets = {
'birth_date': DateInput(format='%d.%m.%Y', attrs={'class': 'datepicker'}) 'birth_date': DateInput(format='%d.%m.%Y', attrs={'class': 'datepicker'})
} }
@ -24,13 +25,10 @@ class MemberRegistrationForm(ModelForm):
for field in self.Meta.required: for field in self.Meta.required:
self.fields[field].required = True self.fields[field].required = True
self.fields['cc_email_parents'].initial = False
class Meta: class Meta:
model = Member model = Member
fields = ['prename', 'lastname', 'street', 'plz', 'town', 'address_extra', 'country', fields = ['prename', 'lastname', 'street', 'plz', 'town', 'address_extra', 'country',
'phone_number_private', 'phone_number_mobile', 'phone_number_parents', 'phone_number', 'birth_date', 'gender', 'email', 'alternative_email',
'birth_date', 'email', 'email_parents', 'cc_email_parents',
'registration_form'] 'registration_form']
widgets = { widgets = {
'birth_date': DateInput(format='%d.%m.%Y', attrs={'class': 'datepicker'}) 'birth_date': DateInput(format='%d.%m.%Y', attrs={'class': 'datepicker'})
@ -45,11 +43,9 @@ class MemberRegistrationWaitingListForm(ModelForm):
for field in self.Meta.required: for field in self.Meta.required:
self.fields[field].required = True self.fields[field].required = True
self.fields['cc_email_parents'].initial = False
class Meta: class Meta:
model = MemberWaitingList model = MemberWaitingList
fields = ['prename', 'lastname', 'birth_date', 'email', 'email_parents', 'cc_email_parents'] fields = ['prename', 'lastname', 'birth_date', 'gender', 'email', 'application_text']
widgets = { widgets = {
'birth_date': DateInput(format='%d.%m.%Y', attrs={'class': 'datepicker'}) 'birth_date': DateInput(format='%d.%m.%Y', attrs={'class': 'datepicker'})
} }
@ -178,20 +174,7 @@ def register(request):
form = MemberRegistrationForm(request.POST, request.FILES) form = MemberRegistrationForm(request.POST, request.FILES)
try: try:
new_member = form.save() new_member = form.save()
new_member.group.add(group) needs_mail_confirmation = new_member.create_from_registration(waiter, group)
new_member.confirmed = False
needs_mail_confirmation = True
if waiter:
if new_member.email == waiter.email and new_member.email_parents == waiter.email_parents:
new_member.confirmed_mail = True
new_member.confirmed_mail_parents = True
needs_mail_confirmation = False
new_member.notify_jugendleiters_about_confirmed_mail()
waiter.delete()
new_member.save()
if needs_mail_confirmation:
new_member.request_mail_confirmation()
return render_register_success(request, group.name, new_member.prename, needs_mail_confirmation) return render_register_success(request, group.name, new_member.prename, needs_mail_confirmation)
except ValueError: except ValueError:
# when input is invalid # when input is invalid
@ -202,16 +185,11 @@ def register(request):
def confirm_mail(request): def confirm_mail(request):
if request.method == 'GET' and 'key' in request.GET: if request.method == 'GET' and 'key' in request.GET:
key = request.GET['key'] res = confirm_mail_by_key(request.GET['key'])
matching_unconfirmed = MemberUnconfirmedProxy.objects.filter(confirm_mail_key=key) \ if res:
| MemberUnconfirmedProxy.objects.filter(confirm_mail_parents_key=key) return render_mail_confirmation_success(request, res[1], res[0].prename, False)
matching_waiter = MemberWaitingList.objects.filter(confirm_mail_key=key) \ else:
| MemberWaitingList.objects.filter(confirm_mail_parents_key=key)
if len(matching_unconfirmed) + len(matching_waiter) != 1:
return render_mail_confirmation_invalid(request) return render_mail_confirmation_invalid(request)
person = matching_unconfirmed[0] if len(matching_unconfirmed) == 1 else matching_waiter[0]
email, parents = person.confirm_mail(key)
return render_mail_confirmation_success(request, email, person.prename, parents)
return HttpResponseRedirect(reverse('startpage:index')) return HttpResponseRedirect(reverse('startpage:index'))

Loading…
Cancel
Save