Implement custom notification settings

This commit is contained in:
Mark Veidemanis 2022-12-18 17:21:52 +00:00
parent 4c463e88f2
commit 7ee698f457
Signed by: m
GPG Key ID: 5ACFCEED46C0904F
9 changed files with 141 additions and 8 deletions

View File

@ -27,6 +27,7 @@ from core.views import (
callbacks,
hooks,
limits,
notifications,
positions,
profit,
signals,
@ -208,4 +209,9 @@ urlpatterns = [
limits.TrendDirectionList.as_view(),
name="trenddirections",
),
path(
"notifications/<str:type>/update/",
notifications.NotificationsUpdate.as_view(),
name="notifications_update",
),
] + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)

View File

@ -3,7 +3,16 @@ from django.contrib.auth.forms import UserCreationForm
from django.core.exceptions import FieldDoesNotExist
from django.forms import ModelForm
from .models import Account, Hook, Signal, Strategy, Trade, TradingTime, User
from .models import (
Account,
Hook,
NotificationSettings,
Signal,
Strategy,
Trade,
TradingTime,
User,
)
# flake8: noqa: E501
@ -276,3 +285,16 @@ class TradingTimeForm(RestrictedFormMixin, ModelForm):
"end_day": "The day of the week to stop trading.",
"end_time": "The time of day to stop trading.",
}
class NotificationSettingsForm(RestrictedFormMixin, ModelForm):
class Meta:
model = NotificationSettings
fields = (
"ntfy_topic",
"ntfy_url",
)
help_texts = {
"ntfy_topic": "The topic to send notifications to.",
"ntfy_url": "Custom NTFY server. Leave blank to use the default server.",
}

View File

@ -592,12 +592,12 @@ def execute_strategy(callback, strategy, func):
new_trade.save()
else:
info = new_trade.post()
print("INFO", info)
log.debug(f"Posted trade: {info}")
# Send notification with limited number of fields
wanted_fields = ["requestID", "type", "symbol", "units", "reason"]
sendmsg(
user,
", ".join([str(v) for k, v in info.items() if k in wanted_fields]),
title=f"{direction} {amount_rounded} on {symbol}",
)

View File

@ -1,5 +1,4 @@
import requests
from django.conf import settings
from core.util import logs
@ -8,7 +7,8 @@ NTFY_URL = "https://ntfy.sh"
log = logs.get_logger(__name__)
def sendmsg(msg, title=None, priority=None, tags=None):
# Actual function to send a message to a topic
def _sendmsg(msg, title=None, priority=None, tags=None, url=None, topic=None):
headers = {"Title": "Fisk"}
if title:
headers["Title"] = title
@ -17,7 +17,24 @@ def sendmsg(msg, title=None, priority=None, tags=None):
if tags:
headers["Tags"] = tags
requests.post(
f"{NTFY_URL}/{settings.NOTIFY_TOPIC}",
f"{url}/{topic}",
data=msg,
headers=headers,
)
# Sendmsg helper to send a message to a user's notification settings
def sendmsg(user, *args, **kwargs):
notification_settings = user.get_notification_settings()
if notification_settings.ntfy_url is None:
url = NTFY_URL
else:
url = notification_settings.ntfy_url
if notification_settings.ntfy_topic is None:
# No topic set, so don't send
return
else:
topic = notification_settings.ntfy_topic
_sendmsg(*args, **kwargs, url=url, topic=topic)

View File

@ -0,0 +1,24 @@
# Generated by Django 4.1.3 on 2022-12-18 17:10
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('core', '0046_remove_hook_type_signal_type'),
]
operations = [
migrations.CreateModel(
name='NotificationSettings',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('ntfy_topic', models.CharField(blank=True, max_length=255, null=True)),
('ntfy_url', models.CharField(blank=True, max_length=255, null=True)),
('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
),
]

View File

@ -97,6 +97,9 @@ class User(AbstractUser):
plan_list = [plan.name for plan in self.plans.all()]
return plan in plan_list
def get_notification_settings(self):
return NotificationSettings.objects.get_or_create(user=self)[0]
class Account(models.Model):
EXCHANGE_CHOICES = (("alpaca", "Alpaca"), ("oanda", "OANDA"))
@ -363,6 +366,15 @@ class Strategy(models.Model):
return self.name
class NotificationSettings(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
ntfy_topic = models.CharField(max_length=255, null=True, blank=True)
ntfy_url = models.CharField(max_length=255, null=True, blank=True)
def __str__(self):
return f"Notification settings for {self.user}"
# class Perms(models.Model):
# class Meta:
# permissions = (

View File

@ -272,6 +272,20 @@
</a>
</div>
</div>
<div class="navbar-item has-dropdown is-hoverable">
<a class="navbar-link">
Account
</a>
<div class="navbar-dropdown">
<a class="navbar-item" href="{% url 'two_factor:profile' %}">
Security
</a>
<a class="navbar-item" href="{% url 'notifications_update' type='page' %}">
Notifications
</a>
</div>
</div>
{% endif %}
{% if settings.STRIPE_ENABLED %}
{% if user.is_authenticated %}
@ -298,7 +312,6 @@
{% endif %}
{% if user.is_authenticated %}
<a class="button" href="{% url 'two_factor:profile' %}">Security</a>
<a class="button" href="{% url 'logout' %}">Logout</a>
{% endif %}

View File

@ -343,9 +343,13 @@ class ObjectUpdate(RestrictedViewMixin, ObjectNameMixin, UpdateView):
model = None
submit_url_name = None
submit_url_args = ["type", "pk"]
request = None
# Whether pk is required in the get request
pk_required = True
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.title = "Update " + self.context_object_name_singular
@ -376,6 +380,7 @@ class ObjectUpdate(RestrictedViewMixin, ObjectNameMixin, UpdateView):
if not type:
return HttpResponseBadRequest("No type specified")
if not pk:
if self.pk_required:
return HttpResponseBadRequest("No pk specified")
if type not in self.allowed_types:
return HttpResponseBadRequest("Invalid type specified")
@ -385,7 +390,15 @@ class ObjectUpdate(RestrictedViewMixin, ObjectNameMixin, UpdateView):
type = "modal"
self.object = self.get_object()
submit_url = reverse(self.submit_url_name, kwargs={"type": type, "pk": pk})
submit_url_args = {}
for arg in self.submit_url_args:
if arg in locals():
submit_url_args[arg] = locals()[arg]
elif arg in kwargs:
submit_url_args[arg] = kwargs[arg]
submit_url = reverse(self.submit_url_name, kwargs=submit_url_args)
context = self.get_context_data()
form = kwargs.get("form", None)
if form:

View File

@ -0,0 +1,26 @@
from django.contrib.auth.mixins import LoginRequiredMixin
from core.forms import NotificationSettingsForm
from core.models import NotificationSettings
from core.views import ObjectUpdate
# Notifications - we create a new notification settings object if there isn't one
# Hence, there is only an update view, not a create view.
class NotificationsUpdate(LoginRequiredMixin, ObjectUpdate):
model = NotificationSettings
form_class = NotificationSettingsForm
# list_url_name = "notifications"
# list_url_args = ["type"]
submit_url_name = "notifications_update"
submit_url_args = ["type"]
pk_required = False
def get_object(self, **kwargs):
notification_settings, _ = NotificationSettings.objects.get_or_create(
user=self.request.user
)
return notification_settings