Implement custom notification settings
This commit is contained in:
parent
4c463e88f2
commit
7ee698f457
|
@ -27,6 +27,7 @@ from core.views import (
|
||||||
callbacks,
|
callbacks,
|
||||||
hooks,
|
hooks,
|
||||||
limits,
|
limits,
|
||||||
|
notifications,
|
||||||
positions,
|
positions,
|
||||||
profit,
|
profit,
|
||||||
signals,
|
signals,
|
||||||
|
@ -208,4 +209,9 @@ urlpatterns = [
|
||||||
limits.TrendDirectionList.as_view(),
|
limits.TrendDirectionList.as_view(),
|
||||||
name="trenddirections",
|
name="trenddirections",
|
||||||
),
|
),
|
||||||
|
path(
|
||||||
|
"notifications/<str:type>/update/",
|
||||||
|
notifications.NotificationsUpdate.as_view(),
|
||||||
|
name="notifications_update",
|
||||||
|
),
|
||||||
] + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
|
] + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
|
||||||
|
|
|
@ -3,7 +3,16 @@ from django.contrib.auth.forms import UserCreationForm
|
||||||
from django.core.exceptions import FieldDoesNotExist
|
from django.core.exceptions import FieldDoesNotExist
|
||||||
from django.forms import ModelForm
|
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
|
# flake8: noqa: E501
|
||||||
|
|
||||||
|
@ -276,3 +285,16 @@ class TradingTimeForm(RestrictedFormMixin, ModelForm):
|
||||||
"end_day": "The day of the week to stop trading.",
|
"end_day": "The day of the week to stop trading.",
|
||||||
"end_time": "The time of day 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.",
|
||||||
|
}
|
||||||
|
|
|
@ -592,12 +592,12 @@ def execute_strategy(callback, strategy, func):
|
||||||
new_trade.save()
|
new_trade.save()
|
||||||
else:
|
else:
|
||||||
info = new_trade.post()
|
info = new_trade.post()
|
||||||
print("INFO", info)
|
|
||||||
log.debug(f"Posted trade: {info}")
|
log.debug(f"Posted trade: {info}")
|
||||||
|
|
||||||
# Send notification with limited number of fields
|
# Send notification with limited number of fields
|
||||||
wanted_fields = ["requestID", "type", "symbol", "units", "reason"]
|
wanted_fields = ["requestID", "type", "symbol", "units", "reason"]
|
||||||
sendmsg(
|
sendmsg(
|
||||||
|
user,
|
||||||
", ".join([str(v) for k, v in info.items() if k in wanted_fields]),
|
", ".join([str(v) for k, v in info.items() if k in wanted_fields]),
|
||||||
title=f"{direction} {amount_rounded} on {symbol}",
|
title=f"{direction} {amount_rounded} on {symbol}",
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
import requests
|
import requests
|
||||||
from django.conf import settings
|
|
||||||
|
|
||||||
from core.util import logs
|
from core.util import logs
|
||||||
|
|
||||||
|
@ -8,7 +7,8 @@ NTFY_URL = "https://ntfy.sh"
|
||||||
log = logs.get_logger(__name__)
|
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"}
|
headers = {"Title": "Fisk"}
|
||||||
if title:
|
if title:
|
||||||
headers["Title"] = title
|
headers["Title"] = title
|
||||||
|
@ -17,7 +17,24 @@ def sendmsg(msg, title=None, priority=None, tags=None):
|
||||||
if tags:
|
if tags:
|
||||||
headers["Tags"] = tags
|
headers["Tags"] = tags
|
||||||
requests.post(
|
requests.post(
|
||||||
f"{NTFY_URL}/{settings.NOTIFY_TOPIC}",
|
f"{url}/{topic}",
|
||||||
data=msg,
|
data=msg,
|
||||||
headers=headers,
|
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)
|
||||||
|
|
|
@ -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)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
]
|
|
@ -97,6 +97,9 @@ class User(AbstractUser):
|
||||||
plan_list = [plan.name for plan in self.plans.all()]
|
plan_list = [plan.name for plan in self.plans.all()]
|
||||||
return plan in plan_list
|
return plan in plan_list
|
||||||
|
|
||||||
|
def get_notification_settings(self):
|
||||||
|
return NotificationSettings.objects.get_or_create(user=self)[0]
|
||||||
|
|
||||||
|
|
||||||
class Account(models.Model):
|
class Account(models.Model):
|
||||||
EXCHANGE_CHOICES = (("alpaca", "Alpaca"), ("oanda", "OANDA"))
|
EXCHANGE_CHOICES = (("alpaca", "Alpaca"), ("oanda", "OANDA"))
|
||||||
|
@ -363,6 +366,15 @@ class Strategy(models.Model):
|
||||||
return self.name
|
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 Perms(models.Model):
|
||||||
# class Meta:
|
# class Meta:
|
||||||
# permissions = (
|
# permissions = (
|
||||||
|
|
|
@ -272,6 +272,20 @@
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</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 %}
|
{% endif %}
|
||||||
{% if settings.STRIPE_ENABLED %}
|
{% if settings.STRIPE_ENABLED %}
|
||||||
{% if user.is_authenticated %}
|
{% if user.is_authenticated %}
|
||||||
|
@ -298,7 +312,6 @@
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if user.is_authenticated %}
|
{% if user.is_authenticated %}
|
||||||
<a class="button" href="{% url 'two_factor:profile' %}">Security</a>
|
|
||||||
<a class="button" href="{% url 'logout' %}">Logout</a>
|
<a class="button" href="{% url 'logout' %}">Logout</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
|
|
@ -343,9 +343,13 @@ class ObjectUpdate(RestrictedViewMixin, ObjectNameMixin, UpdateView):
|
||||||
|
|
||||||
model = None
|
model = None
|
||||||
submit_url_name = None
|
submit_url_name = None
|
||||||
|
submit_url_args = ["type", "pk"]
|
||||||
|
|
||||||
request = None
|
request = None
|
||||||
|
|
||||||
|
# Whether pk is required in the get request
|
||||||
|
pk_required = True
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
self.title = "Update " + self.context_object_name_singular
|
self.title = "Update " + self.context_object_name_singular
|
||||||
|
@ -376,7 +380,8 @@ class ObjectUpdate(RestrictedViewMixin, ObjectNameMixin, UpdateView):
|
||||||
if not type:
|
if not type:
|
||||||
return HttpResponseBadRequest("No type specified")
|
return HttpResponseBadRequest("No type specified")
|
||||||
if not pk:
|
if not pk:
|
||||||
return HttpResponseBadRequest("No pk specified")
|
if self.pk_required:
|
||||||
|
return HttpResponseBadRequest("No pk specified")
|
||||||
if type not in self.allowed_types:
|
if type not in self.allowed_types:
|
||||||
return HttpResponseBadRequest("Invalid type specified")
|
return HttpResponseBadRequest("Invalid type specified")
|
||||||
self.template_name = f"wm/{type}.html"
|
self.template_name = f"wm/{type}.html"
|
||||||
|
@ -385,7 +390,15 @@ class ObjectUpdate(RestrictedViewMixin, ObjectNameMixin, UpdateView):
|
||||||
type = "modal"
|
type = "modal"
|
||||||
|
|
||||||
self.object = self.get_object()
|
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()
|
context = self.get_context_data()
|
||||||
form = kwargs.get("form", None)
|
form = kwargs.get("form", None)
|
||||||
if form:
|
if form:
|
||||||
|
|
|
@ -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
|
Loading…
Reference in New Issue