From 4211b2827c1e0383bf8fc1e7e849013c9551ed22 Mon Sep 17 00:00:00 2001 From: Rodrigo Date: Mon, 21 Aug 2023 11:33:15 -0300 Subject: [PATCH] Improve TraderoBot UI/UX - Make jump action accessible through UI --- base/forms.py | 6 +++ base/templates/base/_jumping_form.html | 11 +++++ base/templates/base/botzinhos_detail.html | 5 +++ base/tests.py | 31 +++++++++++++ base/views.py | 55 ++++++++++++++++++----- 5 files changed, 98 insertions(+), 10 deletions(-) create mode 100644 base/templates/base/_jumping_form.html diff --git a/base/forms.py b/base/forms.py index 2c1b198..e020232 100644 --- a/base/forms.py +++ b/base/forms.py @@ -126,3 +126,9 @@ class Meta: "api_key", "api_secret", ] + + +class JumpingForm(forms.Form): + to_symbol = forms.ModelChoiceField( + queryset=Symbol.objects.available(), empty_label="(Selecionar)" + ) diff --git a/base/templates/base/_jumping_form.html b/base/templates/base/_jumping_form.html new file mode 100644 index 0000000..0fd8ca9 --- /dev/null +++ b/base/templates/base/_jumping_form.html @@ -0,0 +1,11 @@ +{% load bootstrap5 %} + +
+ {% csrf_token %} +
+ +
+
+ {% bootstrap_field form_jumping.to_symbol show_label=False wrapper_class="col-s12" %} +
+
diff --git a/base/templates/base/botzinhos_detail.html b/base/templates/base/botzinhos_detail.html index cd4334b..62a3b14 100644 --- a/base/templates/base/botzinhos_detail.html +++ b/base/templates/base/botzinhos_detail.html @@ -74,6 +74,11 @@

Actions

{% action_button action_url "Force SELL" "btn-danger" %} {% endif %} + {% if not bot.price_buying %} +
  • + {% include "base/_jumping_form.html" with view="base:botzinhos-action" obj=bot %} +
  • + {% endif %}
  • {% if bot.status > bot.Status.INACTIVE %} {% url "base:botzinhos-action" bot.pk "off" as action_url %} diff --git a/base/tests.py b/base/tests.py index 532f92e..70d660e 100644 --- a/base/tests.py +++ b/base/tests.py @@ -545,6 +545,37 @@ def test_botzinhos_actions(self): ) response = self.client.post(url, follow=True) self.assertEqual(response.status_code, 200) + with requests_mock.Mocker() as m: + m.get( + f"{BINANCE_API_URL}/api/v3/ticker/price", + json={"symbol": "S1BUSD", "price": "1.0"}, + ) + url = reverse( + "base:botzinhos-action", + kwargs={ + "pk": self.bot1.pk, + "action": "jump", + }, + ) + response = self.client.post( + url, {"to_symbol": self.s1.pk}, follow=True + ) + self.assertEqual(response.status_code, 200) + self.assertIn(b"SUCCESS at", response.content) + with mock.patch( + "base.views.BotzinhosActionView.ACTIONS", + {"on": {"params": {"test_param": {"type": "test"}}}}, + ): + url = reverse( + "base:botzinhos-action", + kwargs={ + "pk": self.bot1.pk, + "action": "on", + }, + ) + response = self.client.post(url, follow=True) + self.assertEqual(response.status_code, 200) + self.assertIn(b"SUCCESS at", response.content) with mock.patch("base.models.TraderoBot.on") as bot_on_mock: bot_on_mock.side_effect = Exception("New Exception") url = reverse( diff --git a/base/views.py b/base/views.py index 15ddefe..9b654d8 100644 --- a/base/views.py +++ b/base/views.py @@ -16,6 +16,7 @@ ) from .forms import ( + JumpingForm, TraderoBotForm, TraderoBotGroupEditForm, TraderoBotGroupForm, @@ -122,6 +123,7 @@ def get_context_data(self, **kwargs): context["page_obj"] = page_obj context["time_interval"] = settings.TIME_INTERVAL_BOTS context["summary"] = summary + context["form_jumping"] = JumpingForm() return context @@ -171,14 +173,17 @@ class ActionView(OwnerMixin, LoginRequiredMixin, RedirectView): def get_object(self): raise NotImplementedError - def run_action(self, action): + def run_action(self): try: - action_method = getattr(self.object, action) - action_method() + action_method = getattr(self.object, self.action) + if self.action_params: + action_method(**self.action_params) + else: + action_method() messages.success( self.request, - f"SUCCESS at {action.upper()} for {self.object.pk}:" - f"{self.object.name}", + f"SUCCESS at {self.action.upper()}({self.get_params_str()}) " + f"for [{self.object.pk}] {self.object.name}", ) except Exception as e: msg = e.args[0] @@ -187,26 +192,56 @@ def run_action(self, action): modname = mod.__name__ if mod else frm[1] messages.error( self.request, - f"ERROR at {action.upper()} for {self.object.pk}:" - f"{self.object.name}", + f"ERROR at {self.action.upper()}({self.get_params_str()}) " + f"for [{self.object.pk}] {self.object.name}: " f"[{modname}] {str(msg)}", ) def get_redirect_url(self, *args, **kwargs): - if kwargs["action"] not in self.ACTIONS: + self.action = kwargs["action"] + if self.action not in self.ACTIONS: raise Http404("Action not Found") + self.action_params = self.process_params(self.request.POST) self.pk = kwargs["pk"] self.get_object() - self.run_action(kwargs["action"]) + self.run_action() return self.request.META.get("HTTP_REFERER", "/") + def get_params_str(self): + if self.action_params: + return ",".join( + [f"{k}={v}" for k, v in self.action_params.items()] + ) + return "" + + def process_params(self, data): + data = {k: v for k, v in data.items() if k != "csrfmiddlewaretoken"} + action_params = {} + action_params_conf = self.ACTIONS[self.action]["params"] + if action_params_conf: + for param in action_params_conf: + if action_params_conf[param]["type"] == "Model": + action_params[param] = get_object_or_404( + action_params_conf[param]["class"], + pk=data[param], + ) + # Ignore non-compliant parameters + return action_params + class BotzinhosActionView(ActionView): """ Runs Actions on Botzinhos """ - ACTIONS = ["buy", "sell", "on", "off", "reset"] + ACTIONS = { + "buy": {"params": None}, + "sell": {"params": None}, + "on": {"params": None}, + "off": {"params": None}, + "reset": {"params": None}, + "jump": {"params": {"to_symbol": {"type": "Model", "class": Symbol}}}, + } def get_object(self): self.object = get_object_or_404(TraderoBot, pk=self.pk)