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

feat: [AXM-252] add settings for edx-ace push notifications #2541

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
10 changes: 10 additions & 0 deletions lms/djangoapps/mobile_api/notifications/urls.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
from django.urls import path
from .views import GCMDeviceViewSet


CREATE_GCM_DEVICE = GCMDeviceViewSet.as_view({'post': 'create'})


urlpatterns = [
path('create-token/', CREATE_GCM_DEVICE, name='gcmdevice-list'),
]
50 changes: 50 additions & 0 deletions lms/djangoapps/mobile_api/notifications/views.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
from django.conf import settings
from rest_framework import status
from rest_framework.response import Response

from edx_ace.push_notifications.views import GCMDeviceViewSet as GCMDeviceViewSetBase

from ..decorators import mobile_view


@mobile_view(is_user=True)
class GCMDeviceViewSet(GCMDeviceViewSetBase):
"""
**Use Case**
This endpoint allows clients to register a device for push notifications.

If the device is already registered, the existing registration will be updated.
If setting PUSH_NOTIFICATIONS_SETTINGS is not configured, the endpoint will return a 501 error.

**Example Request**
POST /api/mobile/{version}/notifications/create-token/
**POST Parameters**
The body of the POST request can include the following parameters.
* name (optional) - A name of the device.
* registration_id (required) - The device token of the device.
* device_id (optional) - ANDROID_ID / TelephonyManager.getDeviceId() (always as hex)
* active (optional) - Whether the device is active, default is True.
If False, the device will not receive notifications.
* cloud_message_type (required) - You should choose FCM or GCM. Currently, only FCM is supported.
* application_id (optional) - Opaque application identity, should be filled in for multiple
key/certificate access.
**Example Response**
```json
{
"id": 1,
"name": "My Device",
"registration_id": "fj3j4",
"device_id": 1234,
"active": true,
"date_created": "2024-04-18T07:39:37.132787Z",
"cloud_message_type": "FCM",
"application_id": "my_app_id"
}
```
"""

def create(self, request, *args, **kwargs):
if not getattr(settings, 'PUSH_NOTIFICATIONS_SETTINGS', None):
return Response('Push notifications are not configured.', status.HTTP_501_NOT_IMPLEMENTED)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍


return super().create(request, *args, **kwargs)
1 change: 1 addition & 0 deletions lms/djangoapps/mobile_api/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,6 @@
urlpatterns = [
path('users/', include('lms.djangoapps.mobile_api.users.urls')),
path('my_user_info', my_user_info, name='user-info'),
path('notifications/', include('lms.djangoapps.mobile_api.notifications.urls')),
path('course_info/', include('lms.djangoapps.mobile_api.course_info.urls')),
]
29 changes: 29 additions & 0 deletions openedx/core/djangoapps/ace_common/settings/common.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
"""
Settings for ace_common app.
"""
from openedx.core.djangoapps.ace_common.utils import setup_firebase_app

ACE_ROUTING_KEY = 'edx.lms.core.default'


def plugin_settings(settings): # lint-amnesty, pylint: disable=missing-function-docstring, missing-module-docstring
if 'push_notifications' not in settings.INSTALLED_APPS:
settings.INSTALLED_APPS.append('push_notifications')
settings.ACE_ENABLED_CHANNELS = [
'django_email'
]
Expand All @@ -22,3 +25,29 @@ def plugin_settings(settings): # lint-amnesty, pylint: disable=missing-function
settings.ACE_ROUTING_KEY = ACE_ROUTING_KEY

settings.FEATURES['test_django_plugin'] = True
settings.FCM_APP_NAME = 'fcm-edx-platform'

if getattr(settings, 'FIREBASE_SETUP_STATUS', None) is None:
settings.ACE_CHANNEL_DEFAULT_PUSH = 'push_notification'

# Note: To local development with Firebase, you must set FIREBASE_CREDENTIALS.
settings.FCM_APP_NAME = 'fcm-edx-platform'
settings.FIREBASE_CREDENTIALS = None

if firebase_app := setup_firebase_app(settings.FIREBASE_CREDENTIALS, settings.FCM_APP_NAME):
settings.ACE_ENABLED_CHANNELS.append(settings.ACE_CHANNEL_DEFAULT_PUSH)
settings.ACE_ENABLED_POLICIES.append(settings.ACE_CHANNEL_DEFAULT_PUSH)

settings.PUSH_NOTIFICATIONS_SETTINGS = {
'CONFIG': 'push_notifications.conf.AppConfig',
'APPLICATIONS': {
settings.FCM_APP_NAME: {
'PLATFORM': 'FCM',
'FIREBASE_APP': firebase_app,
},
},
'UPDATE_ON_DUPLICATE_REG_ID': True,
}
settings.FIREBASE_SETUP_STATUS = True
else:
settings.FIREBASE_SETUP_STATUS = False
22 changes: 22 additions & 0 deletions openedx/core/djangoapps/ace_common/settings/production.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"""Common environment variables unique to the ace_common plugin."""
from openedx.core.djangoapps.ace_common.utils import setup_firebase_app


def plugin_settings(settings):
Expand Down Expand Up @@ -26,3 +27,24 @@ def plugin_settings(settings):
settings.ACE_CHANNEL_TRANSACTIONAL_EMAIL = settings.ENV_TOKENS.get(
'ACE_CHANNEL_TRANSACTIONAL_EMAIL', settings.ACE_CHANNEL_TRANSACTIONAL_EMAIL
)
settings.FCM_APP_NAME = settings.ENV_TOKENS.get('FCM_APP_NAME', 'fcm-edx-platform')
settings.FIREBASE_CREDENTIALS = settings.ENV_TOKENS.get('FIREBASE_CREDENTIALS', {})

if getattr(settings, 'FIREBASE_SETUP_STATUS', None) is None:
if firebase_app := setup_firebase_app(settings.FIREBASE_CREDENTIALS, settings.FCM_APP_NAME):
settings.ACE_ENABLED_CHANNELS.append(settings.ACE_CHANNEL_DEFAULT_PUSH)
settings.ACE_ENABLED_POLICIES.append(settings.ACE_CHANNEL_DEFAULT_PUSH)

settings.PUSH_NOTIFICATIONS_SETTINGS = {
'CONFIG': 'push_notifications.conf.AppConfig',
'APPLICATIONS': {
settings.FCM_APP_NAME: {
'PLATFORM': 'FCM',
'FIREBASE_APP': firebase_app,
},
},
'UPDATE_ON_DUPLICATE_REG_ID': True,
}
settings.FIREBASE_SETUP_STATUS = True
else:
settings.FIREBASE_SETUP_STATUS = False
20 changes: 20 additions & 0 deletions openedx/core/djangoapps/ace_common/utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
"""
Utility functions for edx-ace.
"""
import logging

log = logging.getLogger(__name__)


def setup_firebase_app(firebase_credentials, app_name='fcm-app'):
"""
Returns a Firebase app instance if the Firebase credentials are provided.
"""
try:
import firebase_admin # pylint: disable=import-outside-toplevel
except ImportError:
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe log here with text: "Could not import firebase_admin package"?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Logs added.

log.error('Could not import firebase_admin package.')
return
if firebase_credentials:
certificate = firebase_admin.credentials.Certificate(firebase_credentials)
return firebase_admin.initialize_app(certificate, name=app_name)
41 changes: 41 additions & 0 deletions openedx/core/djangoapps/notifications/policies.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
"""Policies for the notifications app."""

from edx_ace.channel import ChannelType
from edx_ace.policy import Policy, PolicyResult
from opaque_keys.edx.keys import CourseKey

from .models import CourseNotificationPreference


class CoursePushNotificationOptout(Policy):
"""
Course Push Notification optOut Policy.
"""

def check(self, message):
"""
Check if the user has opted out of push notifications for the given course.
:param message:
:return:
"""
course_ids = message.context.get('course_ids', [])
app_label = message.context.get('app_label')

if not (app_label or message.context.get('send_push_notification', False)):
return PolicyResult(deny={ChannelType.PUSH})

course_keys = [CourseKey.from_string(course_id) for course_id in course_ids]
for course_key in course_keys:
course_notification_preference = CourseNotificationPreference.get_user_course_preference(
message.recipient.lms_user_id,
course_key
)
push_notification_preference = course_notification_preference.get_notification_type_config(
app_label,
notification_type='push',
).get('push', False)

if not push_notification_preference:
return PolicyResult(deny={ChannelType.PUSH})

return PolicyResult(deny=frozenset())
87 changes: 84 additions & 3 deletions requirements/edx/base.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@
#
# make upgrade
#
-e git+https://github.com/jazzband/django-push-notifications.git@906fe52058bad36b6af2bb292fdb9292ccaa94e5#egg=django_push_notifications
# via -r requirements/edx/github.in
-e git+https://github.com/raccoongang/edx-ace.git@mob-develop#egg=edx_ace
# via
# -r requirements/edx/github.in
# -r requirements/edx/kernel.in
-e git+https://github.com/anupdhabarde/edx-proctoring-proctortrack.git@31c6c9923a51c903ae83760ecbbac191363aa2a2#egg=edx_proctoring_proctortrack
# via -r requirements/edx/github.in
acid-xblock==0.3.0
Expand Down Expand Up @@ -90,6 +96,10 @@ botocore==1.34.45
# s3transfer
bridgekeeper==0.9
# via -r requirements/edx/kernel.in
cachecontrol==0.14.0
# via firebase-admin
cachetools==5.3.3
# via google-auth
camel-converter[pydantic]==3.1.1
# via meilisearch
celery==5.3.6
Expand Down Expand Up @@ -196,6 +206,7 @@ django==4.2.10
# django-multi-email-field
# django-mysql
# django-oauth-toolkit
# django-push-notifications
# django-sekizai
# django-ses
# django-statici18n
Expand Down Expand Up @@ -411,8 +422,6 @@ drf-yasg==1.21.5
# -c requirements/edx/../constraints.txt
# django-user-tasks
# edx-api-doc-tools
edx-ace==1.8.0
# via -r requirements/edx/kernel.in
edx-api-doc-tools==1.8.0
# via
# -r requirements/edx/kernel.in
Expand Down Expand Up @@ -572,6 +581,8 @@ fastavro==1.9.4
# via openedx-events
filelock==3.13.1
# via snowflake-connector-python
firebase-admin==5.0.0
# via edx-ace
frozenlist==1.4.1
# via
# aiohttp
Expand All @@ -592,6 +603,49 @@ geoip2==4.8.0
# via -r requirements/edx/kernel.in
glob2==0.7
# via -r requirements/edx/kernel.in
google-api-core[grpc]==1.34.1
# via
# firebase-admin
# google-api-python-client
# google-cloud-core
# google-cloud-firestore
# google-cloud-storage
google-api-python-client==2.127.0
# via firebase-admin
google-auth==2.29.0
# via
# google-api-core
# google-api-python-client
# google-auth-httplib2
# google-cloud-core
# google-cloud-firestore
# google-cloud-storage
google-auth-httplib2==0.2.0
# via google-api-python-client
google-cloud-core==2.4.1
# via
# google-cloud-firestore
# google-cloud-storage
google-cloud-firestore==2.16.0
# via firebase-admin
google-cloud-storage==2.14.0
# via firebase-admin
google-crc32c==1.5.0
# via
# google-cloud-storage
# google-resumable-media
google-resumable-media==2.7.0
# via google-cloud-storage
googleapis-common-protos==1.63.0
# via
# google-api-core
# grpcio-status
grpcio==1.62.2
# via
# google-api-core
# grpcio-status
grpcio-status==1.48.2
# via google-api-core
gunicorn==22.0.0
# via -r requirements/edx/kernel.in
help-tokens==2.4.0
Expand All @@ -600,6 +654,10 @@ html5lib==1.1
# via
# -r requirements/edx/kernel.in
# ora2
httplib2==0.22.0
# via
# google-api-python-client
# google-auth-httplib2
icalendar==5.0.11
# via -r requirements/edx/kernel.in
idna==3.6
Expand Down Expand Up @@ -733,6 +791,8 @@ monotonic==1.6
# py2neo
mpmath==1.3.0
# via sympy
msgpack==1.0.8
# via cachecontrol
multidict==6.0.5
# via
# aiohttp
Expand Down Expand Up @@ -850,6 +910,15 @@ polib==1.2.0
# via edx-i18n-tools
prompt-toolkit==3.0.43
# via click-repl
proto-plus==1.23.0
# via google-cloud-firestore
protobuf==3.20.3
# via
# google-api-core
# google-cloud-firestore
# googleapis-common-protos
# grpcio-status
# proto-plus
psutil==5.9.8
# via
# -r requirements/edx/paver.txt
Expand All @@ -859,7 +928,12 @@ py2neo @ https://github.com/overhangio/py2neo/releases/download/2021.2.3/py2neo-
# -c requirements/edx/../constraints.txt
# -r requirements/edx/bundled.in
pyasn1==0.5.1
# via pgpy
# via
# pgpy
# pyasn1-modules
# rsa
pyasn1-modules==0.4.0
# via google-auth
pycountry==23.12.11
# via -r requirements/edx/kernel.in
pycparser==2.21
Expand Down Expand Up @@ -921,6 +995,7 @@ pyopenssl==22.0.0
pyparsing==3.1.1
# via
# chem
# httplib2
# openedx-calc
pyrsistent==0.20.0
# via optimizely-sdk
Expand Down Expand Up @@ -1005,13 +1080,16 @@ requests==2.31.0
# -r requirements/edx/paver.txt
# algoliasearch
# analytics-python
# cachecontrol
# coreapi
# django-oauth-toolkit
# edx-bulk-grades
# edx-drf-extensions
# edx-enterprise
# edx-rest-api-client
# geoip2
# google-api-core
# google-cloud-storage
# mailsnake
# meilisearch
# openai
Expand All @@ -1033,6 +1111,8 @@ rpds-py==0.18.0
# via
# jsonschema
# referencing
rsa==4.9
# via google-auth
ruamel-yaml==0.18.6
# via drf-yasg
ruamel-yaml-clib==0.2.8
Expand Down Expand Up @@ -1179,6 +1259,7 @@ uritemplate==4.1.1
# coreapi
# drf-spectacular
# drf-yasg
# google-api-python-client
urllib3==1.26.18
# via
# -c requirements/edx/../constraints.txt
Expand Down
Loading
Loading