models.py 6.88 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
from utils.tasks import revoke_task, schedule_task
11
from utils.translation import MultilingualField, ModelTranslateMeta
12
from .tasks import send_message
13
14
15


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

18
19
20
21
22
23
24
25
    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
26
27
28
29
30
31
    description = MultilingualField(
        models.TextField,
        _("description"),
        default=""
    )

32
33
    def __str__(self):
        return self.name_en
34
35


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


40
class Device(models.Model):
41
42
    """Describes a device"""

43
44
45
46
47
    DEVICE_TYPES = (
        ('ios', 'iOS'),
        ('android', 'Android')
    )

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

68
69
70
71
72
    receive_category = models.ManyToManyField(
        Category,
        default=default_receive_category
    )

73
74
75
76
    class Meta:
        unique_together = ('registration_id', 'user',)


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

    def get_queryset(self):
        return (super().get_queryset()
                .filter(scheduledmessage__task_id=None))


85
class Message(models.Model, metaclass=ModelTranslateMeta):
86
87
88
89
    """Describes a push notification"""

    objects = MessageManager()

90
    users = models.ManyToManyField(settings.AUTH_USER_MODEL)
91
92
    title = MultilingualField(
        models.CharField,
93
94
95
        max_length=150,
        verbose_name=_('title')
    )
96
97
    body = MultilingualField(
        models.TextField,
98
99
        verbose_name=_('body')
    )
100
101
102
    url = models.CharField(
        verbose_name=_('url'),
        max_length=256,
103
104
        null=True,
        blank=True,
105
    )
106
107
    category = models.ForeignKey(
        Category,
108
        on_delete=models.CASCADE,
109
        verbose_name=_('category'),
Luko van der Maas's avatar
Luko van der Maas committed
110
111
        default="general"
    )
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
    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:
132
133
            success_total = 0
            failure_total = 0
134
            ttl = kwargs.get('ttl', 3600)
135
136
137
138

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

146
                    data = kwargs.get('data', {})
147
                    if self.url is not None:
148
                        data['url'] = self.url
149

150
151
152
153
154
155
156
157
158
159
160
161
162
163
                    message = messaging.Message(
                        notification=messaging.Notification(
                            title=self.title,
                            body=str(self.body),
                        ),
                        data=data,
                        android=messaging.AndroidConfig(
                            ttl=datetime.timedelta(seconds=ttl),
                            priority='normal',
                            notification=messaging.AndroidNotification(
                                color='#E62272',
                                sound='default',
                            ),
                        ),
164
165
                    )

166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
                    for reg_id in reg_ids:
                        message.token = reg_id
                        try:
                            messaging.send(message)
                            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)

            if success_total > 0 or failure_total > 0:
182
183
184
185
                self.sent = True
                self.success = success_total
                self.failure = failure_total
                self.save()
186
        return None
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225


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()

    task_id = models.CharField(max_length=50, blank=True, null=True)
    time = models.DateTimeField()

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self._time = self.time

    def schedule(self):
        """Schedules a Celery task to send this message"""
        return schedule_task(send_message, args=(self.pk,), eta=self.time)

    def save(self, *args, **kwargs):
        """Custom save method which also schedules the task"""

        if not (self._time == self.time):
            if self.task_id:
                # Revoke that task in case its time has changed
                revoke_task(self.task_id)
            super().save(*args, **kwargs)
            self.task_id = self.schedule()

        super().save(*args, **kwargs)

    def delete(self, using=None, keep_parents=False):
        if self.task_id:
226
            revoke_task(self.task_id)
227
        return super().delete(using, keep_parents)