Simplify DB object management with Django CRUD helpers

This commit is contained in:
Mark Veidemanis 2022-10-29 12:43:13 +01:00
parent 8f705e2f25
commit c685b6d25f
Signed by: m
GPG Key ID: 5ACFCEED46C0904F
26 changed files with 435 additions and 936 deletions

View File

@ -46,82 +46,63 @@ urlpatterns = [
), ),
path("accounts/", include("django.contrib.auth.urls")), path("accounts/", include("django.contrib.auth.urls")),
path("accounts/signup/", base.Signup.as_view(), name="signup"), path("accounts/signup/", base.Signup.as_view(), name="signup"),
path("hooks/<str:type>/", hooks.Hooks.as_view(), name="hooks"), path("hooks/<str:type>/", hooks.HookList.as_view(), name="hooks"),
path("hooks/<str:type>/add/", hooks.HookAction.as_view(), name="hook_action"), path("hooks/<str:type>/create/", hooks.HookCreate.as_view(), name="hook_create"),
path( path(
"hooks/<str:type>/add/<str:name>/", "hooks/<str:type>/update/<str:pk>/",
hooks.HookAction.as_view(), hooks.HookUpdate.as_view(),
name="hook_action", name="hook_update",
), ),
path( path(
"hooks/<str:type>/del/<str:hook_id>/", "hooks/<str:type>/delete/<str:pk>/",
hooks.HookAction.as_view(), hooks.HookDelete.as_view(),
name="hook_action", name="hook_delete",
),
path(
"hooks/<str:type>/edit/<str:hook_id>/",
hooks.HookAction.as_view(),
name="hook_action",
), ),
path( path(
f"{settings.HOOK_PATH}/<str:hook_name>/", hooks.HookAPI.as_view(), name="hook" f"{settings.HOOK_PATH}/<str:hook_name>/", hooks.HookAPI.as_view(), name="hook"
), ),
path( path(
"callbacks/<str:type>/<str:hook_id>/", "callbacks/<str:type>/<str:pk>/",
callbacks.Callbacks.as_view(), callbacks.Callbacks.as_view(),
name="callbacks", name="callbacks",
), ),
path("callbacks/<str:type>/", callbacks.Callbacks.as_view(), name="callbacks"), path("callbacks/<str:type>/", callbacks.Callbacks.as_view(), name="callbacks"),
path("accounts/<str:type>/", accounts.Accounts.as_view(), name="accounts"), path("accounts/<str:type>/", accounts.AccountList.as_view(), name="accounts"),
path( path(
"accounts/<str:type>/add/", "accounts/<str:type>/create/",
accounts.AccountAction.as_view(), accounts.AccountCreate.as_view(),
name="account_action", name="account_create",
), ),
path( path(
"accounts/<str:type>/add/<str:name>/", "accounts/<str:type>/info/<str:pk>/",
accounts.AccountAction.as_view(),
name="account_action",
),
path(
"accounts/<str:type>/del/<str:account_id>/",
accounts.AccountAction.as_view(),
name="account_action",
),
path(
"accounts/<str:type>/edit/<str:account_id>/",
accounts.AccountAction.as_view(),
name="account_action",
),
path(
"accounts/<str:type>/info/<str:account_id>/",
accounts.AccountInfo.as_view(), accounts.AccountInfo.as_view(),
name="account_info", name="account_info",
), ),
path("trades/<str:type>/", trades.Trades.as_view(), name="trades"),
path("trades/<str:type>/add/", trades.TradeAction.as_view(), name="trade_action"),
path( path(
"trades/<str:type>/<str:account_id>/", "accounts/<str:type>/update/<str:pk>/",
trades.Trades.as_view(), accounts.AccountUpdate.as_view(),
name="trades", name="account_update",
), ),
path( path(
"trades/<str:type>/add/<str:name>/", "accounts/<str:type>/delete/<str:pk>/",
trades.TradeAction.as_view(), accounts.AccountDelete.as_view(),
name="trade_action", name="account_delete",
),
path("trades/<str:type>/", trades.TradeList.as_view(), name="trades"),
path(
"trades/<str:type>/create/", trades.TradeCreate.as_view(), name="trade_create"
), ),
path( path(
"trades/<str:type>/del/<str:trade_id>/", "trades/<str:type>/update/<str:pk>/",
trades.TradeAction.as_view(), trades.TradeUpdate.as_view(),
name="trade_action", name="trade_update",
), ),
path( path(
"trades/<str:type>/edit/<str:trade_id>/", "trades/<str:type>/delete/<str:pk>/",
trades.TradeAction.as_view(), trades.TradeDelete.as_view(),
name="trade_action", name="trade_delete",
), ),
path("positions/<str:type>/", positions.Positions.as_view(), name="positions"), path("positions/<str:type>/", positions.Positions.as_view(), name="positions"),
# path("trades/<str:type>/add/", trades.TradeAction.as_view(), name="trade_action"),
path( path(
"positions/<str:type>/<str:account_id>/", "positions/<str:type>/<str:account_id>/",
positions.Positions.as_view(), positions.Positions.as_view(),
@ -132,40 +113,22 @@ urlpatterns = [
positions.PositionAction.as_view(), positions.PositionAction.as_view(),
name="position_action", name="position_action",
), ),
# path(
# "trades/<str:type>/add/<str:name>/",
# trades.TradeAction.as_view(),
# name="trade_action",
# ),
# path(
# "trades/<str:type>/del/<str:trade_id>/",
# trades.TradeAction.as_view(),
# name="trade_action",
# ),
# path(
# "trades/<str:type>/edit/<str:trade_id>/",
# trades.TradeAction.as_view(),
# name="trade_action",
# ),
path("strategies/<str:type>/", strategies.Strategies.as_view(), name="strategies"),
path( path(
"strategies/<str:type>/add/", "strategies/<str:type>/", strategies.StrategyList.as_view(), name="strategies"
strategies.StrategiesAction.as_view(),
name="strategies_action",
), ),
path( path(
"strategies/<str:type>/add/<str:name>/", "strategies/<str:type>/create/",
strategies.StrategiesAction.as_view(), strategies.StrategyCreate.as_view(),
name="strategies_action", name="strategy_create",
), ),
path( path(
"strategies/<str:type>/del/<str:strategy_id>/", "strategies/<str:type>/update/<str:pk>/",
strategies.StrategiesAction.as_view(), strategies.StrategyUpdate.as_view(),
name="strategies_action", name="strategy_update",
), ),
path( path(
"strategies/<str:type>/edit/<str:strategy_id>/", "strategies/<str:type>/delete/<str:pk>/",
strategies.StrategiesAction.as_view(), strategies.StrategyDelete.as_view(),
name="strategies_action", name="strategy_delete",
), ),
] + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT) ] + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)

View File

@ -92,12 +92,12 @@ class Account(models.Model):
try: try:
request = GetAssetsRequest(status="active", asset_class="crypto") request = GetAssetsRequest(status="active", asset_class="crypto")
assets = self.client.get_all_assets(filter=request) assets = self.client.get_all_assets(filter=request)
asset_list = [x["symbol"] for x in assets if "symbol" in x]
self.supported_symbols = asset_list
print("Supported symbols", self.supported_symbols)
except APIError as e: except APIError as e:
log.error(f"Could not get asset list: {e}") log.error(f"Could not get asset list: {e}")
return False # return False
asset_list = [x["symbol"] for x in assets if "symbol" in x]
self.supported_symbols = asset_list
print("Supported symbols", self.supported_symbols)
super().save(*args, **kwargs) super().save(*args, **kwargs)

View File

@ -10,7 +10,7 @@
<th>sandbox</th> <th>sandbox</th>
<th>actions</th> <th>actions</th>
</thead> </thead>
{% for item in items %} {% for item in object_list %}
<tr> <tr>
<td>{{ item.id }}</td> <td>{{ item.id }}</td>
<td>{{ item.user }}</td> <td>{{ item.user }}</td>
@ -32,7 +32,7 @@
<div class="buttons"> <div class="buttons">
<button <button
hx-headers='{"X-CSRFToken": "{{ csrf_token }}"}' hx-headers='{"X-CSRFToken": "{{ csrf_token }}"}'
hx-get="{% url 'account_action' type=type account_id=item.id %}" hx-get="{% url 'account_update' type=type pk=item.id %}"
hx-trigger="click" hx-trigger="click"
hx-target="#{{ type }}s-here" hx-target="#{{ type }}s-here"
class="button is-info"> class="button is-info">
@ -44,9 +44,10 @@
</button> </button>
<button <button
hx-headers='{"X-CSRFToken": "{{ csrf_token }}"}' hx-headers='{"X-CSRFToken": "{{ csrf_token }}"}'
hx-delete="{% url 'account_action' type=type account_id=item.id %}" hx-delete="{% url 'account_delete' type=type pk=item.id %}"
hx-trigger="click" hx-trigger="click"
hx-target="#accounts-table" hx-target="#modals-here"
hx-confirm="Are you sure you wish to delete {{ item.name }}?"
class="button is-danger"> class="button is-danger">
<span class="icon-text"> <span class="icon-text">
<span class="icon"> <span class="icon">
@ -55,7 +56,7 @@
</span> </span>
</button> </button>
{% if type == 'page' %} {% if type == 'page' %}
<a href="{% url 'account_info' type=type account_id=item.id %}"><button <a href="{% url 'account_info' type=type pk=item.id %}"><button
class="button is-success"> class="button is-success">
<span class="icon-text"> <span class="icon-text">
<span class="icon"> <span class="icon">
@ -67,7 +68,7 @@
{% else %} {% else %}
<button <button
hx-headers='{"X-CSRFToken": "{{ csrf_token }}"}' hx-headers='{"X-CSRFToken": "{{ csrf_token }}"}'
hx-get="{% url 'account_info' type=type account_id=item.id %}" hx-get="{% url 'account_info' type=type pk=item.id %}"
hx-trigger="click" hx-trigger="click"
hx-target="#{{ type }}s-here" hx-target="#{{ type }}s-here"
class="button is-success"> class="button is-success">

View File

@ -1,5 +1,4 @@
{% include 'partials/notify.html' %} {% include 'partials/notify.html' %}
<h1 class="title is-4">List of received callbacks</h1>
<table class="table is-fullwidth is-hoverable" id="callbacks-table"> <table class="table is-fullwidth is-hoverable" id="callbacks-table">
<thead> <thead>
@ -17,14 +16,14 @@
<th>contract</th> <th>contract</th>
<th>actions</th> <th>actions</th>
</thead> </thead>
{% for item in items %} {% for item in object_list %}
<tr> <tr>
<td>{{ item.id }}</td> <td>{{ item.id }}</td>
<td>{{ item.hook.id }}</td> <td>{{ item.hook.id }}</td>
<td> <td>
<a <a
hx-headers='{"X-CSRFToken": "{{ csrf_token }}"}' hx-headers='{"X-CSRFToken": "{{ csrf_token }}"}'
hx-get="{% url 'hook_action' type=type hook_id=item.hook.id %}" hx-get="{% url 'hook_update' type=type pk=item.hook.id %}"
hx-trigger="click" hx-trigger="click"
hx-target="#{{ type }}s-here">{{ item.hook.name }} hx-target="#{{ type }}s-here">{{ item.hook.name }}
</a> </a>

View File

@ -10,7 +10,7 @@
<th>received hooks</th> <th>received hooks</th>
<th>actions</th> <th>actions</th>
</thead> </thead>
{% for item in items %} {% for item in object_list %}
<tr> <tr>
<td>{{ item.id }}</td> <td>{{ item.id }}</td>
<td>{{ item.user }}</td> <td>{{ item.user }}</td>
@ -22,7 +22,7 @@
<div class="buttons"> <div class="buttons">
<button <button
hx-headers='{"X-CSRFToken": "{{ csrf_token }}"}' hx-headers='{"X-CSRFToken": "{{ csrf_token }}"}'
hx-get="{% url 'hook_action' type=type hook_id=item.id %}" hx-get="{% url 'hook_update' type=type pk=item.id %}"
hx-trigger="click" hx-trigger="click"
hx-target="#{{ type }}s-here" hx-target="#{{ type }}s-here"
class="button is-info"> class="button is-info">
@ -34,9 +34,10 @@
</button> </button>
<button <button
hx-headers='{"X-CSRFToken": "{{ csrf_token }}"}' hx-headers='{"X-CSRFToken": "{{ csrf_token }}"}'
hx-delete="{% url 'hook_action' type=type hook_id=item.id %}" hx-delete="{% url 'hook_delete' type=type pk=item.id %}"
hx-trigger="click" hx-trigger="click"
hx-target="#hooks-table" hx-target="#modals-here"
hx-confirm="Are you sure you wish to delete {{ item.name }}?"
class="button is-danger"> class="button is-danger">
<span class="icon-text"> <span class="icon-text">
<span class="icon"> <span class="icon">
@ -45,7 +46,7 @@
</span> </span>
</button> </button>
{% if type == 'page' %} {% if type == 'page' %}
<a href="{% url 'callbacks' type='page' hook_id=item.id %}"><button <a href="{% url 'callbacks' type='page' pk=item.id %}"><button
class="button is-success"> class="button is-success">
<span class="icon-text"> <span class="icon-text">
<span class="icon"> <span class="icon">
@ -57,7 +58,7 @@
{% else %} {% else %}
<button <button
hx-headers='{"X-CSRFToken": "{{ csrf_token }}"}' hx-headers='{"X-CSRFToken": "{{ csrf_token }}"}'
hx-get="{% url 'callbacks' type=type hook_id=item.id %}" hx-get="{% url 'callbacks' type=type pk=item.id %}"
hx-trigger="click" hx-trigger="click"
hx-target="#{{ type }}s-here" hx-target="#{{ type }}s-here"
class="button is-success"> class="button is-success">

View File

@ -41,7 +41,8 @@
hx-headers='{"X-CSRFToken": "{{ csrf_token }}"}' hx-headers='{"X-CSRFToken": "{{ csrf_token }}"}'
hx-delete="#trade-close-confirm" hx-delete="#trade-close-confirm"
hx-trigger="click" hx-trigger="click"
hx-target="#accounts-table" hx-target="#positions-table"
hx-confirm="Are you sure you wish to close {{ item.symbol }}?"
class="button is-danger"> class="button is-danger">
<span class="icon-text"> <span class="icon-text">
<span class="icon"> <span class="icon">

View File

@ -11,7 +11,7 @@
<th>SL</th> <th>SL</th>
<th>actions</th> <th>actions</th>
</thead> </thead>
{% for item in items %} {% for item in object_list %}
<tr> <tr>
<td>{{ item.id }}</td> <td>{{ item.id }}</td>
<td>{{ item.name }}</td> <td>{{ item.name }}</td>
@ -34,7 +34,7 @@
<div class="buttons"> <div class="buttons">
<button <button
hx-headers='{"X-CSRFToken": "{{ csrf_token }}"}' hx-headers='{"X-CSRFToken": "{{ csrf_token }}"}'
hx-get="{% url 'strategies_action' type=type strategy_id=item.id %}" hx-get="{% url 'strategy_update' type=type pk=item.id %}"
hx-trigger="click" hx-trigger="click"
hx-target="#{{ type }}s-here" hx-target="#{{ type }}s-here"
class="button is-info"> class="button is-info">
@ -46,9 +46,10 @@
</button> </button>
<button <button
hx-headers='{"X-CSRFToken": "{{ csrf_token }}"}' hx-headers='{"X-CSRFToken": "{{ csrf_token }}"}'
hx-delete="{% url 'strategies_action' type=type strategy_id=item.id %}" hx-delete="{% url 'strategy_delete' type=type pk=item.id %}"
hx-trigger="click" hx-trigger="click"
hx-target="#strategies-table" hx-target="#modals-here"
hx-confirm="Are you sure you wish to delete {{ item.name }}?"
class="button is-danger"> class="button is-danger">
<span class="icon-text"> <span class="icon-text">
<span class="icon"> <span class="icon">
@ -57,7 +58,7 @@
</span> </span>
</button> </button>
{% if type == 'page' %} {% if type == 'page' %}
<a href="{% url 'strategies_action' type=type strategy_id=item.id %}"><button <a href="#"><button
class="button is-success"> class="button is-success">
<span class="icon-text"> <span class="icon-text">
<span class="icon"> <span class="icon">
@ -69,7 +70,7 @@
{% else %} {% else %}
<button <button
hx-headers='{"X-CSRFToken": "{{ csrf_token }}"}' hx-headers='{"X-CSRFToken": "{{ csrf_token }}"}'
hx-get="{% url 'strategies_action' type=type strategy_id=item.id %}" hx-get="#"
hx-trigger="click" hx-trigger="click"
hx-target="#{{ type }}s-here" hx-target="#{{ type }}s-here"
class="button is-success"> class="button is-success">

View File

@ -13,7 +13,7 @@
<th>TL</th> <th>TL</th>
<th>actions</th> <th>actions</th>
</thead> </thead>
{% for item in items %} {% for item in object_list %}
<tr> <tr>
<td>{{ item.id }}</td> <td>{{ item.id }}</td>
<td>{{ item.status }}</td> <td>{{ item.status }}</td>
@ -28,7 +28,7 @@
<div class="buttons"> <div class="buttons">
<button <button
hx-headers='{"X-CSRFToken": "{{ csrf_token }}"}' hx-headers='{"X-CSRFToken": "{{ csrf_token }}"}'
hx-get="{% url 'trade_action' type=type trade_id=item.id %}" hx-get="{% url 'trade_update' type=type pk=item.id %}"
hx-trigger="click" hx-trigger="click"
hx-target="#{{ type }}s-here" hx-target="#{{ type }}s-here"
class="button is-info"> class="button is-info">
@ -40,9 +40,9 @@
</button> </button>
<button <button
hx-headers='{"X-CSRFToken": "{{ csrf_token }}"}' hx-headers='{"X-CSRFToken": "{{ csrf_token }}"}'
hx-delete="{% url 'trade_action' type=type trade_id=item.id %}" hx-delete="{% url 'trade_delete' type=type pk=item.id %}"
hx-trigger="click" hx-trigger="click"
hx-target="#trades-table" hx-target="#modals-here"
class="button is-danger"> class="button is-danger">
<span class="icon-text"> <span class="icon-text">
<span class="icon"> <span class="icon">

View File

@ -1,21 +0,0 @@
{% include 'partials/notify.html' %}
<h1 class="title is-4">List of accounts</h1>
<div class="buttons">
<button
hx-headers='{"X-CSRFToken": "{{ csrf_token }}"}'
hx-get="{% url 'account_action' type=type %}"
hx-trigger="click"
hx-target="#modals-here"
class="button is-info">
<span class="icon-text">
<span class="icon">
<i class="fa-solid fa-plus"></i>
</span>
<span>Account</span>
</span>
</button>
</div>
{% include 'partials/account-list.html' %}

View File

@ -1,31 +0,0 @@
{% include 'partials/notify.html' %}
{% load crispy_forms_tags %}
{% load crispy_forms_bulma_field %}
<form
hx-headers='{"X-CSRFToken": "{{ csrf_token }}"}'
{% if account_id is not None %}
hx-put="{% url 'account_action' type=type account_id=account_id %}"
{% else %}
hx-put="{% url 'account_action' type=type %}"
{% endif %}
hx-target="#accounts-table"
hx-swap="outerHTML">
{% csrf_token %}
{{ form|crispy }}
<button
type="button"
class="button is-light modal-close-button">
Cancel
</button>
<button type="submit" class="button is-info modal-close-button">Submit</button>
</form>

View File

@ -1,31 +0,0 @@
{% include 'partials/notify.html' %}
{% load crispy_forms_tags %}
{% load crispy_forms_bulma_field %}
<form
hx-headers='{"X-CSRFToken": "{{ csrf_token }}"}'
{% if strategy_id is not None %}
hx-put="{% url 'strategies_action' type=type strategy_id=strategy_id %}"
{% else %}
hx-put="{% url 'strategies_action' type=type %}"
{% endif %}
hx-target="#strategies-table"
hx-swap="outerHTML">
{% csrf_token %}
{{ form|crispy }}
<button
type="button"
class="button is-light modal-close-button">
Cancel
</button>
<button type="submit" class="button is-info modal-close-button">Submit</button>
</form>

View File

@ -1,34 +0,0 @@
<h1 class="title is-5">Add or edit a trade</h1>
<h1 class="subtitle">Updates will be posted to exchange!</h1>
{% include 'partials/notify.html' %}
{% load crispy_forms_tags %}
{% load crispy_forms_bulma_field %}
<form
hx-headers='{"X-CSRFToken": "{{ csrf_token }}"}'
{% if trade_id is not None %}
hx-put="{% url 'trade_action' type=type trade_id=trade_id %}"
{% else %}
hx-put="{% url 'trade_action' type=type %}"
{% endif %}
hx-target="#trades-table"
hx-swap="outerHTML">
{% csrf_token %}
{{ form|crispy }}
<button
type="button"
class="button is-light modal-close-button">
Cancel
</button>
<button type="submit" class="button is-info modal-close-button">Submit</button>
</form>

View File

@ -1,23 +0,0 @@
{% include 'partials/notify.html' %}
<h1 class="title is-4">List of active URL endpoints for receiving hooks.</h1>
<h1 class="subtitle">Add URLs here to receive Drakdoo callbacks. Make then unique!</h1>
<h1 class="subtitle">Warning: hooks can only trade in one direction.</h1>
<div class="buttons">
<button
hx-headers='{"X-CSRFToken": "{{ csrf_token }}"}'
hx-get="{% url 'hook_action' type=type %}"
hx-trigger="click"
hx-target="#modals-here"
class="button is-info">
<span class="icon-text">
<span class="icon">
<i class="fa-solid fa-plus"></i>
</span>
<span>Hook</span>
</span>
</button>
</div>
{% include 'partials/hook-list.html' %}

View File

@ -5,13 +5,9 @@
{% load crispy_forms_bulma_field %} {% load crispy_forms_bulma_field %}
<form <form
hx-headers='{"X-CSRFToken": "{{ csrf_token }}"}' hx-headers='{"X-CSRFToken": "{{ csrf_token }}"}'
{% if hook_id is not None %} hx-post="{{ submit_url }}"
hx-put="{% url 'hook_action' type=type hook_id=hook_id %}" hx-target="#modals-here"
{% else %} hx-swap="innerHTML">
hx-put="{% url 'hook_action' type=type %}"
{% endif %}
hx-target="#hooks-table"
hx-swap="outerHTML">
{% csrf_token %} {% csrf_token %}
{{ form|crispy }} {{ form|crispy }}
<button <button

View File

@ -0,0 +1,28 @@
{% include 'partials/notify.html' %}
{% if page_title is not None %}
<h1 class="title is-4">{{ page_title }}</h1>
{% endif %}
{% if page_subtitle is not None %}
<h1 class="subtitle">{{ page_subtitle }}</h1>
{% endif %}
{% if submit_url is not None %}
<div class="buttons">
<button
hx-headers='{"X-CSRFToken": "{{ csrf_token }}"}'
hx-get="{{ submit_url }}"
hx-trigger="click"
hx-target="#modals-here"
class="button is-info">
<span class="icon-text">
<span class="icon">
<i class="fa-solid fa-plus"></i>
</span>
<span>{{ title_singular }}</span>
</span>
</button>
</div>
{% endif %}
{% include list_template %}

View File

@ -1,6 +1,6 @@
<h1 class="title is-4">Live positions from all exchanges</h1> <h1 class="title is-4"></h1>
<h1 class="subtitle">Manual trades are editable under "Bot Trades" tab.</h1> <h1 class="subtitle"></h1>
{% include 'partials/notify.html' %} {% include 'partials/notify.html' %}
{% include 'partials/position-list.html' %} {% include 'partials/position-list.html' %}SSSSSSSSSSSSSSSSS

View File

@ -1,21 +0,0 @@
{% include 'partials/notify.html' %}
<h1 class="title is-4">List of strategies</h1>
<div class="buttons">
<button
hx-headers='{"X-CSRFToken": "{{ csrf_token }}"}'
hx-get="{% url 'strategies_action' type=type %}"
hx-trigger="click"
hx-target="#modals-here"
class="button is-info">
<span class="icon-text">
<span class="icon">
<i class="fa-solid fa-plus"></i>
</span>
<span>Strategy</span>
</span>
</button>
</div>
{% include 'partials/strategy-list.html' %}

View File

@ -1,23 +0,0 @@
<div class="buttons">
<button
hx-headers='{"X-CSRFToken": "{{ csrf_token }}"}'
hx-get="{% url 'trade_action' type=type %}"
hx-trigger="click"
hx-target="#modals-here"
class="button is-info">
<span class="icon-text">
<span class="icon">
<i class="fa-solid fa-plus"></i>
</span>
<span>Trade</span>
</span>
</button>
</div>
{% include 'partials/notify.html' %}
<h1 class="title is-4">List of bot and manual trades. This may not reflect actual live trades.</h1>
<h1 class="subtitle">Trades deleted here will not be closed on the exchange.</h1>
{% include 'partials/trade-list.html' %}

View File

@ -0,0 +1,203 @@
import uuid
from django.http import Http404, HttpResponseBadRequest
from django.urls import reverse
from django.views.generic.detail import DetailView
from django.views.generic.edit import CreateView, DeleteView, UpdateView
from django.views.generic.list import ListView
from rest_framework.parsers import FormParser
from core.util import logs
log = logs.get_logger(__name__)
class ObjectList(ListView):
allowed_types = ["modal", "widget", "window", "page"]
window_content = "window-content/objects.html"
list_template = None
model = None
context_object_name = "objects"
page_title = None
page_subtitle = None
title = "Objects"
title_singular = "Object"
submit_url_name = None
# copied from BaseListView
def get(self, request, *args, **kwargs):
self.object_list = self.get_queryset()
allow_empty = self.get_allow_empty()
type = kwargs.get("type", None)
if not type:
return HttpResponseBadRequest("No type specified")
if type not in self.allowed_types:
return HttpResponseBadRequest("Invalid type specified")
self.template_name = f"wm/{type}.html"
unique = str(uuid.uuid4())[:8]
if type == "page":
type = "modal"
if not allow_empty:
# When pagination is enabled and object_list is a queryset,
# it's better to do a cheap query than to load the unpaginated
# queryset in memory.
if self.get_paginate_by(self.object_list) is not None and hasattr(
self.object_list, "exists"
):
is_empty = not self.object_list.exists()
else:
is_empty = not self.object_list
if is_empty:
raise Http404("Empty list")
submit_url = reverse(self.submit_url_name, kwargs={"type": type})
context = self.get_context_data()
context["title"] = self.title + f" ({type})"
context["title_singular"] = self.title_singular
context["unique"] = unique
context["window_content"] = self.window_content
context["list_template"] = self.list_template
context["page_title"] = self.page_title
context["page_subtitle"] = self.page_subtitle
context["type"] = type
context["submit_url"] = submit_url
return self.render_to_response(context)
class ObjectCreate(CreateView):
allowed_types = ["modal", "widget", "window", "page"]
window_content = "window-content/object-form.html"
parser_classes = [FormParser]
model = None
context_object_name = "objects"
submit_url_name = None
request = None
def form_valid(self, form):
obj = form.save(commit=False)
if self.request is None:
raise Exception("Request is None")
obj.user = self.request.user
obj.save()
form.save_m2m()
context = {"message": "Object created", "class": "success"}
return self.render_to_response(context)
def get(self, request, *args, **kwargs):
self.request = request
type = kwargs.get("type", None)
if not type:
return HttpResponseBadRequest("No type specified")
if type not in self.allowed_types:
return HttpResponseBadRequest("Invalid type specified")
self.template_name = f"wm/{type}.html"
unique = str(uuid.uuid4())[:8]
if type == "page":
type = "modal"
self.object = None
submit_url = reverse(self.submit_url_name, kwargs={"type": type})
context = self.get_context_data()
context["unique"] = unique
context["window_content"] = self.window_content
context["context_object_name"] = self.context_object_name
context["submit_url"] = submit_url
context["type"] = type
return self.render_to_response(context)
def post(self, request, *args, **kwargs):
self.request = request
self.template_name = "partials/notify.html"
return super().post(request, *args, **kwargs)
class ObjectRead(DetailView):
allowed_types = ["modal", "widget", "window", "page"]
window_content = "window-content/object.html"
model = None
context_object_name = "object"
class ObjectUpdate(UpdateView):
allowed_types = ["modal", "widget", "window", "page"]
window_content = "window-content/object-form.html"
parser_classes = [FormParser]
model = None
context_object_name = "objects"
submit_url_name = None
request = None
def form_valid(self, form):
obj = form.save(commit=False)
if self.request is None:
raise Exception("Request is None")
obj.save()
form.save_m2m()
context = {"message": "Object updated", "class": "success"}
return self.render_to_response(context)
def get(self, request, *args, **kwargs):
self.request = request
type = kwargs.get("type", None)
pk = kwargs.get("pk", None)
if not type:
return HttpResponseBadRequest("No type specified")
if not pk:
return HttpResponseBadRequest("No pk specified")
if type not in self.allowed_types:
return HttpResponseBadRequest("Invalid type specified")
self.template_name = f"wm/{type}.html"
unique = str(uuid.uuid4())[:8]
if type == "page":
type = "modal"
self.object = self.get_object()
submit_url = reverse(self.submit_url_name, kwargs={"type": type, "pk": pk})
context = self.get_context_data()
context["unique"] = unique
context["window_content"] = self.window_content
context["context_object_name"] = self.context_object_name
context["submit_url"] = submit_url
context["type"] = type
return self.render_to_response(context)
def post(self, request, *args, **kwargs):
self.request = request
self.template_name = "partials/notify.html"
return super().post(request, *args, **kwargs)
class ObjectDelete(DeleteView):
model = None
template_name = "partials/notify.html"
# Overriden to prevent success URL from being used
def delete(self, request, *args, **kwargs):
"""
Call the delete() method on the fetched object and then redirect to the
success URL.
"""
self.object = self.get_object()
# success_url = self.get_success_url()
self.object.delete()
context = {"message": "Object deleted", "class": "success"}
return self.render_to_response(context)
# This will be used in newer Django versions, until then we get a warning
def form_valid(self, form):
"""
Call the delete() method on the fetched object.
"""
self.object = self.get_object()
self.object.delete()
context = {"message": "Object deleted", "class": "success"}
return self.render_to_response(context)

View File

@ -4,27 +4,21 @@ from django.contrib.auth.mixins import LoginRequiredMixin
from django.http import HttpResponseBadRequest from django.http import HttpResponseBadRequest
from django.shortcuts import render from django.shortcuts import render
from django.views import View from django.views import View
from rest_framework.parsers import FormParser
from rest_framework.views import APIView
from core.forms import AccountForm from core.forms import AccountForm
from core.models import Account from core.models import Account
from core.util import logs from core.util import logs
from core.views import ObjectCreate, ObjectDelete, ObjectList, ObjectUpdate
log = logs.get_logger(__name__) log = logs.get_logger(__name__)
def get_accounts(user):
accounts = Account.objects.filter(user=user)
return accounts
class AccountInfo(LoginRequiredMixin, View): class AccountInfo(LoginRequiredMixin, View):
VIEWABLE_FIELDS_MODEL = ["name", "exchange", "api_key", "sandbox"] VIEWABLE_FIELDS_MODEL = ["name", "exchange", "api_key", "sandbox"]
allowed_types = ["modal", "widget", "window", "page"] allowed_types = ["modal", "widget", "window", "page"]
window_content = "window-content/account-info.html" window_content = "window-content/account-info.html"
def get(self, request, type, account_id): def get(self, request, type, pk):
""" """
Get the account details. Get the account details.
:param account_id: The id of the account. :param account_id: The id of the account.
@ -34,7 +28,7 @@ class AccountInfo(LoginRequiredMixin, View):
template_name = f"wm/{type}.html" template_name = f"wm/{type}.html"
unique = str(uuid.uuid4())[:8] unique = str(uuid.uuid4())[:8]
try: try:
account = Account.get_by_id(account_id, request.user) account = Account.get_by_id(pk, request.user)
except Account.DoesNotExist: except Account.DoesNotExist:
message = "Account does not exist" message = "Account does not exist"
message_class = "danger" message_class = "danger"
@ -56,7 +50,7 @@ class AccountInfo(LoginRequiredMixin, View):
context = { context = {
"db_info": account_info, "db_info": account_info,
"live_info": live_info, "live_info": live_info,
"account_id": account_id, "pk": pk,
"type": type, "type": type,
"unique": unique, "unique": unique,
"window_content": self.window_content, "window_content": self.window_content,
@ -65,145 +59,43 @@ class AccountInfo(LoginRequiredMixin, View):
return render(request, template_name, context) return render(request, template_name, context)
class Accounts(LoginRequiredMixin, View): class AccountList(LoginRequiredMixin, ObjectList):
allowed_types = ["modal", "widget", "window", "page"] list_template = "partials/account-list.html"
window_content = "window-content/accounts.html" model = Account
context_object_name = "accounts"
title = "Accounts"
title_singular = "Account"
page_title = "List of accounts"
async def get(self, request, type): submit_url_name = "account_create"
if type not in self.allowed_types:
return HttpResponseBadRequest
template_name = f"wm/{type}.html"
unique = str(uuid.uuid4())[:8]
accounts = get_accounts(request.user)
if type == "page":
type = "modal"
context = {
"title": f"Accounts ({type})",
"unique": unique,
"window_content": self.window_content,
"items": accounts,
"type": type,
}
return render(request, template_name, context)
class AccountAction(LoginRequiredMixin, APIView): class AccountCreate(LoginRequiredMixin, ObjectCreate):
allowed_types = ["modal", "widget", "window", "page"] model = Account
window_content = "window-content/add-account.html" form_class = AccountForm
parser_classes = [FormParser] context_object_name = "accounts"
submit_url_name = "account_create"
def get(self, request, type, account_id=None):
"""
Get the form for adding or editing a account.
:param account_id: The id of the account to edit. Optional.
"""
if type not in self.allowed_types:
return HttpResponseBadRequest
template_name = f"wm/{type}.html"
unique = str(uuid.uuid4())[:8]
if account_id:
try:
account = Account.objects.get(id=account_id, user=request.user)
form = AccountForm(instance=account)
except Account.DoesNotExist:
message = "Account does not exist"
message_class = "danger"
context = {
"message": message,
"message_class": message_class,
"window_content": self.window_content,
}
return render(request, template_name, context)
else:
form = AccountForm()
if type == "page":
type = "modal"
context = {
"form": form,
"account_id": account_id,
"type": type,
"unique": unique,
"window_content": self.window_content,
}
return render(request, template_name, context) # class AccountRead(LoginRequiredMixin, ObjectRead):
# model = Account
# context_object_name = "accounts"
# submit_url_name = "account_read"
# fields = (
# "name",
# "exchange",
# "api_key",
# "api_secret",
# "sandbox",
# )
def put(self, request, type, account_id=None):
"""
Add or edit a account.
:param account_id: The id of the account to edit. Optional.
"""
if type not in self.allowed_types:
return HttpResponseBadRequest
message = None
message_class = "success"
if account_id: class AccountUpdate(LoginRequiredMixin, ObjectUpdate):
try: model = Account
form = AccountForm( form_class = AccountForm
request.data, context_object_name = "accounts"
instance=Account.objects.get(id=account_id, user=request.user), submit_url_name = "account_update"
)
except Account.DoesNotExist:
message = "Account does not exist"
message_class = "danger"
context = {
"message": message,
"class": message_class,
}
return render(request, self.template_name, context)
else:
form = AccountForm(request.data)
if form.is_valid():
account = form.save(commit=False)
account.user = request.user
account.save()
if account_id:
message = f"Account {account_id} edited successfully"
else:
message = f"Account {account.id} added successfully"
else:
message = "Error adding account"
message_class = "danger"
accounts = get_accounts(request.user)
context = { class AccountDelete(LoginRequiredMixin, ObjectDelete):
"items": accounts, model = Account
"type": type,
}
if message:
context["message"] = message
context["class"] = message_class
template_name = "partials/account-list.html"
return render(request, template_name, context)
def delete(self, request, type, account_id):
"""
Delete a account.
:param account_id: The id of the account to delete.
"""
if type not in self.allowed_types:
return HttpResponseBadRequest
message = None
message_class = "success"
try:
account = Account.objects.get(id=account_id, user=request.user)
account.delete()
message = "Account deleted successfully"
except Account.DoesNotExist:
message = "Error deleting account"
message_class = "danger"
accounts = get_accounts(request.user)
context = {
"items": accounts,
"type": type,
}
if message:
context["message"] = message
context["class"] = message_class
template_name = "partials/account-list.html"
return render(request, template_name, context)

View File

@ -18,17 +18,19 @@ def get_callbacks(user, hook=None):
class Callbacks(LoginRequiredMixin, View): class Callbacks(LoginRequiredMixin, View):
allowed_types = ["modal", "widget", "window", "page"] allowed_types = ["modal", "widget", "window", "page"]
window_content = "window-content/callbacks.html" window_content = "window-content/objects.html"
list_template = "partials/callback-list.html"
page_title = "List of received callbacks"
async def get(self, request, type, hook_id=None): async def get(self, request, type, pk=None):
if type not in self.allowed_types: if type not in self.allowed_types:
return HttpResponseBadRequest return HttpResponseBadRequest
template_name = f"wm/{type}.html" template_name = f"wm/{type}.html"
unique = str(uuid.uuid4())[:8] unique = str(uuid.uuid4())[:8]
if hook_id: if pk:
try: try:
hook = Hook.objects.get(id=hook_id, user=request.user) hook = Hook.objects.get(id=pk, user=request.user)
except Hook.DoesNotExist: except Hook.DoesNotExist:
message = "Hook does not exist." message = "Hook does not exist."
message_class = "danger" message_class = "danger"
@ -48,7 +50,9 @@ class Callbacks(LoginRequiredMixin, View):
"title": f"Callbacks ({type})", "title": f"Callbacks ({type})",
"unique": unique, "unique": unique,
"window_content": self.window_content, "window_content": self.window_content,
"items": callbacks, "list_template": self.list_template,
"object_list": callbacks,
"type": type, "type": type,
"page_title": self.page_title,
} }
return render(request, template_name, context) return render(request, template_name, context)

View File

@ -1,13 +1,10 @@
import re import re
import uuid
import orjson import orjson
from django.conf import settings from django.conf import settings
from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.auth.mixins import LoginRequiredMixin
from django.http import HttpResponse, HttpResponseBadRequest from django.http import HttpResponse, HttpResponseBadRequest
from django.shortcuts import render from rest_framework.parsers import JSONParser
from django.views import View
from rest_framework.parsers import FormParser, JSONParser
from rest_framework.views import APIView from rest_framework.views import APIView
from serde import ValidationError from serde import ValidationError
@ -16,15 +13,11 @@ from core.lib import market
from core.lib.serde import drakdoo_s from core.lib.serde import drakdoo_s
from core.models import Callback, Hook from core.models import Callback, Hook
from core.util import logs from core.util import logs
from core.views import ObjectCreate, ObjectDelete, ObjectList, ObjectUpdate
log = logs.get_logger(__name__) log = logs.get_logger(__name__)
def get_hooks(user):
hooks = Hook.objects.filter(user=user)
return hooks
def extract_price(message): def extract_price(message):
result = re.findall("\d+\.\d+", message) # noqa result = re.findall("\d+\.\d+", message) # noqa
if len(result) != 1: if len(result) != 1:
@ -72,8 +65,8 @@ class HookAPI(APIView):
"message": hook_resp.message, "message": hook_resp.message,
"period": hook_resp.period, "period": hook_resp.period,
"sent": hook_resp.timestamp.sent, "sent": hook_resp.timestamp.sent,
"timestamp_trade": hook_resp.timestamp.trade, "trade": hook_resp.timestamp.trade,
"trade": hook_resp.market.exchange, "exchange": hook_resp.market.exchange,
"base": hook_resp.market.item, "base": hook_resp.market.item,
"quote": hook_resp.market.currency, "quote": hook_resp.market.currency,
"symbol": symbol, "symbol": symbol,
@ -106,142 +99,32 @@ class HookAPI(APIView):
return HttpResponse(orjson.dumps(return_data), content_type="application/json") return HttpResponse(orjson.dumps(return_data), content_type="application/json")
class Hooks(LoginRequiredMixin, View): class HookList(LoginRequiredMixin, ObjectList):
allowed_types = ["modal", "widget", "window", "page"] # window_content = "window-content/hooks.html"
window_content = "window-content/hooks.html" list_template = "partials/hook-list.html"
model = Hook
context_object_name = "hooks"
title = "Hooks"
title_singular = "Hook"
page_title = "List of active URL endpoints for receiving hooks."
page_subtitle = "Add URLs here to receive Drakdoo callbacks. Make then unique!"
async def get(self, request, type): submit_url_name = "hook_create"
if type not in self.allowed_types:
return HttpResponseBadRequest
template_name = f"wm/{type}.html"
unique = str(uuid.uuid4())[:8]
hooks = get_hooks(request.user)
if type == "page":
type = "modal"
context = {
"title": f"Hooks ({type})",
"unique": unique,
"window_content": self.window_content,
"items": hooks,
"type": type,
}
return render(request, template_name, context)
class HookAction(LoginRequiredMixin, APIView): class HookCreate(LoginRequiredMixin, ObjectCreate):
allowed_types = ["modal", "widget", "window", "page"] model = Hook
window_content = "window-content/add-hook.html" form_class = HookForm
parser_classes = [FormParser] context_object_name = "hooks"
submit_url_name = "hook_create"
def get(self, request, type, hook_id=None):
"""
Get the form for adding or editing a hook.
:param hook_id: The id of the hook to edit. Optional.
"""
if type not in self.allowed_types:
return HttpResponseBadRequest
template_name = f"wm/{type}.html"
unique = str(uuid.uuid4())[:8]
if hook_id:
try:
hook = Hook.objects.get(id=hook_id, user=request.user)
form = HookForm(instance=hook)
except Hook.DoesNotExist:
message = "Hook does not exist"
message_class = "danger"
context = {
"message": message,
"message_class": message_class,
"window_content": self.window_content,
}
return render(request, template_name, context)
else:
form = HookForm()
if type == "page":
type = "modal"
context = {
"form": form,
"hook_id": hook_id,
"type": type,
"unique": unique,
"window_content": self.window_content,
}
return render(request, template_name, context) class HookUpdate(LoginRequiredMixin, ObjectUpdate):
model = Hook
form_class = HookForm
context_object_name = "hooks"
submit_url_name = "hook_update"
def put(self, request, type, hook_id=None):
"""
Add or edit a hook.
:param hook_id: The id of the hook to edit. Optional.
"""
if type not in self.allowed_types:
return HttpResponseBadRequest
message = None
message_class = "success"
if hook_id: class HookDelete(LoginRequiredMixin, ObjectDelete):
try: model = Hook
form = HookForm(request.data, instance=Hook.objects.get(id=hook_id))
except Hook.DoesNotExist:
message = "Hook does not exist"
message_class = "danger"
context = {
"message": message,
"class": message_class,
}
return render(request, self.template_name, context)
else:
form = HookForm(request.data)
if form.is_valid():
hook = form.save(commit=False)
hook.user = request.user
hook.save()
if hook_id:
message = f"Hook {hook_id} edited successfully"
else:
message = f"Hook {hook.id} added successfully"
else:
message = "Error adding hook"
message_class = "danger"
hooks = get_hooks(request.user)
context = {
"items": hooks,
"type": type,
}
if message:
context["message"] = message
context["class"] = message_class
template_name = "partials/hook-list.html"
return render(request, template_name, context)
def delete(self, request, type, hook_id):
"""
Delete a hook.
:param hook_id: The id of the hook to delete.
"""
if type not in self.allowed_types:
return HttpResponseBadRequest
message = None
message_class = "success"
try:
hook = Hook.objects.get(id=hook_id, user=request.user)
hook.delete()
message = "Hook deleted successfully"
except Hook.DoesNotExist:
message = "Error deleting hook"
message_class = "danger"
hooks = get_hooks(request.user)
context = {
"items": hooks,
"type": type,
}
if message:
context["message"] = message
context["class"] = message_class
template_name = "partials/hook-list.html"
return render(request, template_name, context)

View File

@ -37,7 +37,10 @@ def get_positions(user, account_id=None):
class Positions(LoginRequiredMixin, View): class Positions(LoginRequiredMixin, View):
allowed_types = ["modal", "widget", "window", "page"] allowed_types = ["modal", "widget", "window", "page"]
window_content = "window-content/positions.html" window_content = "window-content/objects.html"
list_template = "partials/position-list.html"
page_title = "Live positions from all exchanges"
page_subtitle = "Manual trades are editable under 'Bot Trades' tab."
async def get(self, request, type, account_id=None): async def get(self, request, type, account_id=None):
if type not in self.allowed_types: if type not in self.allowed_types:
@ -51,8 +54,11 @@ class Positions(LoginRequiredMixin, View):
"title": f"Hooks ({type})", "title": f"Hooks ({type})",
"unique": unique, "unique": unique,
"window_content": self.window_content, "window_content": self.window_content,
"list_template": self.list_template,
"items": items, "items": items,
"type": type, "type": type,
"page_title": self.page_title,
"page_subtitle": self.page_subtitle,
} }
return render(request, template_name, context) return render(request, template_name, context)

View File

@ -1,15 +1,9 @@
import uuid
from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.auth.mixins import LoginRequiredMixin
from django.http import HttpResponseBadRequest
from django.shortcuts import render
from django.views import View # , CreateView, UpdateView, DeleteView
from rest_framework.parsers import FormParser
from rest_framework.views import APIView
from core.forms import StrategyForm from core.forms import StrategyForm
from core.models import Strategy from core.models import Strategy
from core.util import logs from core.util import logs
from core.views import ObjectCreate, ObjectDelete, ObjectList, ObjectUpdate
# from django.urls import reverse # from django.urls import reverse
@ -17,172 +11,30 @@ from core.util import logs
log = logs.get_logger(__name__) log = logs.get_logger(__name__)
def get_strategies(user): class StrategyList(LoginRequiredMixin, ObjectList):
strategies = Strategy.objects.filter(user=user) list_template = "partials/strategy-list.html"
return strategies model = Strategy
context_object_name = "strategies"
title = "Strategies"
title_singular = "Strategy"
page_title = "List of strategies"
submit_url_name = "strategy_create"
class Strategies(LoginRequiredMixin, View): class StrategyCreate(LoginRequiredMixin, ObjectCreate):
allowed_types = ["modal", "widget", "window", "page"] model = Strategy
window_content = "window-content/strategies.html" form_class = StrategyForm
context_object_name = "strategies"
async def get(self, request, type): submit_url_name = "strategy_create"
if type not in self.allowed_types:
return HttpResponseBadRequest
template_name = f"wm/{type}.html"
unique = str(uuid.uuid4())[:8]
strategies = get_strategies(request.user)
if type == "page":
type = "modal"
context = {
"title": f"Accounts ({type})",
"unique": unique,
"window_content": self.window_content,
"items": strategies,
"type": type,
}
return render(request, template_name, context)
# class AddStrategy(CreateView): class StrategyUpdate(LoginRequiredMixin, ObjectUpdate):
# model = Strategy model = Strategy
# form_class = StrategyForm form_class = StrategyForm
# template_name = "window-content/add-strategy.html" context_object_name = "strategies"
# success_url = reverse("strategies") submit_url_name = "strategy_update"
# def form_valid(self, form):
# form.instance.user = self.request.user
# return super().form_valid(form)
# def get_context_data(self, **kwargs):
# context = super().get_context_data(**kwargs)
# context["title"] = "Add Strategy"
# context["window_content"] = "window-content/add_strategy.html"
# return context
class StrategiesAction(LoginRequiredMixin, APIView): class StrategyDelete(LoginRequiredMixin, ObjectDelete):
allowed_types = ["modal", "widget", "window", "page"] model = Strategy
window_content = "window-content/add-strategy.html"
parser_classes = [FormParser]
def get(self, request, type, strategy_id=None):
"""
Get the form for adding or editing a strategy.
:param strategy_id: The id of the strategy to edit. Optional.
"""
if type not in self.allowed_types:
return HttpResponseBadRequest
template_name = f"wm/{type}.html"
unique = str(uuid.uuid4())[:8]
if strategy_id:
try:
account = Strategy.objects.get(id=strategy_id)
form = StrategyForm(instance=account)
except Strategy.DoesNotExist:
message = "Strategy does not exist"
message_class = "danger"
context = {
"message": message,
"message_class": message_class,
"window_content": self.window_content,
}
return render(request, template_name, context)
else:
form = StrategyForm()
if type == "page":
type = "modal"
context = {
"form": form,
"strategy_id": strategy_id,
"type": type,
"unique": unique,
"window_content": self.window_content,
}
return render(request, template_name, context)
def put(self, request, type, strategy_id=None):
"""
Add or edit a account.
:param account_id: The id of the strategy to edit. Optional.
"""
if type not in self.allowed_types:
return HttpResponseBadRequest
message = None
message_class = "success"
if strategy_id:
try:
form = StrategyForm(
request.data,
instance=Strategy.objects.get(
id=strategy_id, account__user=request.user
),
)
except Strategy.DoesNotExist:
message = "Strategy does not exist"
message_class = "danger"
context = {
"message": message,
"class": message_class,
}
return render(request, self.template_name, context)
else:
form = StrategyForm(request.data)
if form.is_valid():
hooks = list(form.cleaned_data.get("hooks"))
strategy = form.save(commit=False)
strategy.user = request.user
strategy.hooks.set(hooks)
strategy.save()
print("HOOKS SET", strategy.hooks)
if strategy_id:
message = f"Strategy {strategy_id} edited successfully"
else:
message = f"Strategy {strategy.id} added successfully"
else:
message = "Error adding strategy"
message_class = "danger"
accounts = get_strategies(request.user)
context = {
"items": accounts,
"type": type,
}
if message:
context["message"] = message
context["class"] = message_class
template_name = "partials/strategy-list.html"
return render(request, template_name, context)
def delete(self, request, type, strategy_id):
"""
Delete a strategy.
:param strategy_id: The id of the strategy to delete.
"""
if type not in self.allowed_types:
return HttpResponseBadRequest
message = None
message_class = "success"
try:
strategy = Strategy.objects.get(id=strategy_id, user=request.user)
strategy.delete()
message = "Strategy deleted successfully"
except Strategy.DoesNotExist:
message = "Error deleting strategy"
message_class = "danger"
strategies = get_strategies(request.user)
context = {
"items": strategies,
"type": strategies,
}
if message:
context["message"] = message
context["class"] = message_class
template_name = "partials/strategy-list.html"
return render(request, template_name, context)

View File

@ -1,187 +1,40 @@
import uuid
from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.auth.mixins import LoginRequiredMixin
from django.http import HttpResponseBadRequest
from django.shortcuts import render
from django.views import View
from rest_framework.parsers import FormParser
from rest_framework.views import APIView
from core.forms import TradeForm from core.forms import TradeForm
from core.models import Account, Trade from core.models import Trade
from core.util import logs from core.util import logs
from core.views import ObjectCreate, ObjectDelete, ObjectList, ObjectUpdate
log = logs.get_logger(__name__) log = logs.get_logger(__name__)
def get_trades(user, account=None): class TradeList(LoginRequiredMixin, ObjectList):
if user: list_template = "partials/trade-list.html"
trades = Trade.objects.filter(account__user=user) model = Trade
elif account: context_object_name = "trades"
trades = Trade.objects.filter(account=account, account__user=user) title = "Trades"
return trades title_singular = "Trade"
page_title = (
"List of bot and manual trades. This may not reflect actual live trades."
)
page_subtitle = "Trades deleted here will not be closed on the exchange."
submit_url_name = "trade_create"
class Trades(LoginRequiredMixin, View): class TradeCreate(LoginRequiredMixin, ObjectCreate):
allowed_types = ["modal", "widget", "window", "page"] model = Trade
window_content = "window-content/trades.html" form_class = TradeForm
context_object_name = "trades"
async def get(self, request, type, account_id=None): submit_url_name = "trade_create"
if type not in self.allowed_types:
return HttpResponseBadRequest
template_name = f"wm/{type}.html"
unique = str(uuid.uuid4())[:8]
if account_id:
try:
trades = Account.objects.get(id=account_id, user=request.user)
except Account.DoesNotExist:
message = "Account does not exist."
message_class = "danger"
context = {
"message": message,
"class": message_class,
"type": type,
}
return render(request, template_name, context)
trades = get_trades(request.user, account_id)
else:
trades = get_trades(request.user)
if type == "page":
type = "modal"
context = {
"title": f"Trades ({type})",
"unique": unique,
"window_content": self.window_content,
"items": trades,
"type": type,
}
return render(request, template_name, context)
class TradeAction(LoginRequiredMixin, APIView): class TradeUpdate(LoginRequiredMixin, ObjectUpdate):
allowed_types = ["modal", "widget", "window", "page"] model = Trade
window_content = "window-content/add-trade.html" form_class = TradeForm
parser_classes = [FormParser] context_object_name = "trades"
submit_url_name = "trade_update"
def get(self, request, type, trade_id=None):
"""
Get the form for adding or editing a trade.
:param trade_id: The id of the trade to edit. Optional.
"""
if type not in self.allowed_types:
return HttpResponseBadRequest
template_name = f"wm/{type}.html"
unique = str(uuid.uuid4())[:8]
if trade_id:
try:
trade = Trade.objects.get(id=trade_id, account__user=request.user)
form = TradeForm(instance=trade)
except Trade.DoesNotExist:
message = "Trade does not exist"
message_class = "danger"
context = {
"message": message,
"message_class": message_class,
"window_content": self.window_content,
}
return render(request, template_name, context)
else:
form = TradeForm()
if type == "page":
type = "modal"
context = {
"form": form,
"trade_id": trade_id,
"type": type,
"unique": unique,
"window_content": self.window_content,
}
return render(request, template_name, context) class TradeDelete(LoginRequiredMixin, ObjectDelete):
model = Trade
def put(self, request, type, trade_id=None):
"""
Add or edit a trade.
:param trade_id: The id of the trade to edit. Optional.
"""
if type not in self.allowed_types:
return HttpResponseBadRequest
message = None
message_class = "success"
if trade_id:
try:
form = TradeForm(
request.data,
instance=Trade.objects.get(id=trade_id, account__user=request.user),
)
except Trade.DoesNotExist:
message = "Trade does not exist"
message_class = "danger"
context = {
"message": message,
"class": message_class,
}
return render(request, self.template_name, context)
else:
form = TradeForm(request.data)
if form.is_valid():
trade = form.save(commit=False)
print("PRESAVE TRADE", trade)
trade.user = request.user
trade.save()
success, returned = trade.post()
if success:
print("SAVED TRADE", trade)
if trade_id:
message = f"Trade {trade_id} edited successfully"
else:
message = f"Trade {trade.id} added successfully"
else:
message = f"Error adding trade: {returned}"
message_class = "danger"
else:
message = "Error adding trade"
message_class = "danger"
trades = get_trades(request.user)
context = {
"items": trades,
"type": type,
}
if message:
context["message"] = message
context["class"] = message_class
template_name = "partials/trade-list.html"
return render(request, template_name, context)
def delete(self, request, type, trade_id):
"""
Delete a trade.
:param trade_id: The id of the trade to delete.
"""
if type not in self.allowed_types:
return HttpResponseBadRequest
message = None
message_class = "success"
try:
trade = Trade.objects.get(id=trade_id, account__user=request.user)
trade.delete()
message = "Trade deleted successfully"
except Trade.DoesNotExist:
message = "Error deleting trade"
message_class = "danger"
trades = get_trades(request.user)
context = {
"items": trades,
"type": type,
}
if message:
context["message"] = message
context["class"] = message_class
template_name = "partials/trade-list.html"
return render(request, template_name, context)