Add on save hooks for events, feed and jobads
This commit is contained in:
+1
-1
@@ -17,7 +17,7 @@ from kaehmy.models import Application, CustomRole, PresetRole
|
|||||||
from kaehmy.forms import ApplicationForm, CommentForm
|
from kaehmy.forms import ApplicationForm, CommentForm
|
||||||
from kaehmy.tables import ExportTable
|
from kaehmy.tables import ExportTable
|
||||||
from webapp.utils import send_email
|
from webapp.utils import send_email
|
||||||
from webapp.webhook import processHooks
|
from webapp.models import processHooks
|
||||||
|
|
||||||
|
|
||||||
@ensure_csrf_cookie
|
@ensure_csrf_cookie
|
||||||
|
|||||||
+4
-13
@@ -1,26 +1,17 @@
|
|||||||
"""Ohlhafv views."""
|
import logging
|
||||||
|
from django.shortcuts import render
|
||||||
from django.db.models import Count
|
|
||||||
from django.shortcuts import render, redirect
|
|
||||||
from django.contrib.auth import login, logout, authenticate
|
|
||||||
from django.views.decorators.http import require_http_methods
|
from django.views.decorators.http import require_http_methods
|
||||||
from django.views.decorators.csrf import ensure_csrf_cookie
|
from django.views.decorators.csrf import ensure_csrf_cookie
|
||||||
from django.http import HttpResponse, HttpResponseRedirect
|
from django.http import HttpResponseRedirect
|
||||||
from django.contrib.auth.decorators import permission_required, login_required
|
|
||||||
from django.conf import settings
|
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
from django.template.loader import render_to_string
|
from django.template.loader import render_to_string
|
||||||
|
|
||||||
import logging
|
|
||||||
import requests
|
|
||||||
from dealer.git import git
|
|
||||||
from sikweb.settings import URL
|
from sikweb.settings import URL
|
||||||
|
|
||||||
from ohlhafv.models import OhlhafvChallenge
|
from ohlhafv.models import OhlhafvChallenge
|
||||||
from ohlhafv.forms import OhlhafvForm
|
from ohlhafv.forms import OhlhafvForm
|
||||||
from ohlhafv.tables import OhlhafvTable
|
|
||||||
from webapp.utils import send_email
|
from webapp.utils import send_email
|
||||||
from webapp.webhook import processHooks
|
from webapp.models import processHooks
|
||||||
|
|
||||||
|
|
||||||
@require_http_methods(["GET"])
|
@require_http_methods(["GET"])
|
||||||
|
|||||||
+139
-61
@@ -1,24 +1,23 @@
|
|||||||
"""Webapp app models."""
|
"""Webapp app models."""
|
||||||
|
|
||||||
from django.conf import settings
|
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.db.models.fields import CharField
|
from django.template.loader import render_to_string
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
# from datetime import timedelta
|
|
||||||
from django.contrib.auth.models import User
|
|
||||||
from django.db.models.signals import post_save
|
from django.db.models.signals import post_save
|
||||||
from django.dispatch import receiver
|
from django.dispatch import receiver
|
||||||
|
|
||||||
|
# from datetime import timedelta
|
||||||
import requests
|
import requests
|
||||||
from webapp.utils import month_from_now, send_signup_email
|
|
||||||
from django.utils.translation import ugettext_lazy as _
|
|
||||||
from auditlog.registry import auditlog
|
|
||||||
from phonenumber_field.modelfields import PhoneNumberField
|
|
||||||
from django.contrib.postgres.fields import JSONField
|
|
||||||
from polymorphic.models import PolymorphicModel
|
|
||||||
from uuid import uuid4
|
from uuid import uuid4
|
||||||
import logging
|
import logging
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
from django.contrib.postgres.fields import JSONField
|
||||||
|
from auditlog.registry import auditlog
|
||||||
|
from polymorphic.models import PolymorphicModel
|
||||||
|
from webapp.utils import month_from_now, send_signup_email
|
||||||
|
from sikweb.settings import FRONTEND_URL
|
||||||
|
|
||||||
VERBOSE_NAME = _('Webapp')
|
VERBOSE_NAME = _("Webapp")
|
||||||
EMAIL_REGEX = r"(^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$)"
|
EMAIL_REGEX = r"(^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$)"
|
||||||
|
|
||||||
|
|
||||||
@@ -26,15 +25,15 @@ class Tag(models.Model):
|
|||||||
"""Model for tag."""
|
"""Model for tag."""
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = _('Tag')
|
verbose_name = _("Tag")
|
||||||
verbose_name_plural = _('Tags')
|
verbose_name_plural = _("Tags")
|
||||||
|
|
||||||
slug = models.SlugField(unique=True)
|
slug = models.SlugField(unique=True)
|
||||||
name = models.CharField(max_length=127)
|
name = models.CharField(max_length=127)
|
||||||
icon = models.ImageField()
|
icon = models.ImageField()
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return _('Tag: {}').format(self.slug)
|
return _("Tag: {}").format(self.slug)
|
||||||
|
|
||||||
|
|
||||||
class BaseFeed(models.Model):
|
class BaseFeed(models.Model):
|
||||||
@@ -52,8 +51,8 @@ class Feed(BaseFeed):
|
|||||||
"""Model representing feed."""
|
"""Model representing feed."""
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = _('Feed')
|
verbose_name = _("Feed")
|
||||||
verbose_name_plural = _('Feeds')
|
verbose_name_plural = _("Feeds")
|
||||||
|
|
||||||
publish_time = models.DateTimeField(default=timezone.now)
|
publish_time = models.DateTimeField(default=timezone.now)
|
||||||
autohide = models.DateTimeField(default=month_from_now)
|
autohide = models.DateTimeField(default=month_from_now)
|
||||||
@@ -62,26 +61,67 @@ class Feed(BaseFeed):
|
|||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
delete_str = _("Deleted: ") if self.deleted else ""
|
delete_str = _("Deleted: ") if self.deleted else ""
|
||||||
return _('{}Feed: {}').format(delete_str, self.title)
|
return _("{}Feed: {}").format(delete_str, self.title)
|
||||||
|
|
||||||
|
__previousVisible = False
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super(Feed, self).__init__(*args, **kwargs)
|
||||||
|
self.__previousVisible = self.visible
|
||||||
|
|
||||||
|
def save(self, force_insert=False, force_update=False, *args, **kwargs):
|
||||||
|
created = self.pk is None
|
||||||
|
super(Feed, self).save(force_insert, force_update, *args, **kwargs)
|
||||||
|
|
||||||
|
if self.visible and (created or not self.__previousVisible):
|
||||||
|
self.refresh_from_db() # Fetch so we can use primary key
|
||||||
|
url = f"https://{FRONTEND_URL}/feed/{self.pk}"
|
||||||
|
processHooks(
|
||||||
|
message=generateMessage(
|
||||||
|
"Uusi uutinen", self.title, self.description, url
|
||||||
|
),
|
||||||
|
eventType="feed",
|
||||||
|
)
|
||||||
|
self.__previousVisible = self.visible
|
||||||
|
|
||||||
|
|
||||||
class Event(BaseFeed):
|
class Event(BaseFeed):
|
||||||
"""Model for event in guild calendar"""
|
"""Model for event in guild calendar"""
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = _('Event')
|
verbose_name = _("Event")
|
||||||
verbose_name_plural = _('Events')
|
verbose_name_plural = _("Events")
|
||||||
|
|
||||||
start_time = models.DateTimeField(default=timezone.now)
|
start_time = models.DateTimeField(default=timezone.now)
|
||||||
end_time = models.DateTimeField(default=timezone.now)
|
end_time = models.DateTimeField(default=timezone.now)
|
||||||
signupForm = models.ManyToManyField(
|
signupForm = models.ManyToManyField("SignupForm", blank=True, related_name="event")
|
||||||
'SignupForm', blank=True, related_name="event")
|
|
||||||
location = models.CharField(max_length=255, blank=True)
|
location = models.CharField(max_length=255, blank=True)
|
||||||
deleted = models.BooleanField(default=False)
|
deleted = models.BooleanField(default=False)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
delete_str = _("Deleted: ") if self.deleted else ""
|
delete_str = _("Deleted: ") if self.deleted else ""
|
||||||
return _('{}Event: {}').format(delete_str, self.title)
|
return _("{}Event: {}").format(delete_str, self.title)
|
||||||
|
|
||||||
|
__previousVisible = False
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super(Event, self).__init__(*args, **kwargs)
|
||||||
|
self.__previousVisible = self.visible
|
||||||
|
|
||||||
|
def save(self, force_insert=False, force_update=False, *args, **kwargs):
|
||||||
|
created = self.pk is None
|
||||||
|
super(Event, self).save(force_insert, force_update, *args, **kwargs)
|
||||||
|
|
||||||
|
if self.visible and (created or not self.__previousVisible):
|
||||||
|
self.refresh_from_db() # Fetch so we can use primary key
|
||||||
|
url = f"https://{FRONTEND_URL}/events/{self.pk}"
|
||||||
|
processHooks(
|
||||||
|
message=generateMessage(
|
||||||
|
"Uusi tapahtuma", self.title, self.description, url
|
||||||
|
),
|
||||||
|
eventType="event",
|
||||||
|
)
|
||||||
|
self.__previousVisible = self.visible
|
||||||
|
|
||||||
|
|
||||||
class TemplateQuestion(models.Model):
|
class TemplateQuestion(models.Model):
|
||||||
@@ -90,23 +130,23 @@ class TemplateQuestion(models.Model):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = _('Template question')
|
verbose_name = _("Template question")
|
||||||
verbose_name_plural = _('Template questions')
|
verbose_name_plural = _("Template questions")
|
||||||
|
|
||||||
name = models.CharField(max_length=255)
|
name = models.CharField(max_length=255)
|
||||||
questions = JSONField()
|
questions = JSONField()
|
||||||
deleted = models.BooleanField(default=False)
|
deleted = models.BooleanField(default=False)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return _('Template questions: {}').format(self.name)
|
return _("Template questions: {}").format(self.name)
|
||||||
|
|
||||||
|
|
||||||
class SignupForm(models.Model):
|
class SignupForm(models.Model):
|
||||||
"""Model for event signup form. Stores questions in JSON format."""
|
"""Model for event signup form. Stores questions in JSON format."""
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = _('Signup form')
|
verbose_name = _("Signup form")
|
||||||
verbose_name_plural = _('Signup forms')
|
verbose_name_plural = _("Signup forms")
|
||||||
|
|
||||||
title = models.CharField(max_length=255)
|
title = models.CharField(max_length=255)
|
||||||
start_time = models.DateTimeField(default=timezone.now)
|
start_time = models.DateTimeField(default=timezone.now)
|
||||||
@@ -120,11 +160,11 @@ class SignupForm(models.Model):
|
|||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
delete_str = _("Deleted: ") if self.deleted else ""
|
delete_str = _("Deleted: ") if self.deleted else ""
|
||||||
return _('#{} {}{}').format(self.id, delete_str, self.title)
|
return _("#{} {}{}").format(self.id, delete_str, self.title)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def signups(self):
|
def signups(self):
|
||||||
return Signup.objects.filter(signupForm=self, deleted=False).order_by('pk')
|
return Signup.objects.filter(signupForm=self, deleted=False).order_by("pk")
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def isOpen(self):
|
def isOpen(self):
|
||||||
@@ -138,13 +178,14 @@ class Signup(models.Model):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = _('Sign-up')
|
verbose_name = _("Sign-up")
|
||||||
verbose_name_plural = _('Sign-ups')
|
verbose_name_plural = _("Sign-ups")
|
||||||
signupForm = models.ForeignKey('SignupForm', on_delete=models.CASCADE)
|
|
||||||
|
signupForm = models.ForeignKey("SignupForm", on_delete=models.CASCADE)
|
||||||
time = models.DateTimeField(default=timezone.now)
|
time = models.DateTimeField(default=timezone.now)
|
||||||
answer = JSONField()
|
answer = JSONField()
|
||||||
# Answer we use in signupForm signups field. Frontend uses first questions answer as this value.
|
# Answer we use in signupForm signups field. Frontend uses first questions answer as this value.
|
||||||
list_name = models.CharField(_('Name'), max_length=255)
|
list_name = models.CharField(_("Name"), max_length=255)
|
||||||
# If there is email in questions, we save it as own field
|
# If there is email in questions, we save it as own field
|
||||||
email = models.EmailField(blank=True, null=True)
|
email = models.EmailField(blank=True, null=True)
|
||||||
# Random unique identifier. Used for signup editing by the user.
|
# Random unique identifier. Used for signup editing by the user.
|
||||||
@@ -167,26 +208,32 @@ def email_on_signup(sender, instance, created, **kwargs):
|
|||||||
except AttributeError:
|
except AttributeError:
|
||||||
# subject = _(f"Olet ilmoittautunut ilmoon {instance.signupForm.title}")
|
# subject = _(f"Olet ilmoittautunut ilmoon {instance.signupForm.title}")
|
||||||
subject = f"Olet ilmoittautunut ilmoon {instance.signupForm.title}"
|
subject = f"Olet ilmoittautunut ilmoon {instance.signupForm.title}"
|
||||||
send_signup_email(instance.email, subject, instance.id, instance.uuid, instance.signupForm.email_content)
|
send_signup_email(
|
||||||
|
instance.email,
|
||||||
|
subject,
|
||||||
|
instance.id,
|
||||||
|
instance.uuid,
|
||||||
|
instance.signupForm.email_content,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class BaseRole(models.Model):
|
class BaseRole(models.Model):
|
||||||
"""Base model for occupations/roles."""
|
"""Base model for occupations/roles."""
|
||||||
|
|
||||||
name = models.CharField(_('Name'), max_length=255)
|
name = models.CharField(_("Name"), max_length=255)
|
||||||
is_board = models.BooleanField(_('Board member'))
|
is_board = models.BooleanField(_("Board member"))
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
n = self.name.capitalize()
|
n = self.name.capitalize()
|
||||||
return '{} ({})'.format(n, _('board member')) if self.is_board else n
|
return "{} ({})".format(n, _("board member")) if self.is_board else n
|
||||||
|
|
||||||
|
|
||||||
class JobAd(models.Model):
|
class JobAd(models.Model):
|
||||||
"""Job advertisements shown on Corporate relations page"""
|
"""Job advertisements shown on Corporate relations page"""
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = _('JobAd')
|
verbose_name = _("JobAd")
|
||||||
verbose_name_plural = _('JobAds')
|
verbose_name_plural = _("JobAds")
|
||||||
|
|
||||||
title = models.CharField(max_length=255)
|
title = models.CharField(max_length=255)
|
||||||
description = models.CharField(max_length=255)
|
description = models.CharField(max_length=255)
|
||||||
@@ -199,21 +246,57 @@ class JobAd(models.Model):
|
|||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
delete_str = _("Deleted: ") if self.deleted else ""
|
delete_str = _("Deleted: ") if self.deleted else ""
|
||||||
return f'{delete_str}{self.title}'
|
return f"{delete_str}{self.title}"
|
||||||
|
|
||||||
|
__previousVisible = False
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super(JobAd, self).__init__(*args, **kwargs)
|
||||||
|
self.__previousVisible = self.visible
|
||||||
|
|
||||||
|
def save(self, force_insert=False, force_update=False, *args, **kwargs):
|
||||||
|
created = self.pk is None
|
||||||
|
super(JobAd, self).save(force_insert, force_update, *args, **kwargs)
|
||||||
|
|
||||||
|
if self.visible and (created or not self.__previousVisible):
|
||||||
|
self.refresh_from_db() # Fetch so we can use primary key
|
||||||
|
url = f"https://{FRONTEND_URL}/jobads/{self.pk}"
|
||||||
|
processHooks(
|
||||||
|
message=generateMessage(
|
||||||
|
"Uusi työpaikkailmoitus", self.title, self.description, url
|
||||||
|
),
|
||||||
|
eventType="jobad",
|
||||||
|
)
|
||||||
|
self.__previousVisible = self.visible
|
||||||
|
|
||||||
|
def generateMessage(heading: str, title: str, description: str, url: str):
|
||||||
|
return render_to_string(
|
||||||
|
"webapp:tg_message.tpl",
|
||||||
|
{"heading": heading, "title": title, "description": description, "url": url},
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def processHooks(message: str, eventType: str):
|
||||||
|
allHooks = BaseWebhook.objects.all()
|
||||||
|
for hook in list(allHooks):
|
||||||
|
if hook.plugs[eventType] == True:
|
||||||
|
hook.broadcast(message)
|
||||||
|
|
||||||
|
|
||||||
class BaseWebhook(PolymorphicModel):
|
class BaseWebhook(PolymorphicModel):
|
||||||
"""Webhook base class instance"""
|
"""Webhook base class instance"""
|
||||||
|
|
||||||
name = models.CharField(max_length=255)
|
name = models.CharField(max_length=255)
|
||||||
url = models.URLField() # URL where webhook message is broadcast. For example Telegram webhook API
|
url = (
|
||||||
|
models.URLField()
|
||||||
|
) # URL where webhook message is broadcast. For example Telegram webhook API
|
||||||
# "Plugs"; which notifications are sent to this specific webhook instance
|
# "Plugs"; which notifications are sent to this specific webhook instance
|
||||||
kaehmy_submit = models.BooleanField(_("Hook Kaehmys"), default=False)
|
kaehmy_submit = models.BooleanField(_("Hook Kaehmys"), default=False)
|
||||||
ohlhafv_submit = models.BooleanField(_("Hook Ohlhafv challenges"),default=False)
|
ohlhafv_submit = models.BooleanField(_("Hook Ohlhafv challenges"), default=False)
|
||||||
feed_published = models.BooleanField(_("Hook published news"),default=False)
|
feed_published = models.BooleanField(_("Hook published news"), default=False)
|
||||||
jobad_published = models.BooleanField(_("Hook published Job Ads"),default=False)
|
jobad_published = models.BooleanField(_("Hook published Job Ads"), default=False)
|
||||||
event_published = models.BooleanField(_("Hook published events"),default=False)
|
event_published = models.BooleanField(_("Hook published events"), default=False)
|
||||||
signup_opened = models.BooleanField(_("Hook opened signups"),default=False)
|
signup_opened = models.BooleanField(_("Hook opened signups"), default=False)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def plugs(self):
|
def plugs(self):
|
||||||
@@ -223,7 +306,7 @@ class BaseWebhook(PolymorphicModel):
|
|||||||
"feed": self.feed_published,
|
"feed": self.feed_published,
|
||||||
"jobad": self.jobad_published,
|
"jobad": self.jobad_published,
|
||||||
"event": self.event_published,
|
"event": self.event_published,
|
||||||
"signup":self.signup_opened,
|
"signup": self.signup_opened,
|
||||||
}
|
}
|
||||||
|
|
||||||
def parseData(self):
|
def parseData(self):
|
||||||
@@ -231,30 +314,29 @@ class BaseWebhook(PolymorphicModel):
|
|||||||
|
|
||||||
def broadcast(self, message):
|
def broadcast(self, message):
|
||||||
resp = requests.post(self.url, json=self.parseData(message))
|
resp = requests.post(self.url, json=self.parseData(message))
|
||||||
logging.debug(f'Webhook API response: HTTP{resp.status_code}')
|
logging.debug(f"Webhook API response: HTTP{resp.status_code}")
|
||||||
logging.debug(resp.content)
|
logging.debug(resp.content)
|
||||||
|
|
||||||
|
|
||||||
class GenericWebhook(BaseWebhook):
|
class GenericWebhook(BaseWebhook):
|
||||||
class Meta:
|
class Meta:
|
||||||
proxy = True
|
proxy = True
|
||||||
verbose_name = _('Webhook')
|
verbose_name = _("Webhook")
|
||||||
verbose_name_plural = _('Webhooks')
|
verbose_name_plural = _("Webhooks")
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return 'Webhook "{}"'.format(self.name)
|
return 'Webhook "{}"'.format(self.name)
|
||||||
|
|
||||||
def parseData(self, message):
|
def parseData(self, message):
|
||||||
return {
|
return {"text": message}
|
||||||
"text": message
|
|
||||||
}
|
|
||||||
|
|
||||||
class TelegramHook(BaseWebhook):
|
class TelegramHook(BaseWebhook):
|
||||||
"""Model containing the channel id of a Telegram chat"""
|
"""Model containing the channel id of a Telegram chat"""
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = _('Telegram channel')
|
verbose_name = _("Telegram channel")
|
||||||
verbose_name_plural = _('Telegram channels')
|
verbose_name_plural = _("Telegram channels")
|
||||||
|
|
||||||
channel_id = models.CharField(max_length=255, unique=True)
|
channel_id = models.CharField(max_length=255, unique=True)
|
||||||
|
|
||||||
@@ -262,11 +344,7 @@ class TelegramHook(BaseWebhook):
|
|||||||
return 'Telegram channel: "{}"'.format(self.name)
|
return 'Telegram channel: "{}"'.format(self.name)
|
||||||
|
|
||||||
def parseData(self, message):
|
def parseData(self, message):
|
||||||
return {
|
return {"text": message, "chat_id": self.channel_id, "parse_mode": "Markdown"}
|
||||||
'text': message,
|
|
||||||
'chat_id': self.channel_id,
|
|
||||||
'parse_mode': 'Markdown'
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
auditlog.register(Tag)
|
auditlog.register(Tag)
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
{{ heading }}: {{ title }}
|
||||||
|
====================
|
||||||
|
{{ description }}
|
||||||
|
|
||||||
|
Lisätietoa osoitteessa: {{ url }}
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
from webapp.models import BaseWebhook
|
|
||||||
import logging
|
|
||||||
|
|
||||||
def processHooks(message: str, eventType: str):
|
|
||||||
allHooks = BaseWebhook.objects.all()
|
|
||||||
for hook in list(allHooks):
|
|
||||||
logging.debug(hook)
|
|
||||||
if (hook.plugs[eventType] == True):
|
|
||||||
hook.broadcast(message)
|
|
||||||
Reference in New Issue
Block a user