Skip to content

Commit 033839b

Browse files
roosnic1Asif Saif Uddin
authored and
Asif Saif Uddin
committed
Added setting to make registration_id fields on all device models unique (#539)
1 parent 5b792ea commit 033839b

File tree

7 files changed

+128
-5
lines changed

7 files changed

+128
-5
lines changed

README.rst

+1
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ For WNS, you need both the ``WNS_PACKAGE_SECURITY_KEY`` and the ``WNS_SECRET_KEY
8787

8888
- ``USER_MODEL``: Your user model of choice. Eg. ``myapp.User``. Defaults to ``settings.AUTH_USER_MODEL``.
8989
- ``UPDATE_ON_DUPLICATE_REG_ID``: Transform create of an existing Device (based on registration id) into a update. See below `Update of device with duplicate registration ID`_ for more details.
90+
- ``UNIQUE_REG_ID``: Forces the ``registration_id`` field on all device models to be unique.
9091

9192
**APNS settings**
9293

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
from django.db import migrations, models
2+
3+
from ..settings import PUSH_NOTIFICATIONS_SETTINGS as SETTINGS
4+
5+
6+
class Migration(migrations.Migration):
7+
8+
dependencies = [
9+
('push_notifications', '0006_webpushdevice'),
10+
]
11+
12+
operations = [
13+
migrations.AlterField(
14+
model_name='apnsdevice',
15+
name='registration_id',
16+
field=models.CharField(max_length=200, unique=SETTINGS['UNIQUE_REG_ID'], verbose_name='Registration ID'),
17+
),
18+
migrations.AlterField(
19+
model_name='gcmdevice',
20+
name='registration_id',
21+
field=models.TextField(unique=SETTINGS['UNIQUE_REG_ID'], verbose_name='Registration ID'),
22+
),
23+
migrations.AlterField(
24+
model_name='webpushdevice',
25+
name='registration_id',
26+
field=models.TextField(unique=SETTINGS['UNIQUE_REG_ID'], verbose_name='Registration ID'),
27+
),
28+
migrations.AlterField(
29+
model_name='wnsdevice',
30+
name='registration_id',
31+
field=models.TextField(unique=SETTINGS['UNIQUE_REG_ID'], verbose_name='Notification URI'),
32+
),
33+
]

push_notifications/models.py

+4-4
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ class GCMDevice(Device):
9090
verbose_name=_("Device ID"), blank=True, null=True, db_index=True,
9191
help_text=_("ANDROID_ID / TelephonyManager.getDeviceId() (always as hex)")
9292
)
93-
registration_id = models.TextField(verbose_name=_("Registration ID"))
93+
registration_id = models.TextField(verbose_name=_("Registration ID"), unique=SETTINGS["UNIQUE_REG_ID"])
9494
cloud_message_type = models.CharField(
9595
verbose_name=_("Cloud Message Type"), max_length=3,
9696
choices=CLOUD_MESSAGE_TYPES, default="GCM",
@@ -148,7 +148,7 @@ class APNSDevice(Device):
148148
help_text="UDID / UIDevice.identifierForVendor()"
149149
)
150150
registration_id = models.CharField(
151-
verbose_name=_("Registration ID"), max_length=200, unique=True
151+
verbose_name=_("Registration ID"), max_length=200, unique=SETTINGS["UNIQUE_REG_ID"]
152152
)
153153

154154
objects = APNSDeviceManager()
@@ -198,7 +198,7 @@ class WNSDevice(Device):
198198
verbose_name=_("Device ID"), blank=True, null=True, db_index=True,
199199
help_text=_("GUID()")
200200
)
201-
registration_id = models.TextField(verbose_name=_("Notification URI"))
201+
registration_id = models.TextField(verbose_name=_("Notification URI"), unique=SETTINGS["UNIQUE_REG_ID"])
202202

203203
objects = WNSDeviceManager()
204204

@@ -230,7 +230,7 @@ def send_message(self, message, **kwargs):
230230

231231

232232
class WebPushDevice(Device):
233-
registration_id = models.TextField(verbose_name=_("Registration ID"))
233+
registration_id = models.TextField(verbose_name=_("Registration ID"), unique=SETTINGS["UNIQUE_REG_ID"])
234234
p256dh = models.CharField(
235235
verbose_name=_("User public encryption key"),
236236
max_length=88)

push_notifications/settings.py

+3
Original file line numberDiff line numberDiff line change
@@ -49,5 +49,8 @@
4949
# User model
5050
PUSH_NOTIFICATIONS_SETTINGS.setdefault("USER_MODEL", settings.AUTH_USER_MODEL)
5151

52+
# Unique registration ID for all devices
53+
PUSH_NOTIFICATIONS_SETTINGS.setdefault("UNIQUE_REG_ID", False)
54+
5255
# API endpoint settings
5356
PUSH_NOTIFICATIONS_SETTINGS.setdefault("UPDATE_ON_DUPLICATE_REG_ID", False)

tests/settings_unique.py

+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# assert warnings are enabled
2+
import warnings
3+
4+
5+
warnings.simplefilter("ignore", Warning)
6+
7+
8+
DATABASES = {
9+
"default": {
10+
"ENGINE": "django.db.backends.sqlite3",
11+
}
12+
}
13+
14+
INSTALLED_APPS = [
15+
"django.contrib.admin",
16+
"django.contrib.auth",
17+
"django.contrib.contenttypes",
18+
"django.contrib.sessions",
19+
"django.contrib.sites",
20+
"push_notifications",
21+
]
22+
23+
SITE_ID = 1
24+
ROOT_URLCONF = "core.urls"
25+
26+
SECRET_KEY = "foobar"
27+
28+
PUSH_NOTIFICATIONS_SETTINGS = {
29+
"WP_CLAIMS": {"sub": "mailto: [email protected]"},
30+
"UNIQUE_REG_ID": True
31+
}

tests/tst_unique.py

+53
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
2+
from __future__ import absolute_import
3+
4+
import pytest
5+
from django.db import IntegrityError
6+
from django.test import TestCase
7+
from push_notifications.models import APNSDevice, GCMDevice, WNSDevice, WebPushDevice
8+
9+
10+
class GCMModelTestCase(TestCase):
11+
def test_throws_error_for_same_gcm_registration_id(self):
12+
device = GCMDevice.objects.create(
13+
registration_id="unique_id", cloud_message_type="GCM"
14+
)
15+
assert device.id is not None
16+
with pytest.raises(IntegrityError) as excinfo:
17+
GCMDevice.objects.create(
18+
registration_id="unique_id", cloud_message_type="GCM"
19+
)
20+
assert "UNIQUE constraint failed" in str(excinfo.value)
21+
22+
def test_throws_error_for_same_apns_registration_id(self):
23+
device = APNSDevice.objects.create(
24+
registration_id="unique_id",
25+
)
26+
assert device.id is not None
27+
with pytest.raises(IntegrityError) as excinfo:
28+
APNSDevice.objects.create(
29+
registration_id="unique_id",
30+
)
31+
assert "UNIQUE constraint failed" in str(excinfo.value)
32+
33+
def test_throws_error_for_same_wns_registration_id(self):
34+
device = WNSDevice.objects.create(
35+
registration_id="unique_id",
36+
)
37+
assert device.id is not None
38+
with pytest.raises(IntegrityError) as excinfo:
39+
WNSDevice.objects.create(
40+
registration_id="unique_id",
41+
)
42+
assert "UNIQUE constraint failed" in str(excinfo.value)
43+
44+
def test_throws_error_for_same_web_registration_id(self):
45+
device = WebPushDevice.objects.create(
46+
registration_id="unique_id",
47+
)
48+
assert device.id is not None
49+
with pytest.raises(IntegrityError) as excinfo:
50+
WebPushDevice.objects.create(
51+
registration_id="unique_id",
52+
)
53+
assert "UNIQUE constraint failed" in str(excinfo.value)

tox.ini

+3-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@ setenv =
99
PYTHONWARNINGS = all
1010
DJANGO_SETTINGS_MODULE = tests.settings
1111
PYTHONPATH = {toxinidir}
12-
commands = pytest
12+
commands =
13+
pytest
14+
pytest --ds=tests.settings_unique tests/tst_unique.py
1315
deps =
1416
pytest
1517
pytest-django

0 commit comments

Comments
 (0)