diff --git a/kaehmy/views.py b/kaehmy/views.py index 8f839f2..948e24c 100644 --- a/kaehmy/views.py +++ b/kaehmy/views.py @@ -17,7 +17,7 @@ from kaehmy.models import Application, CustomRole, PresetRole from kaehmy.forms import ApplicationForm, CommentForm from kaehmy.tables import ExportTable from webapp.utils import send_email -from webapp.webhook import processHooks +from webapp.models import processHooks @ensure_csrf_cookie diff --git a/ohlhafv/views.py b/ohlhafv/views.py index 2f0b4e2..583e695 100644 --- a/ohlhafv/views.py +++ b/ohlhafv/views.py @@ -1,26 +1,17 @@ -"""Ohlhafv views.""" - -from django.db.models import Count -from django.shortcuts import render, redirect -from django.contrib.auth import login, logout, authenticate +import logging +from django.shortcuts import render from django.views.decorators.http import require_http_methods from django.views.decorators.csrf import ensure_csrf_cookie -from django.http import HttpResponse, HttpResponseRedirect -from django.contrib.auth.decorators import permission_required, login_required -from django.conf import settings +from django.http import HttpResponseRedirect from django.utils.translation import ugettext_lazy as _ from django.template.loader import render_to_string -import logging -import requests -from dealer.git import git from sikweb.settings import URL from ohlhafv.models import OhlhafvChallenge from ohlhafv.forms import OhlhafvForm -from ohlhafv.tables import OhlhafvTable from webapp.utils import send_email -from webapp.webhook import processHooks +from webapp.models import processHooks @require_http_methods(["GET"]) diff --git a/webapp/models.py b/webapp/models.py index e7c697b..183435a 100644 --- a/webapp/models.py +++ b/webapp/models.py @@ -1,24 +1,23 @@ """Webapp app models.""" -from django.conf import settings 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 datetime import timedelta -from django.contrib.auth.models import User from django.db.models.signals import post_save from django.dispatch import receiver + +# from datetime import timedelta 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 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-.]+$)" @@ -26,15 +25,15 @@ class Tag(models.Model): """Model for tag.""" class Meta: - verbose_name = _('Tag') - verbose_name_plural = _('Tags') + verbose_name = _("Tag") + verbose_name_plural = _("Tags") slug = models.SlugField(unique=True) name = models.CharField(max_length=127) icon = models.ImageField() def __str__(self): - return _('Tag: {}').format(self.slug) + return _("Tag: {}").format(self.slug) class BaseFeed(models.Model): @@ -52,8 +51,8 @@ class Feed(BaseFeed): """Model representing feed.""" class Meta: - verbose_name = _('Feed') - verbose_name_plural = _('Feeds') + verbose_name = _("Feed") + verbose_name_plural = _("Feeds") publish_time = models.DateTimeField(default=timezone.now) autohide = models.DateTimeField(default=month_from_now) @@ -62,26 +61,67 @@ class Feed(BaseFeed): def __str__(self): 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): """Model for event in guild calendar""" class Meta: - verbose_name = _('Event') - verbose_name_plural = _('Events') + verbose_name = _("Event") + verbose_name_plural = _("Events") start_time = models.DateTimeField(default=timezone.now) end_time = models.DateTimeField(default=timezone.now) - signupForm = models.ManyToManyField( - 'SignupForm', blank=True, related_name="event") + signupForm = models.ManyToManyField("SignupForm", blank=True, related_name="event") location = models.CharField(max_length=255, blank=True) deleted = models.BooleanField(default=False) def __str__(self): 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): @@ -90,23 +130,23 @@ class TemplateQuestion(models.Model): """ class Meta: - verbose_name = _('Template question') - verbose_name_plural = _('Template questions') + verbose_name = _("Template question") + verbose_name_plural = _("Template questions") name = models.CharField(max_length=255) questions = JSONField() deleted = models.BooleanField(default=False) def __str__(self): - return _('Template questions: {}').format(self.name) + return _("Template questions: {}").format(self.name) class SignupForm(models.Model): """Model for event signup form. Stores questions in JSON format.""" class Meta: - verbose_name = _('Signup form') - verbose_name_plural = _('Signup forms') + verbose_name = _("Signup form") + verbose_name_plural = _("Signup forms") title = models.CharField(max_length=255) start_time = models.DateTimeField(default=timezone.now) @@ -120,11 +160,11 @@ class SignupForm(models.Model): def __str__(self): delete_str = _("Deleted: ") if self.deleted else "" - return _('#{} {}{}').format(self.id, delete_str, self.title) + return _("#{} {}{}").format(self.id, delete_str, self.title) @property 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 def isOpen(self): @@ -138,13 +178,14 @@ class Signup(models.Model): """ class Meta: - verbose_name = _('Sign-up') - verbose_name_plural = _('Sign-ups') - signupForm = models.ForeignKey('SignupForm', on_delete=models.CASCADE) + verbose_name = _("Sign-up") + verbose_name_plural = _("Sign-ups") + + signupForm = models.ForeignKey("SignupForm", on_delete=models.CASCADE) time = models.DateTimeField(default=timezone.now) answer = JSONField() # 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 email = models.EmailField(blank=True, null=True) # Random unique identifier. Used for signup editing by the user. @@ -167,26 +208,32 @@ def email_on_signup(sender, instance, created, **kwargs): except AttributeError: # 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): """Base model for occupations/roles.""" - name = models.CharField(_('Name'), max_length=255) - is_board = models.BooleanField(_('Board member')) + name = models.CharField(_("Name"), max_length=255) + is_board = models.BooleanField(_("Board member")) def __str__(self): 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): """Job advertisements shown on Corporate relations page""" class Meta: - verbose_name = _('JobAd') - verbose_name_plural = _('JobAds') + verbose_name = _("JobAd") + verbose_name_plural = _("JobAds") title = models.CharField(max_length=255) description = models.CharField(max_length=255) @@ -199,21 +246,57 @@ class JobAd(models.Model): def __str__(self): 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): """Webhook base class instance""" 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 - kaehmy_submit = models.BooleanField(_("Hook Kaehmys"), default=False) - ohlhafv_submit = models.BooleanField(_("Hook Ohlhafv challenges"),default=False) - feed_published = models.BooleanField(_("Hook published news"),default=False) - jobad_published = models.BooleanField(_("Hook published Job Ads"),default=False) - event_published = models.BooleanField(_("Hook published events"),default=False) - signup_opened = models.BooleanField(_("Hook opened signups"),default=False) + kaehmy_submit = models.BooleanField(_("Hook Kaehmys"), default=False) + ohlhafv_submit = models.BooleanField(_("Hook Ohlhafv challenges"), default=False) + feed_published = models.BooleanField(_("Hook published news"), default=False) + jobad_published = models.BooleanField(_("Hook published Job Ads"), default=False) + event_published = models.BooleanField(_("Hook published events"), default=False) + signup_opened = models.BooleanField(_("Hook opened signups"), default=False) @property def plugs(self): @@ -223,7 +306,7 @@ class BaseWebhook(PolymorphicModel): "feed": self.feed_published, "jobad": self.jobad_published, "event": self.event_published, - "signup":self.signup_opened, + "signup": self.signup_opened, } def parseData(self): @@ -231,30 +314,29 @@ class BaseWebhook(PolymorphicModel): def broadcast(self, 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) class GenericWebhook(BaseWebhook): class Meta: proxy = True - verbose_name = _('Webhook') - verbose_name_plural = _('Webhooks') + verbose_name = _("Webhook") + verbose_name_plural = _("Webhooks") def __str__(self): return 'Webhook "{}"'.format(self.name) def parseData(self, message): - return { - "text": message - } + return {"text": message} + class TelegramHook(BaseWebhook): """Model containing the channel id of a Telegram chat""" class Meta: - verbose_name = _('Telegram channel') - verbose_name_plural = _('Telegram channels') + verbose_name = _("Telegram channel") + verbose_name_plural = _("Telegram channels") channel_id = models.CharField(max_length=255, unique=True) @@ -262,11 +344,7 @@ class TelegramHook(BaseWebhook): return 'Telegram channel: "{}"'.format(self.name) def parseData(self, message): - return { - 'text': message, - 'chat_id': self.channel_id, - 'parse_mode': 'Markdown' - } + return {"text": message, "chat_id": self.channel_id, "parse_mode": "Markdown"} auditlog.register(Tag) diff --git a/webapp/templates/tg_message.tpl b/webapp/templates/tg_message.tpl new file mode 100644 index 0000000..bab3c8f --- /dev/null +++ b/webapp/templates/tg_message.tpl @@ -0,0 +1,5 @@ +{{ heading }}: {{ title }} +==================== +{{ description }} + +Lisätietoa osoitteessa: {{ url }} \ No newline at end of file diff --git a/webapp/webhook.py b/webapp/webhook.py deleted file mode 100644 index 0e06960..0000000 --- a/webapp/webhook.py +++ /dev/null @@ -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)