Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature: Do Good Hours #5210

Closed
wants to merge 27 commits into from
Closed
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
f26b032
Add setting for create initiatives
gannetson Aug 15, 2022
008801e
Add test
gannetson Aug 17, 2022
1238164
dd admin test too
gannetson Aug 17, 2022
e2c7f4b
Add upcoming filter to activity list api endpoint
eodolphi Aug 16, 2022
7eaa793
Fix test
eodolphi Aug 16, 2022
08e7194
Add has_initiatives to current user api
gannetson Aug 17, 2022
78184c9
First hours spent admin + api
gannetson Aug 18, 2022
16c53c1
Add tests
gannetson Aug 18, 2022
fc7b442
Add api tests too
gannetson Aug 18, 2022
4811f1d
Fix tests
gannetson Aug 19, 2022
44167df
Merge branch 'master' into demo/do-good-hours
gannetson Aug 22, 2022
e40d80b
Add mails and tests
gannetson Aug 25, 2022
3810734
Merge branch 'master' into demo/do-good-hours
gannetson Aug 26, 2022
2eb835b
Schedule quarterly emails
gannetson Aug 30, 2022
a8d0a26
add test for next years reminder emails
gannetson Aug 30, 2022
b10200d
Merge branch 'integration/BB-20411-quarterly-reminder-emails' into ti…
gannetson Aug 30, 2022
4b6749c
Merge remote-tracking branch 'origin/master' into demo/do-good-hours
gannetson Aug 30, 2022
71d866e
Check that receive_reminder_emails is in user profile
gannetson Aug 30, 2022
8057bd7
Add field + test
gannetson Aug 30, 2022
2e34b8e
Add comma
gannetson Aug 30, 2022
a0330eb
Add comma
gannetson Aug 30, 2022
300957e
Fix emssage + test
gannetson Aug 30, 2022
24ce94b
Merge pull request #5219 from onepercentclub/ticket/user-reminder-mai…
gannetson Aug 30, 2022
c9d389b
Merge branch 'integration/BB-20411-quarterly-reminder-emails' into ti…
gannetson Aug 31, 2022
5878d6b
Merge pull request #5220 from onepercentclub/integration/BB-20411-qua…
gannetson Aug 31, 2022
ad18e57
Merge pull request #5218 from onepercentclub/ticket/BB-20531-task-for…
gannetson Aug 31, 2022
e78791f
Merge remote-tracking branch 'origin/master' into demo/do-good-hours
gannetson Sep 7, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions bluebottle/activities/filters.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ class ActivitySearchFilter(ElasticSearchFilter):
'expertise.id',
'type',
'status',
'upcoming',
'location.id',
'segment',
'team_activity'
Expand Down Expand Up @@ -152,6 +153,12 @@ def get_type_filter(self, value, request):

return Term(type=value)

def get_upcoming_filter(self, value, request):
if value == 'true':
return Terms(status=['open', 'full'])
if value == 'false':
return Terms(status=['succeeded', 'partially_funded'])

def get_duration_filter(self, value, request):
start = request.GET.get('filter[start]')
end = request.GET.get('filter[end]')
Expand Down
60 changes: 55 additions & 5 deletions bluebottle/activities/messages.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
from pytz import timezone
from datetime import timedelta

from django.urls import reverse
from django.db.models import Sum, Q
from django.template.defaultfilters import time, date
from django.urls import reverse
from django.utils.timezone import get_current_timezone, now
from django.utils.translation import pgettext_lazy as pgettext
from pytz import timezone

from bluebottle.initiatives.models import InitiativePlatformSettings
from django.utils.timezone import get_current_timezone

from bluebottle.notifications.messages import TransitionMessage
from django.utils.translation import pgettext_lazy as pgettext
from bluebottle.utils.utils import get_current_host, get_current_language


Expand Down Expand Up @@ -445,3 +446,52 @@ def get_recipients(self):
return [self.obj.team.owner]
else:
return []


class BaseDoGoodHoursReminderNotification(TransitionMessage):

@property
def action_link(self):
return self.obj.get_absolute_url()

action_title = pgettext('email', 'Find activities')

def get_recipients(self):
"""members with do good hours"""
from bluebottle.members.models import Member
from bluebottle.members.models import MemberPlatformSettings

year = now().year
do_good_hours = timedelta(hours=MemberPlatformSettings.load().do_good_hours)

members = Member.objects.annotate(
hours=Sum(
'contributor__contributions__timecontribution__value',
filter=Q(contributor__contributions__start__year=year)
),
).filter(
Q(hours__lt=do_good_hours) | Q(hours__isnull=True),
is_active=True,
receive_reminder_emails=True
).distinct()
return members


class DoGoodHoursReminderQ1Notification(BaseDoGoodHoursReminderNotification):
subject = pgettext('email', "Are you ready to do good? Q1")
template = 'messages/do-good-hours/reminder-q1'


class DoGoodHoursReminderQ2Notification(BaseDoGoodHoursReminderNotification):
subject = pgettext('email', "Are you ready to do good? Q2")
template = 'messages/do-good-hours/reminder-q2'


class DoGoodHoursReminderQ3Notification(BaseDoGoodHoursReminderNotification):
subject = pgettext('email', "Are you ready to do good? Q3")
template = 'messages/do-good-hours/reminder-q3'


class DoGoodHoursReminderQ4Notification(BaseDoGoodHoursReminderNotification):
subject = pgettext('email', "Are you ready to do good? Q4")
template = 'messages/do-good-hours/reminder-q4'
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{% extends "base.mail.html" %}
{% load i18n %}

{% block content %}
<p>
{% blocktrans context 'email' %}Hi {{ recipient_name }},{% endblocktrans %}
</p>
{% block message %}{% endblock %}
{% endblock %}

{% block action %}
<a href="{{ action_link }}" class="action-email">{{ action_title }}</a>
{% endblock %}`
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{% extends "mails/messages/do-good-hours/reminder-base.html" %}
{% load i18n %}

{% block message %}
{% blocktrans context 'email' %}
First reminder
{% endblocktrans %}
{% endblock %}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{% extends "mails/messages/do-good-hours/reminder-base.html" %}
{% load i18n %}

{% block message %}
{% blocktrans context 'email' %}
Second reminder
{% endblocktrans %}
{% endblock %}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{% extends "mails/messages/do-good-hours/reminder-base.html" %}
{% load i18n %}

{% block message %}
{% blocktrans context 'email' %}
Third reminder
{% endblocktrans %}
{% endblock %}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{% extends "mails/messages/do-good-hours/reminder-base.html" %}
{% load i18n %}

{% block message %}
{% blocktrans context 'email' %}
Fourth reminder
{% endblocktrans %}
{% endblock %}
47 changes: 47 additions & 0 deletions bluebottle/activities/tests/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -647,6 +647,53 @@ def test_filter_segment(self):
self.assertEqual(data['meta']['pagination']['count'], 1)
self.assertEqual(data['data'][0]['id'], str(first.pk))

def test_filter_upcoming(self):
first = DateActivityFactory.create(
status='open',
)

second = DateActivityFactory.create()
second.status = 'full'
second.save()

succeeded = DateActivityFactory.create()
succeeded.status = 'succeeded'
succeeded.save()

response = self.client.get(
self.url + '?filter[upcoming]=true',
user=self.owner
)

data = json.loads(response.content)

self.assertEqual(data['meta']['pagination']['count'], 2)
self.assertEqual(data['data'][0]['id'], str(first.pk))
self.assertEqual(data['data'][1]['id'], str(second.pk))

def test_filter_upcoming_false(self):
DateActivityFactory.create(
status='open',
)

second = DateActivityFactory.create()
second.status = 'full'
second.save()

succeeded = DateActivityFactory.create()
succeeded.status = 'succeeded'
succeeded.save()

response = self.client.get(
self.url + '?filter[upcoming]=false',
user=self.owner
)

data = json.loads(response.content)

self.assertEqual(data['meta']['pagination']['count'], 1)
self.assertEqual(data['data'][0]['id'], str(succeeded.pk))

def test_filter_segment_mismatch(self):
first = DateActivityFactory.create(
status='open',
Expand Down
116 changes: 114 additions & 2 deletions bluebottle/activities/tests/test_notifications.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,25 @@
from datetime import timedelta

from django.utils.timezone import now

from bluebottle.activities.messages import (
ActivityRejectedNotification, ActivityCancelledNotification,
ActivitySucceededNotification, ActivityRestoredNotification,
ActivityExpiredNotification, TeamAddedMessage,
TeamAppliedMessage, TeamCancelledMessage,
TeamCancelledTeamCaptainMessage, TeamWithdrawnActivityOwnerMessage,
TeamWithdrawnMessage, TeamMemberAddedMessage, TeamMemberWithdrewMessage,
TeamMemberRemovedMessage, TeamReappliedMessage, TeamCaptainAcceptedMessage
TeamMemberRemovedMessage, TeamReappliedMessage, TeamCaptainAcceptedMessage, DoGoodHoursReminderQ1Notification,
DoGoodHoursReminderQ3Notification, DoGoodHoursReminderQ2Notification, DoGoodHoursReminderQ4Notification
)
from bluebottle.activities.tests.factories import TeamFactory
from bluebottle.members.models import MemberPlatformSettings, Member
from bluebottle.notifications.models import NotificationPlatformSettings
from bluebottle.test.factory_models.accounts import BlueBottleUserFactory
from bluebottle.test.utils import NotificationTestCase
from bluebottle.time_based.tests.factories import (
DateActivityFactory, PeriodActivityFactory, PeriodParticipantFactory
DateActivityFactory, PeriodActivityFactory, PeriodParticipantFactory, DateActivitySlotFactory,
DateParticipantFactory, SlotParticipantFactory
)


Expand Down Expand Up @@ -260,3 +268,107 @@ def test_team_member_removed_notification(self):

self.assertActionLink(self.obj.activity.get_absolute_url())
self.assertActionTitle('View activity')


class DoGoodHoursReminderNotificationTestCase(NotificationTestCase):

def setUp(self):
self.obj = NotificationPlatformSettings.load()
self.obj = MemberPlatformSettings.load()
self.obj.do_good_hours = 8
self.obj.save()
activity = DateActivityFactory.create(
slots=[],
slot_selection='free',
)

slot1 = DateActivitySlotFactory.create(
start=now() - timedelta(days=2),
duration=timedelta(hours=4),
activity=activity
)
slot2 = DateActivitySlotFactory.create(
start=now() - timedelta(days=1),
duration=timedelta(hours=4),
activity=activity
)
old_slot = DateActivitySlotFactory.create(
start=now().replace(year=2011),
duration=timedelta(hours=8),
activity=activity
)

self.active_user = BlueBottleUserFactory.create(first_name='Active')
part1 = DateParticipantFactory.create(
user=self.active_user,
activity=activity
)
SlotParticipantFactory.create(
participant=part1,
slot=slot1
)
SlotParticipantFactory.create(
participant=part1,
slot=slot2
)
self.moderate_user = BlueBottleUserFactory.create(first_name='Moderate')
part2 = DateParticipantFactory.create(
user=self.moderate_user,
activity=activity
)
SlotParticipantFactory.create(
participant=part2,
slot=slot1
)
SlotParticipantFactory.create(
participant=part2,
slot=old_slot
)
self.passive_user = BlueBottleUserFactory.create(first_name='Passive')
part3 = DateParticipantFactory.create(
user=self.passive_user,
activity=activity
)

SlotParticipantFactory.create(
participant=part3,
slot=old_slot
)

Member.objects.exclude(id__in=[
self.active_user.id,
self.passive_user.id,
self.moderate_user.id,
]).update(receive_reminder_emails=False)

def test_reminder_q1(self):
self.message_class = DoGoodHoursReminderQ1Notification
self.create()
self.assertRecipients([self.moderate_user, self.passive_user])
self.assertSubject("Are you ready to do good? Q1")
self.assertBodyContains('First reminder')
self.assertActionTitle('Find activities')

def test_reminder_q2(self):
self.message_class = DoGoodHoursReminderQ2Notification
self.create()
self.assertRecipients([self.moderate_user, self.passive_user])
self.assertSubject("Are you ready to do good? Q2")
self.assertBodyContains('Second reminder')
self.assertActionTitle('Find activities')

def test_reminder_q3(self):
self.message_class = DoGoodHoursReminderQ3Notification
self.create()
self.assertRecipients([self.moderate_user, self.passive_user])
self.assertSubject("Are you ready to do good? Q3")
self.assertBodyContains('Third reminder')
self.assertActionTitle('Find activities')

def test_reminder_q4(self):
self.message_class = DoGoodHoursReminderQ4Notification
self.create()
self.assertRecipients([self.moderate_user, self.passive_user])
self.assertSubject("Are you ready to do good? Q4")
self.assertBodyContains('Fourth reminder')
self.assertActionTitle('Find activities')
18 changes: 17 additions & 1 deletion bluebottle/members/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,19 @@ class MemberPlatformSettingsAdmin(BasePlatformSettingsAdmin, NonSortableParentAd
)
}
),
(
_('Engagement'),
{
'fields': (
'create_initiatives',
'do_good_hours',
'reminder_q1',
'reminder_q2'
'reminder_q3',
'reminder_q4'
),
}
),
)

def get_fieldsets(self, request, obj=None):
Expand Down Expand Up @@ -377,6 +390,8 @@ def get_fieldsets(self, request, obj=None):
{
'fields':
[
'hours_spent',
'hours_planned',
'initiatives',
'date_activities',
'period_activities',
Expand Down Expand Up @@ -431,7 +446,8 @@ def get_readonly_fields(self, request, obj=None):
'updated', 'deleted', 'login_as_link',
'reset_password', 'resend_welcome_link',
'initiatives', 'period_activities', 'date_activities',
'funding', 'deeds', 'collect', 'kyc'
'funding', 'deeds', 'collect', 'kyc',
'hours_spent', 'hours_planned'
]

user_groups = request.user.groups.all()
Expand Down
21 changes: 21 additions & 0 deletions bluebottle/members/migrations/0062_auto_20220815_1751.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Generated by Django 2.2.24 on 2022-08-15 15:51

import bluebottle.bb_accounts.models
from django.db import migrations, models
import django.db.models.deletion


class Migration(migrations.Migration):

dependencies = [
('members', '0061_auto_20220808_1113'),
]

operations = [
migrations.AddField(
model_name='memberplatformsettings',
name='create_initiatives',
field=models.BooleanField(default=True, help_text='Members can create activities'),
),

]
Loading