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]
emails_rem = [member.email for member in recipients_with_reminder]
emails_rem.extend([member.email_parents for member in recipients_with_reminder
if member.email_parents and member.cc_email_parents])
emails_rem.extend([member.alternative_email for member in recipients_with_reminder
if member.alternative_email])
emails_no_rem = [member.email for member in recipients_without_reminder]
emails_no_rem.extend([member.email_parents for member in recipients_without_reminder
if member.email_parents and member.cc_email_parents])
emails_no_rem.extend([member.alternative_email for member in recipients_without_reminder
if member.alternative_email])
# remove any underscores from subject to prevent Arne from using
# terrible looking underscores in subjects
self.subject = self.subject.replace('_', ' ')

@ -35,7 +35,7 @@ import nested_admin
from .models import (Member, Group, Freizeit, MemberNoteList, NewMemberOnList, Klettertreff,
MemberWaitingList, LJPProposal, Intervention, PermissionMember,
PermissionGroup, MemberTraining, TrainingCategory,
KlettertreffAttendee, ActivityCategory,
KlettertreffAttendee, ActivityCategory, EmergencyContact,
annotate_activity_score, RegistrationPassword, MemberUnconfirmedProxy)
from finance.models import Statement, BillOnExcursionProxy
from mailer.mailutils import send as send_mail, get_echo_link
@ -102,6 +102,15 @@ class TrainingOnMemberInline(CommonAdminInlineMixin, admin.TabularInline):
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):
list_display = ('name', 'permission_needed')
ordering = ('name', )
@ -109,7 +118,7 @@ class TrainingCategoryAdmin(admin.ModelAdmin):
class RegistrationFilter(admin.SimpleListFilter):
title = _('Registration complete')
parameter_name = 'registered'
parameter_name = 'registration_complete'
default_value = ('All', None)
def lookups(self, request, model_admin):
@ -121,14 +130,14 @@ class RegistrationFilter(admin.SimpleListFilter):
def queryset(self, request, queryset):
if self.value() == 'True':
return queryset.filter(registered=True)
return queryset.filter(registration_complete=True)
elif self.value() == 'False':
return queryset.filter(registered=False)
return queryset.filter(registration_complete=False)
elif self.value() is None:
if self.default_value[1] is None:
return queryset
else:
return queryset.filter(registered=self.default_value[1])
return queryset.filter(registration_complete=self.default_value[1])
elif self.value() == 'All':
return queryset
@ -149,27 +158,25 @@ class RegistrationFilter(admin.SimpleListFilter):
# Register your models here.
class MemberAdmin(CommonAdminMixin, admin.ModelAdmin):
fields = ['prename', 'lastname', 'email',
('email_parents', 'cc_email_parents'),
'street', 'plz', 'town', 'address_extra', 'country', 'nationality',
'phone_number_private', 'phone_number_mobile',
'phone_number_parents', 'birth_date', 'gender', 'civil_status',
'alternative_email',
'street', 'plz', 'town', 'address_extra', 'country',
'phone_number', 'birth_date', 'gender',
'dav_badge_no',
'group',
'swimming_badge', 'climbing_badge', 'rock_experience', 'allergies',
'swimming_badge', 'climbing_badge', 'alpine_experience', 'allergies',
'medication', 'tetanus_vaccination', 'photos_may_be_taken', 'legal_guardians',
('good_conduct_certificate_presented_date', 'good_conduct_certificate_presentation_needed'),
'iban', 'has_key', 'has_free_ticket_gym', 'gets_newsletter', 'registered', 'registration_form',
'image',
'iban', 'has_key', 'has_free_ticket_gym', 'gets_newsletter',
'registration_form', 'image',
'active', 'echoed',
('join_date', 'leave_date'),
'comments', 'technical_comments',
'user']
'comments', 'user']
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')
list_filter = ('group', 'gets_newsletter', RegistrationFilter, 'active')
list_display_links = None
inlines = [TrainingOnMemberInline, PermissionOnMemberInline]
inlines = [EmergencyContactInline, TrainingOnMemberInline, PermissionOnMemberInline]
#formfield_overrides = {
# ManyToManyField: {'widget': forms.CheckboxSelectMultiple},
# ForeignKey: {'widget': apply_select2(forms.Select)}
@ -213,11 +220,8 @@ class MemberAdmin(CommonAdminMixin, admin.ModelAdmin):
for member in queryset:
if not member.gets_newsletter:
continue
send_mail(_("Echo required"),
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)
member.send_mail(_("Echo required"),
settings.ECHO_TEXT.format(name=member.prename, link=get_echo_link(member)))
messages.success(request, _("Successfully requested 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):
fields = ['prename', 'lastname', 'email', 'email_parents', 'cc_email_parents', 'street', 'plz',
'town', 'phone_number_mobile', 'phone_number_private','phone_number_parents', 'birth_date', 'group',
'registered', 'registration_form', 'active', 'comments']
list_display = ('name', 'birth_date', 'age', 'get_group', 'confirmed_mail', 'confirmed_mail_parents')
fields = ['prename', 'lastname', 'email', 'alternative_email', 'street', 'plz',
'town', 'phone_number', 'birth_date', 'gender', 'group',
'registration_form', 'comments']
list_display = ('name', 'birth_date', 'age', 'get_group', 'confirmed_mail', 'confirmed_alternative_mail')
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']
change_form_template = "members/change_member_unconfirmed.html"
@ -306,14 +310,10 @@ class MemberUnconfirmedAdmin(admin.ModelAdmin):
waiter = MemberWaitingList(prename=member.prename,
lastname=member.lastname,
email=member.email,
email_parents=member.email_parents,
cc_email_parents=member.cc_email_parents,
birth_date=member.birth_date,
comments=member.comments,
confirmed_mail=member.confirmed_mail,
confirmed_mail_parents=member.confirmed_mail_parents,
confirm_mail_key=member.confirm_mail_key,
confirm_mail_parents_key=member.confirm_mail_parents_key)
confirm_mail_key=member.confirm_mail_key)
waiter.save()
member.delete()
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):
fields = ['prename', 'lastname', 'email', 'email_parents', 'birth_date', 'application_text', 'application_date', 'comments', 'invited_for_group']
list_display = ('name', 'birth_date', 'age', 'application_date', 'confirmed_mail', 'confirmed_mail_parents',
fields = ['prename', 'lastname', 'email', 'birth_date', 'gender', 'application_text',
'application_date', 'comments', 'invited_for_group']
list_display = ('name', 'birth_date', 'age', 'application_date', 'confirmed_mail',
'waiting_confirmed')
search_fields = ('prename', 'lastname', 'email')
list_filter = ('confirmed_mail', 'confirmed_mail_parents')
list_filter = ('confirmed_mail',)
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):
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
GEMEINSCHAFTS_TOUR = MUSKELKRAFT_ANREISE = 0
FUEHRUNGS_TOUR = OEFFENTLICHE_ANREISE = 1
AUSBILDUNGS_TOUR = FAHRGEMEINSCHAFT_ANREISE = 2
GEMEINSCHAFTS_TOUR = MUSKELKRAFT_ANREISE = MALE = 0
FUEHRUNGS_TOUR = OEFFENTLICHE_ANREISE = FEMALE = 1
AUSBILDUNGS_TOUR = FAHRGEMEINSCHAFT_ANREISE = DIVERSE = 2
class ActivityCategory(models.Model):
@ -77,25 +77,16 @@ class MemberManager(models.Manager):
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'))
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_parents = models.BooleanField(default=True, verbose_name=_('Parents email confirmed'))
confirm_mail_key = models.CharField(max_length=32, default="")
confirm_mail_parents_key = models.CharField(max_length=32, default="")
class Meta(CommonModel.Meta):
abstract = True
@ -109,6 +100,85 @@ class Person(CommonModel):
"""Returning whole name (prename + 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
def age(self):
"""Age of member"""
@ -120,53 +190,18 @@ class Person(CommonModel):
return "---"
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):
"""
Represents a member of the association
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)
plz = models.CharField(max_length=10, verbose_name=_('Postcode'),
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_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)
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_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)
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)
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)
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)
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)
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'))
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'),
default=True)
unsubscribe_key = models.CharField(max_length=32, default="")
unsubscribe_expire = models.DateTimeField(default=timezone.now)
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'))
#not_waiting = models.BooleanField(default=True, verbose_name=_('Not waiting'))
registration_form = RestrictedFileField(verbose_name=_('registration form'),
upload_to='registration_forms',
blank=True,
@ -232,6 +258,11 @@ class Member(Person):
objects = MemberManager()
@property
def email_fields(self):
return [('email', 'confirmed_mail', 'confirm_mail_key'),
('alternative_email', 'confirmed_alternative_mail', 'confirm_alternative_mail_key')]
@property
def place(self):
"""Returning the whole place (plz + town)"""
@ -259,7 +290,7 @@ class Member(Person):
return self.echo_key
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
self.confirmed = True
self.save()
@ -281,23 +312,16 @@ class Member(Person):
@property
def contact_phone_number(self):
"""Returning, if available phone number of parents, else member's phone number"""
if self.phone_number_parents:
return str(self.phone_number_parents)
elif self.phone_number_mobile:
return str(self.phone_number_mobile)
elif self.phone_number_private:
return str(self.phone_number_private)
"""Synonym for phone number field."""
if self.phone_number:
return str(self.phone_number)
else:
return "---"
@property
def contact_email(self):
"""Returning, if available email of parents, else member's email"""
if self.email_parents:
return self.email_parents
else:
return self.email
"""A synonym for the email field."""
return self.email
@property
def association_email(self):
@ -305,6 +329,13 @@ class Member(Person):
raw = "{0}.{1}@{2}".format(self.prename.lower(), self.lastname.lower(), settings.DOMAIN)
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):
"""Returns a string of groups in which the member is."""
groupstring = ''.join(g.name + ',\n' for g in self.group.all())
@ -342,9 +373,29 @@ class Member(Person):
# get activity overview
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):
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()
return ret
@ -556,6 +607,26 @@ class Member(Person):
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):
def get_queryset(self):
return super().get_queryset().filter(confirmed=False)
@ -584,8 +655,8 @@ class MemberWaitingList(Person):
WAITING_CONFIRMATION_EXPIRED = 1
WAITING_CONFIRMED = 2
application_text = models.TextField(_('application text'), default='', blank=True)
application_date = models.DateTimeField(verbose_name=_('application date'), null=True, 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'), auto_now=True)
last_wait_confirmation = models.DateField(auto_now=True, verbose_name=_('Last wait confirmation'))
wait_confirmation_key = models.CharField(max_length=32, default="")
@ -599,7 +670,7 @@ class MemberWaitingList(Person):
blank=True,
default=None,
verbose_name=_('Invited for group'),
on_delete=models.SET_NULL)
on_delete=models.SET_NULL)
class Meta(CommonModel.Meta):
verbose_name = _('Waiter')
verbose_name_plural = _('Waiters')
@ -670,12 +741,9 @@ class MemberWaitingList(Person):
return self.registration_key == key and timezone.now() < self.registration_expire
def invite_to_group(self):
send_mail(_("Good news"),
settings.INVITE_TEXT.format(name=self.prename,
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)
self.send_mail(_("Invitation to trial group meeting"),
settings.INVITE_TEXT.format(name=self.prename,
link=get_registration_link(self)))
class NewMemberOnList(CommonModel):
@ -1302,12 +1370,10 @@ CLUBDESK_TO_KOMPASS = {
'Land': 'country',
'Nationalität': 'nationality',
'E-Mail': 'email',
'E-Mail Alternativ': 'email_parents',
'E-Mail Alternativ': 'alternative_email',
'Status': ('active', parse_status),
'Eintritt': ('join_date', parse_date),
'Austritt': ('leave_date', parse_date),
'Zivilstand': 'civil_status',
'Geschlecht': 'gender',
'Geburtsdatum': ('birth_date', parse_date),
'Geburtstag': ('birth_date', parse_date),
'Bemerkungen': 'comments',
@ -1323,12 +1389,11 @@ CLUBDESK_TO_KOMPASS = {
'DAV Ausweis Nr.': 'dav_badge_no',
'Schwimmabzeichen': 'swimming_badge',
'Kletterschein': 'climbing_badge',
'Felserfahrung': 'rock_experience',
'Felserfahrung': 'alpine_experience',
'Allergien': 'allergies',
'Medikamente': 'medication',
'Tetanusimpfung': 'tetanus_vaccination',
'Fotoerlaubnis': ('photos_may_be_taken', parse_boolean),
'Kommentar': 'technical_comments',
'Erziehungsberechtigte': 'legal_guardians',
'Mobil Eltern': 'phone_number_parents',
'Sonstiges': 'application_text',

@ -35,22 +35,4 @@
<p><input type="submit" value="{% trans "submit" %}"/></p>
</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 %}

@ -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>
</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 %}

@ -2,7 +2,8 @@ from startpage.views import render
from django.utils.translation import gettext_lazy as _
from django.http import HttpResponseRedirect
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.utils import timezone
from django.conf import settings
@ -12,7 +13,7 @@ class MemberForm(ModelForm):
class Meta:
model = Member
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 = {
'birth_date': DateInput(format='%d.%m.%Y', attrs={'class': 'datepicker'})
}
@ -24,13 +25,10 @@ class MemberRegistrationForm(ModelForm):
for field in self.Meta.required:
self.fields[field].required = True
self.fields['cc_email_parents'].initial = False
class Meta:
model = Member
fields = ['prename', 'lastname', 'street', 'plz', 'town', 'address_extra', 'country',
'phone_number_private', 'phone_number_mobile', 'phone_number_parents',
'birth_date', 'email', 'email_parents', 'cc_email_parents',
'phone_number', 'birth_date', 'gender', 'email', 'alternative_email',
'registration_form']
widgets = {
'birth_date': DateInput(format='%d.%m.%Y', attrs={'class': 'datepicker'})
@ -45,11 +43,9 @@ class MemberRegistrationWaitingListForm(ModelForm):
for field in self.Meta.required:
self.fields[field].required = True
self.fields['cc_email_parents'].initial = False
class Meta:
model = MemberWaitingList
fields = ['prename', 'lastname', 'birth_date', 'email', 'email_parents', 'cc_email_parents']
fields = ['prename', 'lastname', 'birth_date', 'gender', 'email', 'application_text']
widgets = {
'birth_date': DateInput(format='%d.%m.%Y', attrs={'class': 'datepicker'})
}
@ -162,7 +158,7 @@ def register(request):
group = pwd.group
except RegistrationPassword.DoesNotExist:
return render_register_wrong_password(request)
elif waiter_key:
elif waiter_key:
try:
waiter = MemberWaitingList.objects.get(registration_key=waiter_key)
group = waiter.invited_for_group
@ -178,20 +174,7 @@ def register(request):
form = MemberRegistrationForm(request.POST, request.FILES)
try:
new_member = form.save()
new_member.group.add(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()
needs_mail_confirmation = new_member.create_from_registration(waiter, group)
return render_register_success(request, group.name, new_member.prename, needs_mail_confirmation)
except ValueError:
# when input is invalid
@ -202,16 +185,11 @@ def register(request):
def confirm_mail(request):
if request.method == 'GET' and 'key' in request.GET:
key = request.GET['key']
matching_unconfirmed = MemberUnconfirmedProxy.objects.filter(confirm_mail_key=key) \
| MemberUnconfirmedProxy.objects.filter(confirm_mail_parents_key=key)
matching_waiter = MemberWaitingList.objects.filter(confirm_mail_key=key) \
| MemberWaitingList.objects.filter(confirm_mail_parents_key=key)
if len(matching_unconfirmed) + len(matching_waiter) != 1:
res = confirm_mail_by_key(request.GET['key'])
if res:
return render_mail_confirmation_success(request, res[1], res[0].prename, False)
else:
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'))
@ -272,7 +250,7 @@ def invited_registration(request):
return render_invited_registration_failed(request, _("invalid"))
except KeyError:
return render_invited_registration_failed(request, _("expired"))
# if its a POST request
return register(request)

Loading…
Cancel
Save