Skip to content

Commit

Permalink
Add check if people are still studying (#3729)
Browse files Browse the repository at this point in the history
* Add new boolean field for study long members

* Add only honorary members can get until is None

* Add new form for membership extensions

* Fix clean function of membership to only allow Honorary members to get an until equal to None

* Add task for study long experation notification

* Add start for function for the notification of study long members

* Add start email

* Add email for study long check

* Change some services logic

* Add a view and template

* Oops?

* Fix renewal form view

* Add url for study long renewal

* Add form in a way that makes dirk angry

* Add form functionality to new year renewal form

* Add migration

* Add succes page for studylong renewal

* Fix the template of the study long renewal page

* Add some messages

* Remove faulty redirect

* Fix the not_member announcement

* Prevent studylong member to make normal registration

* Fix normal registration template

* Fix some services tests

* Remove not needed email test

* Add study_long check on normal renewal form

* Fix mistakes in services

* Remove print statment

* Add tests for study long renewal view

* Fix the renewal view

* Add privacy link to form

* Fix view test of the renewal form

* Fix some logical mistakes made

* Fix services tests

* Add logic of when someone cannot have a study long renewal entry

* Add new year form tests

* Add extra check for honorary members

* Add test for the NewYearForm

* Add some service tests

* Fix the fixtures

* Remove wrong task

* Fix celery beat schedule

* Add manual migrations for the current until of study_long people.

* Fix migration 0051

* Remove redundant templates

* Add expiration reminder

* Fix migration

* Fix fixture

* Fix membership expiration banner

* Add more information to email

* Add some docs for registration

* Small textual changes and some small fixes

* Fix typo

* Fix some email stuff

* Fix email template texts

* Fix registration

* Revert dirks change since it breaks the announcement banner for study_long members

* Fix Honorary entries being study long

* Fix not needed context being added

* Fix accidentally committed too soon

* Fix benefactorers can have membership until end of study

* fix first sentence of email

---------

Co-authored-by: Dirk Doesburg <[email protected]>
  • Loading branch information
T8902 and DeD1rk authored Nov 15, 2024
1 parent 0bc0bee commit 3637391
Show file tree
Hide file tree
Showing 31 changed files with 575 additions and 203 deletions.
7 changes: 6 additions & 1 deletion website/members/apps.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,12 @@ def announcements(self, request) -> list[dict]:
announcements = []
if request.member and not request.member.has_active_membership():
announcements.append(
{"rich_text": render_to_string("members/announcement_not_member.html")}
{
"rich_text": render_to_string(
"members/announcement_not_member.html",
context={"member": request.member},
)
},
)
if request.member and request.member.profile.event_permissions != "all":
announcements.append(
Expand Down
102 changes: 59 additions & 43 deletions website/members/emails.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,53 +3,14 @@

from django.conf import settings
from django.core import mail
from django.template.defaultfilters import floatformat
from django.urls import reverse
from django.utils import timezone

from members.models import Member, Membership
from members.models import Member
from utils.snippets import send_email

logger = logging.getLogger(__name__)


def send_membership_announcement(dry_run=False):
"""Send an email to all members with a never ending membership excluding honorary members.
:param dry_run: does not really send emails if True
"""
members = (
Member.current_members.filter(membership__since__lt=timezone.now())
.filter(membership__until__isnull=True)
.exclude(membership__type=Membership.HONORARY)
.exclude(email="")
.distinct()
)

with mail.get_connection() as connection:
for member in members:
logger.info("Sent email to %s (%s)", member.get_full_name(), member.email)
if not dry_run:
send_email(
to=[member.email],
subject="Membership announcement",
txt_template="members/email/membership_announcement.txt",
html_template="members/email/membership_announcement.html",
context={"name": member.get_full_name()},
connection=connection,
)

if not dry_run:
send_email(
to=[settings.BOARD_NOTIFICATION_ADDRESS],
subject="Membership announcement sent",
txt_template="members/email/membership_announcement_notification.txt",
html_template="members/email/membership_announcement_notification.html",
context={"members": members},
connection=connection,
)


def send_information_request(dry_run=False):
"""Send an email to all members to have them check their personal information.
Expand Down Expand Up @@ -109,6 +70,7 @@ def send_expiration_announcement(dry_run=False):
members = (
Member.current_members.filter(membership__until__lte=expiry_date)
.exclude(membership__until__isnull=True)
.exclude(membership__study_long=True)
.exclude(email="")
.distinct()
)
Expand All @@ -125,9 +87,6 @@ def send_expiration_announcement(dry_run=False):
connection=connection,
context={
"name": member.get_full_name(),
"membership_price": floatformat(
settings.MEMBERSHIP_PRICES["year"], 2
),
"renewal_url": settings.BASE_URL
+ reverse("registrations:renew"),
},
Expand All @@ -144,6 +103,63 @@ def send_expiration_announcement(dry_run=False):
)


def send_expiration_study_long(dry_run=False):
expiry_date = timezone.now() + timedelta(days=31)
members = (
Member.current_members.filter(membership__until__lte=expiry_date)
.exclude(membership__until__isnull=True)
.exclude(membership__study_long=False)
.exclude(email="")
.distinct()
)
with mail.get_connection() as connection:
for member in members:
logger.info("Sent email to %s (%s)", member.get_full_name(), member.email)
if not dry_run:
send_email(
to=[member.email],
subject="Membership expiration warning",
txt_template="members/email/yearly_study_check.txt",
html_template="members/email/yearly_study_check.html",
connection=connection,
context={
"name": member.get_full_name(),
"renewal_url": settings.BASE_URL
+ reverse("registrations:renew"),
},
)


def send_expiration_study_long_reminder(dry_run=False):
recently_expired_minbound = timezone.now() - timedelta(days=30)
recently_expired_maxbound = timezone.now()
members = (
Member.current_members.filter(
membership__until__gte=recently_expired_minbound,
membership__until__lte=recently_expired_maxbound,
membership__study_long=True,
)
.exclude(email="")
.distinct()
)
with mail.get_connection() as connection:
for member in members:
logger.info("Sent email to %s (%s)", member.get_full_name(), member.email)
if not dry_run:
send_email(
to=[member.email],
subject="Membership expiration warning",
txt_template="members/email/yearly_study_check_reminder.txt",
html_template="members/email/yearly_study_check_reminder.html",
connection=connection,
context={
"name": member.get_full_name(),
"renewal_url": settings.BASE_URL
+ reverse("registrations:renew"),
},
)


def send_welcome_message(user, password):
"""Send an email to a new user welcoming them.
Expand Down
3 changes: 2 additions & 1 deletion website/members/fixtures/members.json
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,8 @@
"type": "member",
"user": 1,
"since": "1980-01-01",
"until": null
"until": "9999-12-31",
"study_long": false
}
}
]
21 changes: 21 additions & 0 deletions website/members/migrations/0050_membership_study_long.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Generated by Django 5.0.7 on 2024-10-30 17:59

from django.db import migrations, models


class Migration(migrations.Migration):
dependencies = [
("members", "0049_alter_membership_since"),
]

operations = [
migrations.AddField(
model_name="membership",
name="study_long",
field=models.BooleanField(
default=False,
help_text="Whether the member has paid to be member throughout their studies.",
verbose_name="Study long",
),
),
]
24 changes: 24 additions & 0 deletions website/members/migrations/0051_membership_study_long_until.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import datetime
from django.utils import timezone
from django.db import migrations, models


def change_until(apps, schema_editor):
Membership = apps.get_model("members", "Membership")

new_until = datetime.datetime(
year=timezone.now().year, month=9, day=1
) + datetime.timedelta(days=365)

Membership.objects.filter(until=None, type="member").update(
study_long=True, until=new_until.date()
)


class Migration(migrations.Migration):
dependencies = [
("members", "0050_membership_study_long"),
]
operations = [
migrations.RunPython(change_until, elidable=True),
]
38 changes: 25 additions & 13 deletions website/members/models/membership.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
from django.utils import timezone
from django.utils.translation import gettext_lazy as _
from django.utils.translation import pgettext_lazy

from utils.snippets import overlaps


Expand Down Expand Up @@ -43,7 +42,13 @@ class Membership(models.Model):
verbose_name=_("Membership until"),
help_text=_("The date the member stops holding this membership."),
blank=True,
null=True,
null=True, # This is only for honorary members
)

study_long = models.BooleanField(
verbose_name=_("Study long"),
help_text="Whether the member has paid to be member throughout their studies.",
default=False,
)

def __str__(self):
Expand All @@ -62,17 +67,24 @@ def clean(self):

errors = {}
if self.until and (not self.since or self.until < self.since):
raise ValidationError({"until": _("End date can't be before start date")})

if self.since is not None:
memberships = self.user.membership_set.all()
if overlaps(self, memberships):
errors.update(
{
"since": _("A membership already exists for that period"),
"until": _("A membership already exists for that period"),
}
)
raise ValidationError({"until": "End date can't be before start date"})

memberships = self.user.membership_set.all()
if self.since is not None and overlaps(self, memberships):
errors.update(
{
"since": "A membership already exists for that period.",
"until": "A membership already exists for that period.",
}
)

if self.type != self.HONORARY and self.until is None:
errors.update({"until": "A non-honorary membership must have an end date."})

if self.type == self.BENEFACTOR and self.study_long:
errors.update(
{"study_long": "Benefactors cannot have a study long membership."}
)

if errors:
raise ValidationError(errors)
Expand Down
3 changes: 1 addition & 2 deletions website/members/services.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
from django.conf import settings
from django.db.models import Count, Exists, OuterRef, Q
from django.utils import timezone

from members import emails
from members.models import Member, Membership
from registrations.models import Renewal
Expand Down Expand Up @@ -201,7 +200,7 @@ def process_email_change(change_request) -> None:


def execute_data_minimisation(dry_run=False, members=None) -> list[Member]:
"""Clean the profiles of members/users of whom the last membership ended at least 31 days ago.
"""Clean the profiles of members/users of whom the last membership ended at least 90 days ago.
:param dry_run: does not really remove data if True
:param members: queryset of members to process, optional
Expand Down
15 changes: 10 additions & 5 deletions website/members/tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,6 @@
from members import emails


@shared_task
def membership_announcement():
emails.send_membership_announcement()


@shared_task
def info_request():
emails.send_information_request()
Expand All @@ -16,3 +11,13 @@ def info_request():
@shared_task
def expiration_announcement():
emails.send_expiration_announcement()


@shared_task
def expiration_warning():
emails.send_expiration_study_long()


@shared_task
def expiration_reminder():
emails.send_expiration_study_long_reminder()
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
<p>
<i class="fas fa-id-card me-2"></i>
You're currently not a member of Thalia. <a href="{% url 'registrations:renew' %}">Renew your membership</a> to get access to all parts of the website.
{% if member.latest_membership.study_long and not member.profile.is_minimized %}
You're currently not a member of Thalia. <a href="{% url 'registrations:renew-studylong' %}">Extend your membership</a> if you are still studying and get access to all parts of the website.
{% else %}
You're currently not a member of Thalia. <a href="{% url 'registrations:renew' %}">Renew your membership</a> to get access to all parts of the website.
{% endif %}
</p>

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

Loading

0 comments on commit 3637391

Please sign in to comment.