Skip to content

Commit

Permalink
Enable multiple application support
Browse files Browse the repository at this point in the history
* Provides backwards compatibility through conf adapters that
determine which settings are supported and where the data
can be loaded from. This implementation adds 3:

  * LegacyConfig - supports the existing settings format
  * AppConfig - supports a dictionary of application_ids and their settings
  * AppModelConfig - supports storing the settings in the database for multiple apps
  • Loading branch information
matthewh authored and jleclanche committed Apr 4, 2017
1 parent dcbd63a commit a6bc509
Show file tree
Hide file tree
Showing 20 changed files with 1,128 additions and 102 deletions.
5 changes: 4 additions & 1 deletion push_notifications/api/rest_framework.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,10 @@ def to_representation(self, value):
# Serializers
class DeviceSerializerMixin(ModelSerializer):
class Meta:
fields = ("id", "name", "registration_id", "device_id", "active", "date_created")
fields = (
"id", "name", "application_id", "registration_id", "device_id",
"active", "date_created"
)
read_only_fields = ("date_created",)

# See https://github.com/tomchristie/django-rest-framework/issues/1101
Expand Down
48 changes: 32 additions & 16 deletions push_notifications/apns.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
from . import models
from . import NotificationError
from .apns_errors import reason_for_exception_class
from .settings import PUSH_NOTIFICATIONS_SETTINGS as SETTINGS
from .conf import get_manager


class APNSError(NotificationError):
Expand All @@ -30,20 +30,21 @@ def __init__(self, status):
self.status = status


def _apns_create_socket(certfile=None):
certfile = certfile or SETTINGS.get("APNS_CERTIFICATE")
def _apns_create_socket(certfile=None, application_id=None):
certfile = certfile or get_manager().get_apns_certificate(application_id)
client = apns2_client.APNsClient(
certfile,
use_sandbox=SETTINGS.get("APNS_USE_SANDBOX"),
use_alternative_port=SETTINGS.get("APNS_USE_ALTERNATIVE_PORT"))
use_sandbox=get_manager().get_apns_use_sandbox(application_id),
use_alternative_port=get_manager().get_apns_use_alternative_port(application_id)
)
client.connect()
return client


def _apns_prepare(
token, alert, badge=None, sound=None, category=None, content_available=False,
action_loc_key=None, loc_key=None, loc_args=[], extra={}, mutable_content=False,
thread_id=None, url_args=None):
token, alert, application_id=None, badge=None, sound=None, category=None,
content_available=False, action_loc_key=None, loc_key=None, loc_args=[],
extra={}, mutable_content=False, thread_id=None, url_args=None):
if action_loc_key or loc_key or loc_args:
apns2_alert = apns2_payload.PayloadAlert(
body=alert if alert else {}, body_localized_key=loc_key,
Expand All @@ -59,8 +60,10 @@ def _apns_prepare(
url_args, custom=extra, thread_id=thread_id)


def _apns_send(registration_id, alert, batch=False, **kwargs):
client = _apns_create_socket(kwargs.pop("certfile", None))
def _apns_send(
registration_id, alert, batch=False, application_id=None, certfile=None, **kwargs
):
client = _apns_create_socket(certfile=certfile, application_id=application_id)

notification_kwargs = {}

Expand All @@ -80,14 +83,19 @@ def _apns_send(registration_id, alert, batch=False, **kwargs):
data = [apns2_client.Notification(
token=rid, payload=_apns_prepare(rid, alert, **kwargs)) for rid in registration_id]
return client.send_notification_batch(
data, SETTINGS.get("APNS_TOPIC"), **notification_kwargs)
data, get_manager().get_apns_topic(application_id=application_id),
**notification_kwargs
)

data = _apns_prepare(registration_id, alert, **kwargs)
client.send_notification(
registration_id, data, SETTINGS.get("APNS_TOPIC"), **notification_kwargs)
registration_id, data,
get_manager().get_apns_topic(application_id=application_id),
**notification_kwargs
)


def apns_send_message(registration_id, alert, **kwargs):
def apns_send_message(registration_id, alert, application_id=None, certfile=None, **kwargs):
"""
Sends an APNS notification to a single registration_id.
This will send the notification as form data.
Expand All @@ -100,7 +108,10 @@ def apns_send_message(registration_id, alert, **kwargs):
"""

try:
_apns_send(registration_id, alert, **kwargs)
_apns_send(
registration_id, alert, application_id=application_id,
certfile=certfile, **kwargs
)
except apns2_errors.APNsException as apns2_exception:
if isinstance(apns2_exception, apns2_errors.Unregistered):
device = models.APNSDevice.objects.get(registration_id=registration_id)
Expand All @@ -109,7 +120,9 @@ def apns_send_message(registration_id, alert, **kwargs):
raise APNSServerError(status=reason_for_exception_class(apns2_exception.__class__))


def apns_send_bulk_message(registration_ids, alert, **kwargs):
def apns_send_bulk_message(
registration_ids, alert, application_id=None, certfile=None, **kwargs
):
"""
Sends an APNS notification to one or more registration_ids.
The registration_ids argument needs to be a list.
Expand All @@ -119,7 +132,10 @@ def apns_send_bulk_message(registration_ids, alert, **kwargs):
to this for silent notifications.
"""

results = _apns_send(registration_ids, alert, batch=True, **kwargs)
results = _apns_send(
registration_ids, alert, batch=True, application_id=application_id,
certfile=certfile, **kwargs
)
inactive_tokens = [token for token, result in results.items() if result == "Unregistered"]
models.APNSDevice.objects.filter(registration_id__in=inactive_tokens).update(active=False)
return results
21 changes: 21 additions & 0 deletions push_notifications/conf/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
from django.utils.module_loading import import_string
from .app import AppConfig # noqa: F401
from .appmodel import AppModelConfig # noqa: F401
from .legacy import LegacyConfig # noqa: F401
from ..settings import PUSH_NOTIFICATIONS_SETTINGS as SETTINGS


manager = None


def get_manager(reload=False):
global manager

if not manager or reload is True:
manager = import_string(SETTINGS["CONFIG"])()

return manager


# implementing get_manager as a function allows tests to reload settings
get_manager()
Loading

0 comments on commit a6bc509

Please sign in to comment.