Verified Commit e639971f authored by Sébastiaan Versteeg's avatar Sébastiaan Versteeg
Browse files

Add benefactors registration form

parent fd02959b
django.jQuery(function () {
var $ = django.jQuery;
(django.jQuery || jQuery)(function () {
var $ = django.jQuery || jQuery;
$(".payments-row a.process").click(function(e) {
e.preventDefault();
var type = $(e.target).data('type');
......
......@@ -5,29 +5,35 @@ Registrations
This document explains how the registrations module behaviour is defined.
The behaviour of upgrading an existing 'year' membership to a 'study' membership (until graduation) is taken from the HR. If the HR ever changes this behaviour should be changed to reflect those changes.
*Note that registrations and renewals for benefactors are implemented in the models, there are simply no views providing this functionality. If we ever want to implement this then it would be best to create a complete new form just for benefactors registrations.*
This module both provides registration for members and for benefactors. The only difference is the form and view used for their registration since the information we ask from them is different.
New member registration
=======================
New member/benefactor registration
==================================
Frontend
--------
- User enters information
- If the membership type is 'benefactor':
The amount used in the payment is provided during the registration process by the user.
- User accepts privacy policy
This step is obligatory. We do not accept people that do not accept the privacy policy. It's currently implemented as a checkbox in the forms.
- System validates info
- Correct address
- Valid and unique email address
- Checked against existing users
- Privacy policy accepted
- If the selected member type is 'member':
- If the selected membership type is 'member':
- valid and unique student number
- Checked against existing users
- selected programme
- cohort
- Registration model created (status: Awaiting email confirmation)
- Email address confirmation sent
- User confirms email address
- Registration model status changed (status: Ready for review)
- If the registration is for a benefactor an email is sent with a link to get references
- Existing members of Thalia add references using the link
Backend
-------
......@@ -37,9 +43,12 @@ Backend
- If it's not unique a username can be entered manually
- If it's still not unique the registration cannot be accepted
- If it's unique the generated username will be added to the registration
- Payment model is created (processed: False)
- Amount is calculated based on the selected length ('study' or 'year')
- Values are located in thaliawebsite.settings
- Payment model is created (unprocessed at first)
- If the membership type is 'member':
- Amount is calculated based on the selected length ('study' or 'year')
- Values are located in thaliawebsite.settings
- If the membership type is 'benefactor':
- Amount is determined by the value entered during registration
- Email is sent as acceptance confirmation containg instructions for `Payment processing`_
2. Admin rejects registration
- Email is sent as rejection message
......@@ -56,6 +65,8 @@ Frontend
- If latest membership has ended or ends within 1 month: also allow 'year' length
- If latest membership is 'study' and did not end: do not allow renewal
- Renewal model created (status: Ready for review)
- If the renewal is for a benefactor an email is sent with a link to get references
- Existing members of Thalia add references using the link
Backend
-------
......
......@@ -7,7 +7,12 @@ from django.utils.translation import ugettext_lazy as _
from payments.widgets import PaymentWidget
from . import services
from .models import Entry, Registration, Renewal
from .models import Entry, Registration, Renewal, Reference
class ReferenceInline(admin.StackedInline):
model = Reference
extra = 0
def _show_message(admin, request, n, message, error):
......@@ -29,6 +34,7 @@ class RegistrationAdmin(admin.ModelAdmin):
'created_at', 'payment_status')
list_filter = ('status', 'programme', 'payment__type',
'payment__amount')
inlines = (ReferenceInline,)
search_fields = ('first_name', 'last_name', 'email', 'phone_number',
'student_number',)
date_hierarchy = 'created_at'
......@@ -38,6 +44,7 @@ class RegistrationAdmin(admin.ModelAdmin):
'updated_at',
'username',
'length',
'contribution',
'membership_type',
'status',
'payment',
......@@ -109,8 +116,11 @@ class RegistrationAdmin(admin.ModelAdmin):
obj.status == Entry.STATUS_COMPLETED):
return ['status', 'created_at', 'updated_at']
else:
return [field.name for field in self.model._meta.get_fields()
if field.editable and not field.name == 'payment']
return [
field.name for field in self.model._meta.get_fields()
if not field.name in['payment', 'no_references']
and field.editable
]
@staticmethod
def name(obj):
......@@ -179,6 +189,7 @@ class RenewalAdmin(RegistrationAdmin):
'created_at',
'updated_at',
'length',
'contribution',
'membership_type',
'status',
'payment',
......
"""The emails defined by the registrations package"""
from typing import Union
from django.conf import settings
from django.core import mail
from django.template import loader
......@@ -7,10 +9,11 @@ from django.urls import reverse
from django.utils import translation
from django.utils.translation import ugettext_lazy as _
from . import models
from payments.models import Payment
from registrations.models import Registration, Renewal
def send_registration_email_confirmation(registration):
def send_registration_email_confirmation(registration: Registration) -> None:
"""
Send the email confirmation message
......@@ -23,8 +26,8 @@ def send_registration_email_confirmation(registration):
'registrations/email/registration_confirm_mail.txt',
{
'name': registration.get_full_name(),
'confirm_link': '{}{}'.format(
settings.BASE_URL,
'confirm_link': (
settings.BASE_URL +
reverse('registrations:confirm-email',
args=[registration.pk])
)
......@@ -32,7 +35,8 @@ def send_registration_email_confirmation(registration):
)
def send_registration_accepted_message(registration, payment):
def send_registration_accepted_message(registration: Registration,
payment: Payment) -> None:
"""
Send the registration acceptance email
......@@ -51,7 +55,7 @@ def send_registration_accepted_message(registration, payment):
)
def send_registration_rejected_message(registration):
def send_registration_rejected_message(registration: Registration) -> None:
"""
Send the registration rejection email
......@@ -68,29 +72,28 @@ def send_registration_rejected_message(registration):
)
def send_new_registration_board_message(entry):
def send_new_registration_board_message(registration: Registration) -> None:
"""
Send a notification to the board about a new registration
:param entry: the registration entry
:param registration: the registration entry
"""
try:
_send_email(
settings.BOARD_NOTIFICATION_ADDRESS,
'New registration',
'registrations/email/registration_board.txt',
{
'name': entry.registration.get_full_name(),
'url': settings.BASE_URL
+ reverse('admin:registrations_registration_change',
args=[entry.registration.pk])
}
)
except models.Registration.DoesNotExist:
pass
_send_email(
settings.BOARD_NOTIFICATION_ADDRESS,
'New registration',
'registrations/email/registration_board.txt',
{
'name': registration.get_full_name(),
'url': (
settings.BASE_URL +
reverse('admin:registrations_registration_change',
args=[registration.pk])
)
}
)
def send_renewal_accepted_message(renewal, payment):
def send_renewal_accepted_message(renewal: Renewal, payment: Payment) -> None:
"""
Send the renewal acceptation email
......@@ -109,7 +112,7 @@ def send_renewal_accepted_message(renewal, payment):
)
def send_renewal_rejected_message(renewal):
def send_renewal_rejected_message(renewal: Renewal) -> None:
"""
Send the renewal rejection email
......@@ -126,7 +129,7 @@ def send_renewal_rejected_message(renewal):
)
def send_renewal_complete_message(renewal):
def send_renewal_complete_message(renewal: Renewal) -> None:
"""
Send the email completing the renewal
......@@ -143,7 +146,7 @@ def send_renewal_complete_message(renewal):
)
def send_new_renewal_board_message(renewal):
def send_new_renewal_board_message(renewal: Renewal) -> None:
"""
Send a notification to the board about a new renewal
......@@ -155,14 +158,53 @@ def send_new_renewal_board_message(renewal):
'registrations/email/renewal_board.txt',
{
'name': renewal.member.get_full_name(),
'url': settings.BASE_URL
+ reverse('admin:registrations_renewal_change',
args=[renewal.pk])
'url': (
settings.BASE_URL +
reverse('admin:registrations_renewal_change',
args=[renewal.pk])
)
}
)
def _send_email(to, subject, body_template, context):
def send_references_information_message(
entry: Union[Registration, Renewal]) -> None:
"""
Send a notification to the user with information about references
These are required for benefactors who have not been a Thalia member
and do not work for iCIS
:param entry: the registration or renewal entry
"""
if type(entry).__name__ == 'Registration':
email = entry.email
name = entry.get_full_name()
language = entry.language
else:
email = entry.member.email
name = entry.member.get_full_name()
language = entry.member.profile.language
print(language)
with translation.override(language):
_send_email(
email,
_('Information about references'),
'registrations/email/references_information.txt',
{
'name': name,
'reference_link': (
settings.BASE_URL +
reverse('registrations:reference', args=[entry.pk])
)
}
)
def _send_email(to: str, subject: str,
body_template: str, context: dict) -> None:
"""
Easily send an email with the right subject and a body template
......
"""The forms defined by the registrations package"""
from django import forms
from django.forms import TypedChoiceField
from django.urls import reverse_lazy
from django.utils import timezone
from django.utils.safestring import mark_safe
from django.utils.translation import ugettext_lazy as _
from utils.snippets import datetime_to_lectureyear
from .models import Registration, Renewal
class MemberRegistrationForm(forms.ModelForm):
"""Form for membership registrations"""
class BaseRegistrationForm(forms.ModelForm):
"""Base form for membership registrations"""
birthday = forms.DateField(
widget=forms.widgets.SelectDateWidget(years=[
......@@ -20,9 +22,18 @@ class MemberRegistrationForm(forms.ModelForm):
privacy_policy = forms.BooleanField(
required=True,
label=_('I accept the privacy policy')
)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['privacy_policy'].label = mark_safe(_(
'I accept the <a href="{}">privacy policy</a>.').format(
reverse_lazy('privacy-policy')))
class MemberRegistrationForm(BaseRegistrationForm):
"""Form for member registrations"""
this_year = datetime_to_lectureyear(timezone.now())
years = reversed([(x, "{} - {}".format(x, x + 1)) for x in
range(this_year - 20, this_year + 2)])
......@@ -30,25 +41,52 @@ class MemberRegistrationForm(forms.ModelForm):
starting_year = TypedChoiceField(
choices=years,
coerce=int,
empty_value=this_year
empty_value=this_year,
required=False
)
class Meta:
model = Registration
fields = '__all__'
exclude = ['created_at', 'updated_at', 'status', 'username', 'remarks',
exclude = ['created_at', 'updated_at', 'status', 'username',
'payment', 'membership']
class MemberRenewalForm(forms.ModelForm):
class BenefactorRegistrationForm(BaseRegistrationForm):
"""Form for benefactor registrations"""
icis_employee = forms.BooleanField(
required=False,
label=_('I am an employee of iCIS')
)
class Meta:
model = Registration
fields = '__all__'
exclude = ['created_at', 'updated_at', 'status', 'username',
'starting_year', 'programme', 'payment', 'membership']
class RenewalForm(forms.ModelForm):
"""Form for membership renewals"""
privacy_policy = forms.BooleanField(
required=True,
label=_('I accept the privacy policy')
)
icis_employee = forms.BooleanField(
required=False,
label=_('I am an employee of iCIS')
)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['privacy_policy'].label = mark_safe(_(
'I accept the <a href="{}">privacy policy</a>.').format(
reverse_lazy('privacy-policy')))
class Meta:
model = Renewal
fields = '__all__'
exclude = ['created_at', 'updated_at', 'status', 'remarks']
exclude = ['created_at', 'updated_at', 'status',
'payment', 'membership']
......@@ -7,8 +7,8 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-03-02 16:52+0100\n"
"PO-Revision-Date: 2019-03-02 16:33+0100\n"
"POT-Creation-Date: 2019-04-25 13:34+0200\n"
"PO-Revision-Date: 2019-04-25 13:36+0200\n"
"Last-Translator: Thom Wiggers <thom@thomwiggers.nl>\n"
"Language-Team: \n"
"Language: nl\n"
......@@ -96,13 +96,21 @@ msgstr "Verlenging afgekeurd"
msgid "Renewal successful"
msgstr "Verlenging succesvol"
#: emails.py
msgid "Information about references"
msgstr "Informatie over referenties"
#: forms.py models.py
msgid "birthday"
msgstr "verjaardag"
#: forms.py
msgid "I accept the privacy policy"
msgstr "Ik accepteer het privacybeleid"
msgid "I accept the <a href=\"{}\">privacy policy</a>."
msgstr "Ik accepteer het <a href=\"{}\">privacybeleid</a>."
#: forms.py
msgid "I am an employee of iCIS"
msgstr "Ik ben een medewerker van iCIS"
#: models.py
msgid "created at"
......@@ -148,6 +156,14 @@ msgstr "Tot afstuderen"
msgid "membership length"
msgstr "lengte lidmaatschap"
#: models.py
msgid "contribution"
msgstr "contributie"
#: models.py
msgid "no references required"
msgstr "geen referenties benodigd"
#: models.py templates/registrations/renewal.html
msgid "membership type"
msgstr "soort lidmaatschap"
......@@ -157,8 +173,8 @@ msgid "remarks"
msgstr "opmerkingen"
#: models.py
msgid "Registration entry"
msgstr "Registratie"
msgid "This field is required for benefactors."
msgstr "Dit veld is verplicht voor begunstigers."
#: models.py
msgid "entry"
......@@ -281,6 +297,7 @@ msgid "A user with that username already exists."
msgstr "Er bestaat al een gebruiker met deze gebruikersnaam."
#: models.py templates/registrations/confirm_email.html
#: templates/registrations/register_benefactor.html
#: templates/registrations/register_member.html
#: templates/registrations/register_success.html
msgid "registration"
......@@ -357,8 +374,10 @@ msgstr ""
"lid te worden!"
#: templates/registrations/become_a_member.html
msgid "How do I become a member?"
msgstr "Hoe kan ik lid worden?"
msgid ""
"I'm a Computing Science or Information Sciences student at the Radboud "
"University"
msgstr "Ik studeer Informatica of Informatiekunde aan de Radboud Universiteit"
#: templates/registrations/become_a_member.html
#, python-format
......@@ -376,37 +395,37 @@ msgstr ""
"informatiekunde studeert aan de Radboud Universiteit."
#: templates/registrations/become_a_member.html
msgid "Register now"
msgstr "Nu registreren"
msgid "Become a member"
msgstr "Lid worden"
#: templates/registrations/become_a_member.html
msgid ""
"I'm not a Computing Science and Information Sciences student at the Radboud "
"University, but I do want to attend your events. Now what?"
"University"
msgstr ""
"Ik studeer geen Informatica of Informatiekunde (aan de Radboud "
"Universiteit), maar ik wil wel naar jullie activiteiten gaan. Wat nu?"
"Ik studeer geen Informatica of Informatiekunde aan de Radboud Universiteit"
#: templates/registrations/become_a_member.html
#: templates/registrations/register_benefactor.html
#, python-format
msgid ""
"It is still possible to be associated with Thalia, even if you do not study "
"Computing Science or Information Sciences (anymore): You can become a "
"benefactor. For at least € %(year_fees)s per year, you too can enjoy "
"everything Thalia has to offer. If you are not a former Thalia member, ICIS "
"staff member or alumni, you must submit a written along with two signatures "
"of current Thalia members. You can fill all of this in on the benefactor "
"form, which you can get at the board room (M1.0.08, ground floor of Mercator "
"1)."
"everything Thalia has to offer. If you are not a former Thalia member, iCIS "
"staff member or alumni, you must collect two references of current Thalia "
"members."
msgstr ""
"Mocht je nu geen Informatica of Informatiekunde (meer) studeren, maar toch "
"verbonden willen zijn, dan kan dat ook. Hiervoor kun je begunstiger van "
"Thalia worden. Voor minimaal € %(year_fees)s per jaar kun je ook genieten "
"van alles wat Thalia je te bieden heeft. Ben je geen oud-Thaliaan, ex-"
"Informaticus of -Informatiekundige, dan dien je twee handtekeningen van "
"leden van Thalia samen met een motivatie waarom je begunstiger wil worden af "
"te geven. Dit kun je allemaal invullen op het begunstigerformulier, dat af "
"te halen is in de bestuurskamer (M1.0.08, begane grond Mercator 1)."
"van alles wat Thalia je te bieden heeft. Ben je geen oud-Thaliaan, iCIS "
"medewerker of alumni, dan dien je twee referenties van leden van Thalia te "
"verzamelen."
#: templates/registrations/become_a_member.html
msgid "Become a benefactor"
msgstr "Begunstiger worden"
#: templates/registrations/become_a_member.html
msgid ""
......@@ -433,6 +452,52 @@ msgstr ""
"beoordelen. Mocht je vragen hebben, stuur dan een mailtje naar info@thalia."
"nu."
#: templates/registrations/email/references_information.txt
#, python-format
msgid ""
"Dear %(name)s,\n"
"\n"
"Our information indicates that you're not an iCIS employee or\n"
"a former Thalia member.\n"
"\n"
"This means that before we can review your membership registration we\n"
"need to receive two references of current Thalia members.\n"
"\n"
"Share the following link with them to obtain their reference:\n"
"%(reference_link)s\n"
"\n"
"If you have any questions, then don't hesitate and send an email to "
"info@thalia.nu.\n"
"\n"
"With kind regards,\n"
"\n"
"The board of Study Association Thalia\n"
"\n"
"————\n"
"\n"
"This email was automatically generated."
msgstr ""
"Beste %(name)s,\n"
"\n"
"Volgens onze informatie ben je geen medewerker van iCIS\n"
"of een voormalig lid van Thalia.\n"
"\n"
"Dit betekent dat we twee referenties van Thalia leden moeten ontvangen\n"
"Voordat we je aanmelding kunnen behandelen.\n"
"\n"
"Deel de volgende link met een Thalia lid om een referentie te verkrijgen:\n"
"%(reference_link)s\n"
"\n"
"Mocht je vragen hebben, stuur dan vooral een mailtje naar info@thalia.nu.\n"
"\n"
"Met vriendelijke groet,\n"
"\n"
"Het bestuur der Studievereniging Thalia\n"
"\n"
"————\n"
"\n"
"Deze e-mail is automatisch gegenereerd."
#: templates/registrations/email/registration_accepted.txt
#, python-format
msgid ""
......@@ -693,29 +758,49 @@ msgstr ""
"\n"
"Deze e-mail is automatisch gegenereerd."
#: templates/registrations/register_member.html
#: templates/registrations/reference.html
#: templates/registrations/reference_success.html
msgid "reference"
msgstr "referentie"
#: templates/registrations/reference.html
#: templates/registrations/reference_success.html
msgid "give reference"
msgstr "geef referentie"
#: templates/registrations/reference.html
#: templates/registrations/reference_success.html
#, python-format
msgid ""
"A membership costs € %(year_fees)s per year, or € %(study_fees)s for your "
"entire study duration. <br/> Note: Only Computing Science and Information "
"Sciences students at the Radboud University can become a member.<br/><br/> "
"It is still possible to be associated with Thalia, even if you do not study "
"Computing Science or Information Sciences (anymore): You can become a "
"benefactor. For at least € %(year_fees)s per year, you too can enjoy "
"everything Thalia has to offer.<br/> <em>Note that this form is only for "
"member registration. Please visit the board room if you want to become a "
"benefactor.</em>"
"benefactor. If you are not a former Thalia member, iCIS staff member or "
"alumni, you must collect two references of current Thalia members. <br /"
"><br /> <strong>%(name)s</strong> wants to become a benefactor of Thalia and "
"needs such a reference and has asked you to give it to them."
msgstr ""
"Een lidmaatschap kost € %(year_fees)s per jaar, of € %(study_fees)s voor je "
"hele studieperiode. <br/> Let wel op: Je kunt alleen lid worden wanneer je "
"informatica of informatiekunde studeert aan de Radboud Universiteit.<br/><br/"
">Mocht je nu geen Informatica of Informatiekunde (meer) studeren, maar toch "
"Mocht je nu geen Informatica of Informatiekunde (meer) studeren, maar toch "
"verbonden willen zijn, dan kan dat ook. Hiervoor kun je begunstiger van "
"Thalia worden. Voor minimaal € %(year_fees)s per jaar kun je ook genieten "
"van alles wat Thalia je te bieden heeft.<br/> <em>Let op dat dit formulier "
"alleen gebruikt kan worden voor registratie van leden. Neem contact op met "