Skip to content

Commit 637bb09

Browse files
committed
GCM/FCM device uuid
1 parent 129e599 commit 637bb09

File tree

5 files changed

+50
-95
lines changed

5 files changed

+50
-95
lines changed

push_notifications/api/rest_framework.py

+8-16
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,29 @@
11
from __future__ import absolute_import
22

33
from rest_framework import permissions, status
4-
from rest_framework.fields import IntegerField
4+
from rest_framework.fields import UUIDField
55
from rest_framework.response import Response
66
from rest_framework.serializers import ModelSerializer, Serializer, ValidationError
77
from rest_framework.viewsets import ModelViewSet
88

9-
from ..fields import hex_re, UNSIGNED_64BIT_INT_MAX_VALUE
9+
from ..fields import hex_re
1010
from ..models import APNSDevice, GCMDevice, WNSDevice
1111
from ..settings import PUSH_NOTIFICATIONS_SETTINGS as SETTINGS
1212

1313

14-
# Fields
15-
class HexIntegerField(IntegerField):
14+
class UUIDIntegerField(UUIDField):
1615
"""
17-
Store an integer represented as a hex string of form "0x01".
16+
Store an integer represented as a UUID for backwards compatibiltiy. Also
17+
allows device_ids to be express as UUIDs.
1818
"""
1919

2020
def to_internal_value(self, data):
21-
# validate hex string and convert it to the unsigned
22-
# integer representation for internal use
2321
try:
22+
# maintain some semblence of backwards compatibility
2423
data = int(data, 16) if type(data) != int else data
2524
except ValueError:
2625
raise ValidationError("Device ID is not a valid hex number")
27-
return super(HexIntegerField, self).to_internal_value(data)
26+
return super(UUIDIntegerField, self).to_internal_value(data)
2827

2928
def to_representation(self, value):
3029
return value
@@ -90,9 +89,8 @@ def validate(self, attrs):
9089

9190

9291
class GCMDeviceSerializer(UniqueRegistrationSerializerMixin, ModelSerializer):
93-
device_id = HexIntegerField(
92+
device_id = UUIDIntegerField(
9493
help_text="ANDROID_ID / TelephonyManager.getDeviceId() (e.g: 0x01)",
95-
style={"input_type": "text"},
9694
required=False,
9795
allow_null=True
9896
)
@@ -105,12 +103,6 @@ class Meta(DeviceSerializerMixin.Meta):
105103
)
106104
extra_kwargs = {"id": {"read_only": False, "required": False}}
107105

108-
def validate_device_id(self, value):
109-
# device ids are 64 bit unsigned values
110-
if value > UNSIGNED_64BIT_INT_MAX_VALUE:
111-
raise ValidationError("Device ID is out of range")
112-
return value
113-
114106

115107
class WNSDeviceSerializer(UniqueRegistrationSerializerMixin, ModelSerializer):
116108
class Meta(DeviceSerializerMixin.Meta):

push_notifications/fields.py

+1-66
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
from django.utils.translation import ugettext_lazy as _
88

99

10-
__all__ = ["HexadecimalField", "HexIntegerField"]
10+
__all__ = ["HexadecimalField"]
1111

1212
UNSIGNED_64BIT_INT_MIN_VALUE = 0
1313
UNSIGNED_64BIT_INT_MAX_VALUE = 2 ** 64 - 1
@@ -58,68 +58,3 @@ def prepare_value(self, value):
5858
and connection.vendor in ("mysql", "sqlite"):
5959
value = _unsigned_integer_to_hex_string(value)
6060
return super(forms.CharField, self).prepare_value(value)
61-
62-
63-
class HexIntegerField(models.BigIntegerField):
64-
"""
65-
This field stores a hexadecimal *string* of up to 64 bits as an unsigned integer
66-
on *all* backends including postgres.
67-
68-
Reasoning: Postgres only supports signed bigints. Since we don't care about
69-
signedness, we store it as signed, and cast it to unsigned when we deal with
70-
the actual value (with struct)
71-
72-
On sqlite and mysql, native unsigned bigint types are used. In all cases, the
73-
value we deal with in python is always in hex.
74-
"""
75-
76-
validators = [
77-
MinValueValidator(UNSIGNED_64BIT_INT_MIN_VALUE),
78-
MaxValueValidator(UNSIGNED_64BIT_INT_MAX_VALUE)
79-
]
80-
81-
def db_type(self, connection):
82-
engine = connection.settings_dict["ENGINE"]
83-
if "mysql" in engine:
84-
return "bigint unsigned"
85-
elif "sqlite" in engine:
86-
return "UNSIGNED BIG INT"
87-
else:
88-
return super(HexIntegerField, self).db_type(connection=connection)
89-
90-
def get_prep_value(self, value):
91-
""" Return the integer value to be stored from the hex string """
92-
if value is None or value == "":
93-
return None
94-
if isinstance(value, six.string_types):
95-
value = _hex_string_to_unsigned_integer(value)
96-
if _using_signed_storage():
97-
value = _unsigned_to_signed_integer(value)
98-
return value
99-
100-
def from_db_value(self, value, expression, connection, context):
101-
""" Return an unsigned int representation from all db backends """
102-
if value is None:
103-
return value
104-
if _using_signed_storage():
105-
value = _signed_to_unsigned_integer(value)
106-
return value
107-
108-
def to_python(self, value):
109-
""" Return a str representation of the hexadecimal """
110-
if isinstance(value, six.string_types):
111-
return value
112-
if value is None:
113-
return value
114-
return _unsigned_integer_to_hex_string(value)
115-
116-
def formfield(self, **kwargs):
117-
defaults = {"form_class": HexadecimalField}
118-
defaults.update(kwargs)
119-
# yes, that super call is right
120-
return super(models.IntegerField, self).formfield(**defaults)
121-
122-
def run_validators(self, value):
123-
# make sure validation is performed on integer value not string value
124-
value = _hex_string_to_unsigned_integer(value)
125-
return super(models.BigIntegerField, self).run_validators(value)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
# -*- coding: utf-8 -*-
2+
# Generated by Django 1.10.6 on 2017-04-08 19:17
3+
from __future__ import unicode_literals
4+
5+
from django.db import migrations, models
6+
import uuid
7+
8+
9+
def migrate_device_id(apps, schema_editor):
10+
GCMDevice = apps.get_model("push_notifications", "GCMDevice")
11+
for device in GCMDevice.objects.all():
12+
device.device_uuid = uuid.UUID(int=int(device.device_id, 16))
13+
device.save()
14+
15+
16+
class Migration(migrations.Migration):
17+
18+
dependencies = [
19+
('push_notifications', '0005_applicationid'),
20+
]
21+
22+
operations = [
23+
migrations.AddField(
24+
model_name='gcmdevice',
25+
name='device_uuid',
26+
field=models.UUIDField(blank=True, db_index=True, help_text='ANDROID_ID / TelephonyManager.getDeviceId()', null=True, verbose_name='Device ID'),
27+
),
28+
migrations.RunPython(migrate_device_id),
29+
migrations.RemoveField(
30+
model_name='gcmdevice',
31+
name='device_id',
32+
),
33+
migrations.RenameField(
34+
model_name='gcmdevice',
35+
old_name='device_uuid',
36+
new_name='device_id',
37+
),
38+
]

push_notifications/models.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
from django.utils.encoding import python_2_unicode_compatible
44
from django.utils.translation import ugettext_lazy as _
55

6-
from .fields import HexIntegerField
76
from .settings import PUSH_NOTIFICATIONS_SETTINGS as SETTINGS
87

98

@@ -80,10 +79,11 @@ class GCMDevice(Device):
8079
# device_id cannot be a reliable primary key as fragmentation between different devices
8180
# can make it turn out to be null and such:
8281
# http://android-developers.blogspot.co.uk/2011/03/identifying-app-installations.html
83-
device_id = HexIntegerField(
82+
device_id = models.UUIDField(
8483
verbose_name=_("Device ID"), blank=True, null=True, db_index=True,
85-
help_text=_("ANDROID_ID / TelephonyManager.getDeviceId() (always as hex)")
84+
help_text=_("ANDROID_ID / TelephonyManager.getDeviceId()")
8685
)
86+
8787
registration_id = models.TextField(verbose_name=_("Registration ID"))
8888
cloud_message_type = models.CharField(
8989
verbose_name=_("Cloud Message Type"), max_length=3,

tests/test_rest_framework.py

-10
Original file line numberDiff line numberDiff line change
@@ -108,16 +108,6 @@ def test_device_id_validation_fail_bad_hex(self):
108108
self.assertFalse(serializer.is_valid())
109109
self.assertEqual(serializer.errors, GCM_DRF_INVALID_HEX_ERROR)
110110

111-
def test_device_id_validation_fail_out_of_range(self):
112-
serializer = GCMDeviceSerializer(data={
113-
"registration_id": "foobar",
114-
"name": "Galaxy Note 3",
115-
"device_id": "10000000000000000", # 2**64
116-
"application_id": "XXXXXXXXXXXXXXXXXXXX",
117-
})
118-
self.assertFalse(serializer.is_valid())
119-
self.assertEqual(serializer.errors, GCM_DRF_OUT_OF_RANGE_ERROR)
120-
121111
def test_device_id_validation_value_between_signed_unsigned_64b_int_maximums(self):
122112
"""
123113
2**63 < 0xe87a4e72d634997c < 2**64

0 commit comments

Comments
 (0)