Verified Commit 70b089d7 authored by Sébastiaan Versteeg's avatar Sébastiaan Versteeg
Browse files

Add reminder notifications for events

parent c1ad2911
......@@ -19,6 +19,7 @@ services:
- 8000:8000
depends_on:
- postgres
- celery
volumes:
- ./website:/usr/src/app/website/
- concrexit:/concrexit/
......@@ -27,15 +28,13 @@ services:
DJANGO_DEBUG: 'True'
DJANGO_POSTGRES_HOST: postgres
CELERY_REDIS_HOST: redis
YOLO_ENV: 'tralala'
celery:
image: registry.gitlab.com/thaliawww/concrexit
build: .
entrypoint: /usr/local/bin/entrypoint_celery.sh
volumes_from:
- web
links:
- postgres
volumes:
- ./website:/usr/src/app/website/
- concrexit:/concrexit/
depends_on:
- redis
environment:
......
......@@ -40,6 +40,14 @@ pushnotifications\.models module
:undoc-members:
:show-inheritance:
pushnotifications\.tasks module
-------------------------------
.. automodule:: pushnotifications.tasks
:members:
:undoc-members:
:show-inheritance:
pushnotifications\.urls module
------------------------------
......
......@@ -33,6 +33,14 @@ utils\.snippets module
:undoc-members:
:show-inheritance:
utils\.tasks module
-------------------
.. automodule:: utils.tasks
:members:
:undoc-members:
:show-inheritance:
utils\.threading module
-----------------------
......
# Generated by Django 2.0.2 on 2018-06-13 18:46
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('pushnotifications', '0008_scheduledmessage'),
('events', '0025_auto_20180112_1214'),
]
operations = [
migrations.AddField(
model_name='event',
name='registration_reminder',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='registration_event', to='pushnotifications.ScheduledMessage'),
),
migrations.AddField(
model_name='event',
name='start_reminder',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='start_event', to='pushnotifications.ScheduledMessage'),
),
]
......@@ -9,6 +9,8 @@ from django.utils.translation import ugettext_lazy as _
from django.utils.text import format_lazy
from tinymce.models import HTMLField
from members.models import Member
from pushnotifications.models import ScheduledMessage, Category
from utils.translation import ModelTranslateMeta, MultilingualField
......@@ -135,6 +137,13 @@ class Event(models.Model, metaclass=ModelTranslateMeta):
published = models.BooleanField(_("published"), default=False)
registration_reminder = models.ForeignKey(
ScheduledMessage, on_delete=models.deletion.SET_NULL,
related_name='registration_event', blank=True, null=True)
start_reminder = models.ForeignKey(
ScheduledMessage, on_delete=models.deletion.SET_NULL,
related_name='start_event', blank=True, null=True)
@property
def after_cancel_deadline(self):
return self.cancel_deadline and self.cancel_deadline <= timezone.now()
......@@ -268,6 +277,47 @@ class Event(models.Model, metaclass=ModelTranslateMeta):
def get_absolute_url(self):
return reverse('events:event', args=[str(self.pk)])
def save(self, *args, **kwargs):
if self.registration_required:
registration_reminder = ScheduledMessage()
if self.registration_reminder is not None:
registration_reminder = self.registration_reminder
registration_reminder.title_en = 'Event registration'
registration_reminder.title_nl = 'Evenement registratie'
registration_reminder.body_en = ('Registration for \'{}\' '
'starts in 1 hour'
.format(self.title_en))
registration_reminder.body_nl = ('Registratie voor \'{}\' '
'start in 1 uur'
.format(self.title_nl))
registration_reminder.category = Category.objects.get(key='event')
registration_reminder.time = (self.registration_start -
timezone.timedelta(hours=1))
registration_reminder.save()
self.registration_reminder = registration_reminder
self.registration_reminder.users.set(Member.active_members.all())
start_reminder = ScheduledMessage()
if self.start_reminder is not None:
start_reminder = self.start_reminder
start_reminder.title_en = 'Event'
start_reminder.title_nl = 'Evenement'
start_reminder.body_en = ('\'{}\' starts in '
'1 hour'.format(self.title_en))
start_reminder.body_nl = ('\'{}\' begint over '
'1 uur'.format(self.title_nl))
start_reminder.category = Category.objects.get(key='event')
start_reminder.time = (self.start - timezone.timedelta(hours=1))
start_reminder.save()
self.start_reminder = start_reminder
if self.registration_required:
self.start_reminder.users.set(self.participants.values_list(
'member', flat=True))
else:
self.start_reminder.users.set(Member.active_members.all())
super().save(*args, **kwargs)
def __str__(self):
return '{}: {}'.format(
self.title,
......@@ -392,6 +442,16 @@ class Registration(models.Model):
def validate_unique(self, exclude=None):
super().validate_unique(exclude)
def save(self, *args, **kwargs):
super().save(*args, **kwargs)
if self.event.start_reminder and self.date_cancelled:
self.event.start_reminder.users.remove(self.member)
elif (self.event.start_reminder and self.member is not None and
not self.event.start_reminder.users
.filter(pk=self.member.pk).exists()):
self.event.start_reminder.users.add(self.member)
def __str__(self):
if self.member:
return '{}: {}'.format(self.member.get_full_name(), self.event)
......
......@@ -52,3 +52,28 @@ class MessageAdmin(TranslatedModelAdmin):
obj = Message.objects.filter(id=object_id)[0]
return super(MessageAdmin, self).change_view(
request, object_id, form_url, {'message': obj})
@admin.register(models.ScheduledMessage)
class ScheduledMessageAdmin(TranslatedModelAdmin):
list_display = ('title', 'body', 'time', 'category', 'sent', 'success',
'failure')
date_hierarchy = 'time'
filter_horizontal = ('users',)
list_filter = ('sent', 'category')
def get_fields(self, request, obj=None):
if obj and obj.sent:
return ('users', 'title_nl', 'title_en', 'body_nl', 'body_en',
'category', 'success', 'failure', 'time', 'task_id')
return ('users', 'title_nl', 'title_en', 'body_nl', 'body_en',
'category', 'time', 'task_id')
def get_readonly_fields(self, request, obj=None):
if obj and obj.sent:
return ('users', 'title_nl', 'title_en', 'body_nl', 'body_en',
'category', 'success', 'failure', 'time', 'task_id')
return 'task_id',
def has_add_permission(self, request):
return False
# Generated by Django 2.0.2 on 2018-06-13 18:46
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('pushnotifications', '0007_auto_20180307_2148'),
]
operations = [
migrations.CreateModel(
name='ScheduledMessage',
fields=[
('message_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='pushnotifications.Message')),
('task_id', models.CharField(blank=True, max_length=50, null=True)),
('time', models.DateTimeField()),
],
bases=('pushnotifications.message',),
),
]
......@@ -2,14 +2,19 @@ from __future__ import unicode_literals
from django.conf import settings
from django.db import models
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import override
from django.utils.translation import ugettext_lazy as _
from pyfcm import FCMNotification
from utils.tasks import revoke_task, schedule_task
from utils.translation import MultilingualField, ModelTranslateMeta
from .tasks import send_message
from thaliawebsite import celery_app
class Category(models.Model, metaclass=ModelTranslateMeta):
"""Describes a Message category"""
key = models.CharField(max_length=16, primary_key=True)
name = MultilingualField(
......@@ -27,6 +32,8 @@ def default_receive_category():
class Device(models.Model):
"""Describes a device"""
DEVICE_TYPES = (
('ios', 'iOS'),
('android', 'Android')
......@@ -61,7 +68,19 @@ class Device(models.Model):
unique_together = ('registration_id', 'user',)
class MessageManager(models.Manager):
"""Returns manual messages only"""
def get_queryset(self):
return (super().get_queryset()
.filter(scheduledmessage__task_id=None))
class Message(models.Model, metaclass=ModelTranslateMeta):
"""Describes a push notification"""
objects = MessageManager()
GENERAL = 'general'
PIZZA = 'pizza'
EVENT = 'event'
......@@ -173,3 +192,44 @@ class Message(models.Model, metaclass=ModelTranslateMeta):
return result_list
return None
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:
celery_app.control.revoke(self.task_id)
return super().delete(using, keep_parents)
from __future__ import absolute_import
from celery import shared_task
from django.apps import apps
@shared_task
def send_message(message_id):
"""Send a push notification"""
print('Sending push notification {}'.format(message_id))
ScheduledMessage = apps.get_model('pushnotifications', 'ScheduledMessage')
try:
message = ScheduledMessage.objects.get(pk=message_id)
except ScheduledMessage.DoesNotExist:
print('Cannot find ScheduledMessage')
return
message.send()
......@@ -129,6 +129,7 @@ if os.environ.get('DJANGO_EMAIL_HOST'):
CELERY_BROKER_URL = 'redis://{}:6379/0'.format(
os.environ.get('CELERY_REDIS_HOST')
)
CELERY_ENABLED = True
# Secure headers
X_FRAME_OPTIONS = 'DENY'
......
......@@ -247,6 +247,7 @@ CELERY_BROKER_URL = 'redis://localhost:6379/0'
# http://docs.celeryproject.org/en/latest/getting-started/brokers/redis.html#id1
CELERY_BROKER_TRANSPORT_OPTIONS = {'visibility_timeout': 15778800}
CELERY_RESULT_BACKEND = 'django-db'
CELERY_ENABLED = False
# Membership prices
MEMBERSHIP_PRICES = {
......
......@@ -41,3 +41,6 @@ PASSWORD_HASHERS = (
'django.middleware.csrf.CsrfViewMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
)]
# Celery not needed for testing
CELERY_ENABLED = False
from django.utils import timezone
from thaliawebsite import celery_app
from django.conf import settings
def schedule_task(task, args=(), eta=timezone.now()):
if settings.CELERY_ENABLED:
result = task.apply_async(args, eta=eta)
return result.id
return None
def revoke_task(task_id):
if settings.CELERY_ENABLED:
celery_app.control.revoke(task_id)
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment