Skip to content

Commit

Permalink
feat: add push notifications
Browse files Browse the repository at this point in the history
  • Loading branch information
aarushtools committed Jul 31, 2024
1 parent f164994 commit 51736f7
Show file tree
Hide file tree
Showing 52 changed files with 2,099 additions and 187 deletions.
7 changes: 7 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -56,3 +56,10 @@ package-lock.json
# Virtual environments
venv/
.venv/

# Webpush
/keys/webpush/
/keys/

# Keys
*.pem
4 changes: 4 additions & 0 deletions Ion.egg-info/PKG-INFO
Original file line number Diff line number Diff line change
Expand Up @@ -74,10 +74,14 @@ Requires-Dist: sphinx-bootstrap-theme==0.8.1
Requires-Dist: tblib==1.7.0
Requires-Dist: vine==5.0.0
Requires-Dist: xhtml2pdf==0.2.11
Requires-Dist: django-push-notifications[WP]==3.1.0
Requires-Dist: py-vapid==1.9.1
Requires-Dist: pywebpush==2.0.0
Requires-Dist: asgiref>=3.3.4
Requires-Dist: pillow>=9.0.0
Requires-Dist: tinycss2
Requires-Dist: twisted>=21.7.0
Requires-Dist: python-bidi==0.4.2

**********
Intranet 3
Expand Down
20 changes: 19 additions & 1 deletion Ion.egg-info/SOURCES.txt
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ config/scripts/README.md
config/scripts/create_activities.py
config/scripts/create_blocks.py
config/scripts/create_users.py
config/scripts/create_vapid_keys.py
config/vagrant/devconfig.json.sample
config/vagrant/ion_env_setup.sh
config/vagrant/provision_vagrant.sh
Expand Down Expand Up @@ -310,6 +311,7 @@ intranet/apps/eighth/forms/admin/sponsors.py
intranet/apps/eighth/management/__init__.py
intranet/apps/eighth/management/commands/__init__.py
intranet/apps/eighth/management/commands/absence_email.py
intranet/apps/eighth/management/commands/absence_notify.py
intranet/apps/eighth/management/commands/delete_duplicate_signups.py
intranet/apps/eighth/management/commands/dev_create_blocks.py
intranet/apps/eighth/management/commands/dev_generate_signups.py
Expand Down Expand Up @@ -387,6 +389,7 @@ intranet/apps/eighth/migrations/0062_auto_20200116_1926.py
intranet/apps/eighth/migrations/0063_auto_20201224_1745.py
intranet/apps/eighth/migrations/0064_auto_20210205_1153.py
intranet/apps/eighth/migrations/0065_auto_20220903_0038.py
intranet/apps/eighth/migrations/0066_auto_20240725_1929.py
intranet/apps/eighth/migrations/__init__.py
intranet/apps/eighth/tests/__init__.py
intranet/apps/eighth/tests/eighth_test.py
Expand Down Expand Up @@ -602,10 +605,15 @@ intranet/apps/nomination/migrations/0001_initial.py
intranet/apps/nomination/migrations/0002_auto_20160929_2156.py
intranet/apps/nomination/migrations/__init__.py
intranet/apps/notifications/__init__.py
intranet/apps/notifications/api.py
intranet/apps/notifications/emails.py
intranet/apps/notifications/forms.py
intranet/apps/notifications/models.py
intranet/apps/notifications/serializers.py
intranet/apps/notifications/tasks.py
intranet/apps/notifications/tests.py
intranet/apps/notifications/urls.py
intranet/apps/notifications/utils.py
intranet/apps/notifications/views.py
intranet/apps/notifications/migrations/0001_initial.py
intranet/apps/notifications/migrations/0002_auto_20150729_1734.py
Expand All @@ -614,6 +622,10 @@ intranet/apps/notifications/migrations/0004_notificationconfig_android_gcm_optou
intranet/apps/notifications/migrations/0005_auto_20151221_2008.py
intranet/apps/notifications/migrations/0006_auto_20151221_2028.py
intranet/apps/notifications/migrations/0007_auto_20151221_2259.py
intranet/apps/notifications/migrations/0008_userpushnotificationpreferences.py
intranet/apps/notifications/migrations/0009_userpushnotificationpreferences_is_subscribed.py
intranet/apps/notifications/migrations/0010_remove_userpushnotificationpreferences_silent_notifications.py
intranet/apps/notifications/migrations/0011_webpushnotification.py
intranet/apps/notifications/migrations/__init__.py
intranet/apps/oauth/__init__.py
intranet/apps/oauth/admin.py
Expand Down Expand Up @@ -647,6 +659,7 @@ intranet/apps/polls/__init__.py
intranet/apps/polls/admin.py
intranet/apps/polls/forms.py
intranet/apps/polls/models.py
intranet/apps/polls/notifications.py
intranet/apps/polls/tests.py
intranet/apps/polls/urls.py
intranet/apps/polls/views.py
Expand Down Expand Up @@ -882,7 +895,6 @@ intranet/settings/secret.sample
intranet/static/browserconfig.xml
intranet/static/manifest.json
intranet/static/robots.txt
intranet/static/serviceworker.js
intranet/static/teacher-guide.pdf
intranet/static/css/_colors.scss
intranet/static/css/_reset.scss
Expand Down Expand Up @@ -992,6 +1004,7 @@ intranet/static/img/favicon/favicon.ico
intranet/static/img/favicon/favicon.svg
intranet/static/img/favicon/mstile-150x150.png
intranet/static/img/favicon/safari-pinned-tab.svg
intranet/static/img/guides/add_to_home_screen_ios.png
intranet/static/img/logos/Header-Logo.png
intranet/static/img/logos/Header-Logo.svg
intranet/static/img/logos/[email protected]
Expand Down Expand Up @@ -3472,6 +3485,11 @@ intranet/templates/lostfound/lostitem_form.html
intranet/templates/monitoring/prometheus-metrics.txt
intranet/templates/notifications/gcm_list.html
intranet/templates/notifications/gcm_post.html
intranet/templates/notifications/ios_notifications_guide.html
intranet/templates/notifications/webpush_device_info.html
intranet/templates/notifications/webpush_list.html
intranet/templates/notifications/webpush_post.html
intranet/templates/notifications/js/serviceworker.js
intranet/templates/oauth2_provider/application_confirm_delete.html
intranet/templates/oauth2_provider/application_detail.html
intranet/templates/oauth2_provider/application_form.html
Expand Down
4 changes: 4 additions & 0 deletions Ion.egg-info/requires.txt
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,11 @@ sphinx-bootstrap-theme==0.8.1
tblib==1.7.0
vine==5.0.0
xhtml2pdf==0.2.11
django-push-notifications[WP]==3.1.0
py-vapid==1.9.1
pywebpush==2.0.0
asgiref>=3.3.4
pillow>=9.0.0
tinycss2
twisted>=21.7.0
python-bidi==0.4.2
3 changes: 3 additions & 0 deletions config/docker/initial_setup.sh
Original file line number Diff line number Diff line change
Expand Up @@ -49,3 +49,6 @@ python3 -u manage.py import_sports $(date +%m)

echo -e "${BLUE}${BOLD}Creating CSL apps...${CLEAR}"
python3 -u manage.py dev_create_cslapps

echo -e "${BLUE}${BOLD}Generating vapid keys...${CLEAR}"
python3 create_vapid_keys.py
24 changes: 24 additions & 0 deletions config/scripts/create_vapid_keys.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import base64
import os

from cryptography.hazmat.primitives.serialization import Encoding, PublicFormat
from py_vapid import Vapid

PROJECT_ROOT = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))

# Generate VAPID key pair
vapid = Vapid()
vapid.generate_keys()

# Get public and private keys for the vapid key pair
vapid.save_public_key(os.path.join(PROJECT_ROOT, "keys", "webpush", "public_key.pem"))
public_key_bytes = vapid.public_key.public_bytes(Encoding.X962, PublicFormat.UncompressedPoint)

vapid.save_key(os.path.join(PROJECT_ROOT, "keys", "webpush", "private_key.pem"))


# Convert the public key to applicationServerKey format
application_server_key = base64.urlsafe_b64encode(public_key_bytes).replace(b"=", b"").decode("utf8")

with open(os.path.join(PROJECT_ROOT, "keys", "webpush", "ApplicationServerKey.key"), "w", encoding="utf-8") as f:
f.write(application_server_key)
3 changes: 2 additions & 1 deletion cron/eighth-absence.sh
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@
timestamp=$(date +"%Y-%m-%d-%H%M")
cd /usr/local/www/intranet3
./cron/env.sh ./manage.py absence_email --silent
echo "Absence email sent at $timestamp." >> /var/log/ion/email.log
./cron/env.sh ./manage.py absence_notify --silent
echo "Absence email and push notification sent at $timestamp." >> /var/log/ion/email.log
8 changes: 8 additions & 0 deletions docs/sourcedoc/intranet.apps.eighth.management.commands.rst
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,14 @@ intranet.apps.eighth.management.commands.absence\_email module
:undoc-members:
:show-inheritance:

intranet.apps.eighth.management.commands.absence\_notify module
---------------------------------------------------------------

.. automodule:: intranet.apps.eighth.management.commands.absence_notify
:members:
:undoc-members:
:show-inheritance:

intranet.apps.eighth.management.commands.delete\_duplicate\_signups module
--------------------------------------------------------------------------

Expand Down
40 changes: 40 additions & 0 deletions docs/sourcedoc/intranet.apps.notifications.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,14 @@ intranet.apps.notifications package
Submodules
----------

intranet.apps.notifications.api module
--------------------------------------

.. automodule:: intranet.apps.notifications.api
:members:
:undoc-members:
:show-inheritance:

intranet.apps.notifications.emails module
-----------------------------------------

Expand All @@ -12,6 +20,14 @@ intranet.apps.notifications.emails module
:undoc-members:
:show-inheritance:

intranet.apps.notifications.forms module
----------------------------------------

.. automodule:: intranet.apps.notifications.forms
:members:
:undoc-members:
:show-inheritance:

intranet.apps.notifications.models module
-----------------------------------------

Expand All @@ -20,6 +36,14 @@ intranet.apps.notifications.models module
:undoc-members:
:show-inheritance:

intranet.apps.notifications.serializers module
----------------------------------------------

.. automodule:: intranet.apps.notifications.serializers
:members:
:undoc-members:
:show-inheritance:

intranet.apps.notifications.tasks module
----------------------------------------

Expand All @@ -28,6 +52,14 @@ intranet.apps.notifications.tasks module
:undoc-members:
:show-inheritance:

intranet.apps.notifications.tests module
----------------------------------------

.. automodule:: intranet.apps.notifications.tests
:members:
:undoc-members:
:show-inheritance:

intranet.apps.notifications.urls module
---------------------------------------

Expand All @@ -36,6 +68,14 @@ intranet.apps.notifications.urls module
:undoc-members:
:show-inheritance:

intranet.apps.notifications.utils module
----------------------------------------

.. automodule:: intranet.apps.notifications.utils
:members:
:undoc-members:
:show-inheritance:

intranet.apps.notifications.views module
----------------------------------------

Expand Down
8 changes: 8 additions & 0 deletions docs/sourcedoc/intranet.apps.polls.rst
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,14 @@ intranet.apps.polls.models module
:undoc-members:
:show-inheritance:

intranet.apps.polls.notifications module
----------------------------------------

.. automodule:: intranet.apps.polls.notifications
:members:
:undoc-members:
:show-inheritance:

intranet.apps.polls.tests module
--------------------------------

Expand Down
21 changes: 18 additions & 3 deletions intranet/apps/announcements/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,12 @@ def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields["expiration_date"].help_text = "By default, announcements expire after two weeks. To change this, click in the box above."

self.fields["notify_post"].help_text = "If this box is checked, students who have signed up for notifications will receive an email."
self.fields["notify_post"].help_text = (
"If this box is checked, students who have signed up for email "
"notifications will receive an email "
"and those who have signed up for push notifications will receive a "
"push notification."
)

self.fields["notify_email_all"].help_text = (
"This will send an email notification to all of the users who can see this post. This option "
Expand Down Expand Up @@ -41,7 +46,12 @@ def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields["expiration_date"].help_text = "By default, announcements expire after two weeks. To change this, click in the box above."

self.fields["notify_post_resend"].help_text = "If this box is checked, students who have signed up for notifications will receive an email."
self.fields["notify_post_resend"].help_text = (
"If this box is checked, students who have signed up for email "
"notifications will receive an email "
"and those who have signed up for push notifications will "
"receive a push notification."
)

self.fields["notify_email_all_resend"].help_text = (
"This will resend an email notification to all of the users who can see this post. This option "
Expand Down Expand Up @@ -105,7 +115,12 @@ class AnnouncementAdminForm(forms.Form):

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields["notify_post"].help_text = "If this box is checked, students who have signed up for notifications will receive an email."
self.fields["notify_post"].help_text = (
"If this box is checked, students who have signed up for email "
"notifications will receive an email "
"and those who have signed up for push notifications will receive a "
"push notification."
)
self.fields["notify_email_all"].help_text = (
"This will send an email notification to all of the users who can see this post. This option "
"does NOT take users' email notification preferences into account, so please use with care."
Expand Down
34 changes: 32 additions & 2 deletions intranet/apps/announcements/notifications.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,23 @@
import re

import requests
from push_notifications.models import WebPushDevice
from requests_oauthlib import OAuth1
from sentry_sdk import capture_exception

from django.conf import settings
from django.contrib import messages
from django.contrib.auth import get_user_model
from django.core import exceptions
from django.db.models import Q
from django.urls import reverse
from django.utils.html import strip_tags

from ...utils.date import get_senior_graduation_year
from ..notifications.tasks import email_send_task
from ..notifications.tasks import email_send_task, send_bulk_notification
from ..notifications.utils import truncate_content, truncate_title
from ..users.models import User
from .models import Announcement

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -136,7 +142,7 @@ def announcement_posted_email(request, obj, send_all=False):
emails.append(u.notification_email)
users_send.append(u)

if not settings.PRODUCTION and len(emails) > 3:
if not settings.PRODUCTION and len(emails) > 3 and not settings.FORCE_EMAIL_SEND:
raise exceptions.PermissionDenied("You're about to email a lot of people, and you aren't in production!")

base_url = request.build_absolute_uri(reverse("index"))
Expand Down Expand Up @@ -201,3 +207,27 @@ def notify_twitter(status):
req = requests.post(url, data=data, auth=auth, timeout=15)

return req.text


def announcement_posted_push_notification(obj: Announcement) -> None:
"""Send a (Web)push notification to users when an announcement is posted.
obj: The announcement object
"""

if not obj.groups.all():
users = User.objects.filter(push_notification_preferences__announcement_notifications=True)
devices = WebPushDevice.objects.filter(user__in=users)
else:
users = User.objects.filter(Q(groups__in=obj.groups.all()) & Q(push_notification_preferences__announcement_notifications=True))
devices = WebPushDevice.objects.filter(user__in=users)

send_bulk_notification.delay(
filtered_objects=devices,
title=f"Announcement: {truncate_title(obj.title)} ({obj.get_author()})",
body=truncate_content(strip_tags(obj.content_no_links)),
data={
"url": settings.PUSH_NOTIFICATIONS_BASE_URL + reverse("view_announcement", args=[obj.id]),
},
)
5 changes: 3 additions & 2 deletions intranet/apps/announcements/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@
from ..groups.models import Group
from .forms import AnnouncementAdminForm, AnnouncementEditForm, AnnouncementForm, AnnouncementRequestForm
from .models import Announcement, AnnouncementRequest
from .notifications import (admin_request_announcement_email, announcement_approved_email, announcement_posted_email, announcement_posted_twitter,
request_announcement_email)
from .notifications import (admin_request_announcement_email, announcement_approved_email, announcement_posted_email,
announcement_posted_push_notification, announcement_posted_twitter, request_announcement_email)

logger = logging.getLogger(__name__)

Expand All @@ -43,6 +43,7 @@ def announcement_posted_hook(request, obj):
"""
if obj.notify_post:
announcement_posted_twitter(request, obj)
announcement_posted_push_notification(obj)
try:
notify_all = obj.notify_email_all
except AttributeError:
Expand Down
Loading

0 comments on commit 51736f7

Please sign in to comment.