From cdab970bfcfdfe0bbe4a7f042aa912d07bc53d71 Mon Sep 17 00:00:00 2001
From: Christian Merten
Date: Sun, 13 Oct 2024 21:06:20 +0200
Subject: [PATCH] members: emergency contacts, replace parent email with
alternative email
---
jdav_web/mailer/models.py | 8 +-
jdav_web/members/admin.py | 71 ++---
.../0014_remove_fields_alternative_email.py | 156 +++++++++++
...lter_emergencycontact_lastname_and_more.py | 28 ++
jdav_web/members/models.py | 255 +++++++++++-------
.../members/templates/members/register.html | 18 --
.../members/register_waiting_list.html | 18 --
jdav_web/members/views.py | 46 +---
8 files changed, 396 insertions(+), 204 deletions(-)
create mode 100644 jdav_web/members/migrations/0014_remove_fields_alternative_email.py
create mode 100644 jdav_web/members/migrations/0015_alter_emergencycontact_lastname_and_more.py
diff --git a/jdav_web/mailer/models.py b/jdav_web/mailer/models.py
index 6965c7c..0f8324a 100644
--- a/jdav_web/mailer/models.py
+++ b/jdav_web/mailer/models.py
@@ -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('_', ' ')
diff --git a/jdav_web/members/admin.py b/jdav_web/members/admin.py
index d3b41d0..fb2f534 100644
--- a/jdav_web/members/admin.py
+++ b/jdav_web/members/admin.py
@@ -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
diff --git a/jdav_web/members/migrations/0014_remove_fields_alternative_email.py b/jdav_web/members/migrations/0014_remove_fields_alternative_email.py
new file mode 100644
index 0000000..ae1be0b
--- /dev/null
+++ b/jdav_web/members/migrations/0014_remove_fields_alternative_email.py
@@ -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,
+ ),
+ ]
diff --git a/jdav_web/members/migrations/0015_alter_emergencycontact_lastname_and_more.py b/jdav_web/members/migrations/0015_alter_emergencycontact_lastname_and_more.py
new file mode 100644
index 0000000..02be80b
--- /dev/null
+++ b/jdav_web/members/migrations/0015_alter_emergencycontact_lastname_and_more.py
@@ -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'),
+ ),
+ ]
diff --git a/jdav_web/members/models.py b/jdav_web/members/models.py
index eed858b..c81af89 100644
--- a/jdav_web/members/models.py
+++ b/jdav_web/members/models.py
@@ -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',
diff --git a/jdav_web/members/templates/members/register.html b/jdav_web/members/templates/members/register.html
index bd5efa9..92b5608 100644
--- a/jdav_web/members/templates/members/register.html
+++ b/jdav_web/members/templates/members/register.html
@@ -35,22 +35,4 @@
-
-
{% endblock %}
diff --git a/jdav_web/members/templates/members/register_waiting_list.html b/jdav_web/members/templates/members/register_waiting_list.html
index a9668e6..cf7f62f 100644
--- a/jdav_web/members/templates/members/register_waiting_list.html
+++ b/jdav_web/members/templates/members/register_waiting_list.html
@@ -39,22 +39,4 @@ Die Anmeldung für die Gruppen
-
-
{% endblock %}
diff --git a/jdav_web/members/views.py b/jdav_web/members/views.py
index 158095a..722415d 100644
--- a/jdav_web/members/views.py
+++ b/jdav_web/members/views.py
@@ -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)