Skip to content
Open
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
5 changes: 2 additions & 3 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
language: python

dist: xenial
dist: bionic

python:
- "2.7"
- "3.5"
- "3.6"
- "3.7"
- "3.8"

install:
- pip install tox-travis
Expand Down
9 changes: 2 additions & 7 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,11 @@ RUN apt-get update && apt-get install -y \
python3.5 \
python3.6 \
python3.7 \
libpq-dev \
python3.8 \
gdal-bin \
python3-distutils \
python3-pip

WORKDIR /app

COPY requirements.txt .
COPY requirements_dev.txt .

RUN pip3 install --upgrade pip
RUN pip3 install tox
RUN pip3 install -r requirements_dev.txt
RUN pip3 install tox
94 changes: 87 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,15 @@ class Device(AbstractSNSDevice):
pass
```

4. Make sure that you fill necessary information at the settings file:
4. Add your Device model to django admin.

```python
from django_sloop.admin import DeviceAdmin

admin.site.register(Device, DeviceAdmin)
```

5. Make sure that you fill necessary information at the settings file:

```python
# settings.py
Expand All @@ -37,7 +45,7 @@ DJANGO_SLOOP_SETTINGS = {
"AWS_ACCESS_KEY_ID": "",
"AWS_SECRET_ACCESS_KEY": "",
"SNS_IOS_APPLICATION_ARN": "test_ios_arn",
"SNS_IOS_SANDBOX_ENABLED": False,
"SNS_IOS_SANDBOX_APPLICATION_ARN": "test_ios_sandbox_arn",
"SNS_ANDROID_APPLICATION_ARN": "test_android_arn",
"LOG_SENT_MESSAGES": False, # False by default.
"DEFAULT_SOUND": "",
Expand All @@ -48,7 +56,7 @@ DJANGO_SLOOP_SETTINGS = {
You cannot change the DEVICE_MODEL setting during the lifetime of a project (i.e. once you have made and migrated models that depend on it) without serious effort. The model it refers to must be available in the first migration of
the app that it lives in.

5. Create migrations for newly created Device model and migrate.
6. Create migrations for newly created Device model and migrate.

**Note:** django_sloop's migrations must run after your Device is created. If you run into a problem while running migrations add following to the your migration file where the Device is created.
```
Expand All @@ -57,7 +65,7 @@ run_before = [
]
```

6. Add django_sloop.models.PushNotificationMixin to your User model.
7. Add django_sloop.models.PushNotificationMixin to your User model.
```python
class User(PushNotificationMixin, ...):
pass
Expand All @@ -67,7 +75,7 @@ user.send_push_notification_async(message="Sample push notification.")
```


7. Add django_sloop.admin.SloopAdminMixin to your UserAdmin to enable sending push messages to users from Django admin panel.
8. Add django_sloop.admin.SloopAdminMixin to your UserAdmin to enable sending push messages to users from Django admin panel.

```python
# admin.py
Expand All @@ -81,7 +89,7 @@ class UserAdmin(SloopAdminMixin, admin.ModelAdmin):

```

8. Add django rest framework urls to create and delete device.
9. Add django rest framework urls to create and delete device.

```python
# urls.py
Expand All @@ -95,4 +103,76 @@ urlpatterns = [
]
```

Done!
Done!


## Mobile Client Integration

- Mobile clients should call the `create-or-update-device` endpoint in the following cases.
1. First time you access the push token.
2. If you already have push token you should send the same request to update last time the device is used. It's best to call this endpoint each time app is opened or come back from background.

Request: **POST /api/devices/**
Payload:
```
{
"push_token": "required string",
"platform": "required ios or android",
"model": "optional string",
"locale": "optional string default to `en_US`",
}
```

- Mobile clients should call the `delete-device` endpoint when user log outs.

Request: **DELETE /api/devices/**
Payload:
```
{
"push_token": "required string",
}
```


**Endpoint details will be available in the projects api documentation as well. There can be project level changes so please go to projects api documentation.**

CONTRIBUTION
=================

**TESTS**
- Make sure that you add the test for contributed field to test/test_fields.py
and run with command before sending a pull request:

```bash
$ pip install tox # if not already installed
$ tox
```

Or, if you prefer using Docker (recommended):

```bash
docker build -t django-sloop .
docker run -v $(pwd):/app -it django-sloop /bin/bash
tox
```

**README**
- Make sure that you add the documentation for the field added to README.md


LICENSE
====================

Copyright DRF EXTRA FIELDS HIPO

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
57 changes: 56 additions & 1 deletion django_sloop/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import json

from django_sloop.models import PushMessage
from django.utils.translation import ugettext_lazy as _


class PushNotificationForm(forms.Form):
Expand Down Expand Up @@ -121,15 +122,69 @@ def send_push_notification(self, request, queryset):
return TemplateResponse(request, 'django_sloop/push_notification.html', context=context)


class DeviceAdmin(admin.ModelAdmin):

list_display = ["user", "platform", "model", "is_sandbox_enabled", "deleted_at", "date_created", "date_updated"]
readonly_fields = ["user", "platform", "model", "deleted_at", "date_created", "date_updated"]
search_fields = ["user_id"]

def save_model(self, request, obj, form, change):
# Clear sns_platform_endpoint_arn if sandbox mode is changed.
# https://docs.djangoproject.com/en/2.2/ref/models/instances/#refreshing-objects-from-database
new_value = obj.is_sandbox_enabled
delattr(obj, "is_sandbox_enabled")
# Fetch field from database.
old_value = obj.is_sandbox_enabled

if new_value != old_value:
obj.sns_platform_endpoint_arn = ""

obj.is_sandbox_enabled = new_value

super(DeviceAdmin, self).save_model(request, obj, form, change)


class PushMessageAdmin(admin.ModelAdmin):

search_fields = ["body", "sns_message_id"]
list_display = ["id", "body", "error_message", "device", "sns_message_id", "date_created", "date_updated"]
readonly_fields = ["id", "device", "body", "data", "sns_message_id", "sns_response", "date_created", "date_updated"]
readonly_fields = ["id", "device", "body", "data", "sns_message_id", "sns_response", "payload", "date_created", "date_updated"]

actions = ["resend_push_notification"]

def error_message(self, obj):
error = json.loads(obj.sns_response).get("Error")
if error:
return error.get("Message")

def resend_push_notification(self, request, queryset):
if queryset.count() > 1:
messages.add_message(request, messages.ERROR, _("You can only send one push message at a time."))
push_message = queryset.get()
device = push_message.device
try:
payload = self.payload(push_message)
payload = payload.get("data") or payload.get("aps")

message = payload.get("alert")
sound = payload.get("sound")
extra = payload.get("custom")
badge_count = payload.get("badge")
category = payload.get("category")
if message:
device.send_push_notification(message, url=extra.get("url"), badge_count=badge_count, sound=sound, extra=extra, category=category)
messages.add_message(request, messages.SUCCESS, _("Push message has been sent."))
else:
device.send_silent_push_notification(extra=extra, badge_count=badge_count, content_available=None)
messages.add_message(request, messages.SUCCESS, _("Silent push message has been sent."))
except Exception as exc:
messages.add_message(request, messages.ERROR, str(exc))

def payload(self, push_message):
payload = json.loads(push_message.data)
payload = payload.get("GCM") or payload.get("APNS") or payload.get("APNS_SANDBOX")
payload = json.loads(payload)
return payload


admin.site.register(PushMessage, PushMessageAdmin)
9 changes: 6 additions & 3 deletions django_sloop/handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,10 @@ def get_client(self):
@property
def application_arn(self):
if self.device.platform == AbstractSNSDevice.PLATFORM_IOS:
application_arn = DJANGO_SLOOP_SETTINGS.get("SNS_IOS_APPLICATION_ARN")
if self.device.is_sandbox_enabled:
application_arn = DJANGO_SLOOP_SETTINGS.get("SNS_IOS_SANDBOX_APPLICATION_ARN")
else:
application_arn = DJANGO_SLOOP_SETTINGS.get("SNS_IOS_APPLICATION_ARN")
elif self.device.platform == AbstractSNSDevice.PLATFORM_ANDROID:
application_arn = DJANGO_SLOOP_SETTINGS.get("SNS_ANDROID_APPLICATION_ARN")
else:
Expand Down Expand Up @@ -125,7 +128,7 @@ def generate_apns_push_notification_message(self, message, url, badge_count, sou
}
apns_string = json.dumps(apns_bundle, ensure_ascii=False)

if DJANGO_SLOOP_SETTINGS.get("SNS_IOS_SANDBOX_ENABLED"):
if self.device.is_sandbox_enabled:
return {
'APNS_SANDBOX': apns_string
}
Expand All @@ -149,7 +152,7 @@ def generate_apns_silent_push_notification_message(self, extra, badge_count, con
}
apns_string = json.dumps(apns_bundle, ensure_ascii=False)

if DJANGO_SLOOP_SETTINGS.get("SNS_IOS_SANDBOX_ENABLED"):
if self.device.is_sandbox_enabled:
return {
'APNS_SANDBOX': apns_string
}
Expand Down
8 changes: 7 additions & 1 deletion django_sloop/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

from django.conf import settings
from django.contrib.gis.db import models
from django.core.exceptions import ObjectDoesNotExist
from django.core.exceptions import ObjectDoesNotExist, ValidationError
from django.utils import timezone
from django.utils.encoding import smart_text
from django.utils.translation import ugettext_lazy as _
Expand Down Expand Up @@ -91,6 +91,8 @@ class AbstractSNSDevice(models.Model):
model = models.CharField(max_length=255, blank=True)
sns_platform_endpoint_arn = models.CharField(_("SNS Platform Endpoint"), max_length=255, null=True, blank=True, unique=True)

is_sandbox_enabled = models.BooleanField(default=False, help_text="Changing this will clear the sns_platform_endpoint_arn")

deleted_at = models.DateTimeField(null=True, blank=True)

date_created = models.DateTimeField(default=timezone.now)
Expand All @@ -110,6 +112,10 @@ def __str__(self):
"push_token": self.push_token,
})

def clean(self):
if self.is_sandbox_enabled and self.platform != self.PLATFORM_IOS:
raise ValidationError({"is_sandbox_enabled": _("Sandbox can be enabled for iOS platforms")})

def invalidate(self):
self.deleted_at = timezone.now()
self.save()
Expand Down
2 changes: 1 addition & 1 deletion django_sloop/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
DJANGO_SLOOP_SETTINGS.setdefault("AWS_ACCESS_KEY_ID", None)
DJANGO_SLOOP_SETTINGS.setdefault("AWS_SECRET_ACCESS_KEY", None)
DJANGO_SLOOP_SETTINGS.setdefault("SNS_IOS_APPLICATION_ARN", None)
DJANGO_SLOOP_SETTINGS.setdefault("SNS_IOS_SANDBOX_ENABLED", False)
DJANGO_SLOOP_SETTINGS.setdefault("SNS_IOS_SANDBOX_APPLICATION_ARN", None)
DJANGO_SLOOP_SETTINGS.setdefault("SNS_ANDROID_APPLICATION_ARN", None)
DJANGO_SLOOP_SETTINGS.setdefault("LOG_SENT_MESSAGES", False)
DJANGO_SLOOP_SETTINGS.setdefault("DEFAULT_SOUND", None)
Expand Down
Loading