Make signals configurable
This commit is contained in:
parent
f7242f4dd8
commit
851d021af2
19
app/urls.py
19
app/urls.py
|
@ -28,6 +28,7 @@ from core.views import (
|
|||
hooks,
|
||||
limits,
|
||||
positions,
|
||||
signals,
|
||||
strategies,
|
||||
trades,
|
||||
)
|
||||
|
@ -69,8 +70,24 @@ urlpatterns = [
|
|||
path(
|
||||
f"{settings.HOOK_PATH}/<str:hook_name>/", hooks.HookAPI.as_view(), name="hook"
|
||||
),
|
||||
path("signals/<str:type>/", signals.SignalList.as_view(), name="signals"),
|
||||
path(
|
||||
"callbacks/<str:type>/<str:pk>/",
|
||||
"signals/<str:type>/create/",
|
||||
signals.SignalCreate.as_view(),
|
||||
name="signal_create",
|
||||
),
|
||||
path(
|
||||
"signals/<str:type>/update/<str:pk>/",
|
||||
signals.SignalUpdate.as_view(),
|
||||
name="signal_update",
|
||||
),
|
||||
path(
|
||||
"signals/<str:type>/delete/<str:pk>/",
|
||||
signals.SignalDelete.as_view(),
|
||||
name="signal_delete",
|
||||
),
|
||||
path(
|
||||
"callbacks/<str:type>/<str:object_type>/<str:object_id>/",
|
||||
callbacks.Callbacks.as_view(),
|
||||
name="callbacks",
|
||||
),
|
||||
|
|
|
@ -3,7 +3,7 @@ from django.contrib.auth.forms import UserCreationForm
|
|||
from django.core.exceptions import FieldDoesNotExist
|
||||
from django.forms import ModelForm
|
||||
|
||||
from .models import Account, Hook, Strategy, Trade, TradingTime, User
|
||||
from .models import Account, Hook, Signal, Strategy, Trade, TradingTime, User
|
||||
|
||||
# flake8: noqa: E501
|
||||
|
||||
|
@ -61,23 +61,36 @@ class CustomUserCreationForm(UserCreationForm):
|
|||
fields = "__all__"
|
||||
|
||||
|
||||
# All string/multiple choice fields
|
||||
class HookForm(RestrictedFormMixin, ModelForm):
|
||||
class Meta:
|
||||
model = Hook
|
||||
fields = (
|
||||
"name",
|
||||
"hook",
|
||||
"direction",
|
||||
)
|
||||
help_texts = {
|
||||
"name": "Name of the hook. Informational only.",
|
||||
"hook": "The URL slug to use for the hook. Make it unique.",
|
||||
"direction": "The direction of the hook. This is used to determine if the hook is a buy or sell.",
|
||||
}
|
||||
|
||||
|
||||
# All string/multiple choice fields
|
||||
class SignalForm(RestrictedFormMixin, ModelForm):
|
||||
class Meta:
|
||||
model = Signal
|
||||
fields = (
|
||||
"name",
|
||||
"signal",
|
||||
"hook",
|
||||
"direction",
|
||||
)
|
||||
help_texts = {
|
||||
"name": "Name of the signal. Informational only.",
|
||||
"signal": "The name of the signal in Drakdoo. Copy it from there.",
|
||||
"hook": "The hook this signal belongs to.",
|
||||
"direction": "The direction of the signal. This is used to determine if the signal is a buy or sell.",
|
||||
}
|
||||
|
||||
|
||||
class AccountForm(RestrictedFormMixin, ModelForm):
|
||||
class Meta:
|
||||
model = Account
|
||||
|
@ -97,7 +110,6 @@ class AccountForm(RestrictedFormMixin, ModelForm):
|
|||
}
|
||||
|
||||
|
||||
# Restricted mixin for account and hooks
|
||||
class StrategyForm(RestrictedFormMixin, ModelForm):
|
||||
class Meta:
|
||||
model = Strategy
|
||||
|
@ -108,7 +120,8 @@ class StrategyForm(RestrictedFormMixin, ModelForm):
|
|||
"trading_times",
|
||||
"order_type",
|
||||
"time_in_force",
|
||||
"hooks",
|
||||
"entry_signals",
|
||||
"exit_signals",
|
||||
"enabled",
|
||||
"take_profit_percent",
|
||||
"stop_loss_percent",
|
||||
|
@ -125,7 +138,8 @@ class StrategyForm(RestrictedFormMixin, ModelForm):
|
|||
"trading_times": "When the strategy will place new trades.",
|
||||
"order_type": "Market: Buy/Sell at the current market price. Limit: Buy/Sell at a specified price. Limits protect you more against market slippage.",
|
||||
"time_in_force": "The time in force controls how the order is executed.",
|
||||
"hooks": "The hooks to attach to this strategy. Callbacks received to these hooks will trigger a trade.",
|
||||
"entry_signals": "The entry signals to attach to this strategy. Callbacks received to these signals will trigger a trade.",
|
||||
"exit_signals": "The exit signals to attach to this strategy. Callbacks received to these signals will close all trades for the symbol on the account.",
|
||||
"enabled": "Whether the strategy is enabled.",
|
||||
"take_profit_percent": "The take profit will be set at this percentage above/below the entry price.",
|
||||
"stop_loss_percent": "The stop loss will be set at this percentage above/below the entry price.",
|
||||
|
@ -135,15 +149,23 @@ class StrategyForm(RestrictedFormMixin, ModelForm):
|
|||
"trade_size_percent": "Percentage of the account balance to use for each trade.",
|
||||
}
|
||||
|
||||
hooks = forms.ModelMultipleChoiceField(
|
||||
queryset=Hook.objects.all(), widget=forms.CheckboxSelectMultiple
|
||||
entry_signals = forms.ModelMultipleChoiceField(
|
||||
queryset=Signal.objects.all(),
|
||||
widget=forms.CheckboxSelectMultiple,
|
||||
help_text=Meta.help_texts["entry_signals"],
|
||||
required=False,
|
||||
)
|
||||
exit_signals = forms.ModelMultipleChoiceField(
|
||||
queryset=Signal.objects.all(),
|
||||
widget=forms.CheckboxSelectMultiple,
|
||||
help_text=Meta.help_texts["exit_signals"],
|
||||
required=False,
|
||||
)
|
||||
trading_times = forms.ModelMultipleChoiceField(
|
||||
queryset=TradingTime.objects.all(), widget=forms.CheckboxSelectMultiple
|
||||
)
|
||||
|
||||
|
||||
# Restricted mixin for account
|
||||
class TradeForm(RestrictedFormMixin, ModelForm):
|
||||
class Meta:
|
||||
model = Trade
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
# Generated by Django 4.1.3 on 2022-12-01 18:22
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('core', '0035_alter_tradingtime_end_day_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name='hook',
|
||||
name='direction',
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='hook',
|
||||
name='name',
|
||||
field=models.CharField(default='Unknown', max_length=1024),
|
||||
preserve_default=False,
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Signal',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(max_length=1024)),
|
||||
('signal', models.CharField(max_length=256)),
|
||||
('direction', models.CharField(choices=[('buy', 'Buy'), ('sell', 'Sell')], max_length=255)),
|
||||
('received', models.IntegerField(default=0)),
|
||||
('hook', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='core.hook')),
|
||||
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
|
||||
],
|
||||
),
|
||||
]
|
|
@ -0,0 +1,20 @@
|
|||
# Generated by Django 4.1.3 on 2022-12-01 18:33
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('core', '0036_remove_hook_direction_alter_hook_name_signal'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='callback',
|
||||
name='signal',
|
||||
field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, to='core.signal'),
|
||||
preserve_default=False,
|
||||
),
|
||||
]
|
|
@ -0,0 +1,33 @@
|
|||
# Generated by Django 4.1.3 on 2022-12-01 18:40
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('core', '0037_callback_signal'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name='strategy',
|
||||
name='hooks',
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='strategy',
|
||||
name='entry_signals',
|
||||
field=models.ManyToManyField(related_name='entry_strategies', to='core.signal'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='strategy',
|
||||
name='exit_signals',
|
||||
field=models.ManyToManyField(related_name='exit_signals', to='core.signal'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='trade',
|
||||
name='signal',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='core.signal'),
|
||||
),
|
||||
]
|
|
@ -0,0 +1,18 @@
|
|||
# Generated by Django 4.1.3 on 2022-12-01 18:42
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('core', '0038_remove_strategy_hooks_strategy_entry_signals_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='strategy',
|
||||
name='exit_signals',
|
||||
field=models.ManyToManyField(related_name='exit_strategies', to='core.signal'),
|
||||
),
|
||||
]
|
|
@ -0,0 +1,23 @@
|
|||
# Generated by Django 4.1.3 on 2022-12-01 18:44
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('core', '0039_alter_strategy_exit_signals'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='strategy',
|
||||
name='entry_signals',
|
||||
field=models.ManyToManyField(blank=True, null=True, related_name='entry_strategies', to='core.signal'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='strategy',
|
||||
name='exit_signals',
|
||||
field=models.ManyToManyField(blank=True, null=True, related_name='exit_strategies', to='core.signal'),
|
||||
),
|
||||
]
|
|
@ -0,0 +1,23 @@
|
|||
# Generated by Django 4.1.3 on 2022-12-01 18:48
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('core', '0040_alter_strategy_entry_signals_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='strategy',
|
||||
name='entry_signals',
|
||||
field=models.ManyToManyField(blank=True, related_name='entry_strategies', to='core.signal'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='strategy',
|
||||
name='exit_signals',
|
||||
field=models.ManyToManyField(blank=True, related_name='exit_strategies', to='core.signal'),
|
||||
),
|
||||
]
|
|
@ -166,19 +166,31 @@ class Session(models.Model):
|
|||
|
||||
class Hook(models.Model):
|
||||
user = models.ForeignKey(User, on_delete=models.CASCADE)
|
||||
name = models.CharField(max_length=1024, null=True, blank=True, unique=True)
|
||||
hook = models.CharField(max_length=255, unique=True)
|
||||
direction = models.CharField(choices=DIRECTION_CHOICES, max_length=255)
|
||||
name = models.CharField(max_length=1024)
|
||||
hook = models.CharField(max_length=255, unique=True) # hook URL
|
||||
received = models.IntegerField(default=0)
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.name} ({self.hook})"
|
||||
|
||||
|
||||
class Signal(models.Model):
|
||||
user = models.ForeignKey(User, on_delete=models.CASCADE)
|
||||
name = models.CharField(max_length=1024)
|
||||
signal = models.CharField(max_length=256) # signal name
|
||||
hook = models.ForeignKey(Hook, on_delete=models.CASCADE)
|
||||
direction = models.CharField(choices=DIRECTION_CHOICES, max_length=255)
|
||||
received = models.IntegerField(default=0)
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.name} ({self.signal}) - {self.direction}"
|
||||
|
||||
|
||||
class Trade(models.Model):
|
||||
user = models.ForeignKey(User, on_delete=models.CASCADE)
|
||||
account = models.ForeignKey(Account, on_delete=models.CASCADE)
|
||||
hook = models.ForeignKey(Hook, on_delete=models.CASCADE, null=True, blank=True)
|
||||
signal = models.ForeignKey(Signal, on_delete=models.CASCADE, null=True, blank=True)
|
||||
symbol = models.CharField(max_length=255)
|
||||
time_in_force = models.CharField(choices=TIF_CHOICES, max_length=255, default="gtc")
|
||||
type = models.CharField(choices=TYPE_CHOICES, max_length=255)
|
||||
|
@ -224,6 +236,7 @@ class Trade(models.Model):
|
|||
|
||||
class Callback(models.Model):
|
||||
hook = models.ForeignKey(Hook, on_delete=models.CASCADE)
|
||||
signal = models.ForeignKey(Signal, on_delete=models.CASCADE)
|
||||
title = models.CharField(max_length=1024, null=True, blank=True)
|
||||
message = models.CharField(max_length=1024, null=True, blank=True)
|
||||
period = models.CharField(max_length=255, null=True, blank=True)
|
||||
|
@ -310,7 +323,12 @@ class Strategy(models.Model):
|
|||
choices=TYPE_CHOICES, max_length=255, default="market"
|
||||
)
|
||||
time_in_force = models.CharField(choices=TIF_CHOICES, max_length=255, default="gtc")
|
||||
hooks = models.ManyToManyField(Hook)
|
||||
entry_signals = models.ManyToManyField(
|
||||
Signal, related_name="entry_strategies", blank=True
|
||||
)
|
||||
exit_signals = models.ManyToManyField(
|
||||
Signal, related_name="exit_strategies", blank=True
|
||||
)
|
||||
enabled = models.BooleanField(default=False)
|
||||
take_profit_percent = models.FloatField(default=1.5)
|
||||
stop_loss_percent = models.FloatField(default=1.0)
|
||||
|
|
|
@ -226,6 +226,9 @@
|
|||
<a class="navbar-item" href="{% url 'hooks' type='page' %}">
|
||||
Hooks
|
||||
</a>
|
||||
<a class="navbar-item" href="{% url 'signals' type='page' %}">
|
||||
Signals
|
||||
</a>
|
||||
<a class="navbar-item" href="{% url 'accounts' type='page' %}">
|
||||
Accounts
|
||||
</a>
|
||||
|
|
|
@ -12,7 +12,6 @@
|
|||
<th>user</th>
|
||||
<th>name</th>
|
||||
<th>hook</th>
|
||||
<th>direction</th>
|
||||
<th>received hooks</th>
|
||||
<th>actions</th>
|
||||
</thead>
|
||||
|
@ -22,7 +21,6 @@
|
|||
<td>{{ item.user }}</td>
|
||||
<td>{{ item.name }}</td>
|
||||
<td><code>{{settings.URL}}/{{settings.HOOK_PATH}}/{{ item.hook }}/</code></td>
|
||||
<td>{{ item.direction }}</td>
|
||||
<td>{{ item.received }}</td>
|
||||
<td>
|
||||
<div class="buttons">
|
||||
|
@ -54,7 +52,7 @@
|
|||
</span>
|
||||
</button>
|
||||
{% if type == 'page' %}
|
||||
<a href="{% url 'callbacks' type='page' pk=item.id %}"><button
|
||||
<a href="{% url 'callbacks' type='page' object_type='hook' object_id=item.id %}"><button
|
||||
class="button is-success">
|
||||
<span class="icon-text">
|
||||
<span class="icon">
|
||||
|
@ -66,7 +64,7 @@
|
|||
{% else %}
|
||||
<button
|
||||
hx-headers='{"X-CSRFToken": "{{ csrf_token }}"}'
|
||||
hx-get="{% url 'callbacks' type=type pk=item.id %}"
|
||||
hx-get="{% url 'callbacks' type=type object_type='hook' object_id=item.id %}"
|
||||
hx-trigger="click"
|
||||
hx-target="#{{ type }}s-here"
|
||||
hx-swap="innerHTML"
|
||||
|
|
|
@ -0,0 +1,96 @@
|
|||
{% include 'partials/notify.html' %}
|
||||
|
||||
<table
|
||||
class="table is-fullwidth is-hoverable"
|
||||
hx-target="#{{ context_object_name }}-table"
|
||||
id="{{ context_object_name }}-table"
|
||||
hx-swap="outerHTML"
|
||||
hx-trigger="{{ context_object_name_singular }}Event from:body"
|
||||
hx-get="{{ list_url }}">
|
||||
<thead>
|
||||
<th>id</th>
|
||||
<th>user</th>
|
||||
<th>name</th>
|
||||
<th>signal</th>
|
||||
<th>hook</th>
|
||||
<th>direction</th>
|
||||
<th>received hooks</th>
|
||||
<th>actions</th>
|
||||
</thead>
|
||||
{% for item in object_list %}
|
||||
<tr>
|
||||
<td>{{ item.id }}</td>
|
||||
<td>{{ item.user }}</td>
|
||||
<td>{{ item.name }}</td>
|
||||
<td>{{ item.signal }}</td>
|
||||
<td>
|
||||
<a
|
||||
hx-headers='{"X-CSRFToken": "{{ csrf_token }}"}'
|
||||
hx-get="{% url 'hook_update' type=type pk=item.hook.id %}"
|
||||
hx-trigger="click"
|
||||
hx-target="#{{ type }}s-here"
|
||||
hx-swap="innerHTML">{{ item.hook.name }}
|
||||
</a>
|
||||
</td>
|
||||
<td>{{ item.direction }}</td>
|
||||
<td>{{ item.received }}</td>
|
||||
<td>
|
||||
<div class="buttons">
|
||||
<button
|
||||
hx-headers='{"X-CSRFToken": "{{ csrf_token }}"}'
|
||||
hx-get="{% url 'signal_update' type=type pk=item.id %}"
|
||||
hx-trigger="click"
|
||||
hx-target="#{{ type }}s-here"
|
||||
hx-swap="innerHTML"
|
||||
class="button is-info">
|
||||
<span class="icon-text">
|
||||
<span class="icon">
|
||||
<i class="fa-solid fa-pencil"></i>
|
||||
</span>
|
||||
</span>
|
||||
</button>
|
||||
<button
|
||||
hx-headers='{"X-CSRFToken": "{{ csrf_token }}"}'
|
||||
hx-delete="{% url 'signal_delete' type=type pk=item.id %}"
|
||||
hx-trigger="click"
|
||||
hx-target="#modals-here"
|
||||
hx-swap="innerHTML"
|
||||
hx-confirm="Are you sure you wish to delete {{ item.name }}?"
|
||||
class="button is-danger">
|
||||
<span class="icon-text">
|
||||
<span class="icon">
|
||||
<i class="fa-solid fa-trash"></i>
|
||||
</span>
|
||||
</span>
|
||||
</button>
|
||||
{% if type == 'page' %}
|
||||
<a href="{% url 'callbacks' type='page' object_type='signal' object_id=item.id %}"><button
|
||||
class="button is-success">
|
||||
<span class="icon-text">
|
||||
<span class="icon">
|
||||
<i class="fa-solid fa-eye"></i>
|
||||
</span>
|
||||
</span>
|
||||
</button>
|
||||
</a>
|
||||
{% else %}
|
||||
<button
|
||||
hx-headers='{"X-CSRFToken": "{{ csrf_token }}"}'
|
||||
hx-get="{% url 'callbacks' type=type object_type='signal' object_id=item.id %}"
|
||||
hx-trigger="click"
|
||||
hx-target="#{{ type }}s-here"
|
||||
hx-swap="innerHTML"
|
||||
class="button is-success">
|
||||
<span class="icon-text">
|
||||
<span class="icon">
|
||||
<i class="fa-solid fa-eye"></i>
|
||||
</span>
|
||||
</span>
|
||||
</button>
|
||||
{% endif %}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
|
||||
</table>
|
|
@ -5,14 +5,15 @@ from django.http import HttpResponseBadRequest
|
|||
from django.shortcuts import render
|
||||
from django.views import View
|
||||
|
||||
from core.models import Callback, Hook
|
||||
from core.models import Callback, Hook, Signal
|
||||
|
||||
|
||||
def get_callbacks(user, hook=None):
|
||||
def get_callbacks(user, hook=None, signal=None):
|
||||
if hook:
|
||||
callbacks = Callback.objects.filter(hook=hook, hook__user=user)
|
||||
else:
|
||||
callbacks = Callback.objects.filter(hook__user=user)
|
||||
cast = {"hook": hook, "hook__user": user}
|
||||
elif signal:
|
||||
cast = {"signal": signal, "signal__user": user}
|
||||
callbacks = Callback.objects.filter(**cast)
|
||||
return callbacks
|
||||
|
||||
|
||||
|
@ -22,15 +23,15 @@ class Callbacks(LoginRequiredMixin, View):
|
|||
list_template = "partials/callback-list.html"
|
||||
page_title = "List of received callbacks"
|
||||
|
||||
async def get(self, request, type, pk=None):
|
||||
async def get(self, request, type, object_type, object_id):
|
||||
if type not in self.allowed_types:
|
||||
return HttpResponseBadRequest
|
||||
template_name = f"wm/{type}.html"
|
||||
unique = str(uuid.uuid4())[:8]
|
||||
|
||||
if pk:
|
||||
if object_type == "hook":
|
||||
try:
|
||||
hook = Hook.objects.get(id=pk, user=request.user)
|
||||
hook = Hook.objects.get(id=object_id, user=request.user)
|
||||
except Hook.DoesNotExist:
|
||||
message = "Hook does not exist."
|
||||
message_class = "danger"
|
||||
|
@ -41,6 +42,19 @@ class Callbacks(LoginRequiredMixin, View):
|
|||
}
|
||||
return render(request, template_name, context)
|
||||
callbacks = get_callbacks(request.user, hook)
|
||||
elif object_type == "signal":
|
||||
try:
|
||||
signal = Signal.objects.get(id=object_id, user=request.user)
|
||||
except Signal.DoesNotExist:
|
||||
message = "Signal does not exist."
|
||||
message_class = "danger"
|
||||
context = {
|
||||
"message": message,
|
||||
"class": message_class,
|
||||
"type": type,
|
||||
}
|
||||
return render(request, template_name, context)
|
||||
callbacks = get_callbacks(request.user, signal=signal)
|
||||
else:
|
||||
callbacks = get_callbacks(request.user)
|
||||
if type == "page":
|
||||
|
|
|
@ -0,0 +1,47 @@
|
|||
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||
|
||||
from core.forms import SignalForm
|
||||
from core.models import Signal
|
||||
from core.util import logs
|
||||
from core.views import ObjectCreate, ObjectDelete, ObjectList, ObjectUpdate
|
||||
|
||||
log = logs.get_logger(__name__)
|
||||
|
||||
|
||||
class SignalList(LoginRequiredMixin, ObjectList):
|
||||
list_template = "partials/signal-list.html"
|
||||
model = Signal
|
||||
page_title = "List of signals. Linked to hooks and strategies."
|
||||
page_subtitle = "Link signals you have defined in Drakdoo to their corresponding hooks."
|
||||
|
||||
list_url_name = "signals"
|
||||
list_url_args = ["type"]
|
||||
|
||||
submit_url_name = "signal_create"
|
||||
|
||||
|
||||
class SignalCreate(LoginRequiredMixin, ObjectCreate):
|
||||
model = Signal
|
||||
form_class = SignalForm
|
||||
|
||||
list_url_name = "signals"
|
||||
list_url_args = ["type"]
|
||||
|
||||
submit_url_name = "signal_create"
|
||||
|
||||
|
||||
class SignalUpdate(LoginRequiredMixin, ObjectUpdate):
|
||||
model = Signal
|
||||
form_class = SignalForm
|
||||
|
||||
list_url_name = "signals"
|
||||
list_url_args = ["type"]
|
||||
|
||||
submit_url_name = "signal_update"
|
||||
|
||||
|
||||
class SignalDelete(LoginRequiredMixin, ObjectDelete):
|
||||
model = Signal
|
||||
|
||||
list_url_name = "signals"
|
||||
list_url_args = ["type"]
|
Loading…
Reference in New Issue