models.py 6.24 KB
Newer Older
1
"""The models defined by the pushnotifications package"""
2
3
import datetime

4
from django.conf import settings
5
from django.db import models
6
from django.utils.translation import override
7
from django.utils.translation import ugettext_lazy as _
8
from firebase_admin import messaging
9

10
11
12
13
from utils.translation import MultilingualField, ModelTranslateMeta


class Category(models.Model, metaclass=ModelTranslateMeta):
14
15
    """Describes a Message category"""

16
17
18
19
20
21
22
23
    key = models.CharField(max_length=16, primary_key=True)

    name = MultilingualField(
        models.CharField,
        _("name"),
        max_length=32,
    )

Luko van der Maas's avatar
Luko van der Maas committed
24
25
26
27
28
29
    description = MultilingualField(
        models.TextField,
        _("description"),
        default=""
    )

30
31
    def __str__(self):
        return self.name_en
32
33


34
35
36
37
def default_receive_category():
    return Category.objects.filter(key="general")


38
class Device(models.Model):
39
40
    """Describes a device"""

41
42
43
44
45
    DEVICE_TYPES = (
        ('ios', 'iOS'),
        ('android', 'Android')
    )

46
    registration_id = models.TextField(verbose_name=_("registration token"))
47
48
    type = models.CharField(choices=DEVICE_TYPES, max_length=10)
    active = models.BooleanField(
49
        verbose_name=_("active"), default=True,
50
51
        help_text=_("Inactive devices will not be sent notifications")
    )
52
    user = models.ForeignKey(settings.AUTH_USER_MODEL,
Thom Wiggers's avatar
Thom Wiggers committed
53
54
55
                             on_delete=models.CASCADE,
                             blank=False,
                             null=False)
56
    date_created = models.DateTimeField(
57
58
59
60
61
62
        verbose_name=_("registration date"), auto_now_add=True, null=False
    )
    language = models.CharField(
        verbose_name=_('language'),
        max_length=2,
        choices=settings.LANGUAGES,
63
        default='en',
64
65
    )

66
67
68
69
70
    receive_category = models.ManyToManyField(
        Category,
        default=default_receive_category
    )

71
72
73
    class Meta:
        unique_together = ('registration_id', 'user',)

74
75
76
77
78
    def __str__(self):
        return _(
            "{user}s {device_type} device"
        ).format(user=self.user, device_type=self.type)

79

80
81
82
83
84
class MessageManager(models.Manager):
    """Returns manual messages only"""

    def get_queryset(self):
        return (super().get_queryset()
Sébastiaan Versteeg's avatar
Sébastiaan Versteeg committed
85
                .filter(scheduledmessage__scheduled=None))
86
87


88
class Message(models.Model, metaclass=ModelTranslateMeta):
89
90
91
92
    """Describes a push notification"""

    objects = MessageManager()

93
    users = models.ManyToManyField(settings.AUTH_USER_MODEL)
94
95
    title = MultilingualField(
        models.CharField,
96
97
98
        max_length=150,
        verbose_name=_('title')
    )
99
100
    body = MultilingualField(
        models.TextField,
101
102
        verbose_name=_('body')
    )
103
104
105
    url = models.CharField(
        verbose_name=_('url'),
        max_length=256,
106
107
        null=True,
        blank=True,
108
    )
109
110
    category = models.ForeignKey(
        Category,
111
        on_delete=models.CASCADE,
112
        verbose_name=_('category'),
Luko van der Maas's avatar
Luko van der Maas committed
113
114
        default="general"
    )
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
    sent = models.BooleanField(
        verbose_name=_('sent'),
        default=False
    )
    failure = models.IntegerField(
        verbose_name=_('failure'),
        blank=True,
        null=True,
    )
    success = models.IntegerField(
        verbose_name=_('success'),
        blank=True,
        null=True,
    )

    def __str__(self):
        return '{}: {}'.format(self.title, self.body)

    def send(self, **kwargs):
        if self:
135
136
            success_total = 0
            failure_total = 0
137
            ttl = kwargs.get('ttl', 3600)
138
139
140
141

            for lang in settings.LANGUAGES:
                with override(lang[0]):
                    reg_ids = list(
142
                        Device.objects.filter(
143
144
145
146
147
148
                            user__in=self.users.all(),
                            receive_category__key=self.category_id,
                            active=True,
                            language=lang[0]
                        ).values_list('registration_id', flat=True))

149
                    data = kwargs.get('data', {})
150
                    if self.url is not None:
151
                        data['url'] = self.url
152
153
                    data['title'] = self.title
                    data['body'] = str(self.body)
154

155
156
                    message = messaging.Message(
                        notification=messaging.Notification(
157
158
                            title=data['title'],
                            body=data['body'],
159
160
161
162
163
164
165
166
167
168
                        ),
                        data=data,
                        android=messaging.AndroidConfig(
                            ttl=datetime.timedelta(seconds=ttl),
                            priority='normal',
                            notification=messaging.AndroidNotification(
                                color='#E62272',
                                sound='default',
                            ),
                        ),
169
170
                    )

171
172
173
                    for reg_id in reg_ids:
                        message.token = reg_id
                        try:
Sébastiaan Versteeg's avatar
Sébastiaan Versteeg committed
174
                            messaging.send(message, dry_run=kwargs.get(
175
                                'dry_run', False))
176
177
178
179
180
181
182
183
184
185
186
                            success_total += 1
                        except messaging.ApiCallError as e:
                            failure_total += 1
                            d = Device.objects.filter(registration_id=reg_id)
                            if e.code == 'registration-token-not-registered':
                                d.delete()
                            elif (e.code == 'invalid-argument'
                                    or e.code == 'invalid-recipient'
                                    or e.code == 'invalid-registration-token'):
                                d.update(active=False)

187
188
189
190
            self.sent = True
            self.success = success_total
            self.failure = failure_total
            self.save()
191
        return None
192
193
194
195
196
197
198
199
200
201
202
203
204
205


class ScheduledMessageManager(models.Manager):
    """Returns scheduled messages only"""

    def get_queryset(self):
        return super().get_queryset()


class ScheduledMessage(Message, metaclass=ModelTranslateMeta):
    """Describes a scheduled push notification"""

    objects = ScheduledMessageManager()

Sébastiaan Versteeg's avatar
Sébastiaan Versteeg committed
206
    scheduled = models.BooleanField(default=True)
207
    time = models.DateTimeField()
208
    executed = models.DateTimeField(null=True)